一:discuz!登陆验证Cookie机制分析
1 e% w' K) W- U' t m! W8 T 在构建登录验证的过程中,发现管理员管理的便捷与系统安全隐患之间的矛盾.全站采用cookie验证,比如wordpress的验证就是基于cookie的,由于cookie的明文传输,在局域网内极易被截获,或者这个vita在我不发骚的情况下存在了XSS漏洞的话,cookie被人截获,在这种情况下,等于站点被人xxoo了;另一种情况就是利用session来进行管理员身份的认证,但是由于php天生对于session的处理机制的问题,不能长时间保存,利用数据库构建的session系统开销太大,在这种情况下,我就只好先研究先下大家是怎么做的,于是分析了Discuz!的登陆验证机制.每个Discuz!论坛都有一个特定的authkey也就是Discuz!程序中的$_DCACHE[’settings’][’authkey’]并且与用户的浏览器特征值HTTP_USER_AGENT一起组成了discuz_auth_key这个变量如下代码:
; i- q( b; ]& l) J( o3 ?commone.inc.php文件大概130行左右
- D, `7 [6 x0 v5 \* X. `9 B$ G5 HPHP代码
( I6 j$ `- w/ ?# u( e& G- $discuz_auth_key = md5($_DCACHE['settings']['authkey'].$_SERVER['HTTP_USER_AGENT']); , T% O0 K$ ^% A9 O
& M8 B, A% `9 z4 U) Z; S! E0 t
在Discuz!论坛用户登陆以后会有一个cookie,名称为cdb_auth(cdb_是你站点的名称,可以设置不能在config.inc.php文件中设置),Discuz!论坛就靠这个来判断一个用户是否是登陆状态,在分析这个值的内容之前,我们看下他是如何生成的 PHP代码
! r0 K3 d6 Q$ G: [3 i+ h- list($discuz_pw, $discuz_secques, $discuz_uid) = empty($_DCOOKIE['auth']) ? array('', '', 0) : daddslashes(explode("\t", authcode($_DCOOKIE['auth'], 'DECODE')), 1);
! p, A3 B! V. |% g' |8 W' t9 B* F: v 5 Q8 [$ }8 V- q8 m; ?9 R
解释一下,获得的客户端的cookie经过Discuz!的函数authcode解密以后会得到用户输入的用户名,密码,在authcode函数中会用到刚刚提到的$discuz_auth_key这个值,在不知道$discuz_auth_key的情况下,基本上靠cookie里的值反解出用户名密码的几率为0,同样的,在生成cdb_auth就是相逆的一个流程,先获得用户输入的用户名,密码,在验证正确之后,用authcode加密,写入cookie,很简单吧.1 X, i7 ]: ]! P
以上就是Discuz!普通用户的登陆验证过程,写的不是很详细,大概能看明白就行. 二:DISCUZ COOKIE r/ y8 V6 n4 ~: D4 f; v* H, W
' w/ [, s; v8 q( w! K很多人都在用discuz 并会因为种种的需要结合discuz做各种系统.
" y7 a6 C1 l1 i$ d对此,discuz提供了通行证 可有时候使用通行证不一定适用 就比如说那一段很长很长的url吧 看着就让人心烦
: ~+ G' D q O5 u6 ^' J! j2 |$ B有什么解决方法捏?为了社区朋友们以后方便开发 这几天呕心沥血 肝肠寸断 孜孜不倦 终于让偶给想出了一个比较天才的想法
6 e( r) ?- {1 G" @' Q:hoho 那就是生成一个discuz 验证cookie . X" J/ @* Q2 E0 ]/ D& {& H
废话说完了 开始正题:
% z- Z/ a* h0 T1 Z3 K" [: v开始之前 咱先了解一下discuz的登陆机制 % ~# g8 Q4 t+ \" o V
先来一个简单的form XML/HTML代码) ~. B5 A4 M5 ?
- <form action='i_login.php'>
-
- user:<input name='username' type='text'>
-
- password:<input name='password' type='password'>
-
- <input type='submit' value='login'>
-
- </form> + k) x; S+ O' a ?' @- ~
5 p5 m3 |) I: J) R9 Q: i
OK,一个很实用的登录框已经完成。。。 ( j0 q" T& ~2 @+ s8 t
下面 说下php方面的 我们假设改文件和discuz同一目录里
+ \4 t9 P r" k" p6 ?" s$ N看代码 & u& I& C4 ^$ c% c7 }* V2 ^; J6 d
i_login.php PHP代码
3 W+ J$ s: S0 b4 \8 J3 U- ? y- <?php
- /**
- * 加载discuz的入口文件 当然你也可以不加 不过你得提出几个必要的函数
- */
-
- require_once './include/comon.inc.php';
-
- $formUsername = trim($username); // 接受POST传来的$username
- $formPassword = trim($password); // 接受POST传来的$password
-
- /**
- * 说下secques
- * secques是论坛里的安全回答经过编码后提取的一段字符 虽然我们登陆的时候不会用到它
- * 但是生成COOKIE呢 它还是不可或缺滴
- */
- $query = $db->query("SELECT uid,password,secques FROM cdb_members WHERE username='$formUsername'");
- $member = $db->fetch_array($query);
-
- // 验证用户密码是否匹配
-
- if($member['password'] == md5($formPassword))
- {
- // 已经验证用户密码都是匹配的 下面比较关键的就是生成cookie了 可要仔细看好了
- /*
- 先说下dsetcookie函数 这是dz的一个设置cookie的函数 可以在include/global.func.php里查看
- 通常只需要三个参数即可 第一个为cookie键 第二个为键值 第三个为cookie有效时间 这里我就随便设置一个
- 再说 authcode 此为加密函数,俺一直用这个函数,很强大,很难破解,有兴趣的朋友也可在include/global.func.php里
- 找到该函数,研究一下
- */
- dsetcookie('sid','',-2423234234); // 注销掉sid
- dsetcookie('auth', authcode("$formPassword\t$member['secques']\t$member['uid']", 'ENCODE'), '1234243');
- // OK 关键步骤 我们都已经完成了 下面就由你写一个header跳转到论坛首页看是否登陆了 我本地测试可以 你有问题的话那可就是
- // RPWT了(人品问题)~~
- header("location:/index.php");
- }
- else
- {
- // 我们这里返回一个错误信息 告诉那个用户密码错误
- }
- ?> , ]+ \5 d) S; ]7 r; X( |
! H2 ?& R' S+ n% E' F) U
好 一个简单的登陆验证就完成了
' }9 O9 E( L7 s0 r" u& \如果你不想加载common.inc.php文件的话 你需要提取 1 h! t8 k. g9 V- A% L9 R
authcode 和dsetcookie函数 都比较简单哈 / t# {, K' ~: {2 |8 D
OK 我们继续说下discuz验证部分 没兴趣的可以跳过去
( U$ i4 d. ]. L7 {& _6 L, Y9 O8 s* }验证部分 在common.inc.php 大约119行 开始 PHP代码
) H3 T9 P3 w; `1 l; d) F- // 拆解COOKIE auth
-
- list($discuz_pw, $discuz_secques, $discuz_uid) = isset($_DCOOKIE['auth']) ? explode("\t", authcode($_DCOOKIE['auth'], 'DECODE')) : array('', '', 0);
-
- $discuz_pw = addslashes($discuz_pw);
-
- $discuz_secques = addslashes($discuz_secques);
-
- $discuz_uid = intval($discuz_uid);
-
- // 不存在$_DCOOKIE['auth']的话 就直接清楚COOKIE
-
- if(isset($_DCOOKIE['auth']) && !$discuz_uid) {
-
- clearcookies();
-
- }
-
- $newpm = $newpmexists = $sessionexists = $seccode = $bloguid = 0;
-
- if($sid) {
-
- //存在$discuz_uid的话 根据uid取出用户信息 否则根据sid
-
- if($discuz_uid) {
-
- // 取用户信息
-
- } else {
-
- // 用sessions表中取用户信息
-
- }
-
- // 下面就是验证了 我不写了 (sid是用户的一个sessionid 用户登陆后会在sessions表内生成一条用户记录 用户推出后该记录删除 q2 X$ v/ r* B/ H+ P3 \/ l5 j+ [
6 Q2 j) I5 l& b; }8 X
三:Discuz 用户信息加密算法-authcode加密/解密函数详解
4 d, u5 r1 \0 K6 d$ g+ u* _! v) j; }; x; V3 x7 t
Discuz论坛是国内广泛使用的开源论坛,论坛的安全体系构筑严谨,尤其是对用户信息加密的部分。) b/ l8 J# F W7 X8 m$ v
用户信息包括用户名、密码、所属组别等,这些信息经过加密并保存在Cookie里面 ,服务器端读取Cookie信息,并解迷。
8 ]2 z; g) `$ `2 B2 F8 g0 y; v 作为对比,国内另一个基于Perl的论坛-----雷傲BBS,早期版本中,用户名以明文形式保存在Cookie里,密码经过一次md5加密后保存在Cookie里。这样,万一论坛存在跨站漏洞,或其他方式导致管理员密码信息泄露,会对论坛构成威胁。! w3 m$ G9 i; z! J, V
用户信息的加密解密都是在一个函数中实现的,函数定义为: | PHP代码* Z0 q6 t1 p4 W8 `( A- N" ?1 ?
- /**
-
- *用户信息加密解密函数
-
- *
-
- *待加密内容用\t分割
-
- *@return String 加密或解密字符串
-
- *@param String $string 待加密或解密字符串
-
- *@param String $operation 操作类型定义 DECODE=解密 ENDODE=加密
-
- *@param String $key 加密算子
-
- */
-
- function authcode($string, $operation, $key = '') {
-
- /**
-
- *获取密码算子,如未指定,采取系统默认算子
-
- *默认算子是论坛授权码和用户浏览器信息的md5散列值
-
- *$GLOBALS['discuz_auth_key']----全局变量
-
- *取值为:md5($_DCACHE['settings']['authkey'].$_SERVER['HTTP_USER_AGENT'])
-
- *$_DCACHE['settings']['authkey']是论坛安装时生成的15位随机字符串
-
- */
-
- $key = md5($key ? $key : $GLOBALS['discuz_auth_key']);
-
- $key_length = strlen($key);
-
- /**
-
- *如果解密,先对密文解码
-
- *如果加密,将密码算子和待加密字符串进行md5运算后取前8位
-
- *并将这8位字符串和待加密字符串连接成新的待加密字符串
-
- */
-
- $string = $operation == 'DECODE' ? base64_decode($string) : substr(md5($string.$key), 0, 8).$string;
-
- $string_length = strlen($string);
-
- $rndkey = $box = array();
-
- $result = '';
-
-
-
- /**
-
- *初始化加密变量,$rndkey和$box
-
- */
-
- for($i = 0; $i <= 255; $i++) {
-
- $rndkey[$i] = ord($key[$i % $key_length]);
-
- $box[$i] = $i;
-
- }
-
-
-
- /**
-
- *$box数组打散供加密用
-
- */
-
- for($j = $i = 0; $i < 256; $i++) {
-
- $j = ($j + $box[$i] + $rndkey[$i]) % 256;
-
- $tmp = $box[$i];
-
- $box[$i] = $box[$j];
-
- $box[$j] = $tmp;
-
- }
-
-
- /**
-
- *$box继续打散,并用异或运算实现加密或解密
-
- */
-
- for($a = $j = $i = 0; $i < $string_length; $i++) {
-
- $a = ($a + 1) % 256;
-
- $j = ($j + $box[$a]) % 256;
-
- $tmp = $box[$a];
-
- $box[$a] = $box[$j];
-
- $box[$j] = $tmp;
-
- $result .= chr(ord($string[$i]) ^ ($box[($box[$a] + $box[$j]) % 256]));
-
- }
-
- if($operation == 'DECODE') {
-
- if(substr($result, 0, 8) == substr(md5(substr($result, 8).$key), 0, 8)) {
-
- return substr($result, 8);
-
- } else {
-
- return '';
-
- }
-
- } else {
-
- return str_replace('=', '', base64_encode($result));
-
- }
-
- } , A2 S, g* a. A! j- [9 G
% E4 u3 u0 |0 Y( h; z |