htmlspecialchars 和 mysql_real_escape_string 是不是可以防止我的 PHP 代码被注入?

Posted

技术标签:

【中文标题】htmlspecialchars 和 mysql_real_escape_string 是不是可以防止我的 PHP 代码被注入?【英文标题】:Do htmlspecialchars and mysql_real_escape_string keep my PHP code safe from injection?htmlspecialchars 和 mysql_real_escape_string 是否可以防止我的 PHP 代码被注入? 【发布时间】:2010-09-11 18:05:40 【问题描述】:

今天早些时候,有人问了一个关于 input validation strategies in web apps 的问题。

在撰写本文时,最佳答案建议在 php 中仅使用 htmlspecialcharsmysql_real_escape_string

我的问题是:这总是足够的吗?还有更多我们应该知道的吗?这些功能在哪里分解?

【问题讨论】:

【参考方案1】:

当涉及到数据库查询时,请始终尝试使用准备好的参数化查询。 mysqliPDO 库支持这一点。这比使用转义函数(例如mysql_real_escape_string)要安全得多。

是的,mysql_real_escape_string 实际上只是一个字符串转义函数。它不是灵丹妙药。它将做的只是转义危险字符,以便它们可以安全地用于单个查询字符串。但是,如果您不事先对输入进行清理,那么您将容易受到某些攻击向量的攻击。

想象一下下面的 SQL:

$result = "SELECT fields FROM table WHERE id = ".mysql_real_escape_string($_POST['id']);

您应该能够看到这很容易被利用。 想象一下id 参数包含常见的攻击向量:

1 OR 1=1

那里没有要编码的危险字符,因此它将直接通过转义过滤器。离开我们:

SELECT fields FROM table WHERE id= 1 OR 1=1

这是一个可爱的 SQL 注入向量,允许攻击者返回所有行。 或者

1 or is_admin=1 order by id limit 1

产生

SELECT fields FROM table WHERE id=1 or is_admin=1 order by id limit 1

这允许攻击者在这个完全虚构的示例中返回第一个管理员的详细信息。

虽然这些功能很有用,但必须小心使用。您需要确保所有 Web 输入都经过一定程度的验证。在这种情况下,我们看到我们可以被利用,因为我们没有检查我们用作数字的变量是否实际上是数字。在 PHP 中,您应该广泛使用一组函数来检查输入是否为整数、浮点数、字母数字等。但是当涉及到 SQL 时,最要注意准备好的语句的值。如果上面的代码是一个准备好的语句,那么它是安全的,因为数据库函数会知道1 OR 1=1 不是一个有效的文字。

至于htmlspecialchars()。这本身就是一个雷区。

PHP 中存在一个真正的问题,它有一系列不同的与 html 相关的转义函数,并且没有明确的指导说明哪些函数具体做什么。

首先,如果你在一个 HTML 标记中,你就遇到了真正的麻烦。看看

echo '<img src= "' . htmlspecialchars($_GET['imagesrc']) . '" />';

我们已经在一个 HTML 标记中,所以我们不需要 来做任何危险的事情。我们的攻击向量可能只是javascript:alert(document.cookie)

现在生成的 HTML 看起来像

<img src= "javascript:alert(document.cookie)" />

攻击直接通过。

情况变得更糟。为什么?因为htmlspecialchars(以这种方式调用时)只编码双引号而不是单引号。所以如果我们有

echo "<img src= '" . htmlspecialchars($_GET['imagesrc']) . ". />";

我们的邪恶攻击者现在可以注入全新的参数

pic.png' onclick='location.href=xxx' onmouseover='...

给我们

<img src='pic.png' onclick='location.href=xxx' onmouseover='...' />

在这些情况下,没有灵丹妙药,您只需要自己清理输入即可。如果您尝试过滤掉不良字符,您肯定会失败。采取白名单方法,只让好的字符通过。查看XSS cheat sheet 了解向量的多样性的示例

即使您在 HTML 标记之外使用 htmlspecialchars($string),您仍然容易受到多字节字符集攻击向量的攻击。

最有效的方法是使用 mb_convert_encoding 和 htmlentities 的组合,如下所示。

$str = mb_convert_encoding($str, 'UTF-8', 'UTF-8');
$str = htmlentities($str, ENT_QUOTES, 'UTF-8');

即使这样,IE6 也容易受到攻击,因为它处理 UTF 的方式。但是,您可以回退到更有限的编码,例如 ISO-8859-1,直到 IE6 的使用率下降。

关于多字节问题的更深入研究,请参阅https://***.com/a/12118602/1820

【讨论】:

这里唯一遗漏的是数据库查询的第一个示例......一个简单的 intval() 就可以解决注入问题。当需要数字而不是字符串时,请始终使用 intval() 代替 mysqlescape...()。 并记住,使用参数化查询将允许您始终将数据视为数据而不是代码。尽可能使用 PDO 等库并使用参数化查询。 两点说明: 1. 在第一个例子中,如果你也在参数周围加上引号,你会很安全,比如$result = "SELECT fields FROM table WHERE id = '".mysql_real_escape_string($_POST['id'])."'"; 2. 在第二种情况下(包含 URL 的属性),没有完全用于htmlspecialchars;在这些情况下,您应该使用 URL 编码方案对输入进行编码,例如使用 rawurlencode。这样,用户就不能插入javascript: 等人。 “htmlspecialchars 只编码双引号而不是单引号”:这不是真的,这取决于设置的标志,请参阅它的 parameters。 这应该加粗:Take a whitelist approach and only let through the chars which are good. 黑名单总是会漏掉一些东西。 +1【参考方案2】:

除了 Cheekysoft 的出色回答:

是的,它们会确保您的安全,但前提是它们的使用绝对正确。如果使用不当,您仍然很容易受到攻击,并且可能会遇到其他问题(例如数据损坏) 请改用参数化查询(如上所述)。您可以通过例如使用它们PDO 或通过 PEAR DB 之类的包装器 确保magic_quotes_gpc 和magic_quotes_runtime 始终处于关闭状态,绝不会意外打开,哪怕是短暂打开。这是 PHP 开发人员为防止安全问题(破坏数据)而进行的早期且严重误导的尝试

实际上并没有防止 HTML 注入(例如跨站点脚本)的灵丹妙药,但如果您使用库或模板系统来输出 HTML,则可以更轻松地实现它。阅读文档以了解如何适当地逃避事情。

在 HTML 中,需要根据上下文对事物进行不同的转义。将字符串放入 Javascript 中尤其如此。

【讨论】:

【参考方案3】:

我肯定会同意上述帖子,但我有一点要补充以回复 Cheekysoft 的回答,具体来说:

当涉及到数据库查询时, 总是尝试使用准备好的 参数化查询。 mysqli 和 PDO 库支持这一点。这是 比使用转义更安全 功能如 mysql_real_escape_string。

是的,mysql_real_escape_string 是 实际上只是一个字符串转义 功能。它不是灵丹妙药。 它所要做的就是逃离危险 字符,以便它们可以 在单个查询字符串中使用是安全的。 但是,如果您不消毒您的 事先输入,然后你会 容易受到某些攻击媒介的攻击。

想象一下下面的 SQL:

$result = "从表中选择字段 哪里 id = ".mysql_real_escape_string($_POST['id']);

你应该可以看到这是 容易被利用。想象一下身份证 参数包含普通攻击 向量:

1 或 1=1

里面没有危险的字符 编码,所以它会直接通过 通过转义过滤器。离开 我们:

从表 WHERE id = 1 中选择字段 或 1=1

我编写了一个快速的小函数,将其放入我的数据库类中,该函数将删除任何不是数字的内容。它使用 preg_replace,所以可能会有一些更优化的功能,但它可以在紧要关头...

function Numbers($input) 
  $input = preg_replace("/[^0-9]/","", $input);
  if($input == '') $input = 0;
  return $input;

所以不要使用

$result = "SELECT fields FROM table WHERE id = ".mysqlrealescapestring("1 OR 1=1");

我会用

$result = "SELECT fields FROM table WHERE id = ".Numbers("1 OR 1=1");

它会安全地运行查询

从表 WHERE id = 111 中选择字段

当然,这只是阻止它显示正确的行,但我认为这对于试图将 sql 注入您的站点的人来说不是一个大问题;)

【讨论】:

完美!这正是您需要的消毒方式。初始代码失败,因为它没有验证数字是否为数字。您的代码执行此操作。您应该在所有使用整数的变量上调用 Numbers(),这些变量的值来自代码库之外。 值得一提的是 intval() 可以很好地解决这个问题,因为 PHP 会自动将整数强制转换为字符串。 我更喜欢intval。它将 1abc2 变为 1,而不是 12。 intval 更好,特别是在 ID 上。大多数情况下,如果它被损坏,它就像上面一样,1 或 1=1。你真的不应该泄露别人的身份证。因此 intval 将返回正确的 ID。之后,您应该检查原始值和清理后的值是否相同。它不仅可以阻止攻击,而且可以找到攻击者。 如果您显示个人数据,错误的行将是灾难性的,您会看到另一个用户的信息!相反,最好检查return preg_match('/^[0-9]+$/',$input) ? $input : 0;【参考方案4】:

这个难题的一个重要部分是上下文。如果您引用查询中的每个参数,则将“1 OR 1=1”作为 ID 发送的人不是问题:

SELECT fields FROM table WHERE id='".mysql_real_escape_string($_GET['id'])."'"

结果:

SELECT fields FROM table WHERE id='1 OR 1=1'

这是无效的。由于您正在转义字符串,因此输入无法脱离字符串上下文。我已经对 MySQL 5.0.45 版本进行了测试,并且对整数列使用字符串上下文不会导致任何问题。

【讨论】:

然后我将使用多字节字符 0xbf27 开始我的攻击向量,它在您的 latin1 数据库中将被过滤函数转换为 0xbf5c27 - 这是一个单多字节字符,后跟一个单引号. 尽量不要防范单一的已知攻击向量。您最终将追赶您的尾巴,直到将补丁一个接一个地应用于您的代码。退后一步,看看一般案例将导致更安全的代码和更好的以安全为中心的心态。 我同意;理想情况下,OP 将使用准备好的语句。 虽然本文建议的论点引用并非万无一失,但它可以缓解许多常见的 1 OR 1=1 类型的攻击,因此值得一提。【参考方案5】:
$result = "SELECT fields FROM table WHERE id = ".(INT) $_GET['id'];

运行良好,在 64 位系统上效果更好。请注意您的系统在处理大量数字方面的限制,但对于数据库 ID,这在 99% 的情况下都非常有效。

您也应该使用单个函数/方法来清理您的值。即使这个函数只是 mysql_real_escape_string() 的包装器。为什么?因为有一天,当发现您首选的数据清理方法的漏洞利用时,您只需在一个地方更新它,而不是在系统范围内查找和替换。

【讨论】:

【参考方案6】:

为什么,哦,为什么,你会在你的 sql 语句中包含用户输入的引号吗?似乎很傻不!在你的 sql 语句中包含引号会使 "1 or 1=1" 徒劳无功,不是吗?

所以现在,您会说,“如果用户在输入中包含引号(或双引号)怎么办?”

好吧,很容易解决这个问题:只需删除用户输入的引号。例如:input =~ s/'//g;。现在,无论如何,在我看来,用户输入是安全的......

【讨论】:

“为什么,哦,为什么,你不会在你的 sql 语句中包含用户输入的引号吗?” — 这个问题没有说明不引用用户输入。 “好吧,很容易解决这个问题” - 糟糕的解决方法。这会丢弃数据。问题本身提到的解决方案是一种更好的方法。 虽然我同意这个问题没有解决引用用户输入的问题,但似乎仍然不引用输入。而且,我宁愿扔数据也不愿输入坏数据。通常,在注入攻击中,您无论如何都不想要这些数据....对吗? “虽然我同意这个问题没有解决引用用户输入的问题,但似乎仍然不引用输入。” ——不,它没有。这个问题并没有以一种或另一种方式证明它。 @JarettL 要么习惯使用准备好的语句,要么get used to Bobby Tables wrecking your data every Tuesday。参数化 SQL 是保护自己免受 SQL 注入的唯一最佳方法。如果您使用准备好的语句,则不需要执行“SQL 注入检查”。它们非常容易实现(在我看来,使代码更容易阅读),防止字符串连接和 sql 注入的各种特性,最重要的是,您不必重新发明***来实现它.

以上是关于htmlspecialchars 和 mysql_real_escape_string 是不是可以防止我的 PHP 代码被注入?的主要内容,如果未能解决你的问题,请参考以下文章

htmlspecialchars() 应该用于输入信息还是输出之前?

PHP表单安全过滤和防注入 htmlspecialchars() 和test_input()

php_cz

php 编码和解码的函数

将代码保存在单独的文件中

htmlspecialchars() - 如何以及何时使用和避免多次使用