Htmlspecalchars 和 mysql_real_escape_string 能保证 PHP 代码不被注入吗?

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

最高的答案,在写作时,建议在 PHP只使用 htmlspecialcharsmysql_real_escape_string

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

71125 次浏览

When it comes to database queries, always try and use prepared parameterised queries. The mysqli and PDO libraries support this. This is infinitely safer than using escaping functions such as mysql_real_escape_string.

是的,mysql_real_escape_string实际上只是一个字符串转义函数。这不是神奇子弹。它所做的只是转义危险字符,以便在单个查询字符串中安全地使用它们。但是,如果您不事先清理您的输入,那么您将容易受到某些攻击载体的攻击。

设想下面的 SQL:

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

你应该能够看到这是脆弱的利用。
Imagine the id parameter contained the common attack vector:

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

Which allows the attacker to return the first administrator's details in this completely fictional example.

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

至于 htmlspecialchars(),那是个雷区。

PHP 中存在一个真正的问题,它有一整套与 html 相关的转义函数,而且没有关于哪些函数执行哪些操作的明确指导。

首先,如果你在一个 HTML 标签里面,你就真的有麻烦了

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

We're already inside an HTML tag, so we don't need < or > to do anything dangerous. Our attack vector could just be javascript:alert(document.cookie)

现在生成的 HTML 看起来像

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

攻击直接穿过去了。

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

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

Our evil attacker can now inject whole new parameters

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

给了我们

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

In these cases, there is no magic bullet, you just have to santise the input yourself. If you try and filter out bad characters you will surely fail. Take a whitelist approach and only let through the chars which are good. Look at the XSS cheat sheet for examples on how diverse vectors can be

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

最有效的方法是按照下面的方式使用 mb _ trans _ coding 和 htmlentis 的组合。

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

即便如此,由于 IE6处理 UTF 的方式,它还是容易受到攻击。但是,您可以回到更有限的编码,比如 ISO-8859-1,直到 IE6的使用率下降。

有关多字节问题的更深入研究,请参见 https://stackoverflow.com/a/12118602/1820

除此之外,Cheekysoft 还给出了一个绝妙的回答:

  • 是的,它们会保证你的安全,但前提是它们的使用绝对正确。如果不正确地使用它们,您仍然很容易受到攻击,并且可能存在其他问题(例如数据损坏)
  • 请改用参数化查询(如上所述)。您可以通过例如 PDO 或通过像 PEAR DB 这样的包装器来使用它们
  • Make sure that magic_quotes_gpc and magic_quotes_runtime are off at all times, and never get accidentally turned on, not even briefly. These are an early and deeply misguided attempt by PHP's developers to prevent security problems (which destroys data)

没有什么灵丹妙药可以阻止 HTML 注入(比如跨网站脚本) ,但是如果你使用一个库或者模板系统来输出 HTML,你可以更容易地做到这一点。阅读相关文档,了解如何恰当地转义事物。

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

我完全同意上面的帖子,但是我还有一件小事要补充回应 Cheekysoft 的回答,特别是:

当涉及到数据库查询时, 总是尝试和使用准备好的 参数化查询 PDO 库支持这一点 比逃跑安全多了 功能,例如 Mysql _ real _ escape _ string.

是的,mysql _ real _ escape _ string 是 实际上只是一个字符串转义 function. It is not a magic bullet. All it will do is escape dangerous characters in order that they can be 在单个查询字符串中使用是安全的。 However, if you do not sanitise your inputs beforehand, then you will be 容易受到某些攻击载体的攻击。

设想下面的 SQL:

$result = “ SELECT 字段 FROM table WHERE id = ”. mysql _ real _ escape _ string ($_ POST [‘ id’]) ;

你应该能看出来 很容易被利用,想象一下 参数包含了常见的攻击 矢量:

1 OR 1=1

里面没有危险的字符 编码,所以它会直接通过 through the escaping filter. Leaving 我们:

从表中选择字段,其中 id = 1 OR 1 = 1

我在数据库类中编写了一个快速的小函数,它可以去除任何不是数字的内容。它使用 preg _ place,所以可能会有一个更优化的函数,但它在必要时可以正常工作..。

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

所以我们不用

$result = “ SELECT fields FROM table WHERE id =”. mysqlrealesestring (“1 OR 1 = 1”) ;

我会用

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

它将安全地运行查询

从表中选择字段,其中 id = 111

当然,这只是阻止它显示正确的行,但我不认为这是一个大问题,无论是谁试图注入 sql 到您的网站;)

这个难题的一个重要部分是背景。如果引用查询中的每个参数,那么将“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版本,对整数列使用字符串上下文不会引起任何问题。

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

工作良好,甚至更好的64位系统。但是要注意您的系统在寻址大数字方面的限制,但是对于数据库 id,这在99% 的情况下都能很好地工作。

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

为什么,为什么,你会 没有包括引号周围的用户输入在你的 sql 语句?不这么做似乎很愚蠢!在 sql 语句中包含引号会使“1或1 = 1”成为徒劳的尝试,不是吗?

so now, you'll say, "what if the user includes a quote (or double quotes) in the input?"

这个问题很容易解决: 只要删除用户输入的引号。例句: input =~ s/'//g;。现在,在我看来,用户输入应该是安全的..。