网站漏洞防护中的表达式注入,特别是针对Expression Language(EL)和Object-Graph Navigation Language(OGNL)的攻击,是许多Java Web应用面临的高危安全风险。攻击者通过篡改用户输入,将恶意表达式注入到服务器端执行,可能导致数据泄露、权限绕过甚至远程代码执行。例如,在Struts2框架中,OGNL表达式注入曾引发多次严重漏洞,而JSP中的EL表达式若处理不当,同样会成为攻击入口。防护的核心在于严格验证和过滤所有用户输入,禁用危险表达式功能,并及时更新框架补丁。

理解表达式注入的本质:EL与OGNL如何成为攻击载体

EL(Expression Language)最初为JSP设计,用于简化页面中的数据访问,它允许开发者使用如${user.name}的语法直接调用Java对象。而OGNL是Struts2等框架使用的强大表达式语言,支持类型转换、方法调用和对象图遍历。正是这种灵活性埋下了隐患:当用户输入被直接拼接进表达式并执行时,攻击者可以注入如${''.getClass().forName('java.lang.Runtime').getMethod('exec',''.getClass()).invoke(''.getClass().forName('java.lang.Runtime').getMethod('getRuntime').invoke(null),'calc')}这样的恶意EL表达式,或在OGNL中利用#_memberAccess['allowStaticMethodAccess']=true等参数执行系统命令。这两种语言在未受控环境下,都会将用户输入误解为代码指令,而非普通数据。

常见攻击场景与漏洞实例分析

在实际应用中,表达式注入通常发生在参数传递、模板渲染或数据绑定环节。例如,一个JSP页面若直接使用${param.input}显示用户参数,攻击者提交input=%24%7b%22%22%2egetClass%28%29%2eforName%28%22java%2elang%2eRuntime%22%29%2egetMethod%28%22exec%22%2c%22%22%2egetClass%28%29%29%2einvoke%28%22%22%2egetClass%28%29%2eforName%28%22java%2elang%2eRuntime%22%29%2egetMethod%28%22getRuntime%22%29%2einvoke%28null%29%2c%22calc%22%29%7d(即URL编码后的恶意EL),可能触发计算器程序。在Struts2中,历史漏洞如S2-045允许通过Content-Type头注入OGNL,导致远程命令执行。以下是一个简单的漏洞代码示例:

// 危险示例:直接执行用户输入的OGNL
String userInput = request.getParameter("expr");
Object value = Ognl.getValue(userInput, context, root);

这段代码未对userInput做任何检查,攻击者传入如@java.lang.Runtime@getRuntime().exec('rm -rf /')的表达式即可造成灾难。类似问题也出现在Spring框架的SpEL表达式或Thymeleaf模板中,但EL和OGNL因历史包袱和功能强大而风险更高。

主动防护策略:从输入验证到环境加固

防护表达式注入需要多层防御。首先,对所有用户输入进行严格的白名单验证,只允许预期的字符格式,拒绝任何包含${}、#{}、$[]等表达式符号的输入。其次,在代码层面禁用危险功能:对于EL,在JSP中可通过web.xml配置<el-ignored>true</el-ignored>来全局禁用,或使用JSTL函数转义特殊字符;对于OGNL,应在Struts2中设置struts.ognl.allowStaticMethodAccess=false,并限制访问范围。此外,使用安全的API替代动态表达式解析,例如直接调用对象方法而非拼接字符串。环境加固包括及时更新框架版本,Struts2应升级至最新修复版本,并移除不必要的依赖库。

技术实践:代码层面的安全编码示例

开发者应在关键位置植入防护代码。对于EL表达式,避免直接使用${param.value},转而使用JSTL的<c:out>进行输出转义:

// 安全做法:转义输出
<c:out value="${param.userInput}" />
// 或使用自定义函数过滤
${myLib:escapeEl(param.userInput)}

对于OGNL,在Struts2配置中严格限制:

// struts.xml中设置安全参数
<constant name="struts.ognl.allowStaticMethodAccess" value="false"/>
<constant name="struts.ognl.enableExpressionCache" value="true"/>
// 在Action类中避免使用通用getter/setter处理未信任数据

同时,建议在服务端添加全局过滤器,对请求参数进行递归清洗,移除或转义危险字符。例如,实现一个Servlet Filter,使用正则表达式匹配并拦截包含${、#{}的输入。

监测与应急响应:漏洞发生后的处理流程

即使有防护,也应假设漏洞可能存在。部署Web应用防火墙(WAF)规则,专门检测表达式注入模式,如拦截包含class.forName、Runtime.exec等关键词的请求。日志记录所有表达式解析错误和异常访问,设置实时告警。一旦发现入侵,立即隔离受影响系统,审查日志确定注入点,并回滚到安全版本。事后必须进行代码审计,使用自动化工具扫描整个项目,查找类似Ognl.getValue()或ExpressionEvaluator的调用点。

总结:构建纵深防御体系

防护EL与OGNL表达式注入不是单一措施能解决的,它需要结合安全开发、严格配置和持续监控。开发者应遵循最小权限原则,禁用不必要的表达式功能;运维人员保持环境更新,使用安全工具扫描;架构师则考虑在设计中避免动态表达式解析。通过这种纵深防御,才能有效降低因表达式注入导致的数据泄露或系统崩溃风险,确保Web应用的长期安全稳定。