您的位置 首页 java

Java代码审计之SpEL表达式注入

SpEL 表达式注入

Spring Expression Language(简称 SpEL)是一种功能强大的表达式语言、用于在运行时查询和操作对象图;语法上类似于 Unified EL,但提供了更多的特性,特别是方法调用和基本 字符串 模板函数。SpEL 的诞生是为了给 Spring 社区提供一种能够与 Spring 生态系统所有产品无缝对接,能提供一站式支持的表达式语言。

SpEL 表达式

基本表达式

字面量表达式、关系,逻辑与算数运算表达式、字符串链接及截取表达式、三目运算、正则表达式以及括号优先级表达式;

类相关表达式

类类型表达式、类实例化、instanceof 表达式、变量定义及引用、赋值表达式、自定义函数、对象属性存取及安全导航表达式、对象方法调用、Bean 引用;

集合相关表达式

内联 List、内联数组、集合、字典访问、列表、字典;

其他表达式

模版表达式

SpEL 基础

在 pom.xml 导入 maven 或是把”org.springframework.expression-3.0.5.RELEASE.jar”添加到类路径中

<properties>
 <org.springframework.version>5.0.8.RELEASE</org.springframework.version>
</properties>
<dependency>
 <groupId>org.springframework</groupId>
 <artifactId>spring-expression</artifactId>
 <version>${org.springframework.version}</version>
</dependency>
 

SpEL 使用方式

SpEL 在求表达式值时一般分为四步,其中第三步可选:首先构造一个解析器,其次解析器解析字符串表达式,在此构造上下文,最后根据上下文得到表达式运算后的值。

 ExpressionParser parser = new SpelExpressionParser();
  Expression  expression = parser.parseExpression("('Hello' + ' freebuf').concat(#end)");
 EvaluationContext context = new StandardEvaluationContext();
 context.setVariable("end", "!");
 System.out.println(expression.getValue(context));
 

1.创建解析器: SpEL 使用 ExpressionParser 接口表示解析器,提供 SpelExpressionParser 默认实现;

2.解析表达式:使用 ExpressionParser 的 parseExpression 来解析相应的表达式为 Expression 对象。

3.构造上下文:准备比如变量定义等等表达式需要的上下文数据。

4.求值:通过 Expression 接口的 getValue 方法根据上下文获得表达式值。

SpEL 主要接口

1. ExpressionParser 接口 :表示解析器,默认实现是 org.springframework.expression.spel.standard 包中的 SpelExpressionParser 类,使用 parseExpression 方法将字符串表达式转换为 Expression 对象,对于 ParserContext 接口用于定义字符串表达式是不是模板,及模板开始与结束字符;

public interface ExpressionParser { 
 Expression parseExpression(String expressionString); 
 Expression parseExpression(String expressionString, ParserContext context); 
}
 

事例 demo

ExpressionParser parser = new SpelExpressionParser();
ParserContext parserContext = new ParserContext() {
 @Override
 public boolean isTemplate() {
 return true;
 }
 @Override
 public String getExpressionPrefix() {
 return "#{";
 }
 @Override
 public String getExpressionSuffix() {
 return "}";
 }
};
String template = "#{'hello '}#{'freebuf!'}";
Expression expression = parser.parseExpression(template, parserContext);
System.out.println(expression.getValue());
 

演示的是使用 ParserContext 的情况,此处定义了 ParserContext 实现:定义表达式是模块,表达式前缀为「#{」,后缀为「}」;使用 parseExpression 解析时传入的模板必须以「#{」开头,以「}」结尾。

默认传入的字符串表达式不是模板形式,如之前演示的 Hello World。

EvaluationContext 接口 :表示上下文环境,默认实现是 org.springframework.expression.spel.support 包中的 StandardEvaluationContext 类,使用 setRootObject 方法来设置根对象,使用 setVariable 方法来注册自定义变量,使用 registerFunction 来注册自定义函数等等。

Expression 接口 :表示表达式对象,默认实现是 org.springframework.expression.spel.standard 包中的 SpelExpression,提供 getValue 方法用于获取表达式值,提供 setValue 方法用于设置对象值。

SpEL 语法 – 类相关表达式

类类型表达式: 使用”T(Type)”来表示 java.lang.Class 实例,”Type”必须是类全限定名,”java.lang”包除外,即该包下的类可以不指定包名;使用类类型表达式还可以进行访问类 静态方法 及类静态字段。

具体使用方法

ExpressionParser parser = new SpelExpressionParser();
 // java.lang 包类访问
Class<String> result1 = parser.parseExpression("T(String)").getValue(Class.class);
System.out.println(result1);
 //其他包类访问
String expression2 = "T(java.lang.Runtime).getRuntime().exec('open /Applications/Calculator.app')";
Class<Object> result2 = parser.parseExpression(expression2).getValue(Class.class);
System.out.println(result2);
 //类静态字段访问
int result3 = parser.parseExpression("T(Integer).MAX_VALUE").getValue(int.class);
System.out.println(result3);
 //类静态方法调用
int result4 = parser.parseExpression("T(Integer).parseInt('1')").getValue(int.class);
System.out.println(result4);
 

审计过程

这里拿 Spring Message 远程命令执行漏洞来作为例子

环境搭建

git clone 
git checkout 6958af0b02bf05282673826b73cd7a85e84c12d3
 

拿到项目代码,全局搜索一下 org.springframework.expression.spel.standard,发现 DefaultSubscriptionRegistry.java 文件处有导入。

Java代码审计之SpEL表达式注入

再搜索一下 SpelExpressionParser

Java代码审计之SpEL表达式注入

往下跟进发现如下关键代码,具体分析看代码注释

@Override
protected void addSubscriptionInternal(
String sessionId, String subsId, String destination, Message<?> message) {
Expression expression = null;
MessageHeaders headers = message.getHeaders();
 // 这里可以看出 SpEL 表达式 expression 是从 headers 中的 selector 字段中取出来
String selector = SimpMessageHeaderAccessor.getFirstNativeHeader(getSelectorHeaderName(), headers);
if (selector != null) {
try {
 //生成 expression 对象
expression = this.expressionParser.parseExpression(selector);
this.selectorHeaderInUse = true;
if (logger.isTraceEnabled()) {
logger.trace("Subscription selector: [" + selector + "]");
}
}
catch (Throwable ex) {
if (logger.isDebugEnabled()) {
logger.debug("Failed to parse selector: " + selector, ex);
}
}
}
 // expression 传入 addSubscription 这个函数里面,即存放在 this.subscriptionRegistry
this.subscriptionRegistry.addSubscription(sessionId, subsId, destination, expression);
this.destinationCache.updateAfterNewSubscription(destination, sessionId, subsId);
}
 

再搜索一下 this.subscriptionRegistry,看看有没有调用传进去的 expression。

然后发现了!

Java代码审计之SpEL表达式注入

在这里调用了 this.subscriptionRegistry.getSubscriptions(sessionId) 并从中取出 info->sub-> expression。

最关键的是,这里直接调用了 expression.getValue()!这说明如果能控制 SpEL 的表达式,就能直接命令执行!

再来看看这个 filterSubscriptions 函数在哪里调用。从函数的调用回溯追踪调用链如下:

 filterSubscriptions -> findSubscriptionsInternal -> findSubscriptions -> sendMessageToSubscribers
 

sendMessageToSubscribers 即发送消息的功能

回顾一下整个流程,SpEL 表达式从 headers 中 selector 获取,即发送请求时添加 selector 到请求的 header 即可传入,然后生成 expression 对象传入 this.subscriptionRegistry,然后当发送消息的时候,最终会直接从 this.subscriptionRegistry 取出并调用 expression.getValue() 执行我们传入的 SpEL 表达式。

验证过程,在 expression.getValue() 这里打个断点,看看发送消息是否会拦截并查看调用链是否如上述分析一样。

Java代码审计之SpEL表达式注入

Bingo!

简单总结一下 SpEL 表达式注入的分析思路,可以先全局搜索 org.springframework.expression.spel.standard, 或是 expression.getValue()、expression.setValue(),定位到具体漏洞代码,再分析传入的参数能不能利用,最后再追踪参数来源,看看是否可控。Spring Data Commons Remote Code Execution 的 SpEL 注入导致的代码执行同样可以用类似的思路分析。

漏洞修复

SimpleEvaluationContext、StandardEvaluationContext 是 SpEL 提供的两个 EvaluationContext

SimpleEvaluationContext - 针对不需要 SpEL 语言语法的全部范围并且应该受到有意限制的表达式类别,公开 Spal 语言特性和配置选项的子集。StandardEvaluationContext - 公开全套 SpEL 语言功能和配置选项。您可以使用它来指定默认的根对象并配置每个可用的评估相关策略。

SimpleEvaluationContext 旨在仅支持 SpEL 语言语法的一个子集。它不包括 Java 类型引用,构造函数和 bean 引用;所以最直接的修复方式是使用 SimpleEvaluationContext 替换 StandardEvaluationContext。


最后,我自己是一名从事了多年开发的Java老程序员,辞职目前在做自己的Java私人定制课程,今年年初我花了一个月整理了一份最适合2019年学习的Java学习干货,可以送给每一位喜欢Java的小伙伴,想要获取的可以关注我并在后台私信我:01,即可免费获取。

文章来源:智云一二三科技

文章标题:Java代码审计之SpEL表达式注入

文章地址:https://www.zhihuclub.com/198760.shtml

关于作者: 智云科技

热门文章

网站地图