暖冬的源码分享

 找回密码
 立即加入

QQ登录

只需一步,快速开始

搜索
热搜: 巧借
查看: 4532|回复: 0

Ucenter更新到20141101Release版本后管理后台无法登录的问题分析

[复制链接]
发表于 2015-1-21 18:00:00 | 显示全部楼层 |阅读模式
问题的原有来自于最近公司旗下一个论坛需要进入ucenter的管理后台进行一些设置,却发现登录后立即跳转到重新登录界面了。
具体的分析测试流程就不扯了,直接就说引起问题的原因吧!
当然,之所以不说是bug,是因为这是一个和php设置相关的配合性问题引发的,具体的就看下去吧。

在ucenter 20141101 发布版本(随着Discuz! X2.5~X3.2 20141225补丁一起发布)里面,可能是为了禁止采用cookie方式做登录验证吧,对
model/admin.php做了如下修改


  1.         function adminbase() {
  2.                 parent::__construct();
  3.                 $this->cookie_status = 0;
  4.                 $sid = $this->cookie_status ? getgpc('sid', 'C') : rawurlencode(getgpc('sid', 'R'));
  5.                 $this->sid = $this->view->sid = $this->sid_decode($sid) ? $sid : '';
  6.                 $this->view->assign('sid', $this->view->sid);
  7.                 $this->view->assign('iframe', getgpc('iframe'));
  8.                 $a = getgpc('a');
  9.                 if(!(getgpc('m') =='user' && ($a == 'login' || $a == 'logout'))) {
  10.                         $this->check_priv();
  11.                 }
  12.         }
复制代码
其中  $this->cookie_status = 0; ,不再判断cookie里面是否存在sid,而是强制为0,即不打算从cookie获取sid了,只从 $_REQUEST 里面获取sid。

然而,这个修改却忽略了一个问题,那就是 php.ini 里面的一个设置:
  1. request_order = "GP"
复制代码
其解释为:
  1. ; This directive determines which super global data (G,P,C,E & S) should
  2. ; be registered into the super global array REQUEST. If so, it also determines
  3. ; the order in which that data is registered. The values for this directive are
  4. ; specified in the same manner as the variables_order directive, EXCEPT one.
  5. ; Leaving this value empty will cause PHP to use the value set in the
  6. ; variables_order directive. It does not mean it will leave the super globals
  7. ; array REQUEST empty.
  8. ; Default Value: None
  9. ; Development Value: "GP"
  10. ; Production Value: "GP"
  11. ; http://php.net/request-order
复制代码

简单来说,$_REQUEST的值来自于这个设置,也就是相当于 执行了 array_merge($_GET, $_POST);
这里注意了,如果这个设置是
  1. request_order = "GPC"
复制代码
那就相当于是
array_merge($_GET, $_POST, $_COOKIE);
了。

此时键值为字符串的话,就会按照 cookie 覆盖 post, 之后覆盖 get 的同键值
而且,在废弃从cookie获取值的时候,在
control/admin/user.php的登录中


  1.                                         if($errorcode == 0) {
  2.                                                 $this->setcookie('sid', $this->view->sid, 86400);
复制代码
对cookie保存sid并没有取消。

不知道各位看官有没有注意到第一段代码中的这一行:
  1. $sid = $this->cookie_status ? getgpc('sid', 'C') : rawurlencode(getgpc('sid', 'R'));
复制代码
其中
  1. getgpc('sid', 'R')
复制代码
中的值来自于 R,那么R是什么呢,再看 根目录下 admin.php
  1. function getgpc($k, $t='R') {
  2.         switch($t) {
  3.                 case 'P': $var = &$_POST; break;
  4.                 case 'G': $var = &$_GET; break;
  5.                 case 'C': $var = &$_COOKIE; break;
  6.                 case 'R': $var = &$_REQUEST; break;
  7.         }
  8.         return isset($var[$k]) ? (is_array($var[$k]) ? $var[$k] : trim($var[$k])) : NULL;
  9. }
复制代码

R代表的来自于$_REQUEST

从这里可以很清楚的知道,虽然不打算从$_COOKIE获取值了,但是当php.ini的设置为GPC的时候,sid的值依然是从 $_COOKIE 获取的。

上面,我们分析来用于验证的sid的值的来源,那么竟然为什么无法登录的原因我们继续分析

从 control/admin/user.php里面可以知道
  1. else {
  2.                                                 $admin = $this->db->fetch_first("SELECT a.uid,m.username,m.salt,m.password FROM ".UC_DBTABLEPRE."admins a LEFT JOIN ".UC_DBTABLEPRE."members m USING(uid) WHERE a.username='$username'");
  3.                                                 if(!empty($admin)) {
  4.                                                         $md5password =  md5(md5($password).$admin['salt']);
  5.                                                         if($admin['password'] == $md5password) {
  6.                                                                 $this->view->sid = $this->sid_encode($admin['username']);
  7.                                                         } else {
  8.                                                                 $errorcode = UC_LOGIN_ERROR_ADMIN_PW;
  9.                                                         }
  10.                                                 } else {
  11.                                                         $errorcode = UC_LOGIN_ERROR_ADMIN_NOT_EXISTS;
  12.                                                 }
  13.                                         }
复制代码
保存到cookie的值做了sid_encode处理,再看这个方法的原型(model/admin.php):


  1.         function sid_encode($username) {
  2.                 $ip = $this->onlineip;
  3.                 $agent = $_SERVER['HTTP_USER_AGENT'];
  4.                 $authkey = md5($ip.$agent.UC_KEY);
  5.                 $check = substr(md5($ip.$agent), 0, 8);
  6.                 return rawurlencode($this->authcode("$username\t$check", 'ENCODE', $authkey, 1800));
  7.         }
复制代码
可以很清楚的看到,做了 rawurlencode 处理。

这里先说明一下:authcode采用的是base64encode,其中可能产生带有 + 号的特殊结果

如果出现这种情况,rawurlencode之后,除 - / 之外的非字母数字会编码为 %hex 的形式,如 + 号被编码为 %2B (后续用这个做示例处理)

此时cookie保存的值以及URI的地址sid都是这个编码后的字符串

但是,因为浏览器再请求的时候,会对 URI 里面的值进行 urldecode 处理,因为上述的值被还原了。

在PHP端接收到的 $_COOKIE['sid'] != $_GET['sid'] 了
在第一段代码中很清楚的看到,从$_REQUEST获取的值再次进行了rawurlencode处理。


此时,当php.ini的使之 request_order = "GPC"时, 获取的值本身就不是  rawurldecode 的值,再次rawurlencode处理之后:
%号会再次被编码为 %25,和原始字符串已经发生了变化

当进行sid_decode时(model/admin.php)时,只能还原%25到 %,无法将 %2B还原为 + 号了,因此执行authcode的DECODE时,base64decode无法正确解码了。

此时,整个验证宣告失败!!

修正方式:

有效的修正方案(2015年03月18日修正):

修改 model/admin.php
  1. $sid = $this->cookie_status ? getgpc('sid', 'C') : rawurlencode(getgpc('sid', 'R'));
复制代码
替换为

  1. $sid = getgpc('sid', 'P') ? rawurlencode(getgpc('sid', 'P')) : rawurlencode(getgpc('sid', 'G'));
复制代码















回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即加入

本版积分规则

手机版|小黑屋|享码网 ( 京ICP备12003721号 )

GMT+8, 2024-3-29 19:42

Powered by Discuz! X3.5

Copyright © 2001-2021 Tencent Cloud.

快速回复 返回顶部 返回列表