SQL注入篇–基础注入

1.SQL注入原理

sql注入的原理就是在服务器后端对数据库进行操作请求之前,人为地对sql语句做一些恶意注入,从而达到人为预期效果,造成数据泄露甚至数据破坏。注入漏洞在OWASP2021年的总结中位列TOP10的第一名,可见注入漏洞的危害之大,理论上注入漏洞可以帮助我们办到任何后端可以办到的事情。

2.SQL注入条件

SQL注入漏洞发生的前提条件必须是有人为可控的一处sql语句,或者用户的输入可以对sql语句起到控制作用,那么用户便有可能找到可以被利用的漏洞点。即使服务器后端对用户输入做了验证,但是大部分情况下还是都可以想办法绕过的。

3. SQL注入的分类

在常用的方法中,sql注入可以分很多类:

  • 基于注入点分类,可以分为get注入,post注入等
  • 基于数据类型分类,可以分为字符型和数字型注入
  • 基于获取数据的方法可以分为报错注入和盲注

4.SQL注入的一般流程

判断是否存在注入 ==》 寻找注入点 ==》 构造payload ==》 执行payload进行攻击 ==》 结果分析处理

判断是存在注入阶段可以结合开发人员角度做一步初步的分析,比如登录注册,一般都可能存在sql注入漏洞,或者搜索等等一些功能。

然后就是寻找注入点,这一步是最关键的一步,我们要确定好哪一个地方是我们可以利用的,这一步我们可以做一下简单的闭合,比如输入引号或者括号,看运行结果。如果出现了非预期的回应,那么这个点很可能会被我们利用

然后就是构造payload,这一步需要我们有一定的开发基础,要先以开发人员角度去推测后端功能,然后再逆向推断,做进一步的分析。比如我们对一个登录功能进行注入,我们在不知道密码的情况下,在密码栏输入 1’ or 1 = 1 # ,那么这个sql语句执行的就是select * from user where password=‘1’ or 1 =1 # 被注释部分,显而易见这样的查询返回的必定是真,所以我们可以直接登录。

然后就是执行payload并且对结果进行分析了。在结果分析阶段也是要非常重要的,因为后边的报错注入和盲注是要看结果的。报错注入要看报错信息,根据报错信息一步一步调试payload直到成功攻击,盲注是要看人为控制的回显的。

5. UNION联合注入

使用union联合注入的条件是必须要有相似的数据类型(字段数相同)

步骤:

1.寻找注入点。寻找我们可以利用的

这里以pikachu为例,我们通过url里边的内容可以看到name是一个可以被利用的点

java sql注入问题 sql注入_mysql

2.闭合。将sql语句中的单引号或者双引号或者括号进行闭合

我们尝试闭合:

java sql注入问题 sql注入_java sql注入问题_02

可以看到输入单引号就可以完成闭合

3.获取列数(使用ordrr by或者group by分组)

获取列数可以使用order by或者group by来判断。这里我们输入order by 2,查看结果:

java sql注入问题 sql注入_报错信息_03

我们再次输入order by 3,查看结果:

java sql注入问题 sql注入_报错信息_04

所以得出结论,这个查询出的数据行有两列

4.判断显示位。

使用select* from xxx where name=‘asd’ union select 1,2 --+ 这类sql语句,查看1,2在哪里显示。注意,在判断的时候一定要让原来的查询结果不正常显示

java sql注入问题 sql注入_报错信息_05

现在我们就知道了两列数据显示的位置

5.获取所需信息。

当我们得知显示位置之后,可以把显示的1或者2替换成类似database()这类函数,比如我们现在要查看数据库名和用户:

java sql注入问题 sql注入_报错信息_06

这里列出一些常用函数:

user()  # 获取数据库用户
database()  # 获取当前数据库
@@version  # 获取数据库版本
session_user()  # 获取当前连接用户(在mysql5版本以下使用)

我们还可以查看information_schema数据库里边的表来查看整个mysql数据库的信息,比如我们要看pikachu数据库的所有表名:

java sql注入问题 sql注入_数据_07

这里我给出information_schema数据库中常用的表信息:

tables表:		# 存放所有数据库表的信息
	table_schema   # 表对应的数据库
	table_name		# 表名
columns表:		# 存放所有表的列的详细信息
	table_schema	# 同上
	table_name		# 同上
	column_name		# 列名

6.报错注入

步骤:

1,寻找注入点

这里用pikachu来做示范,首先寻找注入点:

java sql注入问题 sql注入_sql_08

查看url能看出name可以注入

2.闭合

尝试闭合:

java sql注入问题 sql注入_数据_09

可以看出想要闭合得用单引号

3.根据报错信息,使用报错函数制造报错

下面是使用floor函数制造报错:

java sql注入问题 sql注入_数据_10

所以我们可以看自己想看的内容了

4.根据报错信息分析得到数据

java sql注入问题 sql注入_数据_11

1) floor函数报错原理

条件:floor函数报错注入在mysql8的版本已经失效,所以mysql版本要小于8,并且数据条数一定要大于等于三条。

rand()函数:mysql的一个随机数函数,返回一个随机的浮点数,范围在0-1之间,如果指定一个种子参数比如rand(0),那么后续再次调用rand(0),出现的随机数不会变化。

floor函数:向下取整函数,返回的是传入数的向下取整整数

报错举例:

java sql注入问题 sql注入_java sql注入问题_12

java sql注入问题 sql注入_sql_13

当我们使用group by对count(*)进行排序的时候,rand()函数会被调用两次。所以在进行分组排序的时候,实际过程是这样的:

首先会建立一个虚拟表,在这个虚拟表里边存放的是主键和计数。当group by查看表中是否有floor(rand(0)*2)的时候,rand就会被执行一次,此时第一次肯定是不存在,所以要将其插入,但是在插入的时候又会执行一次rand,两次rand结果不一样,所以举例来说名义上插入的是0,实际插入的是1。后边再次查询,还是有不一样的,那么又会重复上边的操作,这样举例的说就只能是名义上插入0实际插入1(因为上边实际插入的是1,0没有被插入进去,所以再次检索的时候0还是不存在),但是插入1的时候,1是已经插入过的,会产生碰撞,引发报错,此时报错信息会把引发碰撞的值显示出来。

利用:

java sql注入问题 sql注入_sql_14

具体的注入原理大家可以自行搜索其他博主的了,floor函数报错一般用的不太多,这里我也就不多说了。

2)extractvalue函数报错

extractvalue(xml文件名,xml文件路径)是一个查询xml文档数据查询的xpath函数,查询路径必须是xxx/xxx/xxx这种格式,否则就会报错,所以我们可以利用这个报错信息来获取我们想要的信息。注意,这种报错信息最多返回32位数据。具体操作如下:

java sql注入问题 sql注入_数据_15

或者直接

java sql注入问题 sql注入_sql_16

在pikachu里边使用如下:

java sql注入问题 sql注入_mysql_17

可以看到在报错信息中获取到了数据库名,在payload中的0x7e是~波浪号的16进制。

3)updatexml函数报错

updatexml函数和extractvalue函数类似,只不过extractvalue函数是用来查询xml的,而updatexml是可以修改xml的,所以updatexml函数有三个参数:updatexml(文档名,文档路径,修改值)

updatexml的用法和extractvalue的用法类似,只不过是多了一个参数。它也是最多返回32位数据。

java sql注入问题 sql注入_java sql注入问题_18

或者跟上边一样直接:

java sql注入问题 sql注入_报错信息_19

都可以,只要保证这个函数被运行即可。

在pikachu中运用:

java sql注入问题 sql注入_sql_20

特别的,如果想要的数据超出了32位,我们可以使用substr函数来对数据进行切割,分段获取我们想要的数据,如下:

java sql注入问题 sql注入_报错信息_21

可以看到我们想要的数据没有完全显示出来,这时候我们就用substr进行切分,查看后边的内容(substr(截取内容,起始位置,截取长度)):

java sql注入问题 sql注入_mysql_22

7.盲注

盲注,一般在没有任何返回数据的条件下使用,因为没有了回显数据,所以我们要通过一些其他的状态来判断正确与否,实际做的就是一个猜解的过程。

1)布尔盲注

布尔盲注就是我们在输入数据正确或者错误的情况下,客户端会有两种可以分辨的状态代表正确或者错误,配合我们的payload,可以做到猜解数据。

下面在sqlib里演示下如何获取数据库名:

java sql注入问题 sql注入_mysql_23

java sql注入问题 sql注入_sql_24

像上边这样,有两种回显状态,就可以使用布尔盲注了。下面构造payload:

?id=1’ and substr((select database()),1,1)=‘s’ --+

其中substr可以替换成left函数或者mid函数,他们的功能都类似,具体使用看使用场景和个人喜好。

java sql注入问题 sql注入_mysql_25

可以看到这是正确了,说明第一个字符是s,这样一个一个猜测比较麻烦,我们可以使用bp里边的爆破模块来进行爆破

java sql注入问题 sql注入_sql_26

首先抓到包发到攻击模块中,然后设置爆破点:

java sql注入问题 sql注入_java sql注入问题_27

攻击模式就选狙击手模式,然后设置攻击字典:

java sql注入问题 sql注入_sql_28

bp自带一个字符集的字典,有字母和数字,我们如果要加入特殊符号可以在里边加入,然后设置每次爆破的字符长度,可以看到bp给我们算出来预计要攻击36次。然后我们发动攻击:

java sql注入问题 sql注入_报错信息_29

可以看到用s攻击的那个响应包的长度和其他的不一样,查看响应包得知这就是正确的第一个字符,然后我们可以这样修改攻击位置:

java sql注入问题 sql注入_sql_30

攻击结果:

java sql注入问题 sql注入_java sql注入问题_31

这样我们可以方便的查看结果了

2)时间盲注

当没完全有结果回显时,我们就不好判断对错了,所以我们可以自己制造判断条件,比如如果正确就让他sleep几秒再响应,这就是时间盲注

下面使用sqlib来演示:

首先判断是否可以用sleep:

java sql注入问题 sql注入_sql_32

看到响应时间明显变长了,所以这个可以使用sleep函数来盲注

构造payload:

?id=1’ and if(substr((select database()),1,1)=‘s’,sleep(5),sleep(1)) --+

java sql注入问题 sql注入_数据_33

有延迟就说明这里的盲注是正确的,我们可以进行下一个位置的盲注,直到完全出来。另外BP也是可以爆破的,回显结果的话就用响应时间筛选即可。