Hello Lucee! Let us hack Apple again?

Hello Lucee! Let us hack Apple again?

Last year we conducted an in-depth analysis of multiple vulnerabilities within Adobe ColdFusion, we derived valuable insights, one of which revolved around CFM and CFC handling, parsing and execution. We wondered if there are any other CFML Servers. Does this ring a bell? Allow us to introduce Lucee. We’ve previously compromised Lucee’s Admin panel, showcasing a pre-auth Remote Code Execution (RCE) on multiple Apple servers that utilized Lucee as its underlying server.
去年,我们对 Adob​​e ColdFusion 中的多个漏洞进行了深入分析,得出了宝贵的见解,其中之一涉及 CFM 和 CFC 处理、解析和执行。我们想知道是否还有其他 CFML 服务器。这是否敲响了警钟?请允许我们介绍一下露西。我们之前已经攻破了 Lucee 的管理面板,在使用 Lucee 作为底层服务器的多个 Apple 服务器上展示了预身份验证远程代码执行 (RCE)。

Our journey led us through multiple attempts, we will delve into our unsuccessful endeavours and, ultimately, our achievement of RCE on Apple’s production server. Notably, our exploitation extended to potentially compromising Lucee’s update server, thereby unveiling a classic supply chain attack to compromise any Lucee installation with malicious updates.
我们的旅程引导我们进行了多次尝试,我们将深入研究我们不成功的努力,并最终在苹果生产服务器上实现 RCE。值得注意的是,我们的利用扩展到可能危害 Lucee 的更新服务器,从而揭示了一种经典的供应链攻击,可以通过恶意更新危害任何 Lucee 安装。

Attempt 1 – Request Handling and REST Mappings
尝试 1 – 请求处理和 REST 映射

After checking out Lucee’s admin panel in our earlier research, we found that it’s pretty locked down. There are only four CFM files you can get access while being unauthenticated, so there’s not much room for finding bugs there. We need to dig into how Lucee handles requests. We’re looking for specific paths, parameters, headers, and so on, to understand how requests are handled.
在我们早期的研究中检查了 Lucee 的管理面板后,我们发现它相当锁定。在未经身份验证的情况下,您只能访问四个 CFM 文件,因此没有太多空间可以发现其中的错误。我们需要深入研究 Lucee 如何处理请求。我们正在寻找特定的路径、参数、标头等,以了解请求的处理方式。

After reviewing the web.xml file, We set up the JVM debugger via IntelliJ and added Lucee’s source code. We plan to start going through the code by putting a breakpoint at Request::exe(). This way, we can step through the code bit by bit and see how Lucee handles requests.
检查完 web.xml 文件后,我们通过 IntelliJ 设置了 JVM 调试器并添加了 Lucee 的源代码。我们计划通过在 Request::exe() 处放置断点来开始检查代码。这样,我们就可以一步一步地浏览代码,看看 Lucee 是如何处理请求的。

public static void exe(PageContext pc, short type, ...) {
		try {

			if (type == TYPE_CFML) pc.executeCFML(pc.getHttpServletRequest().getServletPath(), throwExcpetion, true);
			else if (type == TYPE_LUCEE) pc.execute(pc.getHttpServletRequest().getServletPath(), throwExcpetion, true);
			else pc.executeRest(pc.getHttpServletRequest().getServletPath(), throwExcpetion);
		finally {
Java 爪哇


Another interesting class that deals with Request and Response in Lucee is core/src/main/java/lucee/runtime/net/http/ReqRspUtil.java. In this class, there are functions to work with various aspects of the Request, like setting/getting certain headers, query parameters, and the request body, among other things.
Lucee 中处理请求和响应的另一个有趣的类是 core/src/main/java/lucee/runtime/net/http/ReqRspUtil.java 。在此类中,有一些函数可以处理请求的各个方面,例如设置/获取某些标头、查询参数和请求正文等。

While looking into this class, we noticed a call to JavaConverter.deserialize(). As the name suggests, it is a wrapper on readObject() to handle Java Deserialization.
在研究此类时,我们注意到对 JavaConverter.deserialize() 的调用。顾名思义,它是 readObject() 的包装器,用于处理 Java 反序列化。

public static Object getRequestBody(PageContext pc, boolean deserialized, ...) {

		HttpServletRequest req = pc.getHttpServletRequest();

		MimeType contentType = getContentType(pc);
				if(deserialized) {
					int format = MimeType.toFormat(contentType, -1);
					obj = toObject(pc, data, format, cs, obj);
		return defaultValue;

public static Object toObject(PageContext pc, byte[] data, int format, ...) {

		switch (format) {
				try {
					return JavaConverter.deserialize(new ByteArrayInputStream(data));
				catch (Exception pe) {
Java 爪哇

It appears that when the request’s content/type header is set toapplication/java, we should theoretically end up here, right? Well, we promptly dispatched a URLDNS gadget with the required content type. And the result? Drumroll, please… Nothing. Could it be that the deserialized condition didn’t pass? To investigate, we add a breakpoint on getRequestbody() , only to find out that we don’t even reach this point.
看起来,当请求的内容/类型标头设置为 application/java 时,理论上我们应该到这里结束,对吧?好吧,我们立即调度了一个具有所需内容类型的 URLDNS 小工具。结果呢?请击鼓……没什么。难道是 deserialized 条件没有通过?为了进行调查,我们在 getRequestbody() 上添加了一个断点,却发现我们甚至没有到达这一点。

But why? we traced through the function calls and realized that certain configurations must be in place to satisfy the if/else statements to lead us to the sink. Given the complexity of the stack, let’s briefly summarize the key points.
但为什么?我们跟踪了函数调用,并意识到必须进行某些配置才能满足 if/else 语句,从而引导我们到达接收器。鉴于堆栈的复杂性,我们简单总结一下要点。

Request:exe() - Determines the type of request and handles it appropriately.
PageContextImpl:executeRest() - Looks for Rest mappings and executes the RestRequestListener.
RestRequestListener() -- Sets the "client" attribute with the value "lucee-rest-1-0" on the request object.
ComponentPageImpl:callRest() - Examines the "client" attribute; if it's "lucee-rest-1-0", proceeds to execute callRest() followed by _callRest().
ComponentPageImpl:_callRest() - If the rest mapping involves an argument, invokes ReqRspUtil.getRequestBody with the argument deserialized: true.
ReqRspUtil.getRequestBody() - If the deserialized argument is true, triggers the toObject() function, which deserializes the request body based on the provided content type.
toObject() - Java Deserialization on the request body if the content type is "application/java".
JavaConverter.deserialize() - The final step where the Java Deserialization process occurs.

To reproduce this RCE, a rest mapping with a function that takes at least one argument must be configured. Deploy below Rest mapping.
要重现此 RCE,必须配置带有至少一个参数的函数的剩余映射。在休息映射下部署。

component restpath="/java"  rest="true" {
    remote String function getA(String a) httpmethod="GET" restpath="deser" {
        return a;
Java 爪哇
Hello Lucee! Let us hack Apple again?
Java Deserialization via REST Mapping On Lucee
通过 Lucee 上的 REST 映射进行 Java 反序列化

Surprisingly, we discovered that Lucee’s critical update server utilizes a REST endpoint – https://update.lucee.org/rest/update/provider/echoGet. This server is pivotal in managing all update requests originating from various Lucee installations.
令人惊讶的是,我们发现 Lucee 的关键更新服务器使用 REST 端点 – https://update.lucee.org/rest/update/provider/echoGet。该服务器对于管理来自各种 Lucee 安装的所有更新请求至关重要。

Hello Lucee! Let us hack Apple again?

At the time of finding, this server was vulnerable to our exploit which could have allowed an attacker to compromise the update server, opening the door to a supply chain attack. Acknowledging the severity of the situation, Lucee’s maintainers promptly implemented a hotfix to secure their update server, subsequently releasing an updated version of Lucee with the necessary fixes – CVE-2023-38693.
在发现时,该服务器容易受到我们的利用,这可能使攻击者能够破坏更新服务器,从而为供应链攻击打开了大门。 Lucee 的维护人员认识到情况的严重性,立即实施了修补程序来保护其更新服务器的安全,随后发布了包含必要修复的 Lucee 更新版本 – CVE-2023-38693。

However, our finding did not apply to Apple’s host, as they did not expose any REST mappings. Let’s try again!
然而,我们的发现并不适用于 Apple 的主机,因为它们没有公开任何 REST 映射。让我们再试一次!

Attempt 2 – CFML Expression Interpreter, Cookies and Sessions.
尝试 2 – CFML 表达式解释器、Cookie 和会话。

After gaining a more in-depth understanding of the codebase, we began selectively examining classes, and one that caught our attention was CFMLExpressionInterpreter. The intriguing nature of this class prompted us to delve into its details. Upon reviewing the class, it became evident that when the constructor’s boolean argument, limited, is set to False (default is True), the method CFMLExpressionInterpreter.interpret(…) becomes capable of executing CFML expressions.
在对代码库有了更深入的了解后,我们开始有选择地检查类,其中引起我们注意的是 CFMLExpressionInterpreter 。这个类的有趣性质促使我们深入研究它的细节。查看该类后,很明显,当构造函数的布尔参数 limit 设置为 False (默认为 True )时,方法 CFMLExpressionInterpreter.interpret(…) 就可以使用执行 CFML 表达式。

Something like CFMLExpressionInterpreter(false).interpret(“function(arg)”) should let us execute any function of Lucee.
像 CFMLExpressionInterpreter(false).interpret(“function(arg)”) 这样的东西应该让我们执行 Lucee 的任何函数。

With this insight, we conducted a thorough search within the codebase to identify instances where CFMLExpressionInterpreter(false) was initialized, and we discovered several occurrences. One in particular was of interest StorageScopeCookie by the name of it seems to be related to cookies.
有了这种洞察力,我们在代码库中进行了彻底的搜索,以识别初始化 CFMLExpressionInterpreter(false) 的实例,并发现了多个实例。其中一个特别令人感兴趣 StorageScopeCookie ,其名称似乎与 cookie 有关。

public abstract class StorageScopeCookie extends StorageScopeImpl {

protected static CFMLExpressionInterpreter evaluator = new CFMLExpressionInterpreter(false);

	protected static Struct  _loadData(PageContext pc, String cookieName, int type, String strType, Log log) {
		String data = (String) pc.cookieScope().get(cookieName, null);
		if (data != null) {
			try {
				Struct sct = (Struct) evaluator.interpret(pc, data);

Java 爪哇

It appears that the StorageScopeCookie._loadData() function accepts the cookie name as one of its arguments, retrieves its value from PageContext, and subsequently passes it to interpret().
看来 StorageScopeCookie._loadData() 函数接受 cookie 名称作为其参数之一,从 PageContext 检索其值,然后将其传递给terpret()。

After a thorough follow of multiple code flows, these three were standing out and seemed like could be called by the Lucee application.
在彻底跟踪多个代码流程后,这三个代码流程脱颖而出,似乎可以由 Lucee 应用程序调用。

  • sessionInvalidate() -> invalidateUserScope() -> getClientScope() -> ClientCookie.getInstance() -> StorageScopeCookie._loadData(…)
  • sessionRotate() -> invalidateUserScope() -> getClientScope() -> ClientCookie.getInstance() -> StorageScopeCookie._loadData(…)
  • PageContext.scope() -> getClientScope() -> ClientCookie.getInstance() -> StorageScopeCookie._loadData(…)
public final class ClientCookie extends StorageScopeCookie implements Client {

	private static final String TYPE = "CLIENT";

	public static Client getInstance(String name, PageContext pc, Log log) {
		if (!StringUtil.isEmpty(name)) name = StringUtil.toUpperCase(StringUtil.toVariableName(name));
		String cookieName = "CF_" + TYPE + "_" + name;
		return new ClientCookie(pc, cookieName, _loadData(pc, cookieName, SCOPE_CLIENT, "client", log));
Java 爪哇

Upon invoking sessionInvalidate() or sessionRotate(), we successfully accessed ClientCookie.getInstance(), constructing the cookie name as CF_CLIENT_LUCEE.
调用 sessionInvalidate() 或 sessionRotate() 后,我们成功访问了 ClientCookie.getInstance(),并将 cookie 名称构造为 CF_CLIENT_LUCEE 。

Hello Lucee! Let us hack Apple again?

This implies that any application utilizing sessionInvalidate() or sessionRotate() could potentially expose a Remote Code Execution (RCE) vulnerability via the CF_CLIENT_LUCEE cookie. Where, “Lucee” represents the application context name, which might vary depending on the deployed application.
这意味着任何使用 sessionInvalidate() 或 sessionRotate() 的应用程序都可能通过 CF_CLIENT_LUCEE cookie 暴露远程代码执行 (RCE) 漏洞。其中,“Lucee”代表应用程序上下文名称,该名称可能会根据部署的应用程序而有所不同。

Our initial search within the Lucee codebase for the usage of these functions in any unauthenticated CFM file or Component (CFC) yielded no results. Expanding our investigation to Mura/Masa CMS, also deployed by Apple on their Lucee server, we identified two calls. One of these calls was unauthenticated under the logout action.
我们在 Lucee 代码库中最初搜索了这些函数在任何未经身份验证的 CFM 文件或组件 (CFC) 中的使用情况,但没有得到任何结果。将我们的调查范围扩大到 Mura/Masa CMS(Apple 也将其部署在其 Lucee 服务器上),我们发现了两个调用。其中一个调用在注销操作下未经身份验证。

public function logout() output=false {
	if ( getBean('configBean').getValue(property='rotateSessions',defaultValue='false') ) {
Java 爪哇


Unfortunately, the successful exploitation of this vulnerability depends on the rotateSessions setting being enabled in Mura/Masa, which is, by default, set to false. Consequently, we are unable to trigger this vulnerability on Apple’s deployment.
不幸的是,能否成功利用此漏洞取决于 Mura/Masa 中启用的rotateSessions 设置,该设置默认设置为 false。因此,我们无法在 Apple 的部署中触发此漏洞。

Feeling a tinge of disappointment, we redirected our focus to the PageContext.scope() flow. After a thorough debugging session, it became apparent that the cookie name in this scenario would be CF_CLIENT_. More crucially, to exploit this code execution, we would need to enable the Client Management setting from the Lucee admin, which is, by default, disabled. Therefore, once again, we find ourselves unable to trigger this vulnerability on Apple’s configuration.
带着一丝失望,我们将注意力转向 PageContext.scope() 流程。经过彻底的调试会话后,很明显,此场景中的 cookie 名称将是 CF_CLIENT_ 。更重要的是,要利用此代码执行,我们需要从 Lucee 管理员启用客户端管理设置,该设置默认情况下是禁用的。因此,我们再次发现自己无法在 Apple 的配置上触发此漏洞。

Hello Lucee! Let us hack Apple again?

Regardless here’s a PoC for the same:
无论如何,这里有一个 PoC:

Hello Lucee! Let us hack Apple again?

Attempt 3 – Variable Interpreter, Functions and Mura CMS
尝试 3 – 变量解释器、函数和 Mura CMS

After various unsuccessful attempts, an alternative idea struck us. What if we could identify more functions that potentially accept user input as a String and could lead to code execution?

Our attention was drawn to VariableInterpreter.parse(,,limited), which initializes CFMLExpressionInterpreter(limited). It occurred to us that if there are calls to VariableInterpreter.parse(,,false), there might be a way for code execution.
我们的注意力被 VariableInterpreter.parse(,,limited) 吸引,它初始化 CFMLExpressionInterpreter(limited) 。我们想到,如果有对 VariableInterpreter.parse(,,false) 的调用,可能会有一种方法来执行代码。

Considering this, We identified some vulnerable sinks in the VariableInterpreter class. If any of the following functions pass user input to parse(), it could serve our purpose:
考虑到这一点,我们在 VariableInterpreter 类中发现了一些易受攻击的接收器。如果以下任何函数将用户输入传递给 parse(),它就可以达到我们的目的:

  • getVariable → VariableInterpreter.parse(,,false)
  • getVariableEL → VariableInterpreter.parse(,,false)
  • getVariableAsCollection → VariableInterpreter.parse(,,false)
  • getVariableReference → VariableInterpreter.parse(,,false)
  • removeVariable → VariableInterpreter.parse(,,false)
    删除变量 → VariableInterpreter.parse(,,false)
  • isDefined → VariableInterpreter.parse(,,false)

To narrow down the search, we investigated classes importing the VariableInterpreter class and identified the following suspects:
为了缩小搜索范围,我们调查了导入 VariableInterpreter 类的类并确定了以下嫌疑对象:

Given the complexity of PageContextImpl, We chose to initially focus on the other classes. Starting with function classes, We tested StructGet("abc") and successfully hit the breakpoint at VariableInterpreter.parse(). However, attempting the payload used earlier for CFMLExpressionInterpreter.interpret() calls didn’t execute imageRead().
考虑到 PageContextImpl 的复杂性,我们选择首先关注其他类。从函数类开始,我们测试了 StructGet("abc") 并成功命中断点 VariableInterpreter.parse() 。但是,尝试之前用于 CFMLExpressionInterpreter.interpret() 调用的有效负载并未执行 imageRead() 。

After reviewing parse(), We realized that the payload needed to be modified to x[imageRead('')] due to the call being made to CFMLExpressionInterpreter.interpretPart() after splitting the string from [ and it worked. imageRead() executed. We can call arbitrary functions from StrucGet("").
在查看 parse() 后,我们意识到,由于从 [ ,因此需要将有效负载修改为 x[imageRead('')] /b3> 成功了。 imageRead() 执行。我们可以从 StrucGet("") 调用任意函数。

This led us to conclude that the following functions allow CFML evaluation, allowing Remote Code Execution (RCE) when they contain user input:
这使我们得出结论,以下函数允许 CFML 评估,当它们包含用户输入时允许远程代码执行 (RCE):

  • StructGet(“…”) 结构获取(“…”)
  • isDefined(“…”) 被定义为(”…”)
  • Empty(“…”) 空的(”…”)

We did a quick search in Masa/Mura CMS’s codebase, where, despite not finding calls for StructGet() and Empty(), we stumbled upon an abundance of calls for isDefined(). (Cue the happy noises!)
我们在 Masa/Mura CMS 的代码库中进行了快速搜索,尽管没有找到对 StructGet() 和 Empty() 的调用,但我们偶然发现了大量对 isDefined() 的调用。 (提示快乐的声音!)

Now, the reason for so many calls is that isDefined(String var), is used to check if a given string is defined as a variable or not. Meaning that isDefined(”url.search”) doesn’t mean our query parameter search‘s value is being passed here. We’d need a call like isDefined(”#url.search#”) which means our given string will be checked if it is defined as variable or not.
现在,如此多调用的原因是 isDefined(String var) 用于检查给定字符串是否定义为变量。这意味着 isDefined(“url.search”) 并不意味着我们的查询参数 search 的值正在传递到这里。我们需要像 isDefined(”#url.search#”) 这样的调用,这意味着将检查给定的字符串是否被定义为变量。

After grepping for isDefined\(.#\) we came across a few calls, most importantly the call in FEED API at core/mura/client/api/feed/v1/apiUtility.cfc#L122 and in the JSON API both of which could be triggered pre-auth.
在 grep 完 isDefined\(.#\) 之后,我们遇到了一些调用,最重要的是 core/mura/client/api/feed/v1/apiUtility.cfc#L122 的 FEED API 中的调用以及 JSON API 中的调用,其中可以触发预授权。

function processRequest(){
	try {
		var responseObject=getpagecontext().getresponse();
		var params={};
		var result="";


		if (isDefined('params.method') && isDefined('#params.method#')){
Java 爪哇

The param struct is populated from both theurl and form structs, which store GET and POST parameters, respectively. Consequently, the param struct contains user input. Performing isDefined("#param.method#") poses a risk of Remote Code Execution (RCE), when Mura/Masa CMS is deployed on a Lucee server.
param 结构由 url 和 form 结构填充,它们分别存储 GET 和 POST 参数。因此, param 结构包含用户输入。当 Mura/Masa CMS 部署在 Lucee 服务器上时,执行 isDefined("#param.method#") 会带来远程代码执行 (RCE) 的风险。

And finally: We perform our code execution on Apple!
最后:我们在 Apple 上执行代码!

Hello Lucee! Let us hack Apple again?

These findings were reported to both Apple and the Lucee team. Apple fixed the report within 48 hours while Lucie’s team notified us that they are aware of this nature and have already implemented a fix by adding an optional setting within the Admin panel:
这些发现已报告给 Apple 和 Lucee 团队。 Apple 在 48 小时内修复了该报告,而 Lucie 的团队通知我们,他们已经意识到这一性质,并且已经通过在管理面板中添加可选设置来实施修复:

Hello Lucee! Let us hack Apple again?

Vulnerability Detection 漏洞检测

The below template could be used to identify If your Lucee instance is vulnerable to a cookie parsing issue that could lead to Remote Code Execution. We’ve also added detection template into nuclei-templates project.
以下模板可用于识别您的 Lucee 实例是否容易受到可能导致远程代码执行的 cookie 解析问题的影响。我们还在 nuclei-templates 项目中添加了检测模板。

id: lucee-rce

  name: Lucee < - Remote Code Execution
  author: rootxharsh,iamnoooob,pdresearch
  severity: critical
    max-request: 1
    shodan-query: http.title:"Lucee"
    verified: true
  tags: lucee,rce,oast

  - raw:
      - |
        GET / HTTP/1.1
        Host: {{Hostname}}
        Cookie: CF_CLIENT_=render('<cfscript>writeoutput(ToBinary("{{base64('{{randstr}}')}}"))</cfscript>');

      - type: dsl
          - contains(body, "{{randstr}}")
          - contains(header, "cfid")
          - contains(header, "cftoken")
        condition: and

Applying patch 应用补丁

First and foremost, make sure you’re using the latest stable release of Lucee. Then apply the below settings within the Lucee admin panel to disable evaluation of these functions:
首先,确保您使用的是 Lucee 的最新稳定版本。然后在 Lucee 管理面板中应用以下设置以禁用对这些函数的评估:

Hello Lucee! Let us hack Apple again?

They also implemented a fix for the cookies that were being parsed as CFML expressions.
他们还修复了被解析为 CFML 表达式的 cookie。

Conclusion 结论

Our deep dive into Lucee, an alternative CFML server, yielded insightful results and uncovered critical vulnerabilities. We pinpointed vulnerabilities in Lucee’s request handling and REST mappings, exposing a critical Java deserialization flaw. The potential impact was substantial, especially considering the vulnerability’s potential exploitation of Lucee’s vital update server, which could have facilitated supply chain attacks.
我们深入研究了 Lucee(另一种 CFML 服务器),产生了富有洞察力的结果并发现了关键漏洞。我们查明了 Lucee 请求处理和 REST 映射中的漏洞,暴露了一个关键的 Java 反序列化缺陷。潜在影响是巨大的,特别是考虑到该漏洞可能会利用 Lucee 的重要更新服务器,从而促进供应链攻击。

Furthermore, our exploration of Lucee’s CFML expression interpretation, cookies, and sessions uncovered vulnerabilities that could lead to remote code execution. Exploiting functions like sessionInvalidate(), sessionRotate(), StructGet() and IsDefined(), we identified pathways to remote code execution, particularly within Mura/Masa CMS, a CMS deployed on top of Lucee by Apple.
此外,我们对 Lucee 的 CFML 表达式解释、cookie 和会话的探索发现了可能导致远程代码执行的漏洞。利用 sessionInvalidate()、sessionRotate()、StructGet() 和 IsDefined() 等函数,我们确定了远程代码执行的途径,特别是在 Mura/Masa CMS(Apple 部署在 Lucee 之上的 CMS)中。

Promptly following our responsible disclosure to both Apple and the Lucee team, swift action ensued. Apple responded and implemented a fix within 48 hours, swiftly addressing the reported issues and rewarded us with a $20,000 bounty, while Lucee swiftly implemented fixes to shore up the vulnerabilities. This collaborative effort highlights the importance of responsible disclosures and bug bounty programs.
在我们负责任地向 Apple 和 Lucee 团队披露信息后,我们迅速采取了行动。 Apple 在 48 小时内做出回应并实施了修复,迅速解决了报告的问题,并向我们奖励了 20,000 美元的奖金,而 Lucee 也迅速实施了修复以修复漏洞。这项合作凸显了负责任的披露和错误赏金计划的重要性。

原文始发于Harsh Jaiswal & Rahul Maini:Hello Lucee! Let us hack Apple again?

版权声明:admin 发表于 2024年2月28日 下午3:11。
转载请注明:Hello Lucee! Let us hack Apple again? | CTF导航