phpems代码审计——西湖论剑题目复现
0x01 前言
春节之前参加的西湖论剑比赛,数据安全赛道的phpems框架代码审计几乎0解,也是最近才挖出来的洞。打算复现一下却拖到了现在。这几天也玩够了,于是重新看这道题,写下这篇文章记录一下。审计过程中的技巧与思路还是值得学习。
0x02 题目复现
一个phpems框架,有登录注册功能,但是是前台登录,看一下phpems官方手册去分析路由,就能够找到phpems框架的地址(路由就不分析了,调试一下就明白)

需要知道管理员账号密码,题目附件给了sql文件,注册一个账户,发现前台账号密码和后台管理员密码存放在一个表中。

分析一下后台登陆代码逻辑

首先根据用户提交的用户名在数据库中查找该用户的所有信息,如果该用户存在就比对密码的md5值是否相同。最后就是关键的一点,判断该用户是否为管理员权限,则判断用户表的 groupid 字段是否为1。
如果存在sql注入,那么就能够修改我们注册的用户的 groupid 字段,或者修改管理员密码都能够实现后台登录。
一. 漏洞分析
针对于框架的漏洞挖掘,首先分析功能点多且较为明显的地方。比如前台的登录注册。
1.反序列化
当我们点击注册

首先会进入到app的构造方法,这里的make是一个工厂方法,通过给定的参数session创建或者加载处理session的类实例,所以会走到session类的构造方法。

进入到 getSessionId 方法,每次访问都会生成session对象并调用这个方法。

这里获取字段名为 exam_currentuse 的 cookie 值,然后调用decode方法解码。

这里通过一个密钥key,对密文每个字符循环取余然后相减得到明文。
最后就是反序列化触发函数:unserialize

反序列化的内容我们是可控的,接下来就只要破解这个加密方法就可以进行反序列化攻击。
2.破解加密方式
首先密文的长度是131位,密钥的长度为32位。那么取余后$p的值就为0,1,2,3….32,0,1,2等32位的循环。对应的加密函数如下

我们需要找到这个密钥key
加解密的大致模式为: |
所以知道了明文以及对应的密文,就可以计算出密钥key。接下来就是寻找可控的明文。
当我们不携带cookie访问的时候

它会进入这里生成一个cookie,进入到 setSessionUser 方法里
它的参数是个数组

通过getClientIp方法获取客户端IP,这个IP可以通过比如Client-IP
和X-Forwarded-For
请求头字段来伪造。

对args数组进行encode函数加密,序列化后的字符串如下

然后将加密后的密文添加到exam_currentuse 字段里通过返回包返回给客户端。
这个伪造的ip就是我们能够控制的密文的部分了。

不带cookie,返回的密文部分。
为了获取到32位的key,我们需要让我们可以控制、预测的密文长度至少为32。所以根据以上序列化明文字符串,可以取的范围是
";s:9:"sessionip";s:9:"127.0.0.1";s:16:"sessiontimelimit";i: |
一共60位,在加密过程中是通过32位一循环,所以取完整的32位长度,也就是完整密钥的长度。
:"sessionip";s:9:"127.0.0.1";s: |
编写解密脚本
|
运行脚本得到解密密钥为
4b394f264dfcdc724a06b9b05c1e59ed |
3.sql注入
我们需要利用sql注入修改管理员的密码,通过反序列化进行sql注入。在session类的 __destruct 方法

exec 方法用于执行 sql 语句,重点在于 makeUpdate 方法,如何在该方法中可控的sql语句的部分


首先 $tablepre 参数写死为null,然后将 pdosql 类的 tablepre 属性赋值为 $tb_pre 变量,接着与 $tables 变量进行拼接,当作用户表进行操作。最后整合出一条更新sql语句。
我们可以利用+或者–等注释符注释掉后面的sql语句
反序列化链为
session::__destruct()->pdosql::makeUpdate()->pepdo::exec() |
在反序列化过程中,类属性可控。将 pdosql 类的 tablepre 属性赋值为
x2_user SET userpassword = 'e10adc3949ba59abbe56e057f20f883e' WHERE userid = 1 # |
userid为1的一般都是管理员,对管理员账户修改密码,使用md5加密存储。
二.漏洞利用
编写反序列化脚本
|
在用户表中修改管理员的密码为 123456 的md5值,运行exp,在请求包中将cookie的 exam_currentuser 字段值替换为我们的payload。

最后用账号密位为 peadmin 123456 登录到管理员后台。

登录到后台后找找有什么可以代码执行的地方。
要想代码执行,首先得有用户输入的地方,全局搜索 eval 等代码执行函数。在 content 文件夹中存在一个文件里执行了eval函数,在 api.cls.php文件中。

当 blocktype 为 2,3,4时,都会调用eval函数执行,首先将而在后台对应的功能点就是内容模块的标签管理

除了第一个分类模式,剩下三个都可以代码执行。写下一句话木马保存后,会序列化记录在数据库里

当用户点击注册的时候,框架会提示出现注册协议

在获取模板模式的数据的过程中,blockcontent会被反序列化并将内容赋值给 app/content/cls/api.cls.php中的$tp 最后在进入eval执行了代码。
在前台注册功能点会获取模板信息,执行代码。访问路由为 /index.php?user-app-register
连接蚁剑成功
