PHP - 如何根据多字节编码漏洞进行安全输入过滤?

信息安全 Web应用程序 php
2021-09-08 18:39:08

几天来,我试图弄清楚如何用 php 编写一个安全的 Web 应用程序,结果发现它特别困难。我读得越多,我就越陷入充满漏洞的深渊,像马特·罗宾逊克里斯·希夫莱特这样的好心人没有提到这些漏洞。

举几个例子:

简而言之,我看到以下问题:

  • 在过滤输入时,不太清楚以后如何解码该数据,因此字符编码和转义系统可以绕过输入过滤。(如双url解码)
  • 转义输出时,使用标准函数,例如htmlspecialcharshtmlspecialchars 有一个编码参数很好,但这并不妨碍您向它发送 UTF-16 输入,这可能会破坏函数的安全值。

php 中似乎有一个 mbstring 模块,但如果它的安全性与它的文档一样容易理解,那么即使我能弄清楚如何使用它,它也可能毫无用处。只是用于说明的文档示例

mbstring.strict_detection boolean

    Enables the strict encoding detection.

太好了,这很有帮助。

不幸的是,这些功能还取决于您在配置选项中设置的内容......似乎有一个mb_convert_encoding名为)。还有mb_check_encoding这似乎是为了目的,但阅读用户对文档的评论并不能完全激发信心。

所以问题是,鉴于这一切,您如何进行安全输入过滤像这样的东西?

  1. mb_convert_encoding 转为 UTF-8
  2. mb_check_encoding 拒绝无效输入
  3. 循环 url_decode 直到字符串停止变化
  4. 使用文本比较和正则表达式等进行正常输入过滤...

编辑:请注意,3 是有问题的,因为您的正常输入过滤可能会再次引入可以进行 url 解码的实体

编辑我在这里
找到了部分答案,来自希夫莱特。似乎对于 htmlspecialchars 使用它的 encoding 参数并确保将浏览器的字符编码标头设置为相同可以避免浏览器对字符的解释与 htmlspecialchars 不同。这一切都假设 htmlspecialchars 的输入对于给定的编码是有效的,或者对于每个可能的无效输入 htmlspecialchars 以与每个浏览器完全相同的方式解释字符串。我们知道,如果我们无法清理我们的输入,我们就无法确保 htmlspecialchars 的输入是有效编码的,因为攻击者可能会使用无效编码来编造一个字符串。这将我们引向第二种可能性,对于所有可能的输入,htmlspecialchars 的行为将与浏览器相同。这是个问题,

这一切都类似于 msql_real_escape 对数据库所做的事情,尽管我认为您可以通过使用准备好的语句来正确解决 msql 的这个问题。

第三个有问题的输出是使用 php 进行文件上传或其他文件系统操作时的文件系统。关于最后一个,似乎可用的信息很少。我什至不知道一个特定的转义函数,更不用说当它得到弯曲输入时的健壮性了。

3个回答

为了对威胁进行适当的防御,您需要了解它。盲目地分层编码方案是非常危险的,因为这可能会引入问题,而默认情况下这通常不是问题。一般来说,与编码相关的安全问题会出现,因为转义函数对数据的作用可能与它们被解释的不同。但这不是唯一的问题,它也会让程序员感到困惑,因为他们认为字符串被转义了,而实际上不是。

第一个问题是转义函数可以为攻击者构建危险的字符串

mysql_real_escape_string() 是一个特殊的函数,它知道数据库正在使用什么编码类型,并调整它的转义以适应它。因为编码方法、转义函数和解释器之间永远不会有脱节。出于这个原因,简单地使用addslashes()可能非常危险。

作为一个例子,让我们使用0xbf27,它是一个单一的 GBK 字符。addslashes()不懂GBK,只懂ASCII。如果此字符串被解释为 ASCII,则第一个字符将是 0xbf,这是一个不可打印的 ASCII 字符,让我们调用¿. 第二个 ASCII 字符是 0x27,它是一个单引号'在addslashes()0xbf27变成0xbf5c27或者如果它以ASCII 打印它会是¿\'. 问题是它0xbf5c是一个有效的 GBK 字符,因此 addlashes() 将一个多字节字符变成了 2 个字符,其中一个是单引号。另一种思考方式是反斜杠正在被 GBK 编码所消耗。

如果没有转义函数,字符消耗可能是一个问题让我们以 SHIFT-JS 和 HTML 为例。这个例子取自The Tangled Web

<img src="http://fuzzybunnies.com/0xEO">
...this is still a part of the markup...
 " onerror="alret('this will execute!')"
<div>
...the page continues...

在这种情况下">,img 标记的末尾是非常重要的控制字符,正在被使用"正在被编码方案使用,然后>其他一些文本似乎是 HTML src 属性的一部分,直到它命中该" onerror属性才终止。

...但这还不是全部。让我们换个档次。怎么样urldecode()

<?php
$id=mysql_real_escape_string($_GET['id']);
$id=urldecode($id);
mysql_query("select * from user where id='".$id."'");

你看到漏洞了吗?根据我的经验,大多数 PHP 程序员都看不到它。问题是它urldecode()可以用来构建一个易受攻击的字符串,假设攻击者提供了一个类似http://localhost/vuln.php?id=%2527 or sleep(30)--. 好吧,几乎每个 Web 应用程序平台都会自动对所有 HTTP 输入运行 urldecode。所以 $_GET['id'] 的内容实际上是%27 or sleep(30)--那是因为 %25 被解码成了一个%(% 是 hex 25)。之后mysql_real_escape_string()甚至addslashes()值仍然只是%27 or sleep(30)--再次运行 urldecode() 之后,内容$id是 now ' or sleep(30)--,这是危险的。

因此,作为一个黑客提示,当我审核使用 magic_quotes_gpc 或类似内容的代码库时,我会通过 grep 查找解码函数的代码, urldcode(), 和类似函数。即使应用程序在所有输入上盲目地运行addslashes(),解码函数也将允许攻击者构建他的攻击字符串。htmlspecialchars_decode()base64_decode()

那么如何保护自己呢?测试你的代码。一些 Web 应用程序扫描仪会像这样测试编码缺陷。事实上,我在构建易受攻击的扫描仪时研究了这个主题。还有一点,总是在使用的时候清理输入,这样就避免了转义 后解码的问题。您无法知道它将如何使用,因此编码/解码/转义所有内容总是有缺陷的。 使用参数化查询来构建 sql 语句。在大多数情况下(但并非总是),UTF-8 是一个很好的默认设置,并且*大多数*这些问题不会出现在这种多字节编码方案中。

不,我不推荐您提到的方法。

首先,让我备份。从概念上讲,也许最安全的方法是应用输入验证和输出转义的组合。 输入验证意味着您定义预期/良好输入的形式,并检查输入是否具有该形式。 输出转义意味着您根据将使用它们的上下文来转义输出。输入验证通常在您收到输入后立即进行;输出转义通常在最后完成,就在将值插入 HTML 文档或其他输出之前。

对于输入验证和输出转义概念的一般背景以及实现技巧,OWASP 有一些优秀的资源。另请参阅此问题:在数据库之前或显示时过滤用户输入?, 了解更多关于概念的信息。

要进行输入验证,我想我会定义一个允许字符的白名单或一个正则表达式,它代表预期的输入,并检查输入是否与这个正则表达式/白名单匹配。(适当的白名单应该处理与有趣的输入编码相关的问题。)在某些情况下,进行输入验证的另一种方法是将输入转换为特定类型,例如(int). 具体的验证功能将取决于输入的类型和格式;你不能使用一刀切的验证器。

要进行输出转义,我建议使用转义库,例如 OWASP ESAPI。您需要了解可能出现值的不同解析上下文,然后对该解析上下文使用正确的转义函数。例如,对于htmlspecialchars()将插入到标签之间的 HTML 文档中的动态值,它是一个适当的转义函数,但它不适用于 URL(对于 URL,您还必须检查协议是否有效)。OWASP ESAPI 为您可能将数据插入输出的最常见上下文提供了一套转义函数。

确保使用准备好的语句。不要通过字符串连接构建 SQL 查询。

也就是说,PHP 对安全性的支持很弱。许多其他现代 Web 编程框架为安全性提供了更强的支持,例如,通过提供具有上下文敏感的自动转义的模板系统,通过提供对 CSRF 令牌、安全会话管理、用户登录、数据库 ORM 等的自动支持。

另请参阅 对 PHP Web 应用程序的安全攻击PHP 框架应具备哪些安全功能?, strip_tags() 非常不安全吗?, 有 哪些好的免费工具可以为 PHP 代码运行自动安全审计?, 如何对 PHP 应用程序进行安全审计?为什么有人说PHP天生不安全?.

我得出的结论是:

$input = mb_convert_encoding( $input, 'UTF-8' );

为了在执行任何其他操作(如输入验证和输出转义)之前清理字符编码。我可能不得不查看 mb_convert_encoding 的源代码,它是单元测试以确保它是安全的,但我的印象是它总是会返回一个有效的 UTF-8 字符串。

我的应用程序的其余部分将自始至终使用 utf-8,从而避免任何内容被不同地解释,一直到浏览器。