今天早些时候,有人问了一个关于Web 应用程序中的输入验证策略的问题。
在撰写本文时,最佳答案建议PHP
仅使用htmlspecialchars
and mysql_real_escape_string
。
我的问题是:这总是足够吗?还有更多我们应该知道的吗?这些功能在哪里分解?
今天早些时候,有人问了一个关于Web 应用程序中的输入验证策略的问题。
在撰写本文时,最佳答案建议PHP
仅使用htmlspecialchars
and mysql_real_escape_string
。
我的问题是:这总是足够吗?还有更多我们应该知道的吗?这些功能在哪里分解?
当涉及到数据库查询时,请始终尝试使用准备好的参数化查询。mysqli
和PDO
库支持这一点。这比使用转义函数(例如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 备忘单,了解有关向量的多样性的示例
即使您htmlspecialchars($string)
在 HTML 标签之外使用,您仍然容易受到多字节字符集攻击向量的攻击。
最有效的方法是使用 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://stackoverflow.com/a/12118602/1820
除了 Cheekysoft 的出色回答:
防止 HTML 注入(例如跨站点脚本)并没有真正的灵丹妙药,但如果您使用库或模板系统来输出 HTML,您可能能够更轻松地实现它。阅读文档以了解如何适当地逃避事情。
在 HTML 中,需要根据上下文对事物进行不同的转义。对于放入 Javascript 中的字符串尤其如此。
我肯定会同意上述帖子,但我有一件小事要补充以回复 Cheekysoft 的回答,具体来说:
当涉及到数据库查询时,请始终尝试使用准备好的参数化查询。mysqli 和 PDO 库支持这一点。这比使用 mysql_real_escape_string 等转义函数要安全得多。
是的,mysql_real_escape_string 实际上只是一个字符串转义函数。它不是灵丹妙药。它将做的只是转义危险字符,以便它们可以安全地用于单个查询字符串。但是,如果您不事先清理输入,那么您将容易受到某些攻击向量的攻击。
想象一下下面的 SQL:
$result = "从表中选择字段 id = ".mysql_real_escape_string($_POST['id']);
您应该能够看到这很容易被利用。想象一下 id 参数包含常见的攻击向量:
1 或 1=1
那里没有要编码的危险字符,因此它将直接通过转义过滤器。离开我们:
从表中选择字段,其中 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");
它会安全地运行查询
从表中选择字段,其中 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 or 1=1" 徒劳无功,不是吗?
所以现在,你会说,“如果用户在输入中包含引号(或双引号)怎么办?”
好吧,很容易解决这个问题:只需删除用户输入的引号。例如:input =~ s/'//g;
。现在,无论如何,在我看来,用户输入将得到保护......