今天早些时候,有人问了有关Web应用程序中输入验证策略的问题。
在撰写本文时,最高答案建议PHP
仅使用htmlspecialchars
和mysql_real_escape_string
。
我的问题是:这是否总是足够?还有更多我们应该知道的吗?这些功能在哪里分解?
今天早些时候,有人问了有关Web应用程序中输入验证策略的问题。
在撰写本文时,最高答案建议PHP
仅使用htmlspecialchars
和mysql_real_escape_string
。
我的问题是:这是否总是足够?还有更多我们应该知道的吗?这些功能在哪里分解?
Answers:
对于数据库查询,请始终尝试使用准备好的参数化查询。在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的方式,它也容易受到攻击。但是,在IE6的使用率下降之前,您可以使用更有限的编码,例如ISO-8859-1。
有关多字节问题的更深入研究,请参阅https://stackoverflow.com/a/12118602/1820
$result = "SELECT fields FROM table WHERE id = '".mysql_real_escape_string($_POST['id'])."'";
2),则将是安全的。在第二种情况下(属性包含URL),根本没有用htmlspecialchars
;在这种情况下,您应该使用URL编码方案(例如使用)对输入进行编码rawurlencode
。这样,用户将无法插入javascript:
等。
Take a whitelist approach and only let through the chars which are good.
黑名单将始终丢失某些内容。+1
除了Cheekysoft的出色答案之外:
确实没有防止HTML注入的灵丹妙药(例如,跨站点脚本),但是如果您使用库或模板系统来输出HTML,则可以更轻松地实现它。阅读文档,以了解如何适当地转义。
在HTML中,需要根据上下文进行不同的转义。将字符串放入Javascript时尤其如此。
我绝对同意以上文章,但是我需要补充一小件事来回应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注入您的网站的人来说,我都不认为这是个大问题;
return preg_match('/^[0-9]+$/',$input) ? $input : 0;
这个难题的一个重要方面是上下文。如果您引用查询中的每个参数,则将“ 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”的尝试毫无结果,不是吗?
所以现在,您会说:“如果用户在输入中包含引号(或双引号),该怎么办?”
好了,很容易解决:只需删除用户输入的引号即可。例如:input =~ s/'//g;
。现在,在我看来无论如何,用户输入将得到保护...