java 代码审计_Java代码审计之SpEL表达式注入

原文作者:编程艺术思维

SpEL表达式注入

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

SpEL表达式

基本表达式

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

类相关表达式

类类型表达式,类实例化,表达式的实例,变量定义和引用,赋值表达式,自定义函数,对象属性访问以及安全导航表达式,对象方法调用,Bean引用;

集合相关表达式

内联列表,内联副本,集合,字典访问,列表,字典;

其他表达式

模版表达式

SpEL基础

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

5.0.8.RELEASE

properties>

org.springframework groupId>

spring-expression artifactId>

$ {org.springframework.version} version>

dependency>

SpEL使用方式

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

ExpressionParser解析器= new SpelExpressionParser();

表达式expression = parser.parseExpression(“('Hello'+'freebuf')。concat(#end)”);

EvaluationContext上下文=新的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使用定义定义表达式是不是模板,且模板开始与结束字符;

公共接口ExpressionParser {

Expression parseExpression(String expressionString);

表达式parseExpression(String expressionString,ParserContext context);

}

事例演示

ExpressionParser解析器= new SpelExpressionParser();

ParserContext parserContext = new ParserContext(){

@

Override public boolean isTemplate(){

return true;

}

@覆盖

公共字符串getExpressionPrefix(){

回报“#{”;

}

@覆盖

公共字符串getExpressionSuffix(){

回报“}”;

}

};

字符串模板=“#{'hello'}#{'freebuf!'}”;

表达式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来注册自定义函数等等。

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

SpEL语法-类相关表达式

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

具体使用方法

ExpressionParser解析器= new SpelExpressionParser();

// java.lang包类访问

Class 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 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。类实例化:类实例化同样使用java关键字“ new”,类名必须是全限定名,但java.lang包内的类型除外,例如String,Integer。instanceof表达式:SpEL支持instanceof运算符,跟Java内在使用同义;例如T(String)的“'haha”实例将返回true。变量定义以及引用:变量定义通过EvaluationContext接口的setVariable(variableName,value)方法定义;在表达式中使用“ #variableName”引用;除引用自定义变量,SpE还允许引用根对象和当前对象,使用“ #root”引用根对象,使用“ #this”引用当前对象;自定义函数:目前只支持类静态方法注册为自定义函数; SpEL使用StandardEvaluationContext的registerFunction方法进行注册自定义函数,实际上完全可以使用setVariable代替,而实际上本质是一样的

审计过程

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

环境搭建

git clone https://github.com/spring-guides/gs-messaging-stomp-websocket

git检出6958af0b02bf05282673826b73cd7a85e84c12d3

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

再搜索一下SpelExpressionParser

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

@覆盖

保护无效addSubscriptionInternal(

字符串的sessionId,字符串编制者,字符串目的地,消息的消息){

式表达= NULL;

MessageHeaders标头= message.getHeaders();

//这里可以修剪SpEL表达式expression是从headers中的选择器局部中取出来

String selector = SimpMessageHeaderAccessor.getFirstNativeHeader(getSelectorHeaderName(),headers);

if(selector!= null){

try {

//生成表达式对象

expression = this.expressionParser.parseExpression(selector);

this.selectorHeaderInUse = true;

如果(logger.isTraceEnabled()){

logger.trace(“订阅选择器:[” +选择器+“]”);

}

}

catch(Throwable ex){

如果(logger.isDebugEnabled()){logger.debug(

“无法解析选择器:” +选择器,例如);

}

}

}

//表达式添加addSubscription这个函数里面,即放置在this.subscriptionRegistry

this.subscriptionRegistry.addSubscription(sessionId,subsId,destination,expression);

this.destinationCache.updateAfterNewSubscription(destination,sessionId,subsId);

}

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

然后发现了!

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

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

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

filterSubscriptions-> findSubscriptionsInternal-> findSubscriptions-> sendMessageToSubscribers

sendMessageToSubscribers即发送消息的功能

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

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

答对了!

简单总结一下SpEL表达式注入的分析思路,可以先进行逐级搜索org.springframework.expression.spel.standard,或者expression.getValue(),expression.setValue(),定位到具体细分代码,再分析进行的Spring Data Commons的远程执行代码的SpEL注入导致的代码执行同样可以使用类似的思路分析。

进攻修复

SimpleEvaluationContext,StandardEvaluationContext是SpEL提供的两个EvaluationContextSimpleEvaluationContext-针对不需要的SpEL语言语法的全部范围和应该受到有意限制的表达式类别,公开Spal语言特性和配置选项的子集。StandardEvaluationContext-公开提供SpEL语言功能和配置选项。的根对象并配置每个可用的评估相关策略。

它不包括Java类型引用,构造函数和bean引用;所以最直接的修复方式是使用SimpleEvaluationContext替代StandardEvaluationContext。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值