Cisco ISE API未授权远程代码执行漏洞深度分析

本文详细分析了Cisco ISE中的高危漏洞CVE-2025-20281,涉及未授权反序列化漏洞和命令注入漏洞,通过利用Java exec()方法的特性结合Docker特权容器配置,最终实现从容器逃逸到主机获取root权限的全过程。

CVE-2025-20281: Cisco ISE API未授权远程代码执行漏洞

2025年1月25日,趋势科技零日计划(ZDI)收到来自GMO Cybersecurity by Ierae的Kentaro Kawane的报告,涉及Cisco身份服务引擎(ISE)中存在反序列化不可信数据的漏洞。这个认证前漏洞存在于DescriptionRegistrationListener类的enableStrongSwanTunnel方法中。在分析该漏洞时,我注意到同一个函数还存在以root权限执行的命令注入漏洞。Cisco最初将其修补为CVE-2025-20281(ZDI-25-609),但随后又发布了CVE-2025-20337(ZDI-25-607)以完全解决该漏洞。下文将解释原因。

利用过程并不像我最初希望的那样直接,但最终比普通的命令注入要有趣得多。在本博客中,我将分享如何在受影响的Cisco ISE安装上实现root shell,包括从实际执行命令注入的Docker容器中逃逸的过程!

更多内容稍后详述,让我们先看看初始漏洞。

获取命令注入

以下是enableStrongSwanTunnel方法,它导致两个漏洞——一个是ZDI收到的原始报告,另一个是本博客的主题。值得注意的是,Cisco不同意这一观点,并最初为两个提交分配了相同的CVE。这是正确的决定吗?我将把这个判断留给读者。

在[1]和[2]处,可以看到最初报告的不安全反序列化。
在[3]处,我们看到如果攻击者提供一个有效的序列化Java字符串数组,该值将用于调用shell脚本。
不仅如此,invokeStrongSwanShellScript函数还包含另一个有趣的东西:

[4]处的sudo命令更令人兴奋。这不仅看起来非常有希望作为命令注入漏洞,而且该代码将以root权限执行。
[5]处的logger.debug调用看起来很有帮助,因此在重新配置log4j以输出调试日志后,我开始发送一些测试输入。

简要说明:
下面的每个日志输出都来自向/deployment-rpc/enableStrongSwanTunnel发送单独的POST请求。这些请求的主体是序列化Java String[]数组对象的字符串表示,其中数组的第一个元素包含命令注入。
例如,以下数组在序列化并发送到该端点时,在日志中显示为configureStrongSwan.sh enable x; touch /flag。
      String[] arr = {“x; touch /flag”, “”};
为简洁起见,在本博客的其余部分,我只包含了通过exec()使用bash执行的最终命令。实际上,触发每个向量的都是易受攻击的enableStrongSwanTunnel端点。

现在,回到那些测试输入:

日志看起来很好;显然我们可以控制[4]处第二个concat的值。
不幸的是,随后的ls调用显示,利用并不那么直接。因为exec命令调用磁盘上的bash脚本并将攻击者控制的输入作为参数传递,我们不能简单地中断exec调用并执行我们自己的代码。
相反,我们需要查看configureStrongSwan.sh以了解该参数的使用方式。

在[1]处,当使用[3]处指定的enable操作时,我们调用enableStrongSwan函数。这里OPERATION是enable,IKE_ID是我们攻击者控制的有效载荷。
在[2]处,我们看到传递的IKE_ID在函数中的唯一用法,作为另一个函数verifyIpsecConnectionStatus的参数。
在[3]和[4]处,我们使用攻击者控制的有效载荷构建swanctl命令。[3]处的参数$1对应于IKE_ID参数。
最后,在[5]处,注意我们在docker容器中运行此命令:strongswan-container。这是拼图的另一部分。也许代码正在该容器中执行?让我们看看:

仍然没有成功,但更近了一步!
上面需要注意的关键点是易受攻击的参数IKE_ID来自存储在变量$2中的脚本参数。考虑以下来自初始测试输入的示例:
      configureStrongSwan.sh enable x; touch /flag
这里,参数$2只是x;。不像我们希望的那样具有威胁性。额外的参数$3和$4包含touch和/flag.txt,但脚本从未使用它们。
即使我们使用引号或反引号(防止bash拆分有效载荷),磁盘上也没有发生任何变化。但这是为什么呢?该示例中参数$2的值说明了问题:
      configureStrongSwan.sh enable “x; touch /flag”
有趣的是,在configureStrongSwan.sh中用作IKE_ID的参数值$2实际上是"x;。这很奇怪。通常当向bash脚本发送参数时,我们希望引用的参数保持在一起。然而,事实证明Java处理此命令的方式与bash本身不同。

Java exec()调用中的分词

为了正确制作最终的利用代码,我们必须首先更多了解Java的exec方法的工作原理。Cisco ISE使用Java 8,因此让我们看看该版本的Open JDK代码:

在[5]处,我们看到当字符串命令传递给exec()时,Java使用StringTokenizer类对其进行分词。
关于Java 8中的StringTokenizer类的更多信息可以在这里阅读。但就本博客而言,我将直接切入重点:
StringTokenizer,根据设计,不尊重引号或反引号。相反,它的行为是简单地按提供的标记拆分字符串(默认值是\t\n\r\f,用于exec方法)。这解释了为什么当我们尝试使用引号时,来自测试输入的IKE_ID值只是"x;。当bash收到我们的输入时,它已经被分词,因此没有被解析为单个参数。

幸运的是,Bash内置了一个称为内部字段分隔符(${IFS})的特殊变量。关于此变量的目的和功能的更多信息可以在这里阅读。
就本次利用而言,用法很简单:攻击者可以简单地将所有空格替换为${IFS},bash将按预期解释一切。以下示例从bash的角度来看实际上是相同的:

然而,使用${IFS}版本,Java将整个有效载荷视为单个标记,保持命令的完整性。
此外,exec现在实际上为我们工作,我们甚至不需要包含引号。Java将对我们的输入进行分词,并确保将其作为单个参数传递给bash。
我们现在可以使用${IFS}变量运行有效载荷,瞧!

终于,代码执行!但这还不是我想要的。代码在Docker strongswan-container的上下文中执行,但我真正想要的是ISE服务器主机上的完整root shell。

逃逸容器

我们可以从主机看到strongswan-container以特权方式运行:

这是Cisco的一份大礼。因为它以特权方式运行,我们可以利用Brandon Edwards和Nick Freeman在Black Hat USA 2019的这次演讲[PDF]中描述的“用户模式助手”技术。我已经总结了与此特定容器设置相关的细节。更通用的信息可以在这里找到。最终的有效载荷如下所示:

在[1]处,挂载Linux cgroup。
在[2]处,我们指定当cgroup为空时要执行的shell脚本simulate.sh。
在[3]处,创建并配置simulate.sh。这里,我选择将SSH公钥回显到ISE服务器上的/root/.ssh/authorized_keys文件。
在[4]处,清空cgroup。这导致simulate.sh在主机ISE服务器上以root权限执行。

全部整合

上述技术很好,但我们仍然不能使用空格。我没有到处应用${IFS},而是选择对容器逃逸脚本进行base64编码。然后,发送到易受攻击端点的有效载荷可以包括base64 -d调用来解码脚本并将其管道传输到bash: echo${IFS}[base64编码的逃逸脚本]${IFS}|${IFS}base64${IFS}-d${IFS}|${IFS}bash
altogether,最终的恶意请求如下所示:

[查看完整大小]

最后,一个root shell!

结论

这个漏洞利用起来非常有趣,并突出显示了几种值得了解的技术,即使对于像命令注入这样相对直接的漏洞类也很有用。总结一下,攻击者可以利用Cisco身份服务引擎中的一系列错误完美风暴,实现以root权限完全接管系统。通过利用bash中的${IFS}变量,我们可以绕过Java的exec方法限制我们使用空格字符。此外,尽管初始命令注入在Docker容器内执行,但我们能够突破沙箱并在主机上执行代码,因为Cisco将容器配置为特权模式。最后,对于那些在家跟随操作的人:Cisco认为彼此重复的那两个漏洞?它们通过两个不同的代码更改修复。“重复”的定义再次留给读者。请关注未来的博客,我将更详细地介绍我在该领域发现的漏洞。在此之前,你可以在Twitter上关注我@bobbygould5,并在Twitter、Mastodon、LinkedIn或Bluesky上关注团队,以获取最新的利用技术和安全补丁。

comments powered by Disqus
使用 Hugo 构建
主题 StackJimmy 设计