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

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

缺陷编号:wooyun-2015-0131548

漏洞标题:phpcms一个函数引起的安全漏洞(任意密码重置、多个SQL注入、甚至getshell)

相关厂商:phpcms

漏洞作者: 路人甲

提交时间:2015-08-04 15:22

修复时间:2015-11-02 15:44

公开时间:2015-11-02 15:44

漏洞类型:设计缺陷/逻辑错误

危害等级:高

自评Rank:20

漏洞状态:厂商已经确认

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

Tags标签:

4人收藏 收藏
分享漏洞:


漏洞详情

披露状态:

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

简要描述:

拖了好久。。。一直酗酒。。。。忙。。。按照剑心的要求,写上来吧。
最后说一句。。。360就一坨屎。。。一个问题重复的做死的爆。。。我就一次爆完算了。。艹

详细说明:

本文当中所有安全问题都将围绕parse_str()这个函数展开,parse_str函数在php 版本当中,在对变量进行解析过程中,有解码、以及变量后置等安全问题,这里不啰嗦,看问题。
任意密码重置:
在/phpcms/modules/member/index.php中account_manage_password方法

public function account_manage_password() {
if(isset($_POST['dosubmit'])) {
$updateinfo = array();
if(!is_password($_POST['info']['password'])) {
showmessage(L('password_format_incorrect'), HTTP_REFERER);
}
if($this->memberinfo['password'] != password($_POST['info']['password'], $this->memberinfo['encrypt'])) {
showmessage(L('old_password_incorrect'), HTTP_REFERER);
}

//修改会员邮箱
if($this->memberinfo['email'] != $_POST['info']['email'] && is_email($_POST['info']['email'])) {
$email = $_POST['info']['email'];
$updateinfo['email'] = $_POST['info']['email'];
} else {
$email = '';
}
$newpassword = password($_POST['info']['newpassword'], $this->memberinfo['encrypt']);
$updateinfo['password'] = $newpassword;

$this->db->update($updateinfo, array('userid'=>$this->memberinfo['userid']));
if(pc_base::load_config('system', 'phpsso')) {
//初始化phpsso
$this->_init_phpsso();
$res = $this->client->ps_member_edit('', $email, $_POST['info']['password'], $_POST['info']['newpassword'], $this->memberinfo['phpssouid'], $this->memberinfo['encrypt']); //漏洞关键点
$message_error = array('-1'=>L('user_not_exist'), '-2'=>L('old_password_incorrect'), '-3'=>L('email_already_exist'), '-4'=>L('email_error'), '-5'=>L('param_error'));
if ($res < 0) showmessage($message_error[$res]);
}


跟踪ps_member_edit 在phpcms/modules/member/classes/client.class.php中

public function ps_member_edit($username, $email, $password='', $newpassword='', $uid='', $random='') {
if($email && !$this->_is_email($email)) {
return -4;
}
if ((!empty($username) && !is_string($username)) || (!empty($email) && !is_string($email)) || (!empty($password) && !is_string($password)) || (!empty($newpassword) && !is_string($newpassword))) {
return -5;
}
return $this->_ps_send('edit', array('username'=>$username, 'password'=>$password, 'newpassword'=>$newpassword, 'email'=>$email, 'uid'=>$uid, 'random'=>$random));
}


继续跟踪_ps_send

private function _ps_send($action, $data = null) {
return $this->_ps_post($this->ps_api_url."/index.php?m=phpsso&c=index&a=".$action, 500000, $this->auth_data($data));
}


直接发送到/index.php?m=phpsso&c=index&a=edit
继续跟踪_ps_post

private function _ps_post($url, $limit = 0, $post = '', $cookie = '', $ip = '', $timeout = 15, $block = true) {
$return = '';
$matches = parse_url($url);
$host = $matches['host'];
$path = $matches['path'] ? $matches['path'].($matches['query'] ? '?'.$matches['query'] : '') : '/';
$port = !empty($matches['port']) ? $matches['port'] : 80;
$siteurl = $this->_get_url();
if($post) {
$out = "POST $path HTTP/1.1\r\n";
$out .= "Accept: */*\r\n";
$out .= "Referer: ".$siteurl."\r\n";
$out .= "Accept-Language: zh-cn\r\n";
$out .= "Content-Type: application/x-www-form-urlencoded\r\n";
$out .= "User-Agent: $_SERVER[HTTP_USER_AGENT]\r\n";
$out .= "Host: $host\r\n" ;
$out .= 'Content-Length: '.strlen($post)."\r\n" ;
$out .= "Connection: Close\r\n" ;
$out .= "Cache-Control: no-cache\r\n" ;
$out .= "Cookie: $cookie\r\n\r\n" ;
$out .= $post ;
} else {
$out = "GET $path HTTP/1.1\r\n";
$out .= "Accept: */*\r\n";
$out .= "Referer: ".$siteurl."\r\n";
$out .= "Accept-Language: zh-cn\r\n";
$out .= "User-Agent: $_SERVER[HTTP_USER_AGENT]\r\n";
$out .= "Host: $host\r\n";
$out .= "Connection: Close\r\n";
$out .= "Cookie: $cookie\r\n\r\n";
}
$fp = @fsockopen(($ip ? $ip : $host), $port, $errno, $errstr, $timeout);
if(!$fp) return '';

stream_set_blocking($fp, $block);
stream_set_timeout($fp, $timeout);
@fwrite($fp, $out);
$status = stream_get_meta_data($fp);

if($status['timed_out']) return '';
while (!feof($fp)) {
if(($header = @fgets($fp)) && ($header == "\r\n" || $header == "\n")) break;
}

$stop = false;
while(!feof($fp) && !$stop) {
$data = fread($fp, ($limit == 0 || $limit > 8192 ? 8192 : $limit));
$return .= $data;
if($limit) {
$limit -= strlen($data);
$stop = $limit <= 0;
}
}
@fclose($fp);

//部分虚拟主机返回数值有误,暂不确定原因,过滤返回数据格式
$return_arr = explode("\n", $return);
if(isset($return_arr[1])) {
$return = trim($return_arr[1]);
}
unset($return_arr);

return $return;
}


继续上追踪到/phpsso_server/phpcms/modules/phpsso/index.php中
追踪到edit()方法

public function edit() {
$this->email = isset($this->data['email']) ? $this->data['email'] : '';
$this->uid = isset($this->data['uid']) ? $this->data['uid'] : '';
$userinfo = $this->getuserinfo(1);

if (isset($this->data['password']) && !empty($this->data['password'])) {
$this->password = create_password($this->data['password'], $userinfo['random']);
}

$this->random = !empty($this->data['random']) ? $this->data['random'] : $userinfo['random'];
if (isset($this->data['newpassword']) && !empty($this->data['newpassword'])) {
$this->newpassword = create_password($this->data['newpassword'], $this->random);
}
if ($userinfo == -1) {
exit('-1');
}
if (isset($this->password) && !empty($this->password) && $userinfo['password'] != $this->password) {
exit('-2');
}
if ($this->email && $userinfo['email'] != $this->email) {
if($this->checkemail(1) == -1) exit('-3');
}

$data = array();
$data['appname'] = $this->applist[$this->appid]['name'];

if (!empty($this->email) && $userinfo['email'] != $this->email) {
$data['email'] = $this->email;
}
if (isset($this->newpassword) && $userinfo['password'] != $this->newpassword) {
$data['password'] = $this->newpassword;
$data['random'] = $this->random;
}
if (!empty($data)) {

//ucenter部份
if ($this->config['ucuse']) {
pc_base::load_config('uc_config');
require_once PHPCMS_PATH.'api/uc_client/client.php';
$r = uc_user_edit($userinfo['username'], '', (isset($this->data['newpassword']) && !empty($this->data['newpassword']) ? $this->data['newpassword'] : ''), $data['email'],1);
if ($r != 1) {
//{-1:用户不存在;-2:旧密码错误;-3:email已经存在 ;1:成功;0:未作修改}
switch ($r) {
case '-1':
exit('-2');
break;
case '0':
case '-4':
case '-5':
case '-6':
case '-7':
case '-8':
exit('0');
break;
}
}
}
if (empty($data['email'])) unset($data['email']);

/*插入消息队列*/
$noticedata = $data;
$noticedata['uid'] = $userinfo['uid'];
messagequeue::add('member_edit', $noticedata);
if($this->username) {
$res = $this->db->update($data, array('username'=>$this->username));
} else {
$res = $this->db->update($data, array('uid'=>$this->uid));
}
exit("$res");
} else {
exit('0');
}
}


在追踪到该edit()函数之前,其中页面已经pc_base::load_app_class('phpsso', 'phpsso', 0);
调用了phpsso_server/phpcms/modules/phpsso/classes/phpsso.class.php对已加密的数据进行解密
其中该页面当中出现的漏洞即为。

if(isset($_POST['data'])) {
parse_str(sys_auth($_POST['data'], 'DECODE', $this->applist[$this->appid]['authkey']), $this->data);

if(empty($this->data) || !is_array($this->data)) {
exit('0');
}


关键点。。。
parse_str在php高版本当中在解析过程中会对编码进行一次解码,解析过程中会后续存在变量将会覆盖,如
username=111111&password=22222&username=33333,最终为username=33333,password=22222.
根据该特征:
1、通过引入%2527即可带入单引号,即产生注入,甚至getshell。
2、通过username=111111&password=22222&username=33333即可造成前面的变量覆盖,如达到任意用户密码重置

漏洞证明:

一、任意用户密码重置漏洞详细说明如下:
通过$_POST['info']['password']获取的变量设置为:

1-1.png


该值在经过parse_str解析前,应该是info[email]=test@qq.com&info[password]=test1234&info%5Bnewpassword%5D=test1234a%26username%3dtest12345
经过 if(isset($_POST['data'])) {
parse_str(sys_auth($_POST['data'], 'DECODE', $this->applist[$this->appid]['authkey']), $this->data);
由于前面提到的username会覆盖。。。
在/phpsso_server/phpcms/modules/phpsso/index.php第15行中
$this->username = isset($this->data['username']) ? $this->data['username'] : '';
将会产生一个username的变量,即后面可控,即可控制任何人的用户名
在phpsso_server/phpcms/modules/phpsso/index.php中edit方法

….//省略若干代码
$noticedata['uid'] = $userinfo['uid'];
messagequeue::add('member_edit', $noticedata);
if($this->username) {
$res = $this->db->update($data, array('username'=>$this->username));//这里的$this->username即为我们为变量覆盖的值,任意定义,漏洞触发店
} else {
$res = $this->db->update($data, array('uid'=>$this->uid));
}


这里即更改了不属于我们的用户test12345的密码了,这就是任意密码重置漏洞。
二、SQL注入漏洞详细说明如下(注入有多个,前台无需登录也有,会员中心也有):
注入主要是通过%2527经过2次解码进入数据库。。。
涉及SQL注入的方法有login \ public_checkname_ajax \ register \ account_manage_password …..等等,所以你会看到有好多好多的注入。。。哈哈,并且这些SQL注入还可以getshell,因为可以带入单引号进入写shell (前提你得有路径。不过phpcms也有报路径方法的,但是之前的爆路径的方法修复了。)
在这里只说明一处,其他的可以按照这个去发掘。。
我就说一下account_manage_password这个方法,其他的sql注入我就不一一爆了原理一样2次解码%2527带入单引号。。。
我们还是选取account_manage_password方法
我们设置newpassword (新密码)为test123456a%26username%3dtest12345%2527
如图:

1-2.png


其中username进入数据库
我们在mysql.class.php update方法将sql语句记录下来,无法直接回显,因为内部接口通信,所以你直接echo不出来的

$sql = 'UPDATE `'.$this->config['database'].'`.`'.$table.'` SET '.$field.$where;
file_put_contents("fuck4.txt",$sql);//exit();


1-3.png


UPDATE `phpcms`.`v9_sso_members` SET `appname`='phpcms v9',`email`='test12345d67@qq.com',`password`='8a58c2fcafc50bccb9b3b97c1871e31e',`random`='xcnhzq' WHERE `username` = 'test12345''
这个注入点也就是二次注入…..但是这个注入点不是回显的,知道为什么吗?
因为前面已经说了,看看client.class.php中的_ps_send方法(内部通信)
所以这个注入点不回显的,所以利用方式,你就是如何把他变回显,或者直接写shell,延时注入….
后面真正的poc就不再提供,杀人原理已经给出来,杀人工具不再造。。。如果你利用的好,直接sqlmap都可以跑数据,相信自己。。。。
总之围绕这个函数的安全问题就全部总结了,包括任意用户密码重置、SQL注入漏洞、xss、getshell等等

修复方案:

重新对该函数进行修改。。。

版权声明:转载请注明来源 路人甲@乌云


漏洞回应

厂商回应:

危害等级:高

漏洞Rank:15

确认时间:2015-08-04 15:42

厂商回复:

感谢提出

最新状态:

暂无


漏洞评价:

评论

  1. 2015-08-04 15:25 | 小菜虫 ( 路人 | Rank:24 漏洞数:5 )

    老牛逼了

  2. 2015-08-04 15:33 | 剑心 ( 实习白帽子 | Rank:37 漏洞数:9 | xsser)

    老羊逼了

  3. 2015-08-04 15:38 | qhwlpg ( 普通白帽子 | Rank:235 漏洞数:60 | 潜心代码审计。)

    老狗逼了

  4. 2015-08-04 15:40 | Chinalover ( 路人 | Rank:16 漏洞数:4 | 你看得到我打在屏幕上的字,却看不到我落在...)

    老猫逼了

  5. 2015-08-04 15:42 | Annabelle ( 实习白帽子 | Rank:56 漏洞数:16 | .)

    老驴逼了

  6. 2015-08-04 15:48 | 奥利根神棍 ( 路人 | Rank:15 漏洞数:1 | 这个人很懒,现在才来注册。)

    老马*了

  7. 2015-08-04 15:51 | 昌维 ( 路人 | Rank:2 漏洞数:3 | QQ:867597730,百度贴吧ID:昌维001 个人...)

    老子逼了

  8. 2015-08-04 15:56 | xsser_w ( 普通白帽子 | Rank:116 漏洞数:34 | 哎)

    我就是喜欢打断队形, 能告诉我哪个页面吗

  9. 2015-08-04 16:13 | phith0n 认证白帽子 ( 核心白帽子 | Rank:662 漏洞数:108 | 一个想当文人的黑客~)

    漏洞挖挖总还是有的

  10. 2015-08-04 16:16 | 节奏感 ( 路人 | Rank:2 漏洞数:2 | 这地方真合适我 能共同进步 共同奋斗 共同...)

    老草逼了

  11. 2015-08-04 16:22 | 张三 ( 路人 | Rank:2 漏洞数:3 | 张三)

    路人甲XXX

  12. 2015-08-04 16:33 | sOnsec ( 普通白帽子 | Rank:100 漏洞数:27 | 安全是什么...)

    老牛逼了

  13. 2015-08-04 16:34 | xfkxfk 认证白帽子 ( 核心白帽子 | Rank:2199 漏洞数:339 | 呵呵!)

    楼主,你捂了多久了

  14. 2015-08-04 16:48 | 香草 ( 实习白帽子 | Rank:99 漏洞数:14 | javascript,xss,jsp、aspx)

    360就是……

  15. 2015-08-04 16:59 | ppt ( 路人 | Rank:11 漏洞数:2 | ) | ( 我猜出了用户名,可我没猜出密码。)

    猜测是编码的函数

  16. 2015-08-04 17:07 | 白无常 ( 实习白帽子 | Rank:55 漏洞数:7 )

    为啥标题后有3个钱?

  17. 2015-08-04 17:19 | 有归于无 ( 实习白帽子 | Rank:86 漏洞数:16 | 有归于无)

    给跪了

  18. 2015-08-04 19:59 | Elliott ( 实习白帽子 | Rank:44 漏洞数:10 | 绝逼不当程序员)

    占个座!

  19. 2015-08-04 22:05 | 玉林嘎 ( 普通白帽子 | Rank:778 漏洞数:98 )

  20. 2015-08-05 19:16 | Elliott ( 实习白帽子 | Rank:44 漏洞数:10 | 绝逼不当程序员)

    getcache()???

  21. 2015-08-07 16:42 | ( 路人 | Rank:17 漏洞数:4 | -->‮)

    360就是一坨屎

  22. 2015-08-07 16:58 | 不能忍 ( 实习白帽子 | Rank:66 漏洞数:36 | <code><code><code>bug</code></code></cod...)

    6666,尤其是最后一句!

  23. 2015-08-07 17:11 | f4ckbaidu ( 普通白帽子 | Rank:189 漏洞数:25 | 开发真是日了狗了)

    mark

  24. 2015-08-07 23:53 | Stefanie ( 实习白帽子 | Rank:75 漏洞数:9 | 暂无)

    WooYun: phpcmsv9 会员登录中心SQL注入漏洞 这居然还给这么多钱。。。我想说。。这俩漏洞就是一样的。。只是链接里面的这个 发漏洞的人不懂 只放了个exp 分析了过后 就知道是一个漏洞了

  25. 2015-08-08 15:39 | 0c0c0f ( 实习白帽子 | Rank:50 漏洞数:16 | My H34rt c4n 3xploit 4ny h0les!)

    老羊逼了

  26. 2015-08-09 16:37 | Mr.R ( 实习白帽子 | Rank:52 漏洞数:14 | 求大神带我飞 qq2584110147)

    @Stefanie 难道你能看到详情。。。。。

  27. 2015-08-10 09:20 | 天道 ( 普通白帽子 | Rank:204 漏洞数:34 | 我要从攻城狮变身程序猿了!)

    @Mr.R 估计漏洞是他提交的

  28. 2015-08-10 09:37 | tang3 ( 路人 | 还没有发布任何漏洞 | 专业减肥,丰胸)

    完全是重复提交的漏洞,审核人怎么审核的?细节我早就公开了,http://www.tang3.org/blog/2015/07/21/PHPCMS%E7%94%A8%E6%88%B7%E7%99%BB%E9%99%86SQL%E6%B3%A8%E5%85%A5%E6%BC%8F%E6%B4%9E%E5%88%86%E6%9E%90/

  29. 2015-08-10 10:11 | 不能忍 ( 实习白帽子 | Rank:66 漏洞数:36 | <code><code><code>bug</code></code></cod...)

    @tang3 啊大牛屌啊

  30. 2015-08-10 10:18 | wzh ( 路人 | Rank:20 漏洞数:1 | 我了个去)

    老屌逼了

  31. 2015-08-27 16:50 | 胡小树 ( 实习白帽子 | Rank:64 漏洞数:12 | 我是一颗小小树)

    @tang3 tang3大牛。难道这个路人甲,抄袭你的原创? @xsser

  32. 2015-09-06 10:22 | 微尘 ( 普通白帽子 | Rank:218 漏洞数:74 )

    如果这个漏洞跟之前的会员中心注射是一样的话,那么这个漏洞也是个鸡肋漏洞。只有php5.3的情况下可以使用

  33. 2015-09-11 00:31 | 萝董 ( 实习白帽子 | Rank:31 漏洞数:4 | 你们好,我是萝董~QQ:1104360187 ,光交各...)

    老猪逼了

  34. 2015-09-19 17:03 | dr.m1st3r ( 路人 | Rank:23 漏洞数:4 | o)

    mark

  35. 2015-10-08 20:33 | BeenQuiver ( 普通白帽子 | Rank:101 漏洞数:26 | 专注而高效,坚持好的习惯千万不要放弃)

    360就是一坨屎

  36. 2015-10-20 17:29 | goubuli ( 普通白帽子 | Rank:338 漏洞数:67 )

    @tang3 确实是一样的,http://www.tang3.org/blog/2015/07/21/PHPCMS%E7%94%A8%E6%88%B7%E7%99%BB%E9%99%86SQL%E6%B3%A8%E5%85%A5%E6%BC%8F%E6%B4%9E%E5%88%86%E6%9E%90/