由正则引起的 Wecenter 拒绝服务漏洞

本文讲的是由正则引起的 Wecenter 拒绝服务漏洞,一年前在前公司搭建了一个wecenter程序的社区,忽然有一天发现社区打开首页都会超时,后面排查发现是php超时了,当时知道是文章引起的,但是手上还有其他项目在写,就没去跟,把文章删了就没去管他了。  直到前两个星期再次发现了这种问题,刚好手上也没什么事情,就抽空去跟了下代码。

漏洞形成原因:

文章内容65k个字符,字符串太大去匹配贪婪模式,导致php timeout,看看文章的字段类型.

跟下代码:

入口文件 index.php 大概23行:

AWS_APP::run();

继续跟进去 /system/aws_app.inc.php 只看关键代码 大概104行:

$handle_controller->$action_method();

/app/article/main.php  关键代码 大概36行:

public function index_action()
    {
        //...省略部分代码
        // $article_info['message'] 是文章内容
        $article_info['message'] = FORMAT::parse_attachs(nl2br(FORMAT::parse_bbcode($article_info['message'])));
        //...省略部分代码
        TPL::output('article/index');
    }

跟进去看看FORMAT::parse_bbcode 对文章内容做了什么操作:

/system/class/cls_format.inc.php 大概78行:

public static function parse_bbcode($text)
{
    if (!$text)
    {
        return false;
    }
    return self::parse_links(load_class('Services_BBCode')->parse($text));
}

感觉wecenter的版本挺乱的。

这里可以用echo rand();exit;来调试了

继续跟进去self::parse_links :

public static function parse_links($str)
    {
        $str = @preg_replace_callback('/(?<!!![](|"|'|)|>)(https?://[-a-zA-Z0-9@:;%_+.~#?&//=!]+)(?!"|'|)|>)/i', 'parse_link_callback', $str);
        if (strpos($str, 'http') === FALSE)
        {
            $str = @preg_replace_callback('/(www.[-a-zA-Z0-9@:;%_+.~#?&//=]+)/i', 'parse_link_callback', $str);
        }
        // 经过调试发现 问题在这一行。传入的$str的字节大概6w左右,这里用到了贪婪模式 - - 这个地方的已经修复了:https://github.com/wecenter/wecenter/commit/177a9e8bab6aec8725f258df02f8f214e5b2469c
        $str = @preg_replace('/([a-z0-9+_-]+[.]?[a-z0-9+_-]+@[a-z0-9-]+.+[a-z]{2,6}+(.+[a-z]{2,6})?)/is', '<a href="mailto:1">1</a>', $str);
        echo rand();exit;
        return $str;
    }

把 preg_replace 里面的正则暂时改成w,发现还是php还是存在timeout,继续跟代码。

再回到 /app/article/main.php 的 index_action 函数的最后一行 TPL::output('article/index'); 

/system/class/cls_template.inc.php 下的 output 函数,大概56行:

$display_template_filename = 'default/' . $template_filename;
/*省略部分代码*/
$output = self::$view->getOutput($display_template_filename);

看看 self::$view 怎么来的:

/system/class/cls_template.inc.php 下的 initialize 函数:

public static function initialize()
    {
        if (!is_object(self::$view))
        {
            self::$template_path = realpath(ROOT_PATH . 'views/');
            self::$view = new Savant3(
                array(
                    'template_path' => array(self::$template_path),
                    //'filters' => array('Savant3_Filter_trimwhitespace', 'filter')
                )
            );
            if (file_exists(AWS_PATH . 'config.inc.php') AND class_exists('AWS_APP', false))
            {
                self::$in_app = true;
            }
        }
        return self::$view;
}

跟进去 self::$view->getOutput 看看$output的值是什么

/system/Savant3.php 大概1004行:

public function getOutput($tpl = null)
    {
        $output = $this->fetch($tpl);
        if ($this->isError($output)) {
            $text = $this->__config['error_text'];
            return $this->escape($text);
        } else {
            return $output;
        }
    }

$this->fetch  能看到他include了模板,并且把内容return了出去

 public function fetch($tpl = null)
    {
       
        // 省略部分代码      
        } else {
            // yes.  execute the template script.  move the script-path
            // out of the local scope, then clean up the local scope to
            // avoid variable name conflicts.
            $this->__config['fetch'] = $result;
            unset($result);
            unset($tpl);
            // are we doing extraction?
            if ($this->__config['extract']) {
                // pull variables into the local scope.
                extract(get_object_vars($this), EXTR_REFS);
            }
            // buffer output so we can return it instead of displaying.
            ob_start();
            // are we using filters?
            if ($this->__config['filters']) {
                // use a second buffer to apply filters. we used to set
                // the ob_start() filter callback, but that would
                // silence errors in the filters. Hendy Irawan provided
                // the next three lines as a "verbose" fix.
                ob_start();
                include $this->__config['fetch'];
                echo $this->applyFilters(ob_get_clean());
            } else {
                // no filters being used.
                include $this->__config['fetch'];
            }
            // reset the fetch script value, get the buffer, and return.
            $this->__config['fetch'] = null;
            return ob_get_clean();
        }
    }

继续看拿到模板内容后他是怎么处理的:

在这里耽误了很久,一开始直接echo rand();exit;调试的,没注意看有多个模板:

调试代码改成:
if($display_template_filename == 'default/article/index.tpl.htm'){
     echo rand();
     exit;
}

/system/class/cls_template.inc.php 下的 output 函数,大概 134行:

//两个贪婪模式的正则,改一下就ok了。
$output = preg_replace('/[a-zA-Z0-9]+_?[a-zA-Z0-9]*-__/', '', $output);
$output = preg_replace('/(__)?[a-zA-Z0-9]+_?[a-zA-Z0-9]*-(['|"])/', '2', $output);
if($display_template_filename == 'default/article/index.tpl.htm'){
    echo rand();
    exit;
}

刚开始真没想到贪婪模式,正则现在差不多就记得点星问了,后来跟@L3m0n(柠檬) 叔叔在做题的时候提了一下,他说是贪婪模式,复习下正则吧…

为什么贪婪模式会导致php timeout?

参考:

正则表达式的三种模式【贪婪、勉强、侵占】的分析

正则基础之——NFA引擎匹配原理

正则基础之——贪婪与非贪婪模式

<进阶-1> 正则表达式的匹配原理

*贪婪模式图

抽出上面的其中一条正则来说:

[a-zA-Z0-9]+_?[a-zA-Z0-9]*-__

把正则切割成几部分:

认真看贪婪模式的那张图片,假如传入的字符串是:

[img]abc

把字符串切割一下:

正则在线debug:https://regex101.com

正则匹配过程如下(当然我说的也不一样是对,有兴趣的可以自己去看看正则表达式的匹配原理):

第一次匹配:从字符串位置0开始,子表达式"[a-zA-Z0-9]+",匹配"[",匹配失败,继续往前匹配;

第二次匹配:从字符串位置1开始,子表达式"[a-zA-Z0-9]+",匹配"i", 匹配成功,因为是贪婪模式,一直匹配到"g"那个地方才结束;

第三次匹配:从字符串位置4开始,子表达式"_?",匹配"]",同时记录备选状态,匹配失败,此时进行回溯,找到备选状态,"_?"忽略匹配;

第四次匹配:从字符串位置4开始,子表达式"[a-zA-Z0-9]*",匹配"]",同时记录备选状态,匹配失败,此时进行回溯,找到备选状态,"_?"忽略匹配;

第五次匹配:从字符串位置4开始,子表达式"-",匹配"]",匹配失败,向前查找可供回溯的状态,把控制权交给"_?",由前面匹配成功的子表达式让出已匹配的字符"g";

第六次匹配:从字符串位置3开始,子表达式"_?",匹配"g",同时记录备选状态,匹配失败,此时进行回溯,找到备选状态,"_?"忽略匹配;

第七次匹配:从字符串位置3开始,子表达式"[a-zA-Z0-9]*",匹配"g", 匹配成功;

第八次匹配:从字符串位置4开始,子表达式"-",匹配"]",匹配失败,向前查找可供回溯的状态,把控制权交给"_?",由前面匹配成功的子表达式让出已匹配的字符"mg";

第九次匹配:从字符串位置2开始,子表达式"_?",匹配"m",匹配零次或者一次,不存在这个字符,匹配零次;

第十次匹配:从字符串位置2开始,子表达式"[a-zA-Z0-9]*",匹配"m",匹配成功,因为是贪婪模式,一直匹配到"g"那个地方才结束;

第十一次匹配:从字符串位置4开始,子表达式"-",匹配"]",匹配失败,当前位置正则已经尝试了所有可能,现在从新开始匹配,之前是从i开始匹配成功的,下面从m开始匹配。

第十二次匹配:从字符串位置2开始,子表达式"[a-zA-Z0-9]+",匹配"m",匹配成功,因为是贪婪模式,一直匹配到"g"那个地方才结束;

会一直这样循环直到正则尝试过所有的位置都不能找到匹配结果才会匹配失败。

为什么会timeout?

正则是重复的子表达式且贪婪模式组成不能正确匹配,字符串是超大的话,就会尝试匹配很多次很多次很多次,这就导致了php timeout了。

拒绝服务效果:

修复后:

修复方案:

/system/class/cls_template.inc.php 下的 output 函数,大概 134-135 行(正则)修改为如下:

$output = preg_replace('/[a-zA-Z0-9_?]+-__/', '', $output);
$output = preg_replace('/(__)?[a-zA-Z0-9_?]+-(['|"])/', '2', $output);

如何避免这种问题:

    1.子表达式不要重复并且都贪婪模式;

    2.写完正则之后debug一下;

原文发布时间为:2017年8月25日

本文作者:Mosuan_

本文来自合作伙伴嘶吼,了解相关信息可以关注嘶吼网站。

原文链接

时间: 2024-09-14 22:00:11

由正则引起的 Wecenter 拒绝服务漏洞的相关文章

Unify的eWave ServletExec拒绝服务漏洞

servlet 涉及程序: eWave ServletExec 描述: Unify的eWave ServletExec拒绝服务漏洞 详细: Unify的eWave ServletExec是一个JSP和Java Servlet引擎,它们被用作象Apache.IIS.Netscape等等流行的网络服务器的插件. 发送一个URL请求可能引起ServletExec servlet引擎突然终止,但是web服务器不会受到影响. 可以通过在URL前加上servlet路径前缀"/servlet"这样可

绿盟科技互联网安全威胁周报2016.29 ISC BIND buffer.c拒绝服务漏洞CVE-2016-2776

绿盟科技发布了本周安全通告,周报编号NSFOCUS-16-29,绿盟科技漏洞库本周新增140条,其中高危116条.本次周报建议大家关注持续关注ISC BIND buffer.c拒绝服务漏洞CVE-2016-2776,目前漏洞细节已经披露,可能导致大规模对此漏洞的利用,强烈建议用户尽快安装官方发布的修复补丁. 焦点漏洞 ISC BIND buffer.c拒绝服务漏洞 NSFOCUS ID 34961 CVE ID CVE-2016-2776 受影响版本 BIND 9 version 9.0.x -

绿盟科技互联网安全威胁周报2016.34 本周关注ntp拒绝服务漏洞

绿盟科技发布了本周安全通告,周报编号NSFOCUS-16-34,绿盟科技漏洞库本周新增42条,其中高危13条.本次周报建议大家关注 ntp拒绝服务漏洞 ,可导致信息泄露.目前官方已经发布更新,强烈建议用户检查自己使用的ntp是否为受影响的版本,如果是,尽快升级. 焦点漏洞 ntp拒绝服务漏洞 NSFOCUS ID 35472 CVE ID CVE-2016-9312 受影响版本 nptd version < ntp-4.2.8p9 ntp-4.3.0 <= ntpd version <

SMB v3远程拒绝服务漏洞分析

本文讲的是SMB v3远程拒绝服务漏洞分析, 漏洞简介 此漏洞是由于Windows处理SMB协议驱动mrxsmb20.sys在解析Tree Connect Response时,未正确处理包长度导致的空指针引用漏洞.当Tree Connect Response包中smb协议长度大于0x400(1024)时,会造成蓝屏崩溃. 漏洞重现 根据已公开的 POC,在win10 14393中重现了此漏洞.需要注意的是,Windows系统中可能默认监听了445端口,在这种情况下运行POC会出现如下错误: so

OpenSSL告警处理不当远程拒绝服务漏洞 绿盟科技发布安全威胁通告

OpenSSL又出漏洞了 CVE-2016-8610 OpenSSL官方已经修复了补丁,还请广大用户尽快升级,不要犯了之前的错误. APT组织FruityArmor利用微软刚修补的漏洞发起攻击 黄金72小时的威力再次证明 攻防是在比谁更快 .绿盟科技发布<OpenSSL未定义告警远程拒绝服务漏洞安全威胁通告>报告全文如下: seclists.org网站发布了一条关于OpenSSL远程拒绝服务漏洞的通告.OpenSSL在处理未定义告警数据时候存在缺陷:攻击者可以通过重复发送SSL3_AL_WAR

绿盟科技网络安全威胁周报2017.07 请关注OpenSSL拒绝服务漏洞CVE-2017-3733

绿盟科技发布了本周安全通告,周报编号NSFOCUS-17-07,绿盟科技漏洞库本周新增49条,其中高危24条.本次周报建议大家关注 OpenSSL拒绝服务漏洞 CVE-2017-3733 .目前OpenSSL开发小组已经发布补丁,强烈建议受影响的用户进行升级. 焦点漏洞 OpenSSL拒绝服务漏洞 NSFOCUS ID 30935 CVE ID CVE-2017-3733 影响范围 受影响版本 < 1.1.0e 漏洞点评 OpenSSL在握手重协商过程中的某些情况下,如果协商使用Encrypt-

mysql 5.0.45 (修改)拒绝服务漏洞_Mysql

mysql 5.0.45 (修改)拒绝服务漏洞 /* * MySQL <=6.0 possibly affected * Kristian Erik Hermansen * Credit: Joe Gallo * You must have Alter permissions to exploit this bug! * Scenario: You found SQL injection, but you want to punch backend server * in the nuts ju

Struts2框架拒绝服务漏洞

原理是这样的:http://wwwxxx.net/app/secTest.action?new  java.lang.Double(2.2250738585072012e-308)  这段 URL,在处理时,框架会自动转为 Double 类型,理论上应该直接 由于参数名称中存在横线,所以被 struts2 的参数处理类,拦截掉了.于是我想了办法,绕过它. 这个 URL 虽然很长很长,但是确实没有横线出现,所以是被接受的. http://www.inbreak.net/app/secTest.ac

APP漏洞扫描器之本地拒绝服务检测详解

APP漏洞扫描器之本地拒绝服务检测详解 作者:伊樵@阿里聚安全 阿里聚安全的Android应用漏洞扫描器有一个检测项是本地拒绝服务漏洞的检测,采用的是静态分析加动态模糊测试的方法来检测,检测结果准确全面.本文将讲一下应用漏洞扫描器在针对本地拒绝服务的检测方法. 一.本地拒绝服务产生原因和影响 Android应用使用Intent机制在组件之间传递数据,如果应用在使用getIntent(),getAction(),Intent.getXXXExtra()获取到空数据.异常或者畸形数据时没有进行异常捕