htmlspecialchars 和 mysql_real_escape_string 可以保护我的 PHP 代码不被注入吗?

Do htmlspecialchars and mysql_real_escape_string keep my PHP code safe from injection?

提问人:Cheekysoft 提问时间:9/21/2008 最后编辑:CommunityCheekysoft 更新时间:3/18/2017 访问量:72049

问:

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

在撰写本文时,最重要的答案建议仅使用 和 .PHPhtmlspecialcharsmysql_real_escape_string

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

PHP 安全 XSS SQL注入

评论


答:

243赞 Cheekysoft 9/21/2008 #1

当涉及到数据库查询时,请始终尝试使用准备好的参数化查询。和库支持这一点。这比使用转义函数(如 )要安全得多。mysqliPDOmysql_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 的方式。但是,您可以回退到更有限的编码,例如 ISO-8859-1,直到 IE6 使用率下降。

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

评论

24赞 Robert K 4/9/2009
这里唯一遗漏的是,数据库查询的第一个示例...一个简单的 intval() 将解决注入问题。始终使用 intval() 代替 mysqlescape...() 当需要数字而不是字符串时。
11赞 Cheekysoft 4/14/2009
请记住,使用参数化查询将允许您始终将数据视为数据而不是代码。使用 PDO 等库,并尽可能使用参数化查询。
9赞 Marcel Korpel 3/28/2011
两点备注:1.在第一个示例中,如果在参数周围加上引号,则是安全的,例如 2.在第二种情况下(包含 URL 的属性),根本没有用;在这些情况下,您应该使用 URL 编码方案对输入进行编码,例如,使用 rawurlencode。这样,用户就无法插入 et al。$result = "SELECT fields FROM table WHERE id = '".mysql_real_escape_string($_POST['id'])."'";htmlspecialcharsjavascript:
7赞 Marcel Korpel 5/1/2011
“htmlspecialchars 只编码双引号而不是单引号”:这不是真的,这取决于设置的标志,请参阅其参数
2赞 Jo Smo 7/9/2014
这应该加粗:黑名单总是会遗漏一些东西。+1Take a whitelist approach and only let through the chars which are good.
11赞 MarkR 9/21/2008 #2

除了 Cheekysoft 的出色回答:

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

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

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

4赞 BrilliantWinter 9/23/2008 #3

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

当涉及到数据库查询时, 始终尝试并准备好使用 参数化查询。mysqli 和 PDO 库支持此功能。这是 比使用逃生更安全 功能,例如 mysql_real_escape_string。

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

想象一下以下 SQL:

$result = “SELECT 字段 FROM 表 其中 id = “.mysql_real_escape_string($_POST['id']);

您应该能够看到这是 容易受到攻击。想象一下 id 参数包含常见攻击 向量:

1 或 1=1

那里没有危险的字符 encode,所以它会直接传递 通过逃逸过滤器。离开 我们:

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

我编写了一个快速的小函数,我把它放在我的数据库类中,它将去除任何不是数字的东西。它使用preg_replace,因此可能有更多优化的功能,但它在紧要关头工作......

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

因此,与其使用

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

我会用

$result = “SELECT 字段 FROM table WHERE id = ”。数字(“1 OR 1=1”);

它将安全地运行查询

SELECT 字段 FROM table WHERE id = 111

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

评论

1赞 Cheekysoft 9/23/2008
完善!这正是您需要的消毒方式。初始代码失败,因为它未验证数字是否为数字。您的代码可以做到这一点。您应该对所有值来自代码库外部的整数使用变量调用 Numbers()。
1赞 Adam Ernst 10/13/2008
值得一提的是,intval() 将完美地工作,因为 PHP 会自动为您强制将整数强制为字符串。
11赞 jmucchiello 2/6/2009
我更喜欢 intval。它将 1abc2 变为 1,而不是 12。
1赞 triunenature 10/18/2014
intval 更好,尤其是在 ID 上。大多数时候,如果它被损坏了,它就像上面一样,1 或 1=1。你真的不应该泄露别人的身份证。因此,intval 将返回正确的 ID。之后,您应该检查原始值和清理后的值是否相同。这不仅是阻止攻击,而且可以找到攻击者的好方法。
2赞 Frank Forte 2/1/2016
如果您显示个人数据,不正确的行将是灾难性的,您会看到其他用户的信息!相反,最好检查一下return preg_match('/^[0-9]+$/',$input) ? $input : 0;
2赞 Lucas Oman 9/23/2008 #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版本中对此进行了测试,并且对整数列使用字符串上下文不会导致任何问题。

评论

16赞 Cheekysoft 9/23/2008
然后,我将从多字节字符0xbf27开始我的攻击向量,在您的 Latin1 数据库中,它将被过滤器功能转换为0xbf5c27 - 这是一个多字节字符,后跟一个引号。
8赞 Cheekysoft 9/23/2008
尽量不要防范单个已知的攻击媒介。你最终会追逐你的尾巴,直到时间结束,将一个又一个的补丁应用到你的代码中。退后一步,看看一般情况,将倾向于更安全的代码和更好的以安全为中心的思维方式。
0赞 Lucas Oman 2/19/2010
我同意;理想情况下,OP 将使用准备好的语句。
1赞 Night Owl 3/4/2013
虽然本文引用的论点并非万无一失,但它将减轻许多常见的 1 或 1=1 类型的攻击,因此值得一提。
2赞 cnizzardini 10/5/2011 #5
$result = "SELECT fields FROM table WHERE id = ".(INT) $_GET['id'];

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

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

-3赞 Jarett L 3/18/2017 #6

为什么,哦,为什么,你不会在SQL语句中包含用户输入的引号?似乎很傻!在 SQL 语句中包含引号会使“1 或 1=1”成为徒劳的尝试,不是吗?

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

好吧,简单的解决方法:只需删除用户输入的引号即可。例如:.现在,无论如何,在我看来,用户输入将是安全的......input =~ s/'//g;

评论

0赞 Quentin 3/18/2017
“为什么,哦,为什么,你不会在SQL语句中加入用户输入的引号吗?”——这个问题没有提到不引用用户输入。
1赞 Quentin 3/18/2017
“好吧,很容易解决这个问题”——糟糕的修复。这样就丢掉了数据。问题本身提到的解决方案是一种更好的方法。
0赞 Jarett L 3/18/2017
虽然我同意这个问题没有解决引用用户输入的问题,但不引用输入似乎仍然很糟糕。而且,我宁愿扔掉数据也不愿输入坏数据。通常,在注入攻击中,您无论如何都不需要这些数据。右?
0赞 Quentin 3/18/2017
“虽然我同意这个问题没有涉及引用用户输入的问题,但不引用输入似乎仍然很糟糕。这个问题并没有以一种或另一种方式证明它。
1赞 Siyual 3/18/2017
@JarettL 要么习惯于使用准备好的语句,要么习惯于每周二 Bobby Tables 破坏您的数据。参数化 SQL 是保护自己免受 SQL 注入攻击的唯一最佳方法。如果使用预准备语句,则无需执行“SQL 注入检查”。它们非常容易实现(在我看来,使代码更易于阅读),防止字符串连接和 sql 注入的各种特性,最重要的是,您不必重新发明轮子来实现它。