一、SpEL 表达式注入
为市中等地区用户提供了全套网页设计制作服务,及市中网站建设行业解决方案。主营业务为成都网站制作、成都网站建设、市中网站设计,以传统方式定制建设网站,并提供域名空间备案等一条龙服务,秉承以专业、用心的态度为用户提供真诚的服务。我们深信只要达到每一位用户的要求,就会得到认可,从而选择与我们长期合作。这样,我们也可以走得更远!
Spring Expression Language(简称 SpEL)是一种功能强大的表达式语言、用于在运行时查询和操作对象图;语法上类似于 Unified EL,但提供了更多的特性,特别是方法调用和基本字符串模板函数。SpEL 的诞生是为了给 Spring 社区提供一种能够与 Spring 生态系统所有产品无缝对接,能提供一站式支持的表达式语言。
二、SpEL 表达式
三、SpEL 基础
在 pom.xml 导入 maven 或是把”org.springframework.expression-3.0.5.RELEASE.jar”添加到类路径中
5.0.8.RELEASE org.springframework spring-expression ${org.springframework.version}
1. 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));
2. 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。
3. SpEL 语法 – 类相关表达式
类类型表达式:使用”T(Type)”来表示 java.lang.Class 实例,”Type”必须是类全限定名,”java.lang”包除外,即该包下的类可以不指定包名;使用类类型表达式还可以进行访问类静态方法及类静态字段。
具体使用方法:
- ExpressionParser parser = 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
- 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 远程命令执行漏洞来作为例子
1. 环境搭建
- git clone https://github.com/spring-guides/gs-messaging-stomp-websocket
- git checkout 6958af0b02bf05282673826b73cd7a85e84c12d3
拿到项目代码,全局搜索一下 org.springframework.expression.spel.standard,发现 DefaultSubscriptionRegistry.java 文件处有导入。
再搜索一下 SpelExpressionParser
往下跟进发现如下关键代码,具体分析看代码注释
- @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。
然后发现了!
在这里调用了 this.subscriptionRegistry.getSubscriptions(sessionId) 并从中取出 info->sub-> expression。
最关键的是,这里直接调用了 expression.getValue()!这说明如果能控制 SpEL 的表达式,就能直接命令执行!
再来看看这个 filterSubscriptions 函数在哪里调用。从函数的调用回溯追踪调用链如下:
- filterSubscriptions -> findSubscriptionsInternal -> findSubscriptions -> sendMessageToSubscribers
2. sendMessageToSubscribers 即发送消息的功能
回顾一下整个流程,SpEL 表达式从 headers 中 selector 获取,即发送请求时添加 selector 到请求的 header 即可传入,然后生成 expression 对象传入 this.subscriptionRegistry,然后当发送消息的时候,最终会直接从 this.subscriptionRegistry 取出并调用 expression.getValue() 执行我们传入的 SpEL 表达式。
验证过程,在 expression.getValue() 这里打个断点,看看发送消息是否会拦截并查看调用链是否如上述分析一样。
Bingo!
简单总结一下 SpEL 表达式注入的分析思路,可以先全局搜索 org.springframework.expression.spel.standard, 或是 expression.getValue()、expression.setValue(),定位到具体漏洞代码,再分析传入的参数能不能利用,然后再追踪参数来源,看看是否可控。Spring Data Commons Remote Code Execution 的 SpEL 注入导致的代码执行同样可以用类似的思路分析。
五、漏洞修复
SimpleEvaluationContext、StandardEvaluationContext 是 SpEL 提供的两个 EvaluationContext:
SimpleEvaluationContext 旨在仅支持 SpEL 语言语法的一个子集。它不包括 Java 类型引用,构造函数和 bean 引用;所以最直接的修复方式是使用 SimpleEvaluationContext 替换 StandardEvaluationContext。
这是我个人学习代码审计过程中的小总结,可能逻辑性相对来说没那么严谨,但是个人觉得这是一个比较通俗易懂的分析方法,不喜勿喷。
当前标题:Java代码审计之SpEL表达式注入
文章地址:http://www.shufengxianlan.com/qtweb/news25/300825.html
网站建设、网络推广公司-创新互联,是专注品牌与效果的网站制作,网络营销seo公司;服务项目有等
声明:本网站发布的内容(图片、视频和文字)以用户投稿、用户转载内容为主,如果涉及侵权请尽快告知,我们将会在第一时间删除。文章观点不代表本网站立场,如需处理请联系客服。电话:028-86922220;邮箱:631063699@qq.com。内容未经允许不得转载,或转载时需注明来源: 创新互联