说明
该文中使用的数据库为本地测试数据库,数据内容无任何实际意义!
引言
学习SQL注入技术,不是为了成为攻击者,而是为了理解:在这个数据即权力的时代,保护数据库的安全,就是在保护数字世界的基石。每一次的防止注入攻击,你可能保护的是某个人的隐私、某个家庭的财务安全、某个企业的商业未来。这是技术人员的职业责任,也是数字公民的基本伦理。
数据库的核心地位
为了理解SQL注入的重要性,首先需要明白现代Web应用如何运作
用户浏览器 → Web服务器 → 应用程序 → 数据库
↑ ↑ ↑ ↑
界面展示 逻辑处理 业务规则 数据核心
数据库处于整个架构的最底层,却是整个web应用的核心。应用程序只是数据库数据的前台展示,比如常见的个人资料,账户余额等皆来自数据库数据。
你的应用程序不是在与用户交互,而是用户提交的数据与你的数据库进行交互的过程。每一次交互都是一次数据库sql语句执行的过程,都是一次信任与危机共存的过程。
什么是数据库?
一个典型的商业应用数据库中通常包含:
用户数据层,商业数据层,系统控制层:比如用户的账号密码,交易数据,管理员的权限配置等等。
我们通常通过前端访问容易接触的都是登录过程,比如新安装一个app,或者打开一个网页需要输入用户名密码,或者注册,这些数据就需要在服务器上存储,一般存储的位置就是数据库。所以只需要完成数据库的注入攻击,即可获取所有用户的数据信息,将数据变为实际的经济价值。
常见的关系型数据库有:MySQL,Oracle,Microsoft SQL Server,PostgreSQL,SQLite等等国产数据库(TiDB,达梦,openGauss)等等
比如如下mysql数据库使用数据库工具打开之后的UI界面:

非关系型数据库:MongoDB,Redis,Cassandra等
SQL注入主要针对关系型数据库,因为其攻击原理基于SQL语言的语法,而SQL是关系型数据库的标准查询语言。关系型数据库如MySQL、Oracle、SQL Server等均使用SQL进行数据操作,因此易成为注入目标。非关系型数据库(如NoSQL)虽然也有注入风险,但其数据模型和查询方式与SQL不同,注入方法和关系型数据库存在差异。
基础-SQL语言语法
SQL是一种声明式编程语言,这与过程式语言(如Python、Java)有本质区别,它是专门操作数据库的一种语言格式。与其他语言一样,它的执行也是具有优先级的,比如and优先级高于or。
声明式:告诉数据库”我想要什么”,比如:select name from employee :查询name(名字)列的内容从表employee表中如下:得到查询结果

其不指定具体执行步骤,由数据库优化器决定如何执行。
在操作数据库的过程中,我们用的最多的基本上都是增删改查即(IDUS)
增(Insert):往数据库里添加新数据
语法规则:INSERT INTO <表名> [(<列列表>)] VALUES (<值列表>) ;
比如:

删(Delete):删除指定的数据
语法规则:DELETE FROM <表名> [WHERE <条件>];
这个较为简单,自行了解
改(Update):修改已有数据
语法规则:UPDATE <表名> SET <列名> = <表达式> [, …] [WHERE <条件>];
比如修改上面的价格:

查(Select):查询数据库中的现有的内容
其中查询应该是用的最多的,SQL查询语句的结构遵循SELECT 列表名 FROM 表名 WHERE 条件 的基本框架
- SELECT:指定要查询的列(或使用*表示所有列)。
- FROM:定义数据来源的表名。
- WHERE:设置筛选条件,仅保留满足条件的行。
比如最简单的查询语句构成:select name from employee where id = 2,不管多复杂的查询也都遵循这样的规则
以上为直接通过数据库工具对数据库进行的操作,在用户访问web应用的时候,其对数据库中的数据访问其实也是通过sql语句进行操作的。但这漏洞是如何产生的呢?
SQL注入产生的原因
我们通过web服务器的代码来看,比如登录过程存在漏洞的代码可能是下面这样的:
服务端post请求得到用户的username和password

然后通过拼接sql字符串(将用户post请求的数据拼接到字符串中),然后将该sql字符串当作数据库的SQL语言给mysql执行。

在这个数据库中的数据如下:

比如这个示例中的我们启动这个项目:

然后输入正常的用户名,密码得到的结果是这样的:

相当于直接在mysql中执行了SELECT * FROM users WHERE user = ‘test’ AND pass = ‘test’ 这个语句如下图:结果一致。

当然这是假设用户按照开发的预期输入的流程,但是如果用户不按照这个预期输入内容呢?
String sql = "SELECT * FROM users WHERE user = '" + username +
"' AND pass = '" + password + "'";
从源码中可以看到,用户的输入会被拼接到sql中相当于是这样服务端的mysql数据库将会执行这条语句:SELECT * FROM users WHERE user = ‘用户输入‘ AND pass = ‘用户输入‘;那么如果用户手滑了输入了一个单引号’呢?

这样就会导致SQL执行的语法错误,无法正确识别这行语句而报错,通常这也是报错注入的一种判断方法。
那么要让语句正常执行怎么做,当然是需要根据SQL语言的语法规则,让其语法正确了,让其满足SQL语言的语法规则。可以看到是多了一个’单引号。那么加一个单引号就行了,比如手滑输入2个单引号,果然会执行,不会报错,但是无法查询到结果了因为不存在user=test’的内容(在SQL中2个单引号会被解释成一个单引号内容)

SQL中2个单引号会被解释成一个单引号内容

因为在SQL 中,单引号用于表示字符串字面量:字符串以单引号开始:’同时字符串以单引号结束:’,如果想在字符串中包含单引号字符,需要使用两个单引号进行转义:”,所以在SQL语句中,单引号总是成对出现的
上面查询的实际是user= test’ 的内容,数据库中当然没有这个数据,所以返回空未匹配到用户数据。所以SQL注入成功的关键其实就是如何闭合SQL查询语句并让其正确执行后面的语句,而后面的语句是我们可控的。
仅仅是这样肯定无法达到我们想要的目的,我们的目的是注入SQL语句,执行一些非预期的动作,并能返回有意义的或者有价值的内容。
比如这样:

输入单引号,保证语法正确,但密码未知,需要同时保证执行结果为真。所以如下:SQL语句可分成3段:第一段:select * from users where user = ‘test’ 。第二段:” = ” 第三段: pass = ‘asdfadf’ ,当执行第一段时,会查询到test用户的内容,由于and执行的优先级比or高,所以会先执行” = ” and pass = ‘asdfadf’ 得到pass = ‘asdfadf’ ,最后组合到一起实际执行结果是SELECT * FROM users WHERE user = ‘test’ OR pass = ‘asdfadf’,最终返回所有test用户名的信息。

这样就达到了在不知道密码的情况下,登录成功。也就是所谓的万能密码。并非必须是 1=1 为万能密码,其核心原理是保证语法正确,且执行结果为真–即可查询到数据,就是所谓的万能密码—保证SQL执行结果为真。
SQL注入学习
那么在没有靶场或者环境的情况下如何快速进行注入的练习或者学习呢。答案就是搭建一个数据库,直接通过SQL语句操作数据库,同时这样也可以在渗透测试的时候进行一些问题验证。
上面已经知道了SQL注入实际就是用户的输入内容带入了SQL语句中进行了执行,那么直接在数据库中输入用户的数据就可实现模拟注入的练习。
比如上面的实际执行的SQL语句为:SELECT * FROM users WHERE user = ‘test’ or ”=” AND pass = ‘asdfadf’将其在数据库中直接执行:效果一样

当我们在安全检测过程中发现存在得注入漏洞,都可以通过本地的数据库进行模拟注入。当然最主要的前提是,要对SQL语言有一定了解,服务端的查询语句大概内容可能是什么样的。通常情况下注入的方法都不止一种,还是上面的例子,比如我们通过闭合引号,保证语法不出错,同样的我们可以通过注释的方式,更直接的将语句提前终止,让其正确执行,比如使用– 的方式。

其实就是SQL语言的运用,SQL运用得越是熟练,可能注入得技巧就越多。
注入类型
SQL注入的分类
SQL注入的分类,有很多种方式,有的根据注入的回显的方式分为报错注入和盲注,也有根据输入类型分为字符型和数字型的,根据回显方式可分为有回显和无回显两种
根据注入点的输入数据类型分类
这个维度决定了闭合SQL语句的方式
字符串类型的注入
还是上面的例子,也是生产环境中最常见的例子。在SQL中,字符串的包裹可以分为单引号(‘)和双引号(“)

比如如果是双引号,那么要让语法正确,这时候就应该使用对应的双引号进行闭合语句。同样达到目的。

其主要原理是服务端通过拼接SQL语句的方式,将用户的输入作为sql查询语句的一部分,在数据库中执行。在拼接过程中,会有字符串的闭合字符单引号(‘)和双引号(“)的拼接,所以用户注入的时候就需要控制这个闭合,从而注入自己构造的sql语句。
数字类型注入
在SQL中字符串需要使用特殊字符包裹起来,但是数字类型的通常是不需要特殊字符包括,比如见的最多的注入类型:http:://www.test.com/?id=1 这种格式,如果在数据库中那么就可能是如下的体现:

这时候的结束语句其实就是空格(空格的表现形式有很多,比如换行,TAB,分号等等,只需要让数据库识别到当前的id=**这个整体结束就行),比如空格后面在输入注入的语句即可,执行,查询到所有数据。

以上的方式其实也是用于简单判断是否存在注入的方法一种方法。
根据返回信息的“回显”方式(最实用的分类)
报错注入-有回显
原理:利用数据库的报错机制,通过构造特定语句,将想要查询的信息通过错误信息的形式带出
关键点:需要后端将数据库的报错信息直接展示在页面上(开发未做屏蔽)
典型函数:(MySQL):updatexml() , extractvalue()
(SQLServer):cast() , convert()
(Oracle):ctxsys.drithsx.sn() ,dbms_utility.sqlid_to_sqlhash()
盲注-无回显
原理:页面没有直接的数据回显,也没有详细的报错信息。只能通过观察页面行为的差异来推断信息
通常又分为布尔盲注和时间盲注
布尔盲注:根据页面返回的 True 或 False(其实是根据SQL查询的结果为true或者false)来进行判断,因为服务端在执行sql语句之后,通常会根据执行结果的True 或 False进行不同的业务逻辑。那么在客户端就会有不同的表现形式。
时间盲注:当页面没有任何可见的布尔差异时使用(布尔盲注无法判断时)。通过构造语句,根据数据库响应时间的延迟来判断条件是否成立,典型函数:sleep(), benchmark()。
还有一些其他分类,比如联合查询注入,堆叠注入,或者GET/POST/Cookie/头部注入,但是其前端的表现形式其实主要分为回显与无回显两类。有回显的可以直接进行数据获取,而无回显只能通过盲注的方式获取数据。一个实际的SQL注入漏洞往往是多个分类的组合,比如登录框的POST请求参数,可能是一个字符型的、基于布尔的盲注。
判断注入-基于回显类型
首先判断注入类型(数字/字符),该阶段决定了接下来通过什么方式闭合SQL语句,从而注入恶意的SQL语句。
比如通过数字类型的查询,那么我们想到的就是与空格相关的字符,结束当前参数id的赋值,从而进入下一个语句。
直接空格执行测试SQL语句:后面为真,与or整体结果即为真,比如1=1 , 1+1=2 甚至如下直接TRUE也行。

比如使用字符型的,我们就应该知道需要通过单双引号来进行语句的闭合,结束user的赋值,进入后面的可控内容的执行

使用引号结束,然后注释掉多余的内容(服务端拼接语句时留下的引号,见文章开头)比如这样:

如果直接在数据库中操作也可以是下面这样:这样练习比在前端更直接,可以直接接触SQL语言

判断注入的核心其实就是闭合当前语句,再执行构造的SQL语句,根据构造的语句的执行结果确定是否存在注入。以整体执行结果的true(操作成功-比如查询结果有数据)或者false(操作失败–比如查询结果无数据)来判断。
比如用户输入id的查询,当输入不存在的id :123123时,查询结果为空,前端返回空数据:

当输入为1 or 1时,确会返回所有数据,即存在注入,这时候前端通常返回查询到的数据,比如下面的内容

以上为简单的判断操作,通常会通过不断的尝试才能最终确定是否存在注入。
其实SQL语言根其他编程也是有一些相似的。
比如还是上面的查询select* FROM users where id = 1 ,这里的id需要一个数字类型,那么任意方式得到的数字都可以,那么它也可以是一个SQL语句表达式的结果:比如id = (SELECT SUM(1+1)) ,需要使用括号引起来,表示一个整体。所以这同样可以作为判断注入的一些方法。

那么所有的填写数据的地方都可以通过其他SQL语句进行替换。如下:

而其他的操作符比如and,or等两边其实是布尔值,同样可以通过表达式的执行结果替换
不管是有回显的还是没回显的注入判断,根本原理都是闭合原先的SQL语句,执行构造的简单判断逻辑,返回true或者false,根据返回结果的差异确定漏洞。这里的有回显为真会将数据结果直接返回到前端,从而直接看到数据,为false无查询结果。而无回显同样,因为服务端的true或者false的结果,会返回有差异的内容,为true的可能性是某一个页面返回有填充内容,为false可能是其他页面无内容或者同前端页面,但是填充内容为空等等,某些情况下可以通过相应的response长度大小来进行判断。
如果返回无任何差异,也可以通过延时判断是否存在注入。
比如:不管是否有回显,通过延时的方式是所有类型的注入均可实现判断(前提是没有过滤响应的SQL函数)的一种方式,但是这个会比较消耗时间,如下图,数据库有4条数据,因为执行的顺序是每一条都会去查询是否id= (SELECT SLEEP(1)) ,所以会执行4次,那么如果有100万条呢,那要是多发点查询,服务器可能就G 了。

所以更常见的做法是使用确定的查询结果数据,执行一次,如下:当执行到id=1 该结果为真才会执行and的语句,当id不存在是,前面的结果为false,不管后面是否为真整体为false,所以不会执行and的内容。这是其执行的优先顺序与编程语言一样。

到这里我们发现,其实可以不用管服务端前面写了些什么内容比如:select user,id,uuid,pass from users where id = 1 or SLEEP(1)中,我们只需要知道我们从id=后面开始就是我们输入的内容。同时也不用知道后面写了啥(某些情况下除外),因为我们可以通过注释,将后面的所有内容注释掉。在没有过滤的情况,我只需要一些简单的判断逻辑,就能推断是否存在注入了。

注入获取数据
判断存在注入的下一步就是获取数据库中的数据。还是回到上面的例子:

要想查询我们需要的数据那么就需要知道数据存放在哪个数据库中,哪个表中,以及哪个字段中,比如上面的例子,在test数据库的users表中,以及对应的所有字段比如id,pass等,要知道这些数据,才能在注入的时候查找其他表的数据。那如何知道这些字段呢?
在mysql中有一个系统自带的数据库名为information_schema,在这个数据库中存放了mysql中的所有数据库的信息,包括该mysql数据库上有哪些数据库名,表名,列名等等。

我们打开这个数据库的SCHEMATA表如下图:列名schema_name存放了所有的数据库名

同时该数据库中存在TABLES表,里面存放了所有数据库的表,并且在table_schema和table_name字段分别存放了数据库名和表名。

那接下来的列名也是存在的,在表COLUMNS中,同时存在table_schema和table_name以及column_name三列。分别对应数据库名,表名,列名。

所以在找到注入点之后,首先要做的是通过这个系统表(因为系统的数据库和表,列名是固定的,而服务端应用使用的数据库名,表,列名等均未知),查询到当前应用使用的数据库名,表名,以及列名那么就可以随心所欲的查询任何想要的数据了。
一些简单的SQL语法
通常要想直接获取查询到结果,必须是有回显的注入,即SQL执行的结果会返回到用户的前端界面。比如这样:SELECT * FROM users WHERE user = “adfadf” or 1 — ” AND pass = “asldfjadf” 如果只查询user =adfadf是没有结果返回的,然而通过注入的手法查询之后,其实是有结果了,返回了所有内容,那么有回显的前端就能得到查询的结果,所以只需要将后面的注入语句改成查询数据库的内容即可。


因为这里搭建的环境是什么都没过滤的情况,所以可以通过报错注入,也可以通过联合查询进行。首先我们看一个有趣的查询如下:查询的时候如果只有列名而没有其他任何信息,那么就会将列名当作该列的值返回,联合查询注入就是根据这个特性完成的。

比如上面的例子,使用联合查询就可以得到如下的结果,联合查询union的作用其实就是将前后两个select的查询结果放到一起显示,这就要求他们得到的结果列数保持一致

即前面结果4列,后面结果也是4列,否则报错,明确说明前后的列数不一样,如下:

既然union后面的select的查询结果可以与前面的一起显示,那么是不是就可以通过后面的查询语句从系统的数据库中找到当前的数据库相关的信息。从而再一步一步查询想要的内容。
比如通过系统数据库查询当前mysql中的所有数据库名:
SELECT * FROM users where user = “test” union select schema_name,2,3,4 from information_schema.SCHEMATA — ” and pass = “asdfadfadf” 其中加黑的内容就是用户注入的内容

但是无法判断当前数据库是哪个,所以有更直接的方式:使用select DATABASE() 直接得到当前数据库名。

那么用在注入里面就可以是这样的:将其放在第几列,内容就显示在第几列上,同时可以使最前面的查询结果为空,随便输入不存在的user名,导致查询为空,这样就只会得到有用的内容了。

接下来就可以通过得到的数据库名字,在系统数据库information_schema的tables表中查询test的所有表名了。首先查询所有表名

再筛选出属于test的表即TABLE_SCHEMA=test如下:得到所有test数据库的表名

得到表名,再来看列名,重复上面的查询流程:再information_schema.COLUMNS中查询列名,表名为users–在columns表中,无需知道数据库名,只需要根据表名就可以得到列名,除非存在不同数据库,存在相同表名的情况,否则无需添加数据库名的条件选择

所以就是如下查询:SELECT * FROM users where user = “aaaaa” union select COLUMN_name,2,3,4 from information_schema.COLUMNS where TABLE_NAME=”users”– ” and pass = “asdfadfadf”,可以看到内容比我们实际的多一些。

所以如果出现了同名的表名,那可以加条件选择加上数据库名的限制:
SELECT * FROM users where user = “aaaaa” union select COLUMN_name,2,3,4 from information_schema.COLUMNS where TABLE_NAME=”users” and TABLE_SCHEMA=”test” —后面是会被注释掉的内容” and pass = “asdfadfadf”
得到最终想要的内容。

最后就简单了,知道了数据库名test,表名users,列名如上图id,uuid,user,pass那就可以直接查询对应的内容了

那么如果利用报错注入呢?其实也很简单只需要使用报错函数即可,所以这个其实就是对SQL语言的使用,很多复杂的SQL注入的语句,就是对各种函数的组合使用的一个过程。这里使用的是updatexml() ,它在报错注入时,通常一次只能显示一行数据的一个片段,并且错信息有长度限制(通常约32KB),超过限制的部分会被截断。这是它的一大限制。
比如下面的查询,尽管我们将所有结果(多行多列)的数据进行了拼接,但是还是只能显示32个字符,到wa就结束了后面的内容被截断。

不管是联合查询还是报错注入,其实就是对SQL语言的一种运用,只有对SQL语言足够熟练,才能得心应手的使用各种技巧进行注入。
无回显注入
在学会了有回显的注入之后,其实无回显的就简单很多了,因为它的所有原理几乎一样,只是我们无法在界面上看到结果而已,只能通过true和false的不同反应来进行结果推断。比如上面的延时注入一样,前面执行为真就延时几秒钟响应,错误直接返回或者相反均可。
还是上面的例子,将返回取消掉再来看:可以看到即便我们通过简单的注入判断,但是仍然无法确定是否存在注入,因为所有的返回信息均一致(也有不一致的情况,我这里的例子使用的完全一致的情况)。这时候通过这种方式判断,已经无法确定是否存在注入了,不管我们是否正确闭合语句,后面的内容是否执行,我们无法通过页面看到,所以无法推断语句到底执行了还是没有执行。

所以这时候必须通过一些方法,让SQL语言执行之后,其执行结果可以在页面上给我们一些反馈信息。那么延时就是一个很好的方法。因为是盲注,所以无法判断闭合方式,所以这些都需要fuzz尝试,以及经验,比如这里大概率是字符串,所以闭合应该是单引号或者双引号。
所以可以尽量使用确定的延时方式,比如使用and,别使用or,因为or每次查询均会执行or后面的语句,每一行数据的查询,均会延时,如果有超过10条数据,哪怕是延时的1秒,也会超过10秒,大概率会超时。

通过服务端的响应时间可以看到我们注入的sql语句被执行了,证明其漏洞存在。那么就可以通过同样的方式,进行后续的数据库数据获取。
SQL语句拆解组合
比如我们的查询还是select * from users where user = “test”,而查询当前数据库的语句为select database()如下分别执行的结果:
我们知道test的地方是我们输入的内容,在盲注的情况下,前端无返回,我们如何得到这个database()的内容呢?从上面的延时注入我们知道,我们可以根据数据库的反应控制其响应时间。那么我们是否可以这样询问服务器,比如查询到test内容之后,执行sleep(),但是给一个条件,就是比如我们执行的select database()结果为某个值就sleep(),否则直接返回,那么如果它执行了sleep,就表示满足这个条件。比如下面的执行过程:

那就可以这样做:比如判断该数据库的长度是否大于1,大于1就延时3秒
那么在我们测试的时候,使用burpsuit就可以是下面的情况,当然payload 也可以有下面的写法,与上面效果一样。
username=test” and IF((SELECT length(database())) > 1, sleep(3), 0) — &password=teadsfa
解释这个注入的语句,其实就是对SQL语言的利用,and之后使用if函数,if函数中存在一个子查询,查询的是database()的长度,结果判断是否大于1,大于1就执行sleep(3),不大于1,返回0。如下图:bp的效果是这样:

如果太复杂还可以通过更容易理解的方式比如:” and length((select database()))>1 and sleep(3) — 不使用if也可完成。通过多个and连续执行
盲注就是猜测的过程,所以比较耗时间,尤其是通过延时的方式,每一次正确查询都会耗费时间,通常是通过脚本的方式进行。
在后面就是同样的猜数据库名字,字段,表名分别看一个:比如上面通过长度我们最终得到为4,那么如何确定具体的值呢。假设我们询问服务器这个数据库名是不是abcd,当猜对了就会休眠3秒,但是这组合太多了,如果长度较长呢,将需要非常长的时间,那么如果我们询问服务器,数据库名的第一个字符是不是a,那么只需要执行26次加上一些特殊字符可能30多次就能结束,所以通过这样的方法就可以逐步得到数据库名。
猜库名
username=test” and IF((SELECT substr(database(),1,1)) = “t”, sleep(3), 0) — &password=teadsfa
也是sql函数的一些利用,比如substr,将字符串从1开始截取一个字符,使用if判断其是否是t字符,是就执行sleep。

比如猜错:就不会执行sleep。

然后依次对数据库第二个字符进行判断,直到第一步得到的长度未知,就能获取到数据库名字了

接下来与回显的注入一样,需要通过information_schema数据库进行表名,列名的判断,与数据库判断同理
猜表名:mysql数据库将会执行下面的语句:
SELECT * FROM users where user = “test” and if((select substr((select TABLE_name from information_schema.TABLES where TABLE_SCHEMA =’test’ LIMIT 0,1),1,1))=”a”,sleep(3),0) — ” and pass = “asdfadfadf”
将这个拆解成多个部分
if(子句)—–子句=substr(子句)——子句=select TABLE_name from information_schema.TABLES where TABLE_SCHEMA =’test’ 使用 LIMIT 0,1 拆分行,只取第一行结果
需要掌握的是substr使用,limit使用,if函数的使用。我们知道select TABLE_name from information_schema.TABLES where TABLE_SCHEMA =’test’ 执行这个之后得到的是多行数据,所以需要limit单独取一行。这样substr才能截取对应的一行的字符串的某个位置

最后猜列名和对应列的值,其方法是一样的,逐字符猜解。
猜users的列名,逐一猜解得到最终的数据库列名
username=test” and if((select substr((select column_name from information_schema.COLUMNSwhere TABLE_NAME =’users’ LIMIT 0,1),1,1))=”u”,sleep(3),0) — &password=teadsfa
如果要看第二行只需要修改limit的索引即可。

以及最后猜列的值:
username=test” and if((select substr((select user from users LIMIT 0,1),1,1))=”z”,sleep(3),0) — &password=teadsfa

SQL语言分段
当一段很长的SQL语句出现时,很可能无法理清其执行的目的,根据上面的注入例子,其实我们可以发现,通过将一段很长的SQL语句,逐一分解成不同单元的短句,即可理清,比如上面的猜库名的例子:
SELECT * FROM users where user = “test” and if((select substr((select TABLE_name from information_schema.TABLES where TABLE_SCHEMA =’test’ LIMIT 0,1),1,1))=”a”,sleep(3),0) — ” and pass = “asdfadfadf”
可以将其分成几个短的SQL语句,其分段的逻辑可以根据:SQL查询语句的结构–SELECT 列表名 FROM 表名 WHERE 条件 的基本框架,所以第一部分可以得到的是SELECT * FROM users where user = “test” 。这一段就结束了可以看到这查询的是test用户的所有信息。然后看到and 知道后面应该也会得到一个执行结果,并从第一段的结果中进行二次筛选。所以看看第二段的内容if((select substr((select TABLE_name from information_schema.TABLES where TABLE_SCHEMA =’test’ LIMIT 0,1),1,1))=”a”,sleep(3),0) — ” and pass = “asdfadfadf”,最后也有一个and,但是后面的会被注释,那么第二段就是if((select substr((select TABLE_name from information_schema.TABLES where TABLE_SCHEMA =’test’ LIMIT 0,1),1,1))=”a”,sleep(3),0),看IF函数了,IF函数的语法IF(condition, value_if_true, value_if_false),所以可以将其按照逗号分成三个SQL语句。
但这里有很多逗号,如何区分,从后往前,因为IF是最终的执行函数,所以它的闭合肯定是最外层的括号,所以最后的外层括号最近的,逗号就是if函数的分割符号。那么就可以将其分成0,sleep(3),和(select substr((select TABLE_name from information_schema.TABLES where TABLE_SCHEMA =’test’ LIMIT 0,1),1,1))=”a”,这又是一段很长的句子,重复分段原则,=号左边是一个substr函数的执行结果,同样根据其语法,从后往前逗号分割得到三段,1,1,和(select TABLE_name from information_schema.TABLES where TABLE_SCHEMA =’test’ LIMIT 0,1),这样的一段是不是就熟悉了,这正SQL语句的基本结构式SELECT 列表名 FROM 表名 WHERE 条件 limit 是对这个结果的处理。这样就理清了执行流程。
盲注小结
盲注的特点就是:一个字符一个字符的猜解,会比有回显的耗时多一些,因为不是直接得到需要查询的内容的。其就像是与服务器进行的猜谜游戏,你向数据库问问题,它会回到对或者错,根据它的回答逐渐靠近正确答案,需要不断提出试探性问题,并依据反馈缩小范围,直到找到正确答案的一个过程。
总结
SQL注入攻击的核心建立在熟练的SQL语言掌握之上。无论是直接回显的注入还是盲注,本质上都是通过构造巧妙的查询语句来获取或推断数据库中的敏感内容。随着对SQL语法和数据库结构的理解加深,攻击者能够更灵活地实施查询并绕过常见的防御措施。因此,扎实的SQL基础才是掌握并应对各类注入攻击的关键。












暂无评论内容