开发过程中,不对用户输入进行校验,直接参与编译或解释运行就有可能被恶意利用,实现注入。
SQL注入示例
-
检索隐藏数据
-
颠覆应用程序逻辑
-
UNION攻击
-
检查数据库
-
盲注
检索隐藏数据
这种方式可以在其中修改SQL查询以返回其他结果
比如一个显示不同类别产品的购物应用程序。当用户单击礼物类别时,浏览器将请求以下URL:https://insecure-website.com/products?category=Gifts,我们假设后端执行的SQL查询语句为:SELECT * FROM products WHERE category = 'Gifts' AND released = 1。该限制released = 1用于隐藏未发布的产品。对于未发布的产品,大概是released = 0。该应用程序未对SQL注入攻击实施任何防御措施,因此攻击者可以构建如下攻击:https://insecure-website.com/products?category=Gifts'--。这将导致SQL查询语句变成了:SELECT * FROM products WHERE category = 'Gifts'--' AND released = 1。此处的关键是,--是SQL中的注释指示符,这意味着查询的其余部分将被解释为注释。这样可以有效地删除查询的其余部分,因此不再包含AND released = 1。这意味着将显示所有产品,包括未发布的产品。更进一步,攻击者可以使应用程序显示任何类别的所有产品,包括他们不知道的类别:https://insecure-website.com/products?category=Gifts'+OR+1=1--。这将导致SQL查询:SELECT * FROM products WHERE category = 'Gifts' OR 1=1--' AND released = 1。修改后的查询WHERE条件始终为true,因此将会返回库中所有数据。
颠覆应用程序逻辑
这种方式可以在其中更改查询以干扰应用程序的逻辑
考虑一个允许用户使用用户名和密码登录的应用程序。如果用户提交用户名wiener和密码bluecheese,则应用程序将通过执行以下SQL查询来检查凭据:SELECT * FROM users WHERE username = 'wiener' AND password = 'bluecheese'。如果查询返回用户的详细信息,则登录成功;否则,它将被拒绝。这时使用注释--去掉密码检查,即可无需密码即可以任何身份登录。例如,提交用户名administrator'--和空白密码将导致以下查询:SELECT * FROM users WHERE username = 'administrator'--' AND password = ''。该查询将返回用户名为administrator的用户信息并使攻击者以该用户身份成功登录。
UNION攻击
这种方式可以在其中从不同的数据库表中检索数据
如果在应用程序的响应中返回了SQL查询的结果,则攻击者可以利用UNION关键字从数据库中的其他表中检索数据,该关键字使您可以执行附加SELECT查询并将结果附加到原始查询中。
例如,如果应用程序执行以下包含用户输入“ Gifts”的查询:SELECT name, description FROM products WHERE category = 'Gifts',然后攻击者可以提交输入:' UNION SELECT username, password FROM users--,这将导致应用程序返回所有用户名和密码以及产品名称和描述。
为了使UNION查询正常工作,必须满足两个关键要求:
-
各个查询必须返回相同数量的列
-
每列中的数据类型在各个查询之间必须兼容
所以要执行UNION攻击,通常涉及找出:
-
原始查询返回了多少列
-
从原始查询返回的哪些列具有合适的数据类型,可以保存来自注入查询的结果
确定UNION攻击所需的列数
执行SQL注入UNION攻击时,有两种有效的方法来确定从原始查询返回多少列
第一种方法涉及注入一系列ORDER BY子句并递增指定的列索引,直到发生错误为止。例如,假设注入点是WHERE原始查询子句中带引号的字符串,则可以提交:
' ORDER BY 1--
' ORDER BY 2--
' ORDER BY 3--
etc.
这一系列有效负载修改了原始查询,以按结果集中的不同列对结果进行排序。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--
etc.
如果NULL的个数与列数不匹配,则数据库将返回错误,例如:All queries combined using a UNION, INTERSECT or EXCEPT operator must have an equal number of expressions in their target lists.同样,应用程序实际上可能返回此错误消息,或者可能仅返回一般错误或没有结果。当NULL的个数与列数匹配时,数据库将在结果集中返回另一行,其中每一列均包含空值。对产生的HTTP响应的影响取决于应用程序的代码。如果幸运的话,您将在响应中看到一些其他内容,例如HTML表格上的额外行。否则,空值可能会触发其他错误,例如NullPointerException。最坏的情况是,响应可能与由不正确的NULL的个数引起的响应没有区别,这使确定列数的方法无效。
之所以将NULL用作注入的SELECT查询返回的值,是因为每一列中的数据类型在原始查询和注入的查询之间必须兼容。由于NULL可以转换为每种常用的数据类型,因此使用NULL可以最大程度地提高当列数正确时有效负载成功的机会
在Oracle上,每个SELECT查询都必须使用FROM关键字并指定一个有效的表。Oracle上有一个称为
DUAL的内置表,可用于此目的。因此,在Oracle上注入的查询将需要如下所示:' UNION SELECT NULL FROM DUAL--
在
MySQL中,使用--作为注释序列时,在其后面必须有一个空格。我们可以使用#作为注释序列。
检查数据库
这种方式可以在其中提取有关数据库版本和结构的信息
盲注
这种方式应用程序不会在其响应内返回SQL查询的结果或任何数据库错误的详细信息。但是漏洞仍然可以被利用来访问未授权的数据,但是所涉及的技术通常更加复杂并且难以执行。