【发布时间】:2019-05-03 06:50:23
【问题描述】:
仅在 alpine 容器中:当运行启动其他可执行文件 (execve(2)) 的 setuid 二进制文件时,内核[1] BusyBox 似乎放弃了获得的权限通过设置。我认为这可能是出于安全考虑而设计的。
问题:我想了解为什么会发生这种情况以及造成这种情况的原因是什么?
我正在开发一个名为 kamikaze 的一次性 setuid runner,用 rust 编写。 kamikaze 是一个非常简单的二进制文件,由 unlink(2) 本身组成,然后使用 fork(2) 和 execve(2) 启动一个新进程。
主要组件有:
src/main.rs [a47dedc]:实现unlink(2) 和进程生成。
use std::env;
use std::fs;
use std::process::{Command, exit};
fn usage() {
println!("usage: kamikaze <command> <arguments>");
exit(1);
}
fn main() {
// Kill myself
fs::remove_file(
env::current_exe().expect("failed to get path to executable")
).expect("kamikaze failed");
let mut args: Vec<String> = env::args().collect();
match args.len() {
0 => usage(),
1 => usage(),
_ => {
args.remove(0);
let mut child = Command::new(args.remove(0))
.args(&args)
.spawn()
.expect("failed to execute process");
exit(
child
.wait()
.expect("wait failed")
.code().unwrap()
);
},
}
}
install.sh [a47dedc]:一个简单的安装程序,它下载kamikaze,将所有权更改为root 并设置setuid 位。
#!/usr/bin/env sh
set -euo pipefail
REPO="Enteee/kamikaze"
INSTALL="install -m 755 -o root kamikaze-download kamikaze && chmod u+s kamikaze"
curl -s "https://api.github.com/repos/${REPO}/releases/latest" \
| grep "browser_download_url" \
| cut -d '"' -f 4 \
| xargs -n1 curl -s -L --output kamikaze-download
trap 'rm kamikaze-download' EXIT
if [[ $(id -u) -ne 0 ]]; then
sudo sh -c "${INSTALL}"
else
eval "${INSTALL}"
fi
当我在容器外运行kamikaze时[2]:
$ curl https://raw.githubusercontent.com/Enteee/kamikaze/master/install.sh | sh
$ ./kamikaze ps -f
UID PID PPID C STIME TTY TIME CMD
root 3223 9587 0 08:17 pts/0 00:00:00 ./kamikaze ps -f
root 3224 3223 0 08:17 pts/0 00:00:00 ps -f
我得到了预期的行为。子进程 (PID=3224) 以 root 运行。另一方面,在容器内[2]:
$ docker build -t kamikaze - <<EOF
FROM alpine
RUN set -exuo pipefail \
&& apk add curl \
&& curl https://raw.githubusercontent.com/Enteee/kamikaze/master/install.sh | sh
USER nobody
CMD ["/kamikaze", "ps"]
EOF
$ docker run kamikaze
PID USER TIME COMMAND
1 root 0:00 /kamikaze ps
6 nobody 0:00 ps
ps 以nobody 运行。
[1] 我一开始以为这是因为docker和Linux内核实现了一些安全机制。但在深入了解Docker Security、NO_NEW_PRIVILEGES 和seccomp(2) 之后,我终于意识到 BusyBox 只是在放弃特权。
[2]kamikaze [1.0.0] 修复并更改了此行为。因此,此示例不再有效。要重现该示例,请使用 kamikaze [0.0.0] 版本。
【问题讨论】:
-
也许值得一提:我最初认为特权降级发生在
fork(2),但我实现了a version ofkamikazewhich should not fork,我看到了同样的行为。 -
也许man page这部分可以解释:linux.die.net/man/2/execve如果在filename指向的程序文件上设置了set-user-ID位,并且底层文件系统没有挂载nosuid( mount(2)) 的 MS_NOSUID 标志,并且调用进程未被 ptrace,则调用进程的有效用户 ID 更改为程序文件所有者的有效用户 ID。同样,当设置程序文件的set-group-ID位时,调用进程的有效组ID设置为程序文件的组。
-
谢谢。据我了解,手册页的该部分解释了 setuid 的工作原理。这正是我在容器外看到的,但不是在里面。还是我错过了什么?
-
另外可能值得注意:docker 挂载根文件系统没有 nosuid 标志:
$ docker run -ti alpine sh -c 'mount | grep -i "on / "'产生overlay on / type overlay (rw,relatime,lowerdir=/var/lib/docker/overlay2/l/GXDHPBDM7TSYZS5OP43RJVSOWM:/var/lib/docker/overlay2/l/C57DZORPIGMSUEOFG6YYSBNATK,upperdir=/var/lib/docker/overlay2/c9f27caebcbff131d58137067feb75b941b5d7ad4d5677a8140334dce75ad576/diff,workdir=/var/lib/docker/overlay2/c9f27caebcbff131d58137067feb75b941b5d7ad4d5677a8140334dce75ad576/work) -
这里为 setuid 工作指定了 3 个条件。您应该检查这些在常规环境和容器之间是否不同。具体来说,最有可能的是挂载参数。
标签: docker security rust system-calls busybox