在本节中,我们将解释什么是SQL注入,描述一些常见示例,解释如何查找和利用各种SQL注入漏洞以及概述如何防止SQL注入。
SQL注入是一个网络安全漏洞,攻击者可以利用该漏洞来干扰应用程序对其数据库的查询。 通常,它使攻击者可以查看他们通常无法检索的数据。 这可能包括属于其他用户的数据,或者应用程序本身能够访问的任何其他数据。 在许多情况下,攻击者可以修改或删除此数据,从而导致应用程序内容或行为的永久更改。
在某些情况下,攻击者可以升级SQL注入攻击,以破坏基础服务器或其他后端基础结构,或者执行拒绝服务攻击。
成功的SQL注入攻击可能导致未经授权访问敏感数据,例如密码,信用卡详细信息或个人用户信息。 近年来,许多引人注目的数据泄露是SQL注入攻击的结果,导致声誉受损和监管罚款。 在某些情况下,攻击者可以获取组织系统的持久后门,从而导致长期的折衷,而这可能会在很长一段时间内被忽略。
在不同情况下会出现各种SQL注入漏洞、攻击和技术。 一些常见的SQL注入示例包括:
考虑一个显示不同类别产品的购物应用程序。 当用户单击“Gifts”类别时,其浏览器将请求URL:
https://insecure-website.com/products?category=Gifts
这将导致应用程序执行SQL查询,以从数据库中检索相关产品的详细信息:
SELECT * FROM products WHERE category = 'Gifts' AND released = 1
此SQL查询要求数据库返回:
限制 released= 1用于隐藏未发布的产品。 对于未发布的产品,假定发布= 0。
该应用程序未对SQL注入攻击实施任何防御措施,因此攻击者可以构建如下攻击:
https://insecure-website.com/products?category=Gifts'--
这将导致SQL查询:
SELECT * FROM products WHERE category = 'Gifts'--' AND released = 1
这里的关键是双破折号序列-是SQL中的注释指示符,意味着查询的其余部分将被解释为注释。 这有效地删除了查询的其余部分,因此不再包含AND Release =1。这意味着将显示所有产品,包括未发布的产品。
更进一步,攻击者可以使应用程序显示任何类别的所有产品,包括他们不知道的类别:
https://insecure-website.com/products?category=Gifts'+OR+1=1--
这将导致SQL查询:
SELECT * FROM products WHERE category = 'Gifts' OR 1=1--' AND released = 1
修改后的查询将返回类别为Gifts或1等于1的所有项目。由于1 = 1始终为true,因此查询将返回所有项目。
考虑一个允许用户使用用户名和密码登录的应用程序。 如果用户提交用户名wiener和密码bluecheese,则应用程序将通过执行以下SQL查询来检查凭据:
SELECT * FROM users WHERE username = 'wiener' AND password = 'bluecheese'
如果查询返回用户的详细信息,则登录成功。 否则,它将被拒绝。
在这里,攻击者只需使用SQL注释序列即可以没有密码的任何用户身份登录-从查询的WHERE子句中删除密码检查。 例如,提交用户名“ administrator”和空白密码将导致以下查询:
SELECT * FROM users WHERE username = 'administrator'--' AND password = ''
该查询返回用户名为administrator的用户,并以该用户身份成功登录攻击者。
如果在应用程序的响应中返回了SQL查询的结果,则攻击者可以利用SQL注入漏洞从数据库中的其他表中检索数据。 这是使用UNION关键字完成的,该关键字使您可以执行附加的SELECT查询并将结果附加到原始查询中。
例如,如果应用程序执行以下查询,其中包含用户输入“ Gifts”:
SELECT name, description FROM products WHERE category = 'Gifts'
然后攻击者可以提交输入:
' UNION SELECT username, password FROM users--
这将导致应用程序返回所有用户名和密码以及产品名称和描述。
在初步识别出SQL注入漏洞之后,获取有关数据库本身的一些信息通常非常有用。 这些信息通常可以为进一步开发铺平道路。
您可以查询数据库的版本详细信息。 完成此操作的方式取决于数据库类型,因此您可以从任何一种技术推断出数据库类型。 例如,在Oracle上,您可以执行:
SELECT * FROM v$version
您还可以确定存在哪些数据库表以及它们包含哪些列。 例如,在大多数数据库上,您可以执行以下查询以列出表:
SELECT * FROM information_schema.tables
SQL注入的许多实例都是盲目的漏洞。 这意味着该应用程序不会在其响应内返回SQL查询的结果或任何数据库错误的详细信息。 盲目漏洞仍然可以被利用来访问未经授权的数据,但是所涉及的技术通常更加复杂并且难以执行。
根据漏洞的性质和所涉及的数据库,可以使用以下技术来利用盲目的SQL注入漏洞:
使用Burp Suite的web漏洞扫描程序可以快速可靠地找到大多数SQL注入漏洞。
通过对应用程序中的每个入口点使用系统化的测试集,可以手动检测SQL注入。 这通常涉及:
大多数SQL注入漏洞出现在SELECT查询的WHERE子句中。 有经验的测试人员通常会很好地理解这种SQL注入。
但是,原则上,SQL注入漏洞可以出现在查询中的任何位置以及不同的查询类型中。 发生SQL注入的最常见其他位置是:
一阶SQL注入出现在应用程序从HTTP请求获取用户输入的情况下,并且在处理该请求的过程中,以不安全的方式将输入合并到SQL查询中。
在二阶SQL注入(也称为存储的SQL注入)中,应用程序从HTTP请求中获取用户输入并将其存储以备将来使用。 这通常是通过将输入放入数据库来完成的,但是在存储数据时不会出现漏洞。 稍后,当处理其他HTTP请求时,应用程序将以不安全的方式检索存储的数据并将其合并到SQL查询中。
二阶SQL注入通常发生在开发人员意识到SQL注入漏洞并因此安全地处理输入到数据库中的初始位置的情况下。 以后处理数据时,由于先前已将其安全地放置到数据库中,因此认为该数据是安全的。 此时,由于开发人员错误地认为数据是可信的,因此以不安全的方式处理数据。
在流行的数据库平台上,SQL语言的某些核心功能以相同的方式实现,因此,检测和利用SQL注入漏洞的许多方式在不同类型的数据库上均相同。
但是,常见数据库之间也存在许多差异。 这意味着用于检测和利用SQL注入的某些技术在不同平台上的工作方式不同。 例如:
通过使用参数化查询(也称为预处理语句)而不是查询中的字符串串联,可以防止大多数SQL注入实例。
由于用户输入直接连接到查询中,因此以下代码容易受到SQL注入的攻击:
String query = "SELECT * FROM products WHERE category = '"+ input + "'";
Statement statement = connection.createStatement();
ResultSet resultSet = statement.executeQuery(query);
可以以防止用户输入干扰查询结构的方式轻松重写此代码:
PreparedStatement statement = connection.prepareStatement("SELECT * FROM products WHERE category = ?");
statement.setString(1, input);
ResultSet resultSet = statement.executeQuery();
参数化查询可用于不信任输入在查询中显示为数据的任何情况,包括WHERE子句和INSERT或UPDATE语句中的值。 它们不能用于处理查询其他部分中的不可信输入,例如表名或列名或ORDER BY子句。 将不受信任的数据放入查询的那些部分的应用程序功能将需要采用不同的方法,例如将允许的输入值列入白名单,或者使用不同的逻辑来传递所需的行为。
为了使参数化查询有效地防止SQL注入,查询中使用的字符串必须始终是硬编码常量,并且绝不能包含来自任何来源的任何变量数据。 不要试图逐案决定数据项是否受信任,并继续在查询中使用字符串连接来处理认为安全的情况。 可能会很容易就数据的原始来源犯错误,也可能因为其他代码的更改而违反关于污染数据的假设。