富文本本身不触发SQL注入,但未经清洗直接拼接进SQL会成为二次注入高危入口;因其支持HTML/JS标签,易被用作攻击载荷运输通道,尤其在MyBatis ${}拼接、日志记录或审计存储等场景中风险极高。

富文本内容本身不会触发SQL注入,但若后端未做清洗就直接拼接进SQL语句,就会变成高危二次注入入口。
为什么富文本是二次SQL注入的高发场景
富文本编辑器(如 TinyMCE、Quill、CKEditor)允许用户输入 HTML 标签、属性甚至内联 JavaScript。这些内容若未经处理就存入数据库,再在后续查询中被 String.format、concat 或 MyBatis 的 ${} 拼接进 SQL,攻击者就能把一段看似无害的 HTML 转化为恶意 SQL 片段。
典型链路:<img src="x" onerror="fetch('/api/user?id=1%20OR%201=1')" alt="在Java中如何防止富文本编辑器内容引发二次SQL注入?" > → 存库 → 后台取出来拼进 <code>"SELECT * FROM posts WHERE id = " + rawContent → 触发注入。
这不是“富文本导致注入”,而是“富文本成了攻击载荷的运输通道”。
立即学习“Java免费学习笔记(深入)”;
- 富文本字段常被标记为“可信内容”,开发容易跳过输入验证
- 前端过滤(如 DOMPurify)只作用于浏览器,服务端仍收到原始 payload
- ORM 框架(如 MyBatis)对
#{}参数化安全,但若用${content}直接拼接,等同裸奔
MyBatis 中 ${} 与 #{} 混用时的陷阱
很多人以为“用了 MyBatis 就安全了”,但实际项目里常因动态表名、排序字段等需求被迫用 ${}。这时如果变量值来自富文本字段,风险立刻拉满。
例如:
SELECT * FROM ${tableName} WHERE status = #{status} ORDER BY ${sortField}若 sortField 是从富文本编辑器提交的字段名(比如被篡改为 id; DROP TABLE users--),就会执行非法操作。
- 永远不要让富文本内容参与
${}插值,哪怕加了白名单校验也要警惕绕过(如大小写、URL 编码、宽字节) - 必须动态拼接时,仅允许极小范围的硬编码值:如
["created_at", "updated_at", "title"],且用Enum或switch映射,不走字符串比对 - 对富文本字段做 SQL 上下文使用前,强制转为
NULL或抛异常——它本就不该出现在 WHERE / ORDER BY / LIMIT 等位置
存储前清洗:不能只靠 HTML 过滤
DOMPurify 可防 XSS,但对 SQL 注入无效。它不处理 SQL 关键字、注释符、引号逃逸等数据库层语法。
真正要做的,是在富文本入库前,剥离所有可能干扰 SQL 解析的字符结构:
- 移除所有单引号
'、双引号"、反引号`——除非你明确需要它们且已用 PreparedStatement 绑定 - 将 SQL 注释符
--、/*、*/替换为空格或报错拦截(注意:--前后有空格才合法,但攻击者常省略) - 禁用或转义分号
;,防止多语句执行(MySQL 默认关闭,但显式开启后极危险) - 对数字类字段(如富文本中嵌入的 ID),用
Long.parseLong()强转并捕获NumberFormatException,而不是字符串匹配
示例清洗逻辑(非最终方案,仅示意):
String safeText = htmlInput.replaceAll("['"`]", "")
.replaceAll("(?i)--|/\*|\*/", " ")
.replace(";", "");最易被忽略的一点:日志和审计 SQL 也危险
很多团队会在 DAO 层打日志,比如:
log.info("Executing query: SELECT * FROM articles WHERE content LIKE '%{}%'", userInput);如果 userInput 是富文本内容,这段日志本身可能含 SQL 注入片段,被 ELK 或日志系统解析时触发 SSTI 或命令执行(尤其当日志平台支持 Groovy 表达式)。
更隐蔽的是审计模块:某些系统会把用户提交的全文连同 SQL 一起记入操作日志表。一旦该日志表又被另一个报表 SQL 查询并拼接展示,就构成闭环二次注入。
所以:所有进入日志、审计、导出、缓存 key 构造的富文本内容,都应视同原始 SQL 参数,走统一清洗或拒绝策略——不是“存进去就完事”,而是“只要它离开 HTTP body,就得重新过审”。


















