SQL 注入(SQLi)深度解析【原理、检测、风险、防护措施】
一、什么是 SQL 注入(概念)
SQL 注入是指攻击者把恶意 SQL 代码注入到应用的输入里,应用在拼接或执行这些输入构造的 SQL 时,导致数据库执行非预期命令(数据泄露、篡改、权限提升、远程命令执行等)。根源是不安全的用户输入被直接拼接到 SQL 语句,未使用参数化语句或未正确转义。
二、注入类型与原理
-
基于错误(Error‑based)
利用数据库错误消息泄露结构信息或敏感数据。例如通过UNION或构造语法错误使数据库返回可被解析的内容。 -
基于联合(Union‑based)
使用UNION SELECT合并攻击者控制的查询结果到原查询的结果集中,从而读取表数据。 -
布尔盲注(Boolean‑based Blind)
利用不同布尔条件改变页面返回(或返回状态)来逐位推断数据,例如AND (SUBSTR((SELECT password FROM users WHERE id=1),1,1)='a')。 -
时间盲注(Time‑based Blind)
利用延迟函数(如SLEEP()、pg_sleep())判断条件是否成立(用于不能从错误/返回区别的场景)。 -
基于堆叠查询(Stacked Queries)(部分 DB 如 MSSQL/MySQL 的某些配置)
在单个请求中执行多条独立 SQL(例如'; DROP TABLE users; --)。 -
Out‑of‑Band (OOB)
利用数据库或环境与外部服务器通信(DNS/HTTP)来回传数据(当直接通道受限时非常有用)。 -
二次注入(Second‑order)
恶意数据先存入数据库,随后在不同的上下文或查询中被回显并触发注入(常被静态检测漏掉)。
三、关键危险点(为什么会产生)
- 直接把用户输入拼接到 SQL 字符串中。
- 在不可信上下文中动态构建表名/列名/排序字段而不做校验。
- 数据库错误/调试信息在生产中泄露(信息泄露助攻)。
- 过高的数据库账户权限(例如具有 DROP/ADMIN 权限)。
- 缺乏最小权限、审计和监控。
四、易受攻击的源码示例(含修复)
说明:示例同时给出“易受攻击代码”与“推荐修复”,方便审计和直接替换。
1) Java(JDBC) — 易受攻击
// vulnerable
String q = "SELECT id, name FROM users WHERE email = '" + email + "' AND status = '" + status + "'";
Statement s = conn.createStatement();
ResultSet rs = s.executeQuery(q);
问题:email、status 直接拼接。攻击者可注入 ' OR '1'='1 等。
修复(PreparedStatement)
String q = "SELECT id, name FROM users WHERE email = ? AND status = ?";
PreparedStatement ps = conn.prepareStatement(q);
ps.setString(1, email);
ps.setString(2, status);
ResultSet rs = ps.executeQuery();
要点:参数化查询(PreparedStatement)把输入作为值传递,避免语法注入。
2) PHP(mysqli) — 易受攻击
// vulnerable
$q = "SELECT * FROM products WHERE name LIKE '%" . $_GET['q'] . "%'";
$res = $mysqli->query($q);
修复(绑定参数):
$stmt = $mysqli->prepare("SELECT * FROM products WHERE name LIKE CONCAT('%', ?, '%')");
$stmt->bind_param("s", $_GET['q']);
$stmt->execute();
3) Python(psycopg2 / PostgreSQL) — 易受攻击
# vulnerable
cur.execute("SELECT * FROM orders WHERE id = %s" % order_id)
修复:
cur.execute("SELECT * FROM orders WHERE id = %s", (order_id,))
要点:使用 DB API 的参数化接口,不要手动 format 字符串。不同驱动使用不同占位符(%s, ?, :1 等)——要用驱动的参数方式。
4) ORM(如 Hibernate / JPA / ActiveRecord)常见陷阱
易受攻击:
// JPA with string concatenation
String q = "FROM User u WHERE u.name = '" + name + "'";
entityManager.createQuery(q).getResultList();
修复(查询绑定):
Query q = entityManager.createQuery("FROM User u WHERE u.name = :name");
q.setParameter("name", name);
注意:即使使用 ORM,也可能因为使用 nativeQuery、动态拼表名、ORDER BY 字段拼接等导致注入。对列名、表名、排序字段必须做白名单校验,不能直接把用户输入当成结构的一部分。
5) Stored Procedures(存储过程)
存储过程本身不是万能防护:若在存储过程内部仍通过字符串拼接并 EXEC(),仍会注入。正确做法是使用参数化调用与内部参数化 SQL。
五、常见攻击 Payload(用于检测/测试,防御目的)
(用于安全测试,勿用于非法入侵)
- 基本条件绕过:
' OR '1'='1 - 终止并注入:
'; DROP TABLE users; --(注意很多 DB+驱动会禁用多语句) - UNION 读取列:
'+ UNION SELECT username, password FROM users -- - 布尔盲注示例:
1' AND SUBSTRING((SELECT password FROM users WHERE id=1),1,1)='a' -- - 时间盲注(MySQL):
' OR SLEEP(5) --
这些 payload 在现代防护环境下可能被 WAF 拦截或触发 IDS。用于渗透测试时要在授权环境进行。
六、检测与测试方法(审计/渗透/自动化)

-
静态代码审计(SAST)
- 搜索易危险 API:
createStatement,executeQuery(String),rawQuery,execSQL,mysqli->query,PDO->query,odbc_exec,execute带字符串的情况。 - 搜索字符串拼接、
String.format、模板化 SQL、+拼接中的用户输入。 - 检查 ORM 的
nativeQuery、fromSqlRaw等危险方法。
- 搜索易危险 API:
-
动态测试(DAST / 手工)
- 在输入点注入布尔/语法错误/时间延迟 payload,观察响应差异/延迟/错误信息。
- 使用
sqlmap(在授权测试)自动化识别盲注、UNION、时间盲注等。 - 模拟高权限用户/不同角色测试二次注入。
-
日志/监控检测
- 识别异常 SQL 错误、语法异常、频繁的延迟(time‑based),或来自同一 IP 的大量带有 SQL 特征的请求。
- 配置 DB 审计(如 MySQL 连接日志、Oracle audit)来追踪可疑查询。
-
模糊测试 & 代码覆盖率
- 针对拼接发生路径做模糊输入测试,尤其是动态 SQL、搜索、过滤、排序、分页等入口。
七、防护策略(按优先级与细节实现)
1. 最重要:参数化查询 / Prepared Statements / ORM 参数绑定
- 所有 SQL 语句的值部分必须使用参数绑定,不要做字符串拼接。
- 在支持的语言与驱动中使用预编译语句(JDBC PreparedStatement、PDO prepared statements、psycopg2 参数化等)。
2. 对结构性输入使用白名单验证
- 用户不能控制 SQL 的结构(列名、表名、ORDER BY 字段、LIMIT 等)。若必须支持,须对允许的值做严格白名单(枚举)校验。
- 示例:
if (!allowedSorts.contains(userSort)) throw ...;
3. 最小权限数据库账户
- 每个应用只使用权限最小的 DB 用户(只 GRANT 必要的 SELECT/INSERT/UPDATE/DELETE)。
- 拒绝 DROP/ALTER 权限给普通应用账户,防止被注入后破坏。
4. 错误信息与异常处理
- 生产环境关闭详细 SQL 错误输出,避免泄露表结构/列名。
- 记录详细日志并发送到安全审计系统,但不要将详细错误暴露给用户。
5. 输入校验(白名单优先)与类型检查
- 对数字、UUID、日期等强制进行类型验证(避免将用户输入当作字符串直接拼接)。
- 长度限制、字符集限制可以作为额外防线,但不能替代参数化。
6. WAF / 入侵检测(作为补充,而非主防线)
- 部署 WAF 能拦截常见攻击模式(UNION、注入关键词、延迟请求),但不要把 WAF 当作唯一防护。
7. 安全配置
- 禁用不必要的 DB 功能(如允许多语句的选项、允许外部程序执行等)如果不需要就关闭。
- 更新 DB 与驱动,利用现代驱动的安全特性。
8. 审计与监控
- DB 审计日志、慢查询日志、异常 SQL 报警。
- 将异常模式(如包含
' OR '1'='1')建立告警规则。
9. 定期安全测试
- SAST 于 CI 中自动扫描,DAST 定期运行(并在变更后手动复测)。
- 授权渗透测试(包含 sqlmap 扫描、手工盲注测试、二次注入场景)。
10. 防止二次注入
- 当应用先存储数据再在另一处做 SQL 拼接时,必须对回显/二次使用的场景特别注意(例如,一个字段先保存,再在动态 SQL 中作为列名使用)。
八、风险评估(影响面与严重性)
- 高风险:能够读取敏感表(用户、密码/哈希、支付信息)、能够写入/修改关键数据(更改权限、写入管理员账号)、能够执行 OS 命令或写入文件(如通过
xp_cmdshell等在 MSSQL)——可能导致全面失陷。 - 中风险:能枚举内部表结构或大量数据但无法直接变更关键业务数据。
- 低风险:仅影响非敏感字段的查询或仅导致页面异常。
严重性评估应结合:受影响的用户数、被窃取数据的敏感性、是否可导致持久后门或权限提升。
九、工程化 Checklist(代码审计/修复优先级)

- 优先替换所有拼接 SQL(尤其用户输入参与的)。
- 检查所有
exec/query/rawQuery/nativeQuery的调用:是否包含用户输入?若包含,使用参数化或白名单。 - 对所有动态结构(表名、列、order by)做白名单验证。
- 限制 DB 账户权限,确保无法进行 DROP/ALTER/EXEC 等高危操作。
- 关闭生产详细错误输出,启用审计日志并集中告警。
- 将 SAST 规则加入 CI(关键关键字/模式自动告警)。
- 对曾暴露的表/列进行凭证重置(若怀疑泄露),并做应急响应(隔离、审计、回滚/修补)。
- 定期(至少季度)进行授权渗透测试与代码审计。
十、常见误区与陷阱
- “只做输入过滤就够” —— 错误。输入过滤可以减少攻击面,但不能替代参数化。
- “ORM 自动安全” —— ORM 不会自动避免所有注入(特别是使用原生 SQL、拼接查询、分页排序等场景)。
- “WAF 就万无一失” —— WAF 是补充,不能替代安全编码与最小权限。
- “只检查 Web 输入” —— 注入向量还包含批处理、内部系统任务、第三方集成数据、日志/上传文件元数据等。
十一、示例应急响应步骤(如果发现注入)
- 迅速下线或临时修复:对受影响端点临时做输出转义或限制访问(若可行)。
- 查明范围:哪些 API/页面受影响,是否有已被泄露的数据。
- 审计日志:导出数据库访问日志、错误日志,定位可疑查询与时间线。
- 更改凭证:若怀疑凭证被泄露(用户/管理员密码、API keys),按策略重置。
- 修复根本原因:用参数化查询替换拼接逻辑、修复二次注入点。
- 回溯调查:确认是否存在后门/持久化修改,清理并恢复。
- 通报并加固:内部复盘、合规通报(如需),持续监控。
![在这里插入图片描述]()
十二、快速参考(实用命令/关键字 - 审计时 grep)
常用待搜索关键字(代码中):
createStatement|executeQuery\(|query\(|execSQL\(|rawQuery|nativeQuery|format\(|String.format\(|\+ .*sql|\%s.*execute
前端/后端要着重找拼接字符串、使用 exec、Runtime.exec 相关的组合用法。
本文来自博客园,作者:NeoLshu,转载请注明原文链接:https://www.cnblogs.com/neolshu/p/19120275


浙公网安备 33010602011771号