PHPCMS V9 /phpcms/modules/vote/index.php 代码执行漏洞

2015-04-14T00:00:00
ID SSV:89119
Type seebug
Reporter k0shl
Modified 2015-04-14T00:00:00

Description

<p>漏洞影响版本:phpcms v9.5.8</p><p>漏洞分析:hpsso/index.php文件所有的操作都存在严重的注入问题,这个类文件的构造函数最先调用它的父构造函数,通过auth_key来解析POST传入的data内容,解析后data中的内容会作为注册、登陆、删除用户等操作的内容依据,而这些操作都会将这些数据作为数据库查询语句使用。这个问题其实在XXX的《PHPCMS V9 最新版本配置文件未授权访问读取》中已经体现出来了,不过他仅仅只是分析了信息泄露的问题,而忽略的他利用所使用的注入问题。</p><p>我们以phpsso的login流程为例来看这个问题,先看phpsso的解析数据部分的代码:</p><pre class="lang-php" data-lang="php">if(isset($_POST['appid'])) { $this->appid = intval($_POST['appid']); } else { exit('0'); }

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'); </pre><div class="simditor-table"><div class="simditor-resize-handle" contenteditable="false" style="display: block; left: 353px;"></div></div><p>在auth_key解码之后使用parse_str解析成数组格式,这段代码如果在php5.3之前的情况下是没有问题的,因为默认情况下parse_str会启动gpc机制对特殊字符进行转义。但是在php5.3之后gpc机制默认就关闭掉了,这就导致如果解析出来的内容如果带有单引号这类个特殊字符,就原封不动的放到的变量中,这导致了注入的风险。下面我们简单来看一下login行为的代码:</p><div class="simditor-table"><div class="simditor-resize-handle" contenteditable="false" style="left: 353px; display: none;"></div></div><pre class="lang-php" data-lang="php">public function login() { $this->password = isset($this->data['password']) ? $this->data['password'] : ''; $this->email = isset($this->data['email']) ? $this->data['email'] : ''; if($this->email) { $userinfo = $this->db->get_one(array('email'=>$this->email)); } else { $userinfo = $this->db->get_one(array('username'=>$this->username)); }

if ($this->config['ucuse']) { pc_base::load_config('uc_config'); require_once PHPCMS_PATH.'api/uc_client/client.php'; list($uid, $uc['username'], $uc['password'], $uc['email']) = uc_user_login($this->username, $this->password, 0); }

if($userinfo) { //ucenter登陆部份 if ($this->config['ucuse']) { if($uid == -1) { //uc不存在该用户,调用注册接口注册用户 $uid = uc_user_register($this->username , $this->password, $userinfo['email'], $userinfo['random']); if($uid >0) { $this->db->update(array('ucuserid'=>$uid), array('username'=>$this->username)); } } } </pre><p>所有$this->data的内容没有经过任何处理就直接参数到数据库查询当中,如果我们有auth_key的话,完全可以构造带有恶意的内容提交造成SQL注入漏洞。那么如果没有auth_key怎么办呢?我们可以让使用auth_key的页面帮我们编码,甚至帮助我们提交。因此下面这个看似无关紧要的问题,就成了导致注入必不可少的一步。</p><p>说了这么多,终于可以开始入正题了。我们来跟一下登陆的流程,先看member/index.php文件中的login方法:</p><pre class="lang-php" data-lang="php"> $username = isset($_POST['username']) && is_username($_POST['username']) ? trim($_POST['username']) : showmessage(L('username_empty'), HTTP_REFERER); $password = isset($_POST['password']) && trim($_POST['password']) ? trim($_POST['password']) : showmessage(L('password_empty'), HTTP_REFERER); $cookietime = intval($_POST['cookietime']); $synloginstr = ''; //同步登陆js代码

  if(pc_base::load_config('system', 'phpsso')) {
      $this-&gt;_init_phpsso();
      $status = $this-&gt;client-&gt;ps_member_login($username, $password);
      $memberinfo = unserialize($status);

</pre><div class="simditor-table"><div class="simditor-resize-handle" contenteditable="false"></div></div><p>通过这段代码我们需要注意到三个地方,首先是username使用的is_username进行了过滤而password没有做任何处理,然后通过client的ps_member_login方法获取一段数据。最需要关注的是最后一个地方,之后所有操作的内容就完全使用的返回的这套数据。</p><p>下面我们继续来看ps_member_login这个方法的代码:</p><div class="simditor-table"><div class="simditor-resize-handle" contenteditable="false"></div></div><pre class="lang-php" data-lang="php">public function ps_member_login($username, $password, $isemail=0) { if($isemail) { if(!$this->_is_email($username)) { return -3; } $return = $this->_ps_send('login', array('email'=>$username, 'password'=>$password)); } else { $return = $this->_ps_send('login', array('username'=>$username, 'password'=>$password)); } return $return; }

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)); } </pre><p>可以看出使用_ps_post这个方法向phpsso机制的请求login行为,也就是说member的认证其实是有phpsso来完成的。而phpsso的认证数据是需要auth_key编码的,那么这个过程就很直接的呈现在我们眼前:登录用户提交用户名和密码给menber的login,然后member的login通过ps_member_login构造发送phpsso请求login验证的http包,并且将用户名和密码使用auth_key进行编码,作为http包的post数据,phpsso认证完成后,将用户的信息返回给member的login进行后续处理。</p><p>上面的这个过程中我们需要牢记的一点,就是password没有做任何处理。带着这一点,我们再回头看phpsso的login注入问题点,就可以很明确的发现通过password能够造成注入问题。</p><p>我们在这里简单总结下漏洞原因,首先member的login没有对password做过滤便带入到phpsso的login中进行验证,然后phpsso没有对于解码数据进行过滤,从而导致SQL注入。</p>