htmlspecialchars和mysql_real_escape_string是否可以防止我的PHP代码被注入?
今天早些时候,有人问了有关Web应用程序中输入验证策略的问题。
在撰写本文时,最高答案是在PHP中建议仅使用htmlspecialchars和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备忘单,了解有关如何实现多种向量的示例
即使在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的方式,它也容易受到攻击。但是,在IE6的使用率下降之前,我们可以使用更有限的编码,例如ISO-8859-1.
有关多字节问题的更深入研究,请参阅https://stackoverflow.com/a/12118602/1820
除了Cheekysoft的出色答案之外:
- 是的,它们将确保我们安全,但前提是绝对正确地使用它们。不正确地使用它们,我们仍然很容易受到攻击,并且可能会遇到其他问题(例如,数据损坏)
- 请改用参数化查询(如上所述)。我们可以通过例如PDO或者通过包装器(如PEAR DB)
- 确保magic_quotes_gpc和magic_quotes_runtime始终处于关闭状态,并且永远不会被意外打开,甚至不会短暂打开。这些是PHP开发人员为防止安全性问题(会破坏数据)而进行的早期且严重误导的尝试。
确实没有防止HTML注入的灵丹妙药(例如,跨站点脚本),但是如果我们使用库或者模板系统来输出HTML,则可以更轻松地实现。阅读该文档,以了解如何适当地转义。
在HTML中,需要根据上下文进行不同的转义。将字符串放入Javascript时尤其如此。
我肯定会同意上述职位,但是我要补充一点小东西来回应Cheekysoft的回答,特别是:
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. Yes, mysql_real_escape_string is effectively just a string escaping function. It is not a magic bullet. All it will do is escape dangerous characters in order that they can be safe to use in a single query string. However, if you do not sanitise your inputs beforehand, then you will be vulnerable to certain attack vectors. Imagine the following SQL: $result = "SELECT fields FROM table WHERE id = ".mysql_real_escape_string($_POST['id']); You should be able to see that this is vulnerable to exploit. Imagine the id parameter contained the common attack vector: 1 OR 1=1 There's no risky chars in there to encode, so it will pass straight through the escaping filter. Leaving us: SELECT fields FROM table WHERE id = 1 OR 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");
它会安全地运行查询
SELECT fields FROM table WHERE id = 111
当然,这只是阻止了它显示正确的行,但是对于任何试图将sql注入网站的人来说,我都不认为这是个大问题;
这个难题的一个重要方面是上下文。如果我们引用查询中的每个参数,则将ID发送为" 1 OR 1 = 1"的人是没有问题的:
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版进行了测试,对于整数列使用字符串上下文不会引起任何问题。