jstl不防sql注入,仅通过防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 构建。常见误解是:<out value="${param.id}"></out> 看似“安全”,但它只是转义 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 注入并列,但危害路径不同。
引入 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('...')}类写法会直接报错,而非执行
<out></out> 能防什么?不能防什么?
<out></out> 默认对输出做 HTML 实体转义( → <code><),仅用于防止 XSS,跟数据库完全无关。
- ✅ 安全场景:
<out value="${user.name}"></out>—— 防止用户昵称含<script></script>被浏览器执行 - ❌ 无效场景:
String sql = "SELECT * FROM user WHERE name = '" + ${param.name} + "'";—— 这种写法本身非法(JSP 语法错误),但若开发者用再拼 SQL,<out></out>一丁点作用都没有 - ⚠️ 注意:
<out escapexml="false"></out>会关闭转义,等同于裸输出,慎用
真正该在 Legacy 项目里立刻做的三件事
别把精力花在“用 JSTL 防 SQL 注入”这种方向上。遗留系统最常踩的坑,是以为加个标签库就等于加固了——其实只是换了个地方出问题。
- 全局搜索
request.getParameter(、request.getAttribute(、param.,定位所有拼 SQL 的位置,逐个改用PreparedStatement - 删掉所有
Statement.executeUpdate(sql)形式,替换为PreparedStatement+setString()等方法 - 若用 MyBatis,确认所有 DAO 方法用的是
#{}而非${};特别注意ORDER BY ${column}这类写法,必须白名单校验column值
EL 注入和 SQL 注入的修复逻辑完全不同:前者靠关开关、禁反射;后者靠断开字符串拼接、交由预编译引擎处理。混在一起修,最后两边都漏。
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











