SQL 注入——为什么转义引号不再安全了?

信息安全 应用安全 Web应用程序 攻击 数据库 sql注入
2021-08-16 06:03:17

原始 SQL

当您编写 SQL 时——对于任何真正需要人工输入的东西,已经做了很多事情来避免注入。

听说过 SQL 注入的每个人都知道(我将使用 PHP 作为示例)做这样的事情是不安全的:

$sql = "SELECT * FROM `users` 
.  WHERE `userName` = "{$_POST["username"]}" 
.  AND `pass` = "{$_POST["pass"]}";";

魔法

然后,当然,有人提出了使用“魔术转义引号”来处理由于不良做法而没有正确清理并直接放入 sql 的程序输入的想法。这并没有真正解决 SQL 注入的问题,但它确实意味着所有用户输入都被破坏了。

添加斜杠

因此,有些人关闭了魔术引号。然后,他们在 SQL 之前解析用户输入addslashes(),理论上通过 SQL 转义所有引号,而您的黑客无法做到这一点' OR 1=1,但即使是 addlashes 的文档,它自己也说您不应该使用 addlashes,它说使用数据库-等具体功能mysql_real_escape_string(),但有些人仍然认为这还不够。

添加特定于数据库的斜线

所以,我们不能使用特定于 DBMS 的*_real_escape_string,我们不能使用add slashes,“魔术引号”的东西引起了很多问题,而且网络上到处都是简短的引号,例如:

“一个专门的黑客会找到一种方法来跳过你的引号转义循环,只需使用 DBAL 准备好的语句” - John Q 任何程序员

好的,这让我害怕到使用准备语句和 DBAL。它并没有真正解释什么,但听起来不错,因为我听过很多次。

准备好的报表

所以现在我们正在使用 PDO,或者来自框架的 DBAL,或者其他包装我们所有 sql 并确保有人无法运行 sql 注入的东西。

我的问题基本上是“为什么不?”,而不是“我应该使用什么?”。网络上到处都是告诉你使用这个或使用那个或其他的人,但没有解释为什么这些事情必须发生。

直接问题

有针对性的问题(提醒一下,我问的是 SQL,PHP 是一种示例语言,因为它在 SQL 方面的表现不佳,概念是通用的):

  1. 为什么我们不能使用“魔术”转义所有用户输入?
  2. 为什么addlashes“不够好”?
  3. 使用特定于 DB 的转义函数有什么问题,为什么它比 addlashes 更好?
  4. 为什么带有框架和 PDO 的预处理语句被誉为 SQL 的黄金标准?为什么他们更好?为什么我不能使用这些进行 SQL 注入,而我可以使用前面提到的方法呢?程序员不能以某种方式仍然搞砸吗?他们应该注意什么?
  5. 还有什么我没有提出来的担忧吗?
4个回答

为什么我们不能使用“魔术”来逃避所有用户输入?

在应用魔法时,尚不清楚数据将在哪里结束。因此,魔术引号正在破坏数据,除非它未转义地写入数据库。

它可能只是在发送回客户端的 HTML 响应中使用。考虑一个尚未完全填写并因此再次显示给用户的表单。使用魔术引号,第一次尝试输入的数据现在将被 SQL 转义,这在 HTML 页面上毫无意义。更糟糕的是:在第二次提交时,数据再次被 SQL 转义。

为什么addlashes“不够好”?

它有多字节字符的问题:

' 27
\ 5c
뼧 bf 5c

那是两个字节,但只有一个 Unicode 字符。

由于addslashes对 Unicode 一无所知,因此它将输入转换bf 27bf 5c 27. 如果这是由 Unicode 感知程序读取的,则它被视为 뼧'。繁荣。

在http://shiflett.org/blog/2006/jan/addslashes-versus-mysql-real-escape-string上有一个很好的解释

使用特定于 DB 的转义函数有什么问题,为什么它比 addlashes 更好?

它们更好,因为它们确保转义以与数据库相同的方式解释数据(参见最后一个问题)。

从安全的角度来看,如果您将它们用于每个数据库输入,它们是可以的。但是它们存在您可能会在某个地方忘记它们中的任何一个的风险。

编辑:正如 getahobby 添加的那样:或者您使用 xxx_escape_strings 表示数字而不在 SQL 语句中在它们周围添加引号,并且没有通过将输入转换或转换为适当的数据类型来确保它们是实际数字/编辑

从软件开发的角度来看,它们很糟糕,因为它们使添加对其他 SQL 数据库服务器软件的支持变得更加困难。

为什么带有框架和 PDO 的预处理语句被誉为 SQL 的黄金标准?为什么他们更好?

出于软件设计的原因,PDO 主要是一件好事。它使支持其他数据库服务器软件变得更加容易。它有一个面向对象的接口,它抽象了许多小的数据库特定的不兼容性。

为什么我不能使用这些 [prepared statements] 进行 SQL 注入,而我可以使用前面提到的方法?

准备好的语句的“带有可变参数的常量查询”部分在这里很重要。数据库驱动程序将自动转义所有参数,而无需开发人员考虑。

由于语法原因,参数化查询通常比带有转义参数的普通查询更容易阅读。根据环境,它们可能会快一点。

始终使用带参数的预准备语句可以通过静态代码分析工具进行验证。对 xxx_escape_string 的丢失调用不会那么容易且可靠地被发现。

程序员不能以某种方式仍然搞砸吗?他们应该注意什么?

“准备好的陈述”暗示 是恒定的。动态生成准备好的语句 - 尤其是用户输入 - 仍然存在所有注入问题。

[1.] 为什么我们不能使用“魔术”来转义所有用户输入?

因为魔术引号在两个方面被打破:

  1. 并非所有 $_(REQUEST/GET/POST) 数据都进入数据库。为了让你理解它,你必须对输入进行转义。
  2. 它们是addslashes等价的(参见我在 [2.] 中的注释),因此它们实际上并不安全。

[2.] 为什么addlashes“不够好”?

有很多方法可以摆脱addslashes限制,因此,无论您尝试使用它,它都存在根本性的缺陷。

[3.] 使用特定于 DB 的转义函数有什么问题,为什么它比 addlashes 更好?

真的没什么。这就是为什么 PDO/prepared 是“更好”的口头禅。它们比它们要好得多,因为它们考虑了许多因素,包括编码这里有个小秘密;PDO 在内部使用它们,但不...addslashesaddslashes

[4.] 为什么带有框架和 PDO 的预处理语句被誉为 SQL 的黄金标准?为什么他们更好?为什么我不能使用这些进行 SQL 注入,而我可以使用前面提到的方法呢?

它们不是黄金标准,它们并不比特定于 DB 的函数“更”安全,您也可以使用这些函数进行 SQL 注入不,您不能使用经过适当清理的输入进行 SQL 注入。

与现有的所有技术一样,开发人员倾向于为任何“新”事物发展宗教(狂热?)倾向。这就是为什么您会得到经验丰富的 Zend Certified Engineers(TM) 建议 - 不会强迫您切换到准备好的语句。

然而,现实是,这一切都取决于知道你在做什么。如果您通过其数字 ID 加载文章,则不需要任何类型的转义,更不用说 PDO。您只需要确保 ID 确实是一个整数。

从更现实的角度来看,一般规则是不要使用 PDO/prepared 语句,而是使用正确的函数,也就是说,必须使用mysql_real_escape_string()代替addslashes()or mysql_escape_string()

[5.] 程序员不能以某种方式仍然搞砸吗?他们应该注意什么?

当然!但这不是“要注意什么”,而是“知道你在做什么”。

你看,eval($_GET['code'])* 在正确的地方使用时是完全合法的(例如本地 PHP 测试运行器)。

如果您使用 ORM 来运行条件查询,您将直接输入代码,因此如果您操作不当(取决于所讨论的 ORM),可能会导致 SQL 注入。(假设)示例:

// update() sets the value of a column given a condition
//
//                         .--escaping is automatic here
//                         |                           .--hypothetical SQL injection
//                         v                           v
Db()->update('name',$_GET['newname'])->where(' id = '.$_GET['id']);

(* 我只能想象无数专业人士为此拼命)

在一天结束时,必须对输入进行转义,以保护您的查询免受 SQL 注入的影响。PDO 和 ADODB 等参数化查询库使用转义,它们所做的只是以隐式安全的方式强制执行这种做法。通过对比 magic_quotes_gpc 没有这从根本上是有缺陷的解决问题的方法。

1) 如果你不明白你在做什么,逃避可能会有问题。如果启用了magic_quotes_gpc,此类问题仍然可以利用。

mysql_query("select * from users where id=".addslashes($_GET[id]));

PoC 漏洞利用: http://localhost/vuln.php?id=sleep(30 )

修补:

mysql_query("select * from users where id='".addslashes($_GET[id])."'");

教训: 你不需要引号来利用 sql 注入。

2)假设您只转义引号:

mysql_query("select * from users where name='".htmlspecialchars($_GET[name],ENT_QUOTES)."' and id='".htmlspecialchars($_GET[id],ENT_QUOTES)."'");

PoC 漏洞利用: http://localhost/vuln.php?name= \&id=+or+sleep(30)/*

修补:

mysql_query("select * from users where name='".addslashes($_GET[name])."' and id='".addslashes($_GET[id])."'");

教训:反斜杠和引号一样危险。

3)语言编码呢?

mysql_query("SET CHARACTER SET 'gbk'");
mysql_query("select * from user where name='".addslashes($_GET[name])."'");

PoC 漏洞利用:http://localhost/vuln.php?name=%bf%27=sleep(30)/*

修补:

mysql_query("SET CHARACTER SET 'gbk'");
mysql_query("select * from user where name='".mysql_real_escape_string($_GET[name])."'");

结论:

为了防止应用程序中的 sql 注入,绝对需要转义。在 PHP 中,PDO 和 ADOB 是很好的参数化查询库,它们强制用户输入的转义。问题是很多程序员不明白什么是转义。拥有一个库来执行此安全策略是最好的防御方法,因为您不需要了解转义规则。

关于特定于 DB 的转义函数。尽管已经使用了 mysql_real_escape_string(),但在很多情况下都可能发生 SQL 注入。LIMIT 和 ORDER BY 真的很棘手!

这些示例容易受到 SQL 注入的影响:

$offset = isset($_GET['o']) ? $_GET['o'] : 0;
$offset = mysql_real_escape_string($offset);
$sql = "SELECT userid, username FROM sql_injection_test LIMIT $offset, 10";

或者

$order = isset($_GET['o']) ? $_GET['o'] : 'userid';
$order = mysql_real_escape_string($order);
$sql = "SELECT userid, username FROM sql_injection_test ORDER BY `$order`";

在这两种情况下,您都不能使用 ' 来保护封装。

来源:意外的 SQL 注入(当转义不够时)<--阅读此