本文翻译自:https://portswigger.net/web-security/sql-injection/union-attacks
当应用程序易受SQL注入攻击,并且查询结果在应用程序的响应中返回时,可以使用UNION关键字从数据库中的其他表中检索数据。这导致了SQL注入UNION的攻击。
UNION关键字允许您执行一个或多个附加的SELECT查询,并将结果追加到原始查询中。例如:
SELECT a, b FROM table1 UNION SELECT c, d FROM table2
这个SQL查询将返回一个包含两列的结果集,包含table1
中列a
和b
,以及table2
中列c
和d
的值。
要使UNION查询工作,必须满足两个关键要求:
- 各个查询必须返回相同数量的列。
- 每列中的数据类型必须在各个查询之间兼容。
要执行SQL注入联合攻击,您需要确保您的攻击满足这两个要求。这通常包括弄清楚:
- 原始查询返回了多少列?
- 从原始查询返回的哪些列属于合适的数据类型,可以保存注入查询的结果?
确定SQL注入联合攻击所需的列数
执行SQL注入联合攻击时,有两种有效的方法可以确定从原始查询中返回了多少列。
第一种方法包括注入一系列ORDER BY子句,并递增指定的列索引,直到出现错误。例如,假设注入点是原始查询的WHERE子句中的带引号的字符串,您将提交:
' ORDER BY 1--
' ORDER BY 2--
' ORDER BY 3--
...
这一系列有效负载修改了原始查询,以便按结果集中的不同列对结果进行排序。ORDER BY
子句中的列可以由其索引指定,因此您不需要知道任何列的名称。当指定的列索引超过结果集中的实际列数时,数据库将返回一个错误,例如:
The ORDER BY position number 3 is out of range of the number of items in the select list.
应用程序实际上可能会在它的HTTP响应中返回数据库错误,或者它可能会返回一个通用错误,或者干脆不返回任何结果。如果您能够检测到应用程序响应中的一些差异,那么您就可以推断出从查询中返回了多少列。
第二种方法包括提交一系列 UNION SELECT
有效负载,指定不同数量的空值:
' UNION SELECT NULL--
' UNION SELECT NULL,NULL--
' UNION SELECT NULL,NULL,NULL--
...
如果空值的数量与列的数量不匹配,数据库将返回一个错误,例如:
All queries combined using a UNION, INTERSECT or EXCEPT operator must have an equal number of expressions in their target lists.
同样,应用程序实际上可能会返回这个错误消息,或者可能只是返回一个一般性错误或者没有结果。当空值的数量与列的数量匹配时,数据库在结果集中返回一个额外的行,在每个列中包含空值。对结果HTTP响应的影响取决于应用程序的代码。如果你幸运的话,你会在响应中看到一些额外的内容,比如一个HTML表格上的一个额外的行。否则,空值可能会触发不同的错误,例如空指针异常。最坏的情况是,响应可能与由不正确的空值数量导致的响应无法区分,这使得这种确定列数的方法无效。
实验
:SQL注入联合攻击,确定查询返回的列数
笔记
- 使用
NULL
作为从注入的SELECT
查询返回值的原因是,每列中的数据类型必须在原始查询和注入的查询之间兼容。因为NULL
可以转换为每种常用的数据类型,所以当列数正确时,使用NULL
最大化了有效负载成功的机会。- 在Oracle上,每个
SELECT
查询都必须使用FROM
关键字并指定一个有效的表。Oracle上有一个名为dual
的内置表,可以用于此目的。因此,在Oracle上注入的查询需要像这样:' UNION SELECT NULL FROM DUAL--
。- 所描述的有效载荷使用双破折号注释序列
--
来注释掉注入点之后的原始查询的剩余部分。在MySQL上,双破折号序列后面必须跟一个空格。或者,散列字符#
可用于标识注释。- 有关特定于数据库的语法的更多详细信息,请参见 SQL注入备忘单。
在SQL注入联合攻击中查找具有可用数据类型的列
执行SQL注入联合攻击的原因是为了能够从注入的查询中检索结果。通常,您想要检索的感兴趣的数据是字符串形式的,因此您需要在原始查询结果中找到一个或多个数据类型为字符串数据或与字符串数据兼容的列。
已经确定了所需的列数后,您可以通过提交一系列 UNION SELECT
有效负载来探测每个列,以测试它是否可以保存字符串数据,这些有效负载依次将字符串值放入每个列中。例如,如果查询返回四列,您将提交:
' UNION SELECT 'a',NULL,NULL,NULL--
' UNION SELECT NULL,'a',NULL,NULL--
' UNION SELECT NULL,NULL,'a',NULL--
' UNION SELECT NULL,NULL,NULL,'a'--
如果列的数据类型与字符串数据不兼容,则注入的查询将导致数据库错误,例如:
Conversion failed when converting the varchar value 'a' to data type int.
如果没有出现错误,并且应用程序的响应包含一些附加内容,包括注入的字符串值,那么相关的列适合于检索字符串数据。
实验
:SQL注入联合攻击,找到一个可以包含文本的列
使用SQL注入联合攻击来检索敏感的数据
当您确定了原始查询返回的列数并找到了哪些列可以保存字符串数据时,您就可以检索感兴趣的数据了。
假设有这些情况:
- 原始查询返回两列,这两列都可以保存字符串数据。
- 注入点是
WHERE
子句中带引号的字符串。 - 该数据库包含一个名为
users
的表,其中包含用户名和密码列。
在这种情况下,您可以通过提交输入来检索 users
表的内容:
' UNION SELECT username, password FROM users--
当然,执行此攻击所需的关键信息是,有一个名为 users
的表,其中有两列名为 username
和 password
。没有这些信息,您只能猜测表和列的名称。事实上,所有现代数据库都提供了检查数据库结构的方法,以确定它包含哪些表和列。
实验
:SQL注入联合攻击,从其他表中检索数据
在一列中检索多个值
在前面的示例中,假设查询只返回一列。
通过将这些值串联在一起,可以很容易地在这个单独的列中一起检索多个值,理想的情况是包含一个合适的分隔符来区分组合的值。例如,在Oracle上,您可以提交输入:
' UNION SELECT username || '~' || password FROM users--
这使用双管序列 ||
,它是Oracle上的字符串连接运算符。注入的查询将 username
和 password
字段的值连接在一起,用 ~
字符分隔。
查询结果将让您读取所有用户名和密码,例如:
...
administrator~s3cure
wiener~peter
carlos~montoya
...
请注意,不同的数据库使用不同的语法来执行字符串连接。有关更多详细信息,请参见 SQL注入备忘单。