记录黑客技术中优秀的内容, 传播黑客文化,分享黑客技术精华

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

2015-11-02 16:30

本文当中所有安全问题都将围绕parse_str()这个函数展开,parse_str函数在php 版本当中,在对变量进行解析过程中,有解码、以及变量后置等安全问题,这里不啰嗦,看问题。

任意密码重置:

在/phpcms/modules/member/index.php中account_manage_password方法

code 区域
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中

code 区域
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

code 区域
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

code 区域
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()方法

code 区域
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对已加密的数据进行解密

其中该页面当中出现的漏洞即为。

code 区域
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]=[email protected] &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方法

code 区域
….//省略若干代码

$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不出来的

code 区域
$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`='[email protected] ',`password`='8a58c2fcafc50bccb9b3b97c1871e31e',`random`='xcnhzq' WHERE `username` = 'test12345''

这个注入点也就是二次注入…..但是这个注入点不是回显的,知道为什么吗?

因为前面已经说了,看看client.class.php中的_ps_send方法(内部通信)

所以这个注入点不回显的,所以利用方式,你就是如何把他变回显,或者直接写shell,延时注入….

后面真正的poc就不再提供,杀人原理已经给出来,杀人工具不再造。。。如果你利用的好,直接sqlmap都可以跑数据,相信自己。。。。



总之围绕这个函数的安全问题就全部总结了,包括任意用户密码重置、SQL注入漏洞、xss、getshell等等







修复方案:

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

知识来源: www.wooyun.org/bugs/wooyun-2015-0131548

阅读:134250 | 评论:0 | 标签:注入 cms 漏洞 phpcms

想收藏或者和大家分享这篇好文章→复制链接地址

“phpcms一个函数引起的安全漏洞(任意密码重置、多个SQL注入、甚至getshell)”共有0条留言

发表评论

姓名:

邮箱:

网址:

验证码:

公告

关注公众号hackdig,学习最新黑客技术

推广

工具

标签云