问题的原有来自于最近公司旗下一个论坛需要进入ucenter的管理后台进行一些设置,却发现登录后立即跳转到重新登录界面了。
具体的分析测试流程就不扯了,直接就说引起问题的原因吧!
当然,之所以不说是bug,是因为这是一个和php设置相关的配合性问题引发的,具体的就看下去吧。
在ucenter 20141101 发布版本(随着Discuz! X2.5~X3.2 20141225补丁一起发布)里面,可能是为了禁止采用cookie方式做登录验证吧,对
model/admin.php做了如下修改
- function adminbase() {
- parent::__construct();
- $this->cookie_status = 0;
- $sid = $this->cookie_status ? getgpc('sid', 'C') : rawurlencode(getgpc('sid', 'R'));
- $this->sid = $this->view->sid = $this->sid_decode($sid) ? $sid : '';
- $this->view->assign('sid', $this->view->sid);
- $this->view->assign('iframe', getgpc('iframe'));
- $a = getgpc('a');
- if(!(getgpc('m') =='user' && ($a == 'login' || $a == 'logout'))) {
- $this->check_priv();
- }
- }
复制代码 其中 $this->cookie_status = 0; ,不再判断cookie里面是否存在sid,而是强制为0,即不打算从cookie获取sid了,只从 $_REQUEST 里面获取sid。
然而,这个修改却忽略了一个问题,那就是 php.ini 里面的一个设置:
其解释为:
- ; This directive determines which super global data (G,P,C,E & S) should
- ; be registered into the super global array REQUEST. If so, it also determines
- ; the order in which that data is registered. The values for this directive are
- ; specified in the same manner as the variables_order directive, EXCEPT one.
- ; Leaving this value empty will cause PHP to use the value set in the
- ; variables_order directive. It does not mean it will leave the super globals
- ; array REQUEST empty.
- ; Default Value: None
- ; Development Value: "GP"
- ; Production Value: "GP"
- ; http://php.net/request-order
复制代码
简单来说,$_REQUEST的值来自于这个设置,也就是相当于 执行了 array_merge($_GET, $_POST);
这里注意了,如果这个设置是
那就相当于是
array_merge($_GET, $_POST, $_COOKIE);
了。
此时键值为字符串的话,就会按照 cookie 覆盖 post, 之后覆盖 get 的同键值
而且,在废弃从cookie获取值的时候,在
control/admin/user.php的登录中
- if($errorcode == 0) {
- $this->setcookie('sid', $this->view->sid, 86400);
复制代码 对cookie保存sid并没有取消。
不知道各位看官有没有注意到第一段代码中的这一行:
- $sid = $this->cookie_status ? getgpc('sid', 'C') : rawurlencode(getgpc('sid', 'R'));
复制代码 其中
中的值来自于 R,那么R是什么呢,再看 根目录下 admin.php
- function getgpc($k, $t='R') {
- switch($t) {
- case 'P': $var = &$_POST; break;
- case 'G': $var = &$_GET; break;
- case 'C': $var = &$_COOKIE; break;
- case 'R': $var = &$_REQUEST; break;
- }
- return isset($var[$k]) ? (is_array($var[$k]) ? $var[$k] : trim($var[$k])) : NULL;
- }
复制代码
R代表的来自于$_REQUEST
从这里可以很清楚的知道,虽然不打算从$_COOKIE获取值了,但是当php.ini的设置为GPC的时候,sid的值依然是从 $_COOKIE 获取的。
上面,我们分析来用于验证的sid的值的来源,那么竟然为什么无法登录的原因我们继续分析
从 control/admin/user.php里面可以知道
- else {
- $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'");
- if(!empty($admin)) {
- $md5password = md5(md5($password).$admin['salt']);
- if($admin['password'] == $md5password) {
- $this->view->sid = $this->sid_encode($admin['username']);
- } else {
- $errorcode = UC_LOGIN_ERROR_ADMIN_PW;
- }
- } else {
- $errorcode = UC_LOGIN_ERROR_ADMIN_NOT_EXISTS;
- }
- }
复制代码 保存到cookie的值做了sid_encode处理,再看这个方法的原型(model/admin.php):
- function sid_encode($username) {
- $ip = $this->onlineip;
- $agent = $_SERVER['HTTP_USER_AGENT'];
- $authkey = md5($ip.$agent.UC_KEY);
- $check = substr(md5($ip.$agent), 0, 8);
- return rawurlencode($this->authcode("$username\t$check", 'ENCODE', $authkey, 1800));
- }
复制代码 可以很清楚的看到,做了 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
- $sid = $this->cookie_status ? getgpc('sid', 'C') : rawurlencode(getgpc('sid', 'R'));
复制代码 替换为
- $sid = getgpc('sid', 'P') ? rawurlencode(getgpc('sid', 'P')) : rawurlencode(getgpc('sid', 'G'));
复制代码
|