CVE-2017-15715 Apache文件上传绕过黑名单

Report 漏洞复现

0x01:漏洞复现

测试DEMO1:

以Aapache配置文件为例配置如下路由正则,将禁止指定目录执行PHP脚本:

<FilesMatch "\.(?i:php|php3|php4|php5)$">
    Order allow,deny
    Deny from all
</FilesMatch>

简单的前端上传测试页面upload.html:

<html>
    <head>
        <title>CVE-2017-15715</title>
    </head>
    <body>
        <form action="test.php" method="post" enctype="multipart/form-data">
            <input type="file" name='file'>
            <input type="submit" value="upload">
        </form>
    </body>
</html>

接收上传文件页面test.php:

<?php
    if(isset($_FILES['file'])) {
        $name = $_FILES['file']['name'];
        $ext = pathinfo($name,PATHINFO_EXTENSION);
        move_uploaded_file($_FILES['file']['tmp_name'], './'.$name);
    }
?>

该Demo允许上传任意文件,但上传的PHP文件没执行权限,通过修改上传文件名在末尾添加换行符可以绕过限制,只是这样同样不会被PHP的解析器解析。

 

测试DEMO2:

在第二个测试Demo中,我们不需要Apache限制目录执行权限,但需要在代码层以黑名单限制上传文件后缀,前端上传页面upload.html:

<html>
        <head>
                <title>CVE-2017-15715</title>
        </head>
        <body>
                <form action="test.php" method="post" enctype="multipart/form-data">
                        <input type="file" name="file">
                        <input type="submit" value="upload">
                        <input type="text" name="name">
                </form>
        </body>
</html>

接收上传文件页面test.php:

<?php
        if(isset($_FILES['file'])) {
        $name = $_POST['name'];
        $ext = pathinfo($name,PATHINFO_EXTENSION);
        if(in_array($ext, ['php', 'php3', 'php4', 'php5', 'phtml', 'pht'])) {
                exit('[!]No hack');
        }
        move_uploaded_file($_FILES['file']['tmp_name'], '/var/www/html/'.$name);
        }
?>

直接上传php文件进程会被exit掉,但在文件名末尾添加十六进制换行符"%0a"则可以轻松绕过黑名单。

     

0x02:漏洞分析

第一条测试DEMO是Apache限制目录的脚本执行权限,采用的正则匹配方式在实际环境中也算通用方案,但为什么会被绕过呢?仔细看下这条正则,大小写不敏感从头匹配到"$"结尾,问题就出在最后一个修饰符上。

先看下关于D修饰符 (PCRE_DOLLAR_ENDONLY)的描述:如果这个修饰符被设置,模式中的元字符美元符号仅仅匹配目标字符串的末尾。如果这个修饰符没有设置,当字符串以一个换行符结尾时,美元符号还会匹配该换行符(但不会匹配之前的任何换行符)。如果设置了修饰符m,这个修饰符被忽略。"$"除了最常用的匹配字符串末尾外还会匹配换行符,因此当我们上传的文件名末尾为换行符时同样会被正则匹配到。

第二条DEMO在代码层面限制上传文件后缀,apache有一个古老的解析漏洞1.php.jpg,官方修复方案:

<FilesMatch \.php$>
    SetHandler application/x-httpd-php
</FilesMatch>

而这个方案与第一个测试Demo相似,因此我们可以通过十六进制的换行符以匹配到该正则。

     

0x03:影响版本及修复建议

该漏洞影响版本为linux + apache(2.40-2.4.29),在Window + apache(2.40-2.4.29)的环境上传带十六进制换行符的文件名会报错"failed to open stream: Invalid argument"。如果你足够细心就会发现第二个DEMO里传递文件名不是以$name = $_FILES['file']['name'];取值,而是通过POST另一个参数来命名,因为"$_FILES['file']['name']"中的换行符会被自动过滤,只有通过其他方式传递文件名才能触发该漏洞。

总结下该漏洞触发条件:linux + apache(2.40-2.4.29),以黑名单方式过滤上传文件名,上传文件名通过其他传入变量取值,因此该漏洞虽存在危害性,但因为文件命名的原因略显鸡肋。

建议修复方案: 1. 正则匹配中以"/z"替换"$",\.(?i:php|php3|php4|php5)$ -> \.(?i:php|php3|php4|php5)\Z; 2. 上传文件功能在任何情况都以白名单为过滤条件; 3. 上传文件目录限制脚本执行权限;