学习PHP代码审计,审计一套叫做百家CMS的微商城管理系统
看一下网站的目录:
├─addons 扩展插件
├─assets 静态资源文件
├─attachment 附件目录
├─cache 缓存目录
├─config 配置文件目录
├─api 第三方插件
├─error 错误显示页面
├─includes 调用的函数文件
│ └─ runner.inc.php 路由文件
│ └─ mysql.inc.php 数据库配置文件
│ └─ common.inc.php 公共函数文件
│ └─ ......
├─system 功能点实现的控制页面
│ └─ manager
│ └─ eshop
│ └─ weixin
│ └─ ......
├─ index.php 入口文件
路由概况:
这是我喜欢审计的一条线路。就是先确定一个网站是如何运行起来的,快速的找到网站的路由文件,通过路由文件我们可以很快的理清网站运行的流程,之后不管是从功能点入手还是看全部的代码都可以掌握着全局去审计。
此处的"路由":本套CMS并没有按照MVC的模式去写代码,所以它的路由也就是自己写的,通过URL传入的参数:
index.php?mod=site&act=manager&do=store&op=display&beid=1
如上显示一样,会获取传入的参数,例如此处获取到的act=manager,那么会去加载system目录下的manager目录下的web.php文件,然后通过这个文件去包含对应功能点的文件,然后加载完成。
从index.php文件看:
判断完程序是否安装完成之后判断了下是否手机版查看当前的网站,之后获取了一些默认参数,方便页面显示默认的内容,注意第43行包含进来一个文件:
require 'includes/baijiacms.php'
注意:这些文件是网站的配置文件,例如mysql是数据库的配置、common是公共函数配置、runner是网站"路由"的配置等等.......
require WEB_ROOT.'/includes/baijiacms/mysql.inc.php';
}
require WEB_ROOT.'/includes/baijiacms/common.inc.php';
require WEB_ROOT.'/includes/baijiacms/setting.inc.php';
require WEB_ROOT.'/includes/baijiacms/init.inc.php';
$_CMS[WEB_SESSION_ACCOUNT]=$_SESSION[WEB_SESSION_ACCOUNT];
require WEB_ROOT.'/includes/baijiacms/extends.inc.php';
require WEB_ROOT.'/includes/baijiacms/user.inc.php';
require WEB_ROOT.'/includes/baijiacms/auth.inc.php';
require WEB_ROOT.'/includes/baijiacms/weixin.inc.php';
require WEB_ROOT.'/includes/baijiacms/runner.inc.php';
秉承我们看"路由"的原则,直接看runner.inc.php文件:
由于$_CMS['isaddone'] 我们没有传入这个参数,所以会直接进入else中,同样,没有SYSTEM_ACT参数并不等于mobile,所以会进入下一个else,可以看到包含进来了公共函数文件以及一个由$modulename拼接成的php文件。
由baijiacms.php文件中的95-101行可以知道:
$modulename的值是获取的act参数
if(empty($_GP['m']))
{
$modulename = $_GP['act']; //modulename参数获取的是act这个值
}else
{
$modulename = $_GP['m'];
}
/index.php?mod=site&act=manager&do=store&op=display&beid=1
例如此处:$modulename的值自然是manager
接着runner.inc.php文件继续往下看:
if(!is_file($file)) {
exit('ModuleSite Definition File Not Found '.$file);
}
if(!empty($_GP['m']))
{
require(WEB_ROOT.'/system/common/common.php');
}
require $file;
判断文件存在和m参数之后,在第八行直接require包含进来,老手都知道,此处并没有对跨目录的../进行限制,可以直接文件名%00截断造成文件包含。
后面直接整一个图片马文件包含试了一下:
http://www.bjcms.com/index.php?mod=site&act=manager/../../attachment/jpg/2020/11/A9au8ys8SYyYDS8.jpg%00&do=store&op=display&beid=1
%00截断:
php版本5.3.4之后修复了这个问题,所以截断的操作只能在5.3之前的php版本。
通过Seay源代码审计工具自动审计发现common.php文件中存在RCE,随即跟踪进去看了一下:
看一个保存文件的函数:
全局搜索一下看看那个地方使用了这个函数:
/system/weixin/class/web/setting.php
跟进这个函数看看具体的功能:
基本上判断后缀是数txt之后直接调用file_sava这个函,并没有过滤什么。到file_sava这个函数内看看怎么样才可以造成执行命令:
先是判断文件文件是否保存失败,一般正常上传看嘀咕没有问题的
if(!file_move($file_tmp_name, $file_full_path)) {
return error(-1, '保存上传文件失败');
}
之后判断$settings['image_compress_openscale']是否为空,如果为空的话肯定进入不了下面的循环,继而无法RCE了,所以要造成这个变量的值不为空,同时$scal的值大于零
if(!empty($settings['image_compress_openscale']))
{
$scal=$settings['image_compress_scale'];
$quality_command='';
if(intval($scal)>0)
{
$quality_command=' -quality '.intval($scal);
}
// echo 'convert'.$quality_command.' '.$file_full_path.' '.$file_full_path;
// exit;
system('convert'.$quality_command.' '.$file_full_path.' '.$file_full_path);
}
经过功能点分析:找到了设置这个变量的地方:只要打开图片压缩比例,同时设置一个大于零的数值就可以成功进入然后执行system(函数)
看一下system函数中拼接的参数,分别是$quality_command、$file_full_path。由上面可以知道$quality_command的值是为空的,$file_full_path的值由网站路径拼接文件名而成的,在传入的参数中可以看到:
所以,因为是拼接的,我们可以利用windows执行多条命令符号&来直接拼接成命令然后执行,打个断点看一个我们具体执行的语句是啥:
所以最终的POC就是:
1、打开图片压缩功能并设置压缩比大于零
2、新建一个文本文档命名为&whoami&.txt
3、直接上传,然后抓包就可直接执行系统命令
如下:
原文始发于微信公众号(增益安全):代码审计-BaijiaCms
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论