【问题标题】:PHP regex vulnerability betPHP 正则表达式漏洞赌注
【发布时间】:2015-11-17 16:38:06
【问题描述】:

今天的一位同事和我打赌,他知道一种方法可以提供特殊格式的字符串,该字符串可以通过以下正则表达式检查,并且仍然提供扩展名为 .php.jsp.asp 的文件名:

if (preg_match('/\.(jpeg|jpg|gif|png|bmp|jpe)$/i', $var) && preg_match('/\.(asp|jsp|php)$/i', $var) == false) 
{
    echo "No way you have extension .php or .jsp or .asp after this check.";
}

尽管我努力尝试自己并在网上搜索,但我无法找到使这种事情成为可能的缺陷。我可以忽略一些东西吗?鉴于处理了“空字节”漏洞,这里还有什么问题?

注意:我绝不暗示此代码是检查文件扩展名的完整方法,preg_match() 函数可能存在缺陷或文件内容可能具有不同的格式,我只是问正则表达式语法本身的问题。

编辑 - 实际代码:

if (isset($_FILES["image"]) && $_FILES["image"]["name"] && preg_match('/\.(jpeg|jpg|gif|png|bmp|jpe)$/i', $_FILES["image"]["name"]) && preg_match('/\.(asp|jsp|php)$/i', $_FILES["image"]["name"]) == false) {
        $time = time();
        $imgname = $time . "_" . $_FILES["image"]["name"];
        $dest = "../uploads/images/";

        if (file_exists($dest) == false) {
            mkdir($dest);
        }

        copy($_FILES['image']['tmp_name'], $dest . $imgname);

    }else{
        echo "Invalid image file";
    }

PHP 版本:5.3.29

编辑:尾声

原来“漏洞”只出现在 Windows 上。尽管如此,它确实做到了我的同事告诉我的——通过了正则表达式检查并保存了带有可执行扩展名的文件。以下在WampServer 2.2PHP 5.3.13 上进行了测试:

将以下字符串传递给test.php:.jpg上方的正则表达式检查(注意所需扩展名末尾的“:”冒号)将对其进行验证,并且函数copy()似乎省略了冒号之后的所有内容,包括符号本身。 同样,这仅适用于 Windows。在 linux 上,该文件将使用与传递给函数的名称完全相同的名称写入。

【问题讨论】:

  • 为什么 2 个单独的 preg_match
  • 为了更好的说明,这不是实际的生产代码
  • 为什么要问我们?如果您的同事真的知道方法,请让他说出来:P
  • @SeanBright 是的。关于问题本身的任何信息?
  • @revo 这是D (PCRE_DOLLAR_ENDONLY) php.net/manual/en/reference.pcre.pattern.modifiers.php

标签: php regex


【解决方案1】:

没有一个步骤或完全直接的方法来利用您的代码,但这里有一些想法。

在此示例中,您将其传递给 copy(),但您已经提到您已经使用此方法验证文件 ext 一段时间了,所以我假设您有其他情况,可能在其他情况下也使用此过程和其他函数PHP 版本。

将此视为测试过程(利用包含、要求):

$name = "test.php#.txt";
if (preg_match('/\.(xml|csv|txt)$/i', $name) && preg_match('/\.(asp|jsp|php)$/i', $name) == false) {
    echo "in!!!!";
    include $name;
} else {
    echo "Invalid data file";
}

这将通过打印“in!!!!”结束并执行“test.php”,即使它被上传,它也会从 tmp 文件夹中包含它——当然,在这种情况下,你已经被攻击者拥有,但让我们也考虑一下这个选项。 这不是上传过程的常见场景,但它是一个可以通过组合多种方法来利用的概念:

让我们继续 - 如果你执行:

//$_FILES['image']['name'] === "test.php#.jpg";
$name = $_FILES['image']['name'];
if (preg_match('/\.(jpeg|jpg|gif|png|bmp|jpe)$/i', $name) && preg_match('/\.(asp|jsp|php)$/i', $name) == false) {
    echo "in!!!!";
    copy($_FILES['image']['tmp_name'], "../uploads/".$name);
} else {
    echo "Invalid image file";
}

再次完美无缺。该文件被复制到“uploads”文件夹中 - 您无法直接访问它(因为 Web 服务器将删除 # 的右侧)但是您注入了该文件,攻击者可能会找到一个稍后再调用它的方式或其他弱点。

这种执行场景的示例在共享和托管站点中很常见,其中文件由 PHP 脚本提供服务(在某些不安全的情况下)可能会通过包含错误类型的函数来加载文件,例如 require , include, file_get_contents 都是易受攻击的并且可以执行文件。

空字节 空字节攻击是 php

//$_FILES['image']['name'] === "test.php\0.jpg";
$name = $_FILES['image']['name'];
if (preg_match('/\.(jpeg|jpg|gif|png|bmp|jpe)$/i', $name) && preg_match('/\.(asp|jsp|php)$/i', $name) == false) {
    echo "in!!!!";
    copy($_FILES['image']['tmp_name'], "../uploads/".$name);
} else {
    echo "Invalid image file";
}

将打印“in!!!!”并复制名为“test.php”的文件。

php 修复的方法是在将字符串传递给更深层次的 C 过程之前和之后检查字符串长度,该过程创建实际的 char 数组,以及字符串是否被空字节截断(这表示 C 中字符串的结尾) 长度不匹配。 read more

奇怪的是,即使在已修补的现代 PHP 版本中,它仍然存在:

$input = "foo.php\0.gif";
include ($input); // Will load foo.php :)

我的结论: 您验证文件扩展名的方法可以显着改进 - 您的代码允许名为 test.php#.jpg 的 PHP 文件通过,而它不应该通过。成功的攻击主要是通过组合多个漏洞(甚至是次要漏洞)来执行的 - 您应该将任何意外结果和行为视为一个整体。

注意:还有更多关于文件名和图片的问题,因为它们会在以后的页面中包含很多时间,如果它们没有被正确过滤并安全包含,您将暴露于更多 XSS 内容但这不在主题范围内。

【讨论】:

  • 我已经找到你了。 copy 很好。大多数人都这样。但是仍然在想什么时候可以有人include PHP 代码中的图像文件? (主要是可以执行PHP代码)
  • 参考but was reintroduced by a regression in versions 5.4+ in some functions ?
  • 如果您将其限制为图像而不是大多数情况下,但这是一个常见的初学者错误,可能通过 require 或 include 而不是 readfile 或在模板基础系统中将文件返回给任何类型的用户您加载了文件,您可以找到一个基于 url 参数加载其模板的页面,然后调用您注入的文件...发挥您的想象力:)
  • @astralmaster php internals and mailing list - 但是还有一些其他的博客给我一个秒
  • @choz 如果你想危险地跟踪错误报告和修订,也许你甚至可以做出贡献。 :)
【解决方案2】:

试试这个代码。

$allowedExtension = array('jpeg','png','bmp'); // make list of all allowed extension

if(isset($_FILES["image"]["name"])){
     $filenameArray = explode('.',$_FILES["image"]["name"]);
     $extension = end($filenameArray);
     if(in_array($extension,$allowedExtension)){
        echo "allowed extension";
     }else{
          echo "not allowed extension";
     }
}

【讨论】:

    【解决方案3】:

    preg_match() 如果模式匹配给定的主题,则返回 1,如果不匹配,则返回 0,如果发生错误,则返回 FALSE。

    $var = "test.php";
    if (preg_match('/\.(jpeg|jpg|gif|png|bmp|jpe)$/i', $var) === 1 
        && preg_match('/\.(asp|jsp|php)$/i', $var) !== 1) 
    {
        echo "No way you have extension .php or .jsp or .asp after this check.";
    } else{
        echo "Invalid file";
    }
    

    因此,当您要检查您的代码时,请使用=== 1

    理想情况下你应该使用。

    function isImageFile($file) {
        $info = pathinfo($file);
        return in_array(strtolower($info['extension']), 
                        array("jpg", "jpeg", "gif", "png", "bmp"));
    }
    

    【讨论】:

      【解决方案4】:

      我记得在PHP 的某些版本中,PHP允许字符串包含0x00,这个char被认为是字符串的结尾
      因此,例如,如果您的字符串包含:myfile.exe\0.jpg,那么preg_match() 将匹配 jpg,但其他 PHP函数将在 myfile.exe, 中停止,例如 include()copy() 函数

      【讨论】:

      • 自 PHP 5.2.X 起已修补
      • 不,仅从 PHP 5.3.X 开始...至少,我已经在 Windows 上的 PHP 5.2.10 中测试了我的示例,问题仍然存在 :)
      • @HalayemAnis 许多旧版本都得到了修补,特别是在维护良好且实施了安全补丁的系统上——您可以在修订之前构建并自己测试。
      猜你喜欢
      • 1970-01-01
      • 2014-01-29
      • 1970-01-01
      • 2011-08-19
      • 2010-11-14
      • 2011-06-10
      • 2021-09-14
      • 2010-09-12
      • 1970-01-01
      相关资源
      最近更新 更多