JSTL不防SQL注入,仅通过<c:out>防XSS;SQL注入须在Java层用PreparedStatement、MyBatis的#{}、输入校验及白名单等措施防御。

JSTL 本身不解决 SQL 注入,它解决的是 JSP 层的表达式注入(即 EL 表达式执行任意代码或反射调用),和后端 SQL 拼接是两层问题。你在 Legacy Java Web 项目里看到用 ${param.id} 直接拼进 SQL 字符串,那不是 JSTL 的锅,是开发把 EL 当参数用了——而 JSTL 引入后若不加约束,反而可能放大风险。
所以结论很直接:引入 JSTL 不修复 SQL 注入;但正确配置 + 配合禁用动态 EL,能堵住 JSP 层的表达式执行漏洞,并为后续剥离拼接式 SQL 提供安全过渡条件。
为什么不能靠 JSTL 防 SQL 注入
SQL 注入发生在 Java 代码层(如 Statement 拼接)或 DAO 层,JSTL 是视图层标签库,只负责渲染 HTML。它既不接触数据库连接,也不参与 SQL 构建。常见误解是:<c:out value="${param.id}" /> 看似“安全”,但它只是转义 HTML 输出,对后端 String sql = "SELECT * FROM user WHERE id = " + request.getParameter("id") 完全无效。
更危险的是:如果项目已开启 EL 解析(默认开启),攻击者可能通过 ${header['user-agent']}、${pageContext.request.queryString} 甚至 ${class.forName('java.lang.Runtime').getDeclaredMethods()} 触发反射调用——这属于表达式注入(Expression Language Injection),和 SQL 注入并列,但危害路径不同。
立即学习“Java免费学习笔记(深入)”;
引入 JSTL 后必须禁用动态 EL 执行
Legacy 项目常保留 web.xml 中的 <el-ignored>false</el-ignored>(或没配,默认 true),导致 EL 可执行任意方法。引入 JSTL 前,先强制关闭动态执行:
- 在
web.xml中添加:<jsp-config> <jsp-property-group> <url-pattern>*.jsp</url-pattern> <el-ignore>true</el-ignore> </jsp-property-group> </jsp-config> - 若用 Tomcat 8+,还需检查
conf/web.xml全局配置是否覆盖了应用级设置 - 禁用后,
${param.id}仍可取值,但${param.id.getClass().forName('...')}类写法会直接报错,而非执行
<c:out> 能防什么?不能防什么?
<c:out> 默认对输出做 HTML 实体转义(< → ),仅用于防止 XSS,跟数据库完全无关。
- ✅ 安全场景:
<c:out value="${user.name}" />—— 防止用户昵称含<script>被浏览器执行 - ❌ 无效场景:
String sql = "SELECT * FROM user WHERE name = '" + ${param.name} + "'";—— 这种写法本身非法(JSP 语法错误),但若开发者用<% String name = request.getParameter("name"); %>再拼 SQL,<c:out>一丁点作用都没有 - ⚠️ 注意:
<c:out escapeXml="false">会关闭转义,等同于裸输出,慎用
真正该在 Legacy 项目里立刻做的三件事
别把精力花在“用 JSTL 防 SQL 注入”这种方向上。遗留系统最常踩的坑,是以为加个标签库就等于加固了——其实只是换了个地方出问题。
- 全局搜索
request.getParameter(、request.getAttribute(、param.,定位所有拼 SQL 的位置,逐个改用PreparedStatement - 删掉所有
Statement.executeUpdate(sql)形式,替换为PreparedStatement+setString()等方法 - 若用 MyBatis,确认所有 DAO 方法用的是
#{}而非${};特别注意ORDER BY ${column}这类写法,必须白名单校验column值
EL 注入和 SQL 注入的修复逻辑完全不同:前者靠关开关、禁反射;后者靠断开字符串拼接、交由预编译引擎处理。混在一起修,最后两边都漏。


















