Docker Quiz を解いてみる

## Docker Quiz とは

<blockquote class="twitter-tweet"><p lang="ja" dir="ltr">二時間目コンテナの講義ではDocker Quizが出題。君は、すべて解くことができるか？<br>$ docker run --rm -i hatena/intern-2020-docker-quiz <a href="https://twitter.com/hashtag/hatenaintern2020?src=hash&amp;ref_src=twsrc%5Etfw">#hatenaintern2020</a></p>&mdash; hatenatech＠はてなの技術情報アカウント (@hatenatech) <a href="https://twitter.com/hatenatech/status/1302845698732249089?ref_src=twsrc%5Etfw">September 7, 2020</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>

まだ挑戦したことがない方は是非この記事を読む前に挑戦してみてください。Docker の知識があまりなくても、調べれば回答できる問題が多いです。

後に講義の動画もアップされるようです。

## 解いてみる

### Q1 [sanity check] 次の文字列を入力してください: FLAG_3u8z4xst

sanity check です。`FLAG_3u8z4xst` と入力します。

### Q2 -q2 オプション引数を渡して起動せよ

`docker run --rm hatena/intern-2020-docker-quiz -q2` のようにオプション引数付きで起動すると答えが表示されます。

ちなみに `-hint` オプションをつけて起動すると、各問題のヒントを見ることができます。

### Q3 このイメージのentrypointとして指定されているコマンドのフルパスは何か

`docker inspect hatena/intern-2020-docker-quiz` でこのイメージの情報を見ることができます。その中に entrypoint が記載されているので、入力します。

### Q4 /app/flag.txt をホストにコピーしてファイルの内容を取得せよ

`docker cp <コンテナ名 or コンテナ ID>:/app/flag.txt ./flag.txt` のようにしてホストにコピーできます。
コンテナ名は `docker ps` などから確認できます。

### Q5 シェルを起動して /app/get_flag2.exe を実行せよ

`docker run --rm --entrypoint /app/get_flag2.exe hatena/intern-2020-docker-quiz` のようにして entrypoint を指定して実行します。

### Q6 このイメージに含まれるpythonについて、Trivyによって検出されるseverityがHIGHの脆弱性のCVE番号を答えよ

[Trivy](https://github.com/aquasecurity/trivy) というコンテナの脆弱性スキャナーを使います。

下記のコマンドで簡単にスキャンできます（macOS）。

`docker run --rm -v /var/run/docker.sock:/var/run/docker.sock -v $HOME/Library/Caches:/root/.cache/ aquasec/trivy image hatena/intern-2020-docker-quiz`

スキャンが完了すると脆弱性のあるパッケージの一覧が表示されるので、python パッケージの中から severity（重症度）が HIGH の CVE 番号（共通脆弱性識別子）を探して入力する...

...はずですが、python で脆弱性が HIGH の CVE 番号は見つかりませんでした。原因は分かりませんでしたが、表示された python の CVE 番号を片っ端から入力していったら正解することができました。何かご存知の方がいましたら教えていただけるとありがたいです。

（補足）オプションに `--severity HIGH` を指定すると severity が HIGH のものだけを表示できます。

`docker run --rm -v /var/run/docker.sock:/var/run/docker.sock -v $HOME/Library/Caches:/root/.cache/ aquasec/trivy image --severity HIGH hatena/intern-2020-docker-quiz`

### Q7 このイメージのentrypointとして指定されているコマンドのソースコードを復元して、この問題の答えを取得せよ

`docker history hatena/intern-2020-docker-quiz` でこのイメージの履歴を表示することができます。

truncate されている & 見づらいので、`docker history --no-trunc --format "{{.CreatedAt}}: {{.CreatedBy}}" hatena/intern-2020-docker-quiz` のようにして整形して出力します。

履歴を見てみると、下記の 2 ファイルがコンテナにコピーされていることが分かります。

- `docker_quiz.go.enc`（暗号化されたソースコードっぽい）
- `password__delete_me_after_decrypting`（復号に使うパスワードが書かれたファイルっぽい）

残念ながら、 `password__delete_me_after_decrypting` は復号に使われたあと削除されています。

`docker save　hatena/intern-2020-docker-quiz > docker-quiz.tar` で、イメージを tar ファイルとして出力することができます。tar の中身はレイヤの集合になっています。その中から前述の 2 ファイルがコピーされたときのレイヤを探し出せば復元することができそうです。

tar ファイルを展開し、2 つのファイルがコピーされたときっぽいレイヤの layer.tar を見つけ出し、展開します。`tar tvf path/to/layer.tar` で　tar ファイルの中身を表示したり、ファイルサイズから推測したりすると比較的容易に見つけられます。

2 つのファイルを回収することができたら、前述の `docker history` で見つかった履歴と同じコマンドでソースコードを復号します。

```bash
openssl enc -d -aes-256-cbc -pbkdf2 -in docker_quiz.go.enc -out docker_quiz.go -pass file:password__delete_me_after_decrypting
```

ソースコードを見てみると、Q7 は入力した答えのバイト列のそれぞれの値に `0xff` との排他的論理和をとった値が、`[0xb9, 0xb3, 0xbe, 0xb8, 0xa0, 0xce, 0xc9, 0x8a, 0xca, 0x95, 0x88, 0x9d, 0x8d]` と一致するかどうかを判定しているのが分かります。この答えは逆算することができます。下記は JavaScript で逆算する例です。

```js
[0xb9, 0xb3, 0xbe, 0xb8, 0xa0, 0xce, 0xc9, 0x8a, 0xca, 0x95, 0x88, 0x9d, 0x8d].map((b) => String.fromCharCode(b ^ 0xff)).join('');
```

手に入った文字列が答えになります。

#### 失敗した方法

`/app/docker_quiz` をホストにコピーし、 `objdump -d ./docker_quiz` で逆アセンブルをしてみたけど何も分からなかった。

## 感想

Docker は普段から使っていましたが知らないことも多く、勉強になりました。楽しかったです。