代码审计之旅之百家CMS
媒介
之前审计的CMS大多是操纵东西,即 Seay+昆仑镜 联动扫描露马脚点,然后停止审计。觉得本身的才能仍与零无异,因而本次审计CMS绝大大都利用手动探测,即通过搜刮求助紧急函数的体例停止破绽觅觅,以此来提拔审计才能,期看对正在进修代码审计的师傅能有所搀扶帮助。
情况搭建
源码链接如下所示 目次下即可
接下来往创建一个数据库用于存储CMS信息。(在Mysql号令行中施行)
接下来拜候CMS,会默认跳转至安拆界面
展开全文
数据库名称和账密重视一下就好,其他随意写
然后安拆胜利,能够起头停止审计了。
审计 预备工做
我们拿到一套源码时,起首需要对详细文件夹停止一次阐发,如许才气对CMS有一个初步的印象,为后续审计做一些展垫。 根目次如下所示
其对应目次阐明如下
addons 插件
api 接口
assets 静态文件
attachment 上传目次
cache 缓存目次
config 系统文件
include系统文件
system 后端代码
针对 system 目次,那个较为常用,我们能够对其停止进一步阐发
system 系统模块目次
├─alipay 付出宝办事窗模块
├─bonus 优惠券模块
├─common 公共函数模板
├─index 登录页
├─member 会员模块
├─modules 可再扩展模块和模块治理
├─ public公共模块
├─shop 后台商城模块
├─shopwap 前台商城模块
├─user 系统用户
└─weixin 微信模块
对那些有过领会后,还需要看的就是一些后端支持文件,例如那种xxxinc.php 文件,他们经常存在一些破绽,进而招致CMS呈现破绽
所以简单阅读一下那些也是有需要的。接下来预备工做做完,就起头下一步。
路由解析
对一个CMS停止破绽探测前,我们需要起首需要对CMS的路由有所领会。 那里我们间接拜候默认页面baijiacms-master/index.php ,然后登录后台,那里说一下我本身认为找路由还能够的办法,就是存眷一些特殊点,好找一些,好比那里的修改密码界面
我们点击它,发现此时的路由如下
baijiacms-master/index.php?mod=siteact=manager do=changepwdbeid= 1
接下来我们在Vscode中停止全局搜刮,搜password=
成果如下,能够发现它的途径
baijiacms-master\system\manager\ class\ web\ changepwd. php
再找到它的详细位置
我们将它与之前看到的路由停止比对,就能够发现act 其实是system 文件夹下的文件夹名称,do 是所抉择详细文件的名称,对那些有个初步的领会,待会找到文件时能在网页中拜候即可。
破绽查找
那里 Seay+关键词搜刮 的体例停止破绽查找
SQL注进 疑点一(失败)
发现有良多疑似注进点,从第一个起头跟进看
文件路由/addons/activity/class/mobile/index.php 重点代码
global$_W, $_GPC;
$activityid= intval( $_GPC[ 'activityid'] );
$operation= ! empty( $_GPC[ 'op']) ? $_GPC[ 'op'] : 'display';
$pagetitle= "活动报名进口";
$activity= pdo_fetch( "SELECT * FROM ". table( 'activity') . " WHERE uniacid = ' {$_W['uniacid']}' and id = " . $activityid);
能够看到uniacid 变量确实未被单引号包裹,可能存在注进,但我们那里重视到它是$_W['uniacid'] ,逃溯$_W ,看到global $_W,$_GPC; ,那个是全局变量,所以我们间接在vscode中停止查找(ctrl+shift+f全局搜刮)
发现$_GPC=$_GP ,所以我们只需要确定$_GP ,就能够确定$_GPC ,接下来觅觅$_GP ,最末在baijiacms.php 中发现此变量
那里的话能够看出是对所有办法恳求的参数停止了一个stripslashes 函数处置,然后将参数停止了合并,合并后对数组内的参数依次停止遍历,停止htmlspecialchars 函数处置,然后将实体字符 替代为 。不外那个是$_GPC 的,但都是全局变量,$_W 应该也类似,接下来再跟着看一下,我们全局搜刮$_W=
那里能够发现$W=$_CMS ,同时看出我们的$_W['uniacid']=$_CMS['beid'] ,接下来搜刮$_CMS['beid']=
找到它等同于一个函数,即getDomainBeid 函数,所以接下来觅觅getDomainBeid 函数
functiongetDomainBeid
global$_GP;
$system_store= mysqld_select( 'SELECT id,isclose FROM '. table( 'system_store'). " where (`website`=:website1 or `website`=:website2) and `deleted`=0 ", array( ":website1"=WEB_WEBSITE, ":website2"= '));
if( empty( $system_store[ 'id']))
if(! empty( $_GP[ 'beid']))
$system_store= mysqld_select( 'SELECT id,isclose FROM '. table( 'system_store'). " where `id`=:id and `deleted`=0", array( ":id"= $_GP[ 'beid']));
if( empty( $system_store[ 'id']))
message( "未找到相关店展");
if(! empty( $system_store[ 'isclose']))
message( "店展已封闭无法拜候");
return$system_store[ 'id'];
} else
return"";
} else
if(! empty( $system_store[ 'isclose']))
message( "店展已封闭无法拜候");
return$system_store[ 'id'];
那里能够看出 system_store 是由系统数据库中查出来的数据,那个对我们来说是不成控的,我们可控的是 $_GP['beid'] ,此时看着一个SQL语句
$system_store= mysqld_select( 'SELECT id,isclose FROM '. table( 'system_store'). " where `id`=:id and `deleted`=0", array( ":id"= $_GP[ 'beid']));
假设我们的数据一般,他的成果应该是
id isclose
xx xxxxxxx
xx xxxxxxx
而当我们输进 beid 为 xx and sleep(2) 那种,它毫无疑问是不会有查询成果的,那也就意味着 $system_store['id'] ,而那个函数的最末成果是 return $system_store['id']; ,那么此时它就会返回空值,那么回到那个SQL语句
pdo_fetchall( "select * from ". tablename( 'eshop_member') . " where isagent =1 and status=1 and uniacid = ". $_W[ 'uniacid'] . " {$condition}ORDER BY agenttime desc limit " . ( $pindex- 1) * $psize. ','. $psize);
中,假设我们那里一般,想让返回的不为空值,那么那个 $_W['uniacid'] 只能领受到一般的id,也就是数据库中存储着的 id 值,所以那里是无法停止SQL注进的。
类似那个的还有如下文件
文件名:system/eshop/core/mobile/commission/team.php
部门PHP代码
$list= pdo_fetchall( "select * from ". tablename( 'eshop_member') . " where isagent =1 and status=1 and uniacid = ". $_W[ 'uniacid'] . " {$condition}ORDER BY agenttime desc limit " . ( $pindex- 1) * $psize. ','. $psize);
文件名: /addons/activity/ class/ web/ activity. php
部门 PHP代码:
$ activity= pdo_fetch(" SELECT* FROM" . table(' activity') . " WHEREuniacid= '{ $_W[ 'uniacid']} ' and id = " . $activityid );
文件名:/addons/activity/class/mobile/join.php
部门PHP代码:
$row = pdo_fetch ("SELECT id FROM " . table (' activity ') . " WHERE uniacid = '{ $_W[ 'uniacid']} ' and id = " . $activityid );
文件名:/addons/activity/class/web/records.php
部门PHP代码:
$row = pdo_fetch("SELECT id,pic FROM " . table(' activity_records ') . " WHERE id = $id and uniacid = '{ $_W[ 'uniacid']} '");
文件名:/system/eshop/core/web/shop/dispatch.php
部门PHP代码:
$dispatch = pdo_fetch("SELECT id,dispatchname FROM " . tablename(' eshop_dispatch ') . " WHERE id = '$id' AND uniacid=" . $_W['uniacid '] . "");
文件名: /system/eshop/core/web/virtual/category.php
部门PHP代码:
$list = pdo_fetchall("SELECT * FROM " . tablename(' eshop_virtual_category ') . " WHERE uniacid = '{ $_W[ 'uniacid']} ' ORDER BY id DESC");疑点二(失败)
文件途径/system/common/model/virtual.php
那里发现参数id ,跟进id 变量,发现来源于
publicfunctionupdateGoodsStock( $id= 0)
global$_W, $_GPC;
$goods= pdo_fetch( 'select virtual from '. tablename( 'eshop_goods') . ' where id=:id and type=3 and uniacid=:uniacid limit 1', array(
':id'= $id,
':uniacid'= $_W[ 'uniacid']
发现那里的id是间接赋值为0的,我们是不成控的,所以不存在注进。
肆意目次及文件删除
关于破绽觅觅,大多是从一些灵敏函数进手,假设觉得Seay 扫描的不敷全面,我们可自行查找,关于文件删除,我们那里起首想到的就是unlink 函数,所以我们那里翻开Vscode ,ctrl+shift+f 全局搜刮unlink 函数
那里重视到有多个文件,js 及css 前端文件自没必要看,我们那里要存眷的是php 文件,接下来从第一个起头看。
疑点一
文件路由 baijiacms-master\includes\baijiacms\common.inc.php ,涉及代码如下
functionrmdirs( $path= '', $isdir= false)
if( is_dir( $path)) //断定变量能否为目次
$file_list= scandir( $path); //查看途径下的文件
foreach( $file_listas$file) //依次遍历
if( $file!= '.' $file!= '..') //假设不是.和..
if( $file!= 'qrcode')
rmdirs( $path. '/'. $file, true); //删除目次下的文件
if( $path!=WEB_ROOT. '/cache/') //假设变量名不是根目次拼接cache
@ rmdir( $path); //删除目次
else
@ unlink( $path);
能够看到当它断定变量为目次时,会对目次下的文件停止递回,然后删除一切文件,假设它不是目次,那么他此时就会间接删除那个文件。接下来有函数了,那我们就要看哪个文件操纵了那个函数,然后来停止操纵。 所以接下来全局搜刮函数rmdirs
在那里插进图片描述
在文件baijiacms-master\system\manager\class\web\database.php 中发现如下代码
if( $operation== 'delete')
$d= base64_decode( $_GP[ 'id']);
$path= WEB_ROOT . '/config/data_backup/';
if( is_dir( $path. $d)) {
rmdirs( $path. $d);
message( '备份删除胜利!', create_url( 'site', array( 'act'= 'manager', 'do'= 'database', 'op'= 'restore')), 'success');
能够发现那里对变量停止了 base64_decode 处置,那下我们想删除的目次的话,我们起首需要对他停止一个base64编码,同时我们能够看到那里指定了途径
$path= WEB_ROOT . '/config/data_backup/';
但那个我们其实是能够绕过的,后续只校验了是不是目次,而未限制目次,所以我们通过burpsuite挠包修改目次就能够实现肆意目次删除。
接下来停止操纵测验考试 起首我们在根目次下新建一个目次(名字随意,我那里为qwq)
接下来拜候那个数据库备份界面,详细路由如下
开启bp挠包,点击删除功用点。发送到重放包界面,修改id为Li4vLi4vcXdx (../../qwq的Base64编码形式)
此时再回根目次查看
疑点二
除了rmdir 和unlink ,我们经常还能够存眷delete 函数,因为他曲译过来也是删除的意思,所以接下来就全局停止搜刮delete
然后在includes\baijiacms\common.inc.php 中发现相关代码,详细代码如下
functionfile_delete( $file_relative_path) {
if( empty( $file_relative_path)) {
returntrue;
$settings= globaSystemSetting;
if(! empty( $settings[ 'system_isnetattach']))
if( $settings[ 'system_isnetattach']== 1)
require_once(WEB_ROOT. '/includes/lib/lib_ftp.php');
$ftp= newbaijiacms_ftp;
if( true=== $ftp- connect) {
if( $ftp- ftp_delete( $settings[ 'system_ftp_ftproot']. $file_relative_path)) {
returntrue;
} else{
returnfalse;
} else{
returnfalse;
if( $settings[ 'system_isnetattach']== 1)
require_once(WEB_ROOT. '/includes/lib/lib_oss.php');
$oss= newbaijiacms_oss;
$oss- deletefile( $file_relative_path);
returntrue;
} else
if( is_file(SYSTEM_WEBROOT . '/attachment/'. $file_relative_path)) {
unlink(SYSTEM_WEBROOT . '/attachment/'. $file_relative_path);
returntrue;
returntrue;
那里重点存眷那一个
if(! empty( $settings[ 'system_isnetattach']))
当那个施行通过时,就不会往删除,反之,间接将文件删除,因而我们有需要往找一下那个是什么工具,照旧,全局搜刮
那里发现是长途附件,因而我们那里抉择当地的话,按理说就可中转else ,对文件停止间接删除,拜候详细路由
接下来就设置好了,接下来往觅觅运用了那个file_delete 函数的文件,全局搜刮一下
文件路由为system\eshop\core\mobile\util\uploader.php ,部门代码如下
} elseif( $operation== 'remove') {
$file= $_GPC[ 'file'];
file_delete( $file);
show_json( 1);
因而我们那里拜候那个路由并设置 operation 为 remove ,按理说就能够间接删文件了,接下来测验考试操纵。
起首在根目次新建文件,那里定名为qwq.txt
接下来拜候路由
此时查看根目次
文件已胜利删除
同时,我们刚刚还看到了不行那一个文件操纵了 delete 函数,别的的能否存在呢,我们来看一下 文件路由 system\eshop\core\web\shop\category.php ,详细代码
elseif( $operation== 'post') {
if(! empty( $id)) {
unset( $data[ 'parentid']);
pdo_update( 'eshop_category', $data, array(
'id'= $id
file_delete( $_GPC[ 'thumb_old']);
那里能够发现想删除文件,需要有三个前提
1、 $operation== 'post'
2、 $id不为空
3、 $_GPC[ 'thumb_old']为详细文件名
所以我们按理说的话,我们往拜候那个路由,然后修改$operation 为post ,添加参数$id=1 ,同时附加参数$thumb_old 为想删除文件名即可实现删除文件,那个$operation 在前面能够看到其实是参数op
所以我们间接给op 赋值为post ,即可实现文件删除,接下来停止测验考试在根目次新建文件qwq2.txt
接下来拜候路由
此时即可实现删除文件
号令施行
针对号令施行,我们存眷的函数必定是eval 、system 、exec 那几个,所以接下来就测验考试往操纵Vscode的全局搜刮来觅觅可疑点。 起首搜刮的是eval
找到的大大都是带有eval的关键词而非eval 函数,只要寥寥几个文件涉及了eval函数,接下来停止简单阐发
疑点一(失败)
文件路由 baijiacms-master\system\shopwap\template\mobile\login_dingtalk_pc.php ,部门代码如下
functioncheckstatus{
$. get( "?php echo create_url('mobile',array('act' = 'dingtalk','do' = 'fastlogin_pc','op'='dologincheck','skey'= $showkey));?" , {}, function(data){
vardata= eval( "("+ data + ")");
if(data.status== 1)
location.href= "?php echo create_url('mobile',array('act' = 'dingtalk','do' = 'fastlogin_pc','op'='tologin','skey'= $showkey));?" ;
if(data.status==- 1)
alert( "登录失败!从头刷新二维码登录");
location.href= "?php echo create_url('mobile',array('act' = 'shopwap','do' = 'login','op'='dingtalk'));?";
那里的话能够看出是js类代码,简单阐发一下那个函数,不难发现参数第一个是取对应的URL,第二个函数,也就是 function(data) ,它是对从第一个URL中提取出的参数停止施行,那里我们接着看函数,它那里当施行过函数后,对成果的形态取值停止了揣度,成果为 1 时揣度为登录胜利,就会跳转至另一个界面,而当为 -1 时就会登录失败,重回登录界面,所以我们那里能够看到他其实是不存在输出施行成果的处所的,所以我们底子无从下手,那里是无法实现号令施行的,所以Pass。
类似的文件还有如下几个,亦没必要再看
文件路由:baijiacms-master\system\shopwap\template\mobile\login_weixin_pc.php
部门代码:
functioncheckstatus{
$. get( "?php echo create_url('mobile',array('act' = 'weixin','do' = 'fastlogin_pc','op'='dologincheck','skey'= $showkey));?" , {}, function(data){
vardata= eval( "("+ data + ")");
if(data.status== 1)
location.href= "?php echo create_url('mobile',array('act' = 'weixin','do' = 'fastlogin_pc','op'='tologin','skey'= $showkey));?" ;
if(data.status==- 1)
alert( "登录失败!从头刷新二维码登录");
location.href= "?php echo create_url('mobile',array('act' = 'shopwap','do' = 'login','op'='weixin'));?";
setInterval( "checkstatus", 2000);
文件路由:baijiacms-master\system\weixin\template\mobile\badding_weixin_pc.php
部门代码:
functioncheckstatus{
$. get( "?php echo create_url('mobile',array('act' = 'weixin','do' = 'banding_pc','op'='dologincheck','skey'= $showkey));?" , {}, function(data){
vardata= eval( "("+ data + ")");
if(data.status== 1)
location.href= "?php echo create_url('mobile',array('act' = 'shopwap','do' = 'account'));?";
if(data.status==- 1)
alert( "登录失败!从头刷新二维码登录");
location.href= "?php echo create_url('mobile',array('act' = 'weixin','do' = 'fastlogin','bizstate'='banding_weixin'));?";
setInterval( "checkstatus", 2000); 疑点二
接下来我们存眷system 函数,间接Vscode 全局搜
最末在includes\baijiacms\common.inc.php 下找到system 函数,此中部门代码如下
functionfile_save( $file_tmp_name, $filename, $extention, $file_full_path, $file_relative_path, $allownet= true)
$settings= globaSystemSetting;
if(! file_move( $file_tmp_name, $file_full_path)) {
returnerror(- 1, '保留上传文件失败');
if(! empty( $settings[ 'image_compress_openscale']))
$scal= $settings[ 'image_compress_scale'];
$quality_command= '';
if( intval( $scal) 0)
$quality_command= ' -quality '. intval( $scal);
system( 'convert'. $quality_command. ' '. $file_full_path. ' '. $file_full_path);
那里能够看到是保留文件的,在此中停止了一个揣度能否上传胜利的,那个自没必要在意,那里我们看另一个
if(! empty( $settings[ 'image_compress_openscale']))
那个是什么意思呢,我们那里能够看出假设那个揣度能够通过,然后就会对文件名和文件途径停止一个system 施行,那我们就有可能实现号令施行,因而我们的首要使命就是找到那个是什么工具,所以接下来全局搜刮image_compress_openscale
此时就找到了,它就是图片压缩功用 ,所以我们间接往开启那个功用,那里那个if揣度就能够通过啦,所以接下来起首往开启那个,拜候路由如下
接下来我们跟进看一下哪个文件操纵了那个函数,事实找到文件才气操纵。
能够发现那里的话对此函数停止了一个操纵,详细代码如下
$extention= pathinfo( $file[ 'name'], PATHINFO_EXTENSION);
$extention= strtolower( $extention);
if( $extention== 'txt')
$substr= substr( $_SERVER[ 'PHP_SELF'], 0, strrpos( $_SERVER[ 'PHP_SELF'], '/'));
if( empty( $substr))
$substr= "/";
$verify_root= substr(WEB_ROOT. "/", 0, strrpos(WEB_ROOT. "/", $substr)). "/";
//file_save($file['tmp_name'],$file['name'],$extention,$verify_root.$file['name'],$verify_root.$file['name'],false);
file_save( $file[ 'tmp_name'], $file[ 'name'], $extention,WEB_ROOT. "/". $file[ 'name'],WEB_ROOT. "/". $file[ 'name'], false);
if( $verify_root!=WEB_ROOT. "/")
copy(WEB_ROOT. "/". $file[ 'name'], $verify_root. "/". $file[ 'name']);
$cfg[ 'weixin_hasverify']= $file[ 'name'];
那里的话是对上传文件停止了 pathinfo 函数处置,其实也就是获取了拓展名(后缀名),当为 txt 后缀时,会陆续往下停止,继而挪用那个 file_save 函数,所以我们那里的构想就了然了,我们那里新建一个文件,定名为 xxx号令.txt ,此时按理说就能够到达一个号令施行的效果,接下来停止测验考试。
我们那里新建一个txt文件,定名为ipconfig.txt
接下来对其停止上传,详细路由
接下来保留即可以看到效果
肆意文件读取 疑点一(失败)
文件路由 /system/eshop/core/mobile/shop/util.php ,重要代码如下
} elseif( $operation== 'areas') {
require_onceWEB_ROOT . '/includes/lib/json/xml2json.php';
$file= ESHOP_AREA_XMLFILE;
$content= file_get_contents( $file);
$json= xml2json:: transformXmlStringToJson( $content);
$areas= json_decode( $json, true);
die( json_encode( $areas));
其他暂且不看,我们那里先看那两个
$file= ESHOP_AREA_XMLFILE;
$content= file_get_contents( $file);
原来间接包罗$file 的话,确实是可能存在文件读取,但我们那里能够看到它那里是给$file 间接赋值了,那个是什么呢,我们全局搜刮一下能够发现是一个xml 文件
那么它对我们来说是不成控的,所以那里就不存在文件读取了,因而那里属于误报,看下一处。
所以类似那种的可疑点没必要再存眷,那里简单列出几个
文件名:/system/eshop/core/web/sale/enough.php
部门代码:
$content= file_get_contents( $file);
文件名:/system/eshop/core/web/shop/dispatch.php
部门代码:
$content= file_get_contents( $file); 文件上传 疑点一
文件上传,那里Seay并未扫到什么,所以我们手动来停止觅觅,关于文件上传,更先想到的就是上传二字,对应英文为upload ,所以间接Vscode 全局搜刮upload
文件路由为includes\baijiacms\common.inc.php ,详细代码如下
functionfile_upload( $file, $type= 'image') {
if( empty( $file)) {
returnerror(- 1, '没有上传内容');
$limit= 5000;
$extention= pathinfo( $file[ 'name'], PATHINFO_EXTENSION);
$extention= strtolower( $extention);
if( empty( $type)|| $type== 'image')
$extentions= array( 'gif', 'jpg', 'jpeg', 'png');
if( $type== 'music')
$extentions= array( 'mp3', 'wma', 'wav', 'amr', 'mp4');
if( $type== 'other')
$extentions= array( 'gif', 'jpg', 'jpeg', 'png', 'mp3', 'wma', 'wav', 'amr', 'mp4', 'doc');
那里能够看到那个是停止了良多检测的,对文件类型停止了检测,且要求了后缀,所以那个函数应该是文件上传不了了,但还好它不行一个有关 upload 的函数,我们往下看到如许一个函数
functionfetch_net_file_upload( $url) {
$url= trim( $url);
$extention= pathinfo( $url,PATHINFO_EXTENSION );
$path= '/attachment/';
$extpath= " {$extention}/" . date( 'Y/m/');
mkdirs(WEB_ROOT . $path. $extpath);
do{
$filename= random( 15) . ". {$extention}" ;
} while( is_file(SYSTEM_WEBROOT . $path. $extpath. $filename));
$file_tmp_name= SYSTEM_WEBROOT . $path. $extpath. $filename;
$file_relative_path= $extpath. $filename;
if( file_put_contents( $file_tmp_name, file_get_contents( $url)) == false) {
$result[ 'message'] = '提取失败.';
return$result;
$file_full_path= WEB_ROOT . $path. $extpath. $filename;
returnfile_save( $file_tmp_name, $filename, $extention, $file_full_path, $file_relative_path);
能够发现那个只对文件停止了pathinfo 函数处置,取出其后缀名,然后拼接途径及随机数字来构成文件名,那么我们假设通过那个函数停止文件上传,按理说就能够上传php文件实现getshell,接下来看看哪个文件操纵了此函数
文件路由system\public\class\web\file.php ,详细代码
if( $do== 'fetch') {
$url= trim( $_GPC[ 'url']);
$file= fetch_net_file_upload( $url);
if( is_error( $file)) {
$result[ 'message'] = $file[ 'message'];
die( json_encode( $result));
接下来我们只需要称心do=fetch ,然后url 中包罗我们的文件,即可实现文件上传,我那里长途文件内容如下
接下来停止操纵测验考试。拜候路由如下
拜候给出的文件途径
能够发现此时已经实现了文件上传,假设传一句话木马即可Getshell。
后言
本次CMS审计是小白的第一次大幅度操纵手动搜刮求助紧急函数来觅觅破绽,共计耗时半周,对本小白来说已颇为食力,此中颇多审计失败的点,虽审计失败,但仍觉得对代码才能有了进一步领会,也算有所收获。最初,假设文章中有错误,还看列位巨匠傅多多斧正。
参考文献
征集原创手艺文章中,欢送送达
投稿邮箱: edu@antvsion.com
文章类型:黑客极客手艺、信息平安、热点平安研究阐发等平安相关
通过审核并发布能收获200-800不等的稿酬
更多详情介绍,点我查看
靶场实操,戳“阅读原文“