新手第一次发文,欢迎各位师傅们指点和交流。
0x00 前言
DophinPHP(海豚PHP)是一个基于ThinkPHP5.1.41 LTS开发的一套开源PHP快速开发框架,官方地址是DolphinPHP(海豚PHP)。
0x01 漏洞分析
通过查看Release,可以发现在最新版V1.5.1中修复了来自V1.5.0版本的行为日志执行漏洞。
进一步追踪,定位到了漏洞触发点。
代码是开源的,直接拖到本地进行搭建并分析。
首先查看目录结构,典型的TP框架目录结构。
相关的漏洞触发点在application/common.php文件的1086行。
可以知道的是,call_user_func($callback, ...$parameters)
函数用于调用回调函数,在其参数可控的情况下,可导致代码执行等漏洞。
<?php
call_user_func('system', 'whoami'); # 执行系统命令 whoami
?>
回到漏洞触发点,对于如下代码,我们要做的就是控制call_user_func()
函数的2个参数。
先追溯$param[1]
,定位到1084行。
$param = explode('|', $value);
explode()
函数将|竖线作为分隔符,对$value
进行分割,将处理后的字符串变成数组赋值给$param
。
追溯$value
,定位到1084行。
foreach($match[1] as $value){
这句话的意思就是将$match[1]
数组中的值逐个赋值给$value
。
追溯$match[1]
,定位到1072行。
if(preg_match_all('/\[(\S+?)\]/', $action_info['log'], $match)){
$match
位于preg_match_all()
函数的第三个参数,第三个参数用于存储匹配的结果。而正则/\[(\S+?)\]/
匹配包含在方括号中的任何非空白字符序列,并将其作为捕获组进行提取。
所以$match[1]
的值为不带方括号的字符串,其值从$action_info['log']
中匹配而来。
继续往上追溯,定位到1055行。
$action_info = model('admin/action')->where('module', $module)->getByName($action);
由model('admin/action')
,故定位到application/admin/model/Action.php,可以看到当前模型对应的完整数据表名称为:admin_action。
整条语句要表达的意思就是从admin_action数据表中提取出满足module=$module
且name=$action
的记录,所以$action_info['log']
的值自然就是记录中列名为log的值。可以看一下数据库存储信息。
对于$param[1]
的追溯到这其实已经差不多可以结束了,从admin_action表中取出module=$module
且name=$action
的记录,提取其log字段方括号内的值,再将|竖线作为分割符号,取出第二个字符串作为call_user_func()
函数的第一个参数,可以简化为如下demo:
<?php
$log = '[test|system]demo';
preg_match_all('/\[(\S+?)\]/', $log, $match);
foreach ($match[1] as $value){
$param = explode('|', $value);
var_dump($param[1]); // 打印call_user_func()第一个参数值
}
?>
下一步我们只需要控制log字段的值即可。很方便的是,Dolphin管理员后台很贴心的为我们准备了相应的功能用于自定义action_log,相应的功能点在:系统->系统功能->行为管理。
点击编辑,可以对日志规则进行自定义。
日志规则正好对应admin_action数据表中的log字段。
解决完第一个参数后,再看到$log[$param[0]]
,这段表达式用于取出$log
数组中下标为$param[0]
的值。$param[0]
我们可以很轻松地进行控制,而对于$log
,则需要往前进行追溯。
定位到1073行~1080行。
$log = [
'user' => $user_id,
'record' => $record_id,
'model' => $model,
'time' => request()->time(),
'data' => ['user' => $user_id, 'model' => $model, 'record' => $record_id, 'time' => request()->time()],
'details' => $details
];
同样寻找可控变量,以$details
为例。
查找调用action_log方法的地方,可以看到该方法被大量调用。以application/user/admin/Role.php为例,定位到246行。
这是一个编辑角色的功能点,$details
的值来自于$data['name']
,而$data
的值从POST传参中获取,也就是说我们可以通过POST传参name从而修改$details
的值。简化一下代码:
<?php
... # 省略大量代码
$data = $this->request->post();
... # 省略大量代码
action_log('role_edit', 'admin_role', $id, UID, $data['name']);
... # 省略大量代码
梳理一下思路:call_user_func()
的第一个参数由行为管理功能点控制,第二个参数由角色编辑控制。
0x02 漏洞复现
登录后台,默认账号密码:admin/admin
来到行为管理处,搜索role_edit
,编辑相关字段。
编辑日志规则:[details|system]
,其中system
是call_user_func()
第一个参数;details
是键名,该键对应的值则是call_user_func()
的第二个参数。
修改完毕后来到角色管理:用户->权限管理->角色管理。
首先新增一个角色。
编辑该角色,同时抓取相关数据包,将name参数的值修改为系统命令。
成功执行命令。
0x03 参考文章
https://xz.aliyun.com/t/11118
https://www.xx5xx.top/%E6%BC%8F%E6%B4%9E%E5%88%86%E6%9E%90/DolphinPHP-RCE%E6%BC%8F%E6%B4%9E%E5%88%86%E6%9E%90/index.html