技术说|挑战0解薛定谔!RWCTF6th PyGhost——symlink提权

admin 2024年3月20日23:48:06评论12 views字数 11598阅读38分39秒阅读模式

技术说|挑战0解薛定谔!RWCTF6th PyGhost——symlink提权

听我说

技术说|挑战0解薛定谔!RWCTF6th PyGhost——symlink提权

这是一道2024年Real world CTF 0解题,人称“薛定谔”。如果你对CTF比赛、漏洞分析与挖掘、Windows操作系统特性这些领域感兴趣,这篇文章绝对不容错过!

在本文中,作者深入剖析了2024年Real world CTF 6th比赛中挑战难度很高的本地提权(Local Privilege Escalation)题目:PyGhost,同时涵盖了对 CVE-2023-49797 漏洞的详细分析。

总体而言,本题的难度在于:

1、题目蕴藏某组建历史漏洞,如何在不了解该漏洞的情况下做出分析,进而利用?

2、Windows操作系统特性的考察也是本题的一道关卡。解题过程中,需要利用一些windows的文件操作相关特性来完成,不熟悉或不了解该领域的相关知识,都将造成难以通关的局面。

话不多说,开始挑战它!
正文揭晓

01
背景

本题目来自2024 年RealworldCTF 6th。题目类型为Pwn, Misc。题目难度为薛定谔。题目在比赛结束时解出人数为0。此文章是对此题目的赛后复盘。

本题的题目描述是:

This is an LPE(Local Privilege Escalation) challenge. Your task is to pop a highly-privileged(nt authoritysystem) cmd.exe as a low-privileged user. Follow these steps to deploy the challenge locally:

  1. download and install the virtual machine from:https://developer.microsoft.com/en-us/windows/downloads/virtual-machines/

  2. execute the installer (installer.exe in the attachment) as Administrator

  3. the installer will set up the vulnerable component. You can then attempt to find the vulnerability and exploit it

Notes about the demo:

  1. Send your exploit archive file to email and DM on Discord when you're ready. Meanwhile, the email should also contains your team name and team token

  2. You can choose to demo your exploit publicly or privately, according to your preference. If you choose to demo publicly, the entire process will be visible to everyone, so remember to remove sensitive information. If you choose to demo privately, we will set up a private discord channel that only includes the admin and your team members

  3. Our demo VM is slightly configured, including:

  4. a. Windows Defender is disabled. You don't have to contend with it.

  5. b. A standard user(not in the Administrator group, with the username being ctf) is created for demo purposes. We will run your exploit in the context of the standard user.

  6. If your exploit needs multiple steps, please batch them in a single file. We will only execute one of your files and then wait for the result without more user interaction

I will not accept more than 3 emails per team. If you really need more, you will need to explain to me in detail why you messed up your first 3 tries and convince me that you deserve a 4th chance.

The running time for each try cannot exceed 3 minutes.

I will reward you with the flag if the highly-privileged cmd.exe pops up.

此题目需要选手在Windows平台上以BUILTINUsers组用户权限执行一系列操作,获得一个以NT AUTHORITYSYSTEM权限运行的cmd.exe。从题目描述中我们不难分析得,installer.exe以Administrator权限运行后会注册成一个漏洞组件,可以初步猜测选手需要写程序与这个组件进行交互,在此过程中劫持某些资源,来获得高执行权限。

02
题目分析

用虚拟机运行此安装程序,在运行前打开procmon监控程序的行为。可以看到程序访问了sc.exe,说明此漏洞组件是与Windows服务相关的。

技术说|挑战0解薛定谔!RWCTF6th PyGhost——symlink提权

使用service.msc寻找蛛丝马迹,找到了一个叫try to hack me的服务。此服务的服务名称是RWCTF,可执行路径是C:WindowsTempserver.exe。

技术说|挑战0解薛定谔!RWCTF6th PyGhost——symlink提权

定位到server.exe,提取,发现是一个pyinstaller打包的.exe程序。

技术说|挑战0解薛定谔!RWCTF6th PyGhost——symlink提权

Pyinstaller版本尚不明确,程序功能尚不明确。

此时使用procexp可以看到server.exe的运行权限是NT AUTHORITYSYSTEM的,证明高权限的任意写入/重命名/删除是可能存在的。

之后的任务就是python逆向,这一部分省略。直接给出逆向代码:

importsocketimportwin32serviceutilimportservicemanagerimportwin32eventimportwin32serviceimportwin32pipeimportwin32securityimportwin32fileimportpywintypesimportrandomimportmatplotlib.pyplot as pltimportnumpy as npimportosimportsysimportshutilclassSMWinservice(win32serviceutil.ServiceFramework):_svc_name_= "RWCTF"_svc_display_name_= 'try to hack me'_svc_description_= ''@classmethoddefparse_command_line(cls):iflen(sys.argv) == 1:servicemanager.Initialize()servicemanager.PrepareToHostSingle()servicemanager.StartServiceCtrlDispatcher()win32serviceutil.HandleCommandLine(cls)else:win32serviceutil.HandleCommandLine(cls)def__init__(self, args):win32serviceutil.ServiceFramework.__init__(self,args)self.hWaitStop= win32event.CreateEvent(None, 0, 0, None)socket.setdefaulttimeout(60)defSvcStop(self):self.stop()self.ReportServiceStatus(win32service.SERVICE_STOP_PENDING)win32event.SetEvent(self.hWaitStop)defSvcDoRun(self):self.start()servicemanager.LogMsg(servicemanager.EVENTLOG_INFORMATION_TYPE,servicemanager.PYS_SERVICE_STARTED,self._svc_name_)self.main()defstart(self):passdefstop(self):passdefmain(self):passclassRWCTFService(SMWinservice):__qualname__= 'RWCTFService'_svc_name_= 'RWCTF'_svc_display_name_= 'try to hack me'_svc_description_= ''defstart(self):self.LICENSE= 'n                    The Star And Thank Author License (SATA)n                   Version 2.0, April 2021nnCopyright   <2024><RWCTF>([email protected])nnProject Url:https://realworldctf.com/nnPermission is hereby granted, free ofcharge, to any person obtaining a copynof this software andassociated documentation files (the "Software"), todealnin the Software without restriction, including withoutlimitation the rightsnto use, copy, modify, merge, publish,distribute, sublicense, and/or sellncopies of the Software, and topermit persons to whom the Software isnfurnished to do so, subjectto the following conditions:nnThe above copyright notice and thispermission notice shall be included innall copies or substantialportions of the Software.nnAnd wait, the most important, you shouldstar/+1/like the project(s) in project urlnsection above first, andthen thank the author(s) in Copyright section.nnHere are somesuggested ways:nn - Email the authors a thank-you letter, and makefriends with him/her/them.n - Report bugs or issues.n - Tellfriends what a wonderful project this is.n - And, sure, you can justexpress thanks in your mind without telling theworld.nnContributors of this project by forking have the option toadd his/her name andnforked project url at copyright and project urlsections, but shall not deletenor modify anything else in these twosections.nnTHE SOFTWARE IS PROVIDED "AS IS", WITHOUTWARRANTY OF ANY KIND, EXPRESS ORnIMPLIED, INCLUDING BUT NOT LIMITEDTO THE WARRANTIES OF MERCHANTABILITY,nFITNESS FOR A PARTICULARPURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THEnAUTHORS ORCOPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OROTHERnLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OROTHERWISE, ARISING FROM,nOUT OF OR IN CONNECTION WITH THE SOFTWAREOR THE USE OR OTHER DEALINGS INnTHE SOFTWARE.n'self.output_directory= 'C:\Windows\Temp'self.output_prefix= 'C:\Windows\Temp\res.'self.isrunning= Truedefstop(self):try:self.isrunning= FalseexceptFileNotFoundError:self.isrunning= Falseexcept:raisedefchallenge(self, option, key):RAND_32_64= lambda: random.randint(32, 64)nonce= ''ifoption == b'png':nonce= random.getrandbits(RAND_32_64()) + 1elifoption == b'jpg':nonce= random.getrandbits(RAND_32_64()) * 2elifoption == b'eps':nonce= random.getrandbits(RAND_32_64()) + 4660elifoption == b'svg':nonce= random.getrandbits(RAND_32_64()) - 61769elifoption == b'pgf':nonce= random.getrandbits(RAND_32_64()) % random.getrandbits(RAND_32_64())elifoption == b'pdf':nonce= random.getrandbits(RAND_32_64()) //random.getrantbits(random.randint(2, 10))else:returnFalseifkey == nonce:returnTrueelse:returnFalsedefloop(self, pipeHandle, data):RAND_0_100= lambda: random.randint(0, 100)pieces= data.split(b'|')option= pieces[1]key= int(pieces[2])ifoption == b'png':ifself.challenge(option, key):x= np.linspace(0, 10, 200)plt.plot(x,np.sin(x))plt.plot(x,np.cos(x))plt.savefig(self.output_prefix+ str(RAND_0_100()) + '.png')returnNoneelifoption == b'jpg':ifself.challenge(option, key):l= [1, 2, 3, 4]plt.plot(l)plt.ylabel('somenumber')plt.savefig(self.output_prefix+ str(RAND_0_100()) + '.jpg')returnNoneelifoption == b'svg':ifself.challenge(option, key):x= ['A', 'B', 'C', 'D']y= [3, 8, 1, 10]plt.bar(x,y)plt.savefig(self.output_prefix+ str(RAND_0_100()) + '.svg')returnNoneelifoption == b'pdf':ifself.challenge(option, key):x= [1, 2, 3, 4, 5]y= [1, 4, 9, 16, 25]plt.figure()plt.plot(x,y)plt.title('Myfirst matplotlib plot')plt.xlabel('X')plt.ylabel('Y')plt.savefig(self.output_prefix+ str(RAND_0_100()) + '.pdf')returnNoneelifoption == b'LICENSE':win32file.WriteFile(pipeHandle,'Please read the LICENSE carefully: n' + self.LICENSE)returnNoneelifoption == b'I give up':pipeHandle.Close()self.stop()returnNoneelse:return0"""

通过反汇编能看出有这段,但是try catch 的位置还没确认怎么放,不过这个影响不大

try:

exceptException as e:servicemanager.LogMsg(servicemanager.EVENTLOG_INFORMATION_TYPE,servicemanager.PYS_SERVICE_STARTED,(self._svc_name_, str(e)))e= Nonedeleelse:e= Nonedele"""defmain(self):whileself.isrunning:pipeName= '\\.\pipe\rwctf'openMode= win32pipe.PIPE_ACCESS_DUPLEX | win32file.FILE_FLAG_OVERLAPPEDpipeMode= win32pipe.PIPE_TYPE_MESSAGEACL= 'D:(A;;GA;;;SY)(A;;GA;;;BA)(A;;GA;;;AU)'sd=win32security.ConvertStringSecurityDescriptorToSecurityDescriptor(ACL,win32security.SDDL_REVISION_1)sa= pywintypes.SECURITY_ATTRIBUTES()sa.SECURITY_DESCRIPTOR= sdpipeHandle= win32pipe.CreateNamedPipe(pipeName,openMode, pipeMode,win32pipe.PIPE_UNLIMITED_INSTANCES,0,0, 6000, sa)#!!! 待检查whileself.isrunning:hr= win32pipe.ConnectNamedPipe(pipeHandle, None)hr,data = win32file.ReadFile(pipeHandle, 256)self.loop(pipeHandle,data)win32pipe.DisconnectNamedPipe(pipeHandle)"""

整个结构应该是包含在while里面的  同样也有一个 try-catch虽然不确定位置但是影响不大。

whileself.isrunning:#...whileself.isrunning:#...returnNonetry:exceptException as details:NULL+ print('Error connecting pipe!', details)pipeHandle.NULL| self + Close()details= Nonedeldetailscontinueelse:details= Nonedeldetails"""if__name__ == "__main__":RWCTFService.parse_command_line()

代码实现了一个根据传入的信息进行matplotlib画图的功能,代码本身的价值不大。由于过于专注于matplotlib的写入部分,导致本人做题时没有去关注服务本身的问题。通过分析代码,基本可以得出通过challenge基本是不可能的。因此不可能通过文件写入的方式进行提权。用到的部分仅仅是“I give up”结束服务的过程中文件夹的删除,以下是相关的分析过程:

03
Pyinstaller 漏洞分析

此时去考虑1day。通过赛后复盘找到了pyinstaller导致的Windows提权CVE:https://github.com/advisories/GHSA-9w2p-rh8c-v9g5

漏洞中的描述如下:

  1. 用户在代码中使用了matplotlib或者win32com

  2. 程序以Administrator运行(或者至少比攻击者权限高)

  3. 用户的临时文件夹不属于特定用户(TMP/TEMP指向一个Public的,不受保护的文件夹)

达成这些条件,攻击者就可以在cpython的is_symlink和rmtree之间将一个文件夹变成symlink。具体分析如下:

(https://github.com/python/cpython/blob/0fb18b02c8ad56299d6a2910be0bab8ad601ef24/Lib/shutil.py#L623)def_rmtree_unsafe(path, onexc):try:withos.scandir(path) as scandir_it:entries= list(scandir_it)except OSErroras err:onexc(os.scandir, path, err)entries = []for entry inentries:fullname =entry.pathtry:is_dir =entry.is_dir(follow_symlinks=False)exceptOSError:is_dir =Falseif is_dirand not entry.is_junction():try:ifentry.is_symlink():# This can only happen if someone replaces# a directory with a symlink after the call to# os.scandir or entry.is_dir above.raise OSError("Cannot call rmtree on a symbolic link")exceptOSError as err:onexc(os.path.islink, fullname, err)continue_rmtree_unsafe(fullname, onexc)else:try:os.unlink(fullname)exceptOSError as err:onexc(os.unlink, fullname, err)try:os.rmdir(path)except OSErroras err:onexc(os.rmdir, path, err)

此函数使用os.scandir来获取文件夹内容,存入一个list中。然后开始检查文件夹/文件。当检查到文件夹,并且不是junction时,为了防止symlink攻击,又检查了一次symlink。之后递归调用此函数来删除临时文件夹。

那么我们可以构造一个文件夹test:

  1. 代码检查test为文件夹,不是junction,并且不是symlink,通过检查。

  2. 代码开始删除test下的文件。

  3. 在删除的过程中,test被替换成symlink。

由于test下的文件已经被list记录,那么在程序删除这个文件之前主动删除掉这个文件,程序还会再次尝试删除这个文件。

这样就可以轻松地绕过symlink检查从而达成一次任意文件删除。

此漏洞描述中的2已经证实。程序在C:WindowsTemp进行临时文件读写,因此3也满足。条件1也是满足的,但是在本题中的作用没有体现出来。需要回到题目中分析这个点。

注意到Temp中除了server.exe,pyinstaller解压文件夹。还有一个以tmp开头的文件夹被最近创建,进入查看可以看到fontlist-v330.json文件,这正是matplotlib创建的临时文件夹。结合pyinstaller漏洞,我们大胆猜测这个服务在结束时会删除这个文件夹。开启procmon验证想法,得证:

技术说|挑战0解薛定谔!RWCTF6th PyGhost——symlink提权

三个条件都得到验证,因此可以进行利用。

但是当以普通权限用户访问此文件夹时会出现权限问题:

技术说|挑战0解薛定谔!RWCTF6th PyGhost——symlink提权

那么漏洞利用就到此为止了吗?其实不然。

打开acl列表查看相关权限,可以得出Users组的权限继承自C:WindowsTemp,而这个目录是一个典型的可写不可读的目录。因为此目录中有许多随机文件名,微软为了避免有恶意程序预测文件名导致攻击,将C:WindowsTemp设置成了可写不可读。因此尝试在server.exe的tmp目录中建立一个工作目录test,在其中布置自己的文件,得证。

技术说|挑战0解薛定谔!RWCTF6th PyGhost——symlink提权

技术说|挑战0解薛定谔!RWCTF6th PyGhost——symlink提权

那么根据我们之前的分析,按照时间顺序列出我们的劫持计划,利用过程就非常清晰了:

  1. 建立tmptest。

  2. 布置大量文件”%d.txt”%(x : 1-998),999(DIR)为竞争做准备

  3. 在1.txt上设置oplock。

  4. 通过pipe与服务交互,使其停止服务,等待临时文件夹删除。

  5. 当oplock触发时,回调函数中直接删除其他所有文件。

  6. 释放oplock,此时tmptest为空,可以将test变为reparse point。

  7. 将tmptest指向RPC Control,将RPC Control999指向待删除文件夹。

  8. 等待目标文件夹被删除。

当oplock触发时,server.exe试图删除test下的所有文件,即使不存在。那么我们之前建立的大量垃圾文件可以趁此获得竞争窗口期。直接删除其他文件后,oplock一旦释放,test就会变成空文件夹,从而使得此文件夹可以替换成symlink。结合前面的删除操作,目标文件夹会被以高权限删除,导致漏洞。

本地测试时可以使用googleprojectzero的symbolic-testing-tools来测试。参考命令:

  1. SetOplock.exe C:WindowsTemptmpapa32hjotest1.txt d

  2. CreateSymlink.exe C:WindowsTemptmpapa32hjotest999 C:Config.Msi

功能分别是:

  1. 给1.txt上oplock,触发时会阻塞相关的进程。

  2. CreateSymlink.exe可以完成整个第7步,将C:WindowsTemptmpapa32hjotest指向RPC Control,将RPC Control999指向C:Config.Msi

Windows Symlink Attack的基本知识可以搜索James Forshaw的slides进行学习。

这样布置之后,在删除到999时,返回结果REPARSE,导向了C:Config.Msi(symlink指向的文件夹),那么C:Config.Msi就会被高权限删除。

那么如何弹一个cmd.exe呢?既然我们只有一个任意文件删除,这里直接用zdi-team的FilesystemEoPs即可。这个程序是通过高权限删除Config.Msi之后开启msi安装服务进行竞争得到提权的,具体细节见zdi-team的文章。由于我们可以控制tmp中的文件类型,因此任意文件/任意文件删除都是可以的。正好契合此提权程序。

04
总结

本题在Windows 11 Pro 23H2 26040.1000 上复现成功,获得了一个SYSTEM权限的cmd.exe。

技术说|挑战0解薛定谔!RWCTF6th PyGhost——symlink提权

Windows的NTFS权限控制不像POSIX一样。在考虑类型、权限、操作三者之前,一个文件会被如何写入/移动/删除,是不确定的。本题中考虑到了很多点,需要选手对Windows系统非常熟悉,并且需要精心布置文件夹结构。笔者在本题中学到了比较多,也希望大家能够有所收获。

END
点击卡片👇  get本期征稿详情

技术说|挑战0解薛定谔!RWCTF6th PyGhost——symlink提权

原文始发于微信公众号(长亭安全观察):技术说|挑战0解薛定谔!RWCTF6th PyGhost——symlink提权

  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2024年3月20日23:48:06
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   技术说|挑战0解薛定谔!RWCTF6th PyGhost——symlink提权https://cn-sec.com/archives/2589239.html

发表评论

匿名网友 填写信息