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


116

今天早些时候,有人问了有关Web应用程序中输入验证策略的问题。

在撰写本文时,最高答案建议PHP仅使用htmlspecialcharsmysql_real_escape_string

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

Answers:


241

对于数据库查询,请始终尝试使用准备好的参数化查询。在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备忘单,了解有关如何实现多种向量的示例

即使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的方式,它也容易受到攻击。但是,在IE6的使用率下降之前,您可以使用更有限的编码,例如ISO-8859-1。

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


24
这里唯一遗漏的是,数据库查询的第一个示例...一个简单的intval()将解决注入问题。需要数字而不是字符串时,请始终使用intval()代替mysqlescape ...()。
罗伯特K

11
并且请记住,使用参数化查询将使您始终将数据视为数据而不是代码。使用PDO之类的库,并尽可能使用参数化查询。
Cheekysoft

9
有两个说明:1.在第一个示例中,如果还对参数加上引号(例如$result = "SELECT fields FROM table WHERE id = '".mysql_real_escape_string($_POST['id'])."'";2),则将是安全的。在第二种情况下(属性包含URL),根本没有用htmlspecialchars;在这种情况下,您应该使用URL编码方案(例如使用)对输入进行编码rawurlencode。这样,用户将无法插入javascript:等。
Marcel Korpel 2011年

7
“ htmlspecialchars只编码双引号,而不是单引号”:这是不正确的,它取决于设置的标志,请参见其参数
Marcel Korpel 2011年

2
这应该以粗体显示:Take a whitelist approach and only let through the chars which are good.黑名单将始终丢失某些内容。+1
Jo Smo

10

除了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 =“从表WHERE id =中选择字段。” mysql_real_escape_string($ _ POST ['id']);

您应该能够看到这很容易被利用。假设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 =“从表WHERE id =中选择字段” .mysqlrealescapestring(“ 1 OR 1 = 1”);

我会用

$ result =“从表WHERE id =中选择字段=” .Numbers(“ 1 OR 1 = 1”);

它会安全地运行查询

从表WHERE ID = 111中选择字段

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


1
完善!这正是您需要的一种卫生措施。初始代码失败,因为它没有验证数字是否为数字。您的代码将执行此操作。您应该在其值源自代码库外部的所有整数型var上调用Numbers()。
Cheekysoft

1
值得一提的是,由于PHP会自动将整数强制转换为字符串,因此intval()可以很好地工作。
亚当·恩斯特

11
我更喜欢intval。原来1abc2为1,不12.
jmucchiello

1
intval更好,尤其是在ID上。在大多数情况下,如果它已损坏,则与上面相同,即1或1 = 1。您真的不应该泄露其他人的ID。因此,intval将返回正确的ID。之后,您应该检查原始值和清理后的值是否相同。这不仅是阻止攻击,而且是发现攻击者的好方法。
triunenature 2014年

2
如果您显示的是个人数据,那么不正确的行将是灾难性的,您会看到其他用户的信息!相反,最好检查一下return preg_match('/^[0-9]+$/',$input) ? $input : 0;
弗兰克·福特

2

这个难题的一个重要方面是上下文。如果您引用查询中的每个参数,则将“ 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版进行了测试,对于整数列使用字符串上下文不会引起任何问题。


15
然后,我将以多字节字符0xbf27开始我的攻击向量,该字符在您的latin1数据库中将通过过滤器功能转换为0xbf5c27-这是一个多字节字符,后跟一个单引号。
Cheekysoft

8
尽量不要防范单个已知的攻击媒介。您最终将追逐自己的尾巴,直到在您的代码中逐个应用补丁为止。退后一步,看看一般情况,将会寻求更安全的代码和更注重安全性的思维方式。
Cheekysoft

我同意; 理想情况下,OP将使用准备好的语句。
Lucas Oman 2010年

1
尽管这篇文章中建议的引号并不是万无一失的,但它可以减轻许多常见的1 OR 1 = 1类型的攻击,因此值得一提。
熬夜猫

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

效果很好,在64位系统上效果更好。尽管要注意系统在处理大量数据方面的局限性,但是对于数据库ID,这在99%的时间内都是有效的。

您还应该使用一个函数/方法来清理您的值。即使此函数只是mysql_real_escape_string()的包装。为什么?因为有一天可以找到您偏爱的清理数据方法的漏洞,所以您只需要在一个地方进行更新,而无需在系统范围内进行查找和替换。


-3

为什么,为什么不在 sql语句中包含用户输入的引号?似乎挺傻的不去!在SQL语句中包含引号会导致“ 1或1 = 1”的尝试毫无结果,不是吗?

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

好了,很容易解决:只需删除用户输入的引号即可。例如:input =~ s/'//g;。现在,在我看来无论如何,用户输入将得到保护...


“为什么,哦,为什么,您不会在sql语句中包括用户输入的引号?” -该问题没有说明不引用用户输入。
昆汀

1
“好吧,简单地解决这个问题” —糟糕的解决方案。那会丢掉数据。问题本身提到的解决方案是一种更好的方法。
昆汀

虽然我同意这个问题并未解决引用用户输入的问题,但似乎仍然无法引用输入内容。而且,我宁愿扔掉数据也不愿输入坏数据。通常,在注入攻击中,您还是不希望该数据。
Jarett L

“虽然我同意这个问题并未解决引用用户输入的问题,但似乎仍然无法引用输入内容。” —不,不是。这个问题并没有以一种或另一种方式证明它。
昆丁

1
@JarettL要么习惯使用准备好的语句,要么习惯Bobby Tables在每个星期二破坏您的数据。参数化SQL是保护自己免受SQL注入的最佳方法。如果您使用准备好的语句,则无需执行“ SQL注入检查”。它们非常易于实现(我认为,使代码更容易阅读),可以防止字符串串连和sql注入的各种特质,而且最重要的是,您无需重新发明轮子即可实现它。
2015年
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.