Docker公式イメージでたてたWordPressにBurpでActiveScanをかけて、結果に対していくつか対策をしてみた

[シーエー・アドバンス Advent Calendar 2018](https://qiita.com/advent-calendar/2018/ca-advance) 15日目の記事です。

こんにちは。
シーエー・アドバンス技術統括本部の @asami-H-Ishi です。
開発未経験で入社して3年目ですが、2年くらい診断員として業務に携わってきました。
現在は診断チームと開発側の脆弱性診断に関わる調整業務を担当しています。

# はじめに

[6日目の記事](https://qiita.com/asami-H-Ishi/items/defec2363a2574c10886)でDockerの公式イメージを使ってお手軽にWordPressを立ち上げ、WPScanをかけてその結果への対策について検証しました。
そこで更に、脆弱性診断で使っているBurp Suite Professionalの自動診断ツールをかけたらどうなるんだろう、と思い実施してみることにしました。

# 準備するもの（わたしの環境）

- Docker for Mac
- ターミナル
- テキストエディタ（VSCode）
- Burp Suite Professional v1.7.37

# Dockerの公式イメージでWordPressを立ち上げる

1. 下記ymlファイルを作ります。

```yml:docker-compose.yml
version: '3'
services:
  wordpress:
    image: wordpress:5.0.0-apache
    ports: 
      - "8080:80"
    environment:
      WORDPRESS_DB_NAME: wordpress
      WORDPRESS_DB_USER: mysql_user
      WORDPRESS_DB_PASSWORD: mysql_pw
    restart: always
    depends_on:
      - mysql
  mysql:
    image: mysql:5.7
    environment: 
      MYSQL_ROOT_PASSWORD: root_pw
      MYSQL_DATABASE: wordpress
      MYSQL_USER: mysql_user
      MYSQL_PASSWORD: mysql_pw
    restart: always
    volumes:
      - mysql_data:/var/lib/mysql
volumes:
    mysql_data:
```

2. ターミナルでymlファイルを置いているディレクトリまでcdコマンドで移動したら、 `docker-compose up -d` でWordPressを立ち上げます。

3. ブラウザで `localhost:8080` でアクセスしてWordPressのインストールが完了したら、ログインはせずにWordPressの画面を表示します。

4. `ifconfig en0` でWordPressを立ち上げているローカル環境の内部IPを確認します。
`inet` のところのIPがローカル環境の内部IPです。
5. ブラウザでアクセスした `localhost` を　`ifconfig en0` で確認したIPに置き換えてアクセスしましょう。`localhost:8080` と変わらず表示されるはずです。

この状態でActiveScanをかけます。

もしWordPressにログインしてダッシュボードにアクセスしてしまった場合は、ログアウトして **管理者ではない状態** でWordPressの画面にアクセスしてください。

# BurpでActiveScanをかけた結果

最初に、 `localhost:8080` のままだと、Burpで通信がとれませんでした。
ローカルIPに書き換え、更にBurpの `Proxyタブ>Optionsタブ>Match & Replace` でResponse bodyの `localhost:8080` を `(ifconfig en0 で調べたローカルIP):8080` に書き換えるよう設定しないと、クロールしてもHistoryにURLを取得することができません。
Burpで実際に試してみる場合は設定してみてください。

Burpに通信を通しつつ、WordPressのサイト全体をクロールします。

なお、この記事はBurp Suiteの使い方にフォーカスしていないので、Burpの画面とか操作については言及しません。

結果がこちら。  
![ActiveScanの結果](https://qiita-image-store.s3.amazonaws.com/0/148952/29f310b4-d353-7d89-48b8-0988b4bd8d67.png "ActiveScanの結果")

**めっちゃ出た(  Д ) ﾟ ﾟ**

１つずつ結果を確認します。

```
1. Cleartext submission of password
Severity:  High/Confidence:  Certain
```
HTTP通信で暗号化されてない通信だから平文でパスワードを送信できちゃうよ。それはまずいのでSSL化しようね！っていう指摘です。
なので、ちゃんと証明書をとってWeb上に公開しましょう。
**→Web上に公開する場合は真っ先に対応すべき点ですが、今回はローカル環境なのでスルーします**

```
2. Form does not contain an anti-CSRF token
Severity:  High/Confidence:  Tentative
```
これはフォームにCSRFトークンが設定されてませんよ、という指摘なのですが、Issueで検出されたURLごとの詳細を見ると、TOPページの検索フォームと、ログインページのログインフォームを指していました。
どちらもGETリクエストで取得されたレスポンスボディに、CSRFトークンを含まないフォームを検知した、という報告なので、特に脆弱性にはつながりません。
誤検知と判断します。

```
3. Request vulnerable to Cross-site Request Forgery
Severity:  High/Confidence:  Tentative
```
BurpのIssueには、CSRFの説明が下記のとおり記載されています。

> クロスサイトリクエスト偽造（CSRF）は、エンドユーザーが現在認証されているWebアプリケーションで不必要なアクションを強制的に実行する攻撃です。ソーシャルエンジニアリング（電子メール/チャットを介してリンクを送信するなど）の助けを借りて、攻撃者はWebアプリケーションのユーザーを攻撃して、攻撃者が選択したアクションを実行するようにすることができます。 CSRFの脆弱性が悪用されると、エンドユーザーのデータが侵害され、攻撃者がアカウントのハイジャックを実行する可能性があります。ターゲットエンドユーザーが管理者アカウントの場合、Webアプリケーション全体が危険にさらされる可能性があります。

検出されたURLは、WordPressのコメントを送信する機能でした。
BurpのIssueにはこの問題の修復について下記の記載があります。

> アプリケーションは、アプリケーションの状態を変更したり、コンテンツの追加/変更/削除を行うアクションを実行するすべての要求にanti-CSRFトークンを実装する必要があります。反CSRFトークンは、攻撃者が簡単にブルートフォース攻撃を行えないように、各ユーザー固有の長いランダムな値でなければなりません。ユーザーの要求がアプリケーションによって処理されるとき、CSRFトークンが検証されることが重要です。アプリケーションは、要求にトークンが存在することを確認し、ユーザーの現在のトークンと一致することを確認する必要があります。これらのチェックのいずれかが失敗した場合、アプリケーションは要求を拒否する必要があります。

ただ、この機能はWordPressそのもののデフォルト機能なので、下記2通りの対策が考えられます。

- （今作っているWordPressで作成するサイトにコメント機能が必要ないなら）コメント機能そのものをダッシュボードから操作して削除する
- nonceをつける

今回はちょっとわたしの検証時間が足りないため、サイト全体でコメントを受け付けないようにするプラグインを導入することにします。
手順等については下記URLを参照してください。
[ストレスの原因、ネガティブコメントやスパムを「Disable Comments」プラグインで無効化する](https://www.vektor-inc.co.jp/post/disable-comments/)

```
4. Password field with autocomplete enabled
Severity:  Low/Confidence:  Certain
```
[徳丸さんの「セキュリティの都市伝説を暴く」というスライドの42pページあたり](https://www.slideshare.net/ockeghem/uxvssec20170609)から触れられていますが、オートコンプリートは無効にできなくなっているので特に対策しません。

```
5. Unencrypted communications
Severity:  Low/Confidence:  Certain
```
1番と同じ指摘です

```
6. Browser cross-site scripting filter misconfiguration
Severity:  Low/Confidence:  Certain
```
ブラウザのXSSフィルターの誤設定があるよ、という指摘です。
`X-XSS-Protection: 1; mode=block` ヘッダの設定をします。

```
7. Content Sniffing not disabled
Severity:  Low/Confidence:  Certain
```
BurpのIssueに記載されている説明によると
> このヘッダーがないと、特定のブラウザーは、これらのプロパティーが正しく定義されている場合でも、応答のコンテンツ・タイプとエンコーディングを判別しようとします。 これにより、Webアプリケーションはクロスサイトスクリプティング（XSS）攻撃に対して脆弱になる可能性があります。

```
ということなので、 `X-Content-Type-Options: nosniff` ヘッダの設定をします。

```
8. Detailed Error Messages Revealed
Severity:  Low/Confidence:  Certain
```
エラーメッセージの詳細が表示されてしまっているよ、という指摘です。
エラーメッセージの詳細は攻撃者へのヒントになりえるので隠したいところです。
が、確認したところ誤検知のようだったのでスルーします。

```
9. Client-side HTTP parameter pollution (reflected) 
Severity:  Low/Confidence:  Firm
```
これは誤検知で大丈夫でした。(リンク先が外部のサイトになっちゃってたり、XSSが入るなどの状態ではありませんでした)

```
10. Cross-domain POST
Severity:  Information/Confidence:  Certain
```
これは `localhost` と　`ローカルIP` でドメインが違うっていう指摘なので誤検知で良さそうです。

```
11. Input returned in response (reflected)
Severity:  Information/Confidence:  Certain
```
全Issueをチェックした結果、特にリンクやスクリプトで影響が出る要素が見当たらなかったので誤検知の判断で大丈夫です。

```
12. Cross-domain Referer leakage
Severity:  Information/Confidence:  Certain
```
Issueで検出されたドメインを一つずつ確認します。
WordPressを利用しているので、WordPress公式や、使用する外部サービスのドメインなど、把握しているものであれば特に気にせずそのままで大丈夫です。
気になるものがあればGoogle検索して、必要か必要でないかを判断して削除します。

```
13. Cross-domain script include
Severity:  Information/Confidence:  Certain
```
10番に同じです。

```
14. Cookie without HttpOnly flag set
Severity:  Information/Confidence:  Certain
```
クッキーにHttpOnlyフラグをつけていない場合に上がるIssueです。
サイトにXSSが存在した場合、セッションCookieを窃取されてしまうため、セッションCookieにはHttpOnlyflagをセットしたいところです。

```
15. Frameable response (potential Clickjacking)
Severity:  Information/Confidence:  Firm
```
X-Frame-Optionヘッダの設定が必要です。WordPressの機能にはSAMEORIGINで設定されているので、同様にサイト全体に対して設定します。

# 対応する内容について

以上の結果から、今回対応したい内容は

- レスポンスヘッダに下記設定を入れたい

```
X-XSS-Protection: 1; mode=block
X-Content-Type-Options: nosniff
X-Frame-Option: SAMEORIGIN
```
ヘッダ系の指摘は脆弱性診断をしていて必ず出てくるものなので、WordPressをカスタマイズする過程で先んじて対応しておきたいです。
設定は、WPScanの時と同様、Dockerfileに書いておいて、WordPressを立ち上げる時にその設定で立ち上がるようにします。
下記が設定全部載せのDockerfileとｙｍｌファイルです。
ご参考になれば幸いです。

```docker:dockerfile
FROM wordpress:5.0.0-apache

# phpのバージョン情報を隠せた
RUN mv "$PHP_INI_DIR/php.ini-production" "$PHP_INI_DIR/php.ini"
RUN sed -i -e "s|expose_php = On|expose_php = Off|" "$PHP_INI_DIR/php.ini"

# apacheのバージョンを隠す
RUN echo "ServerSignature Off" >> /etc/apache2/apache2.conf
RUN echo "ServerTokens Prod" >> /etc/apache2/apache2.conf

# 不要ファイルの削除
RUN rm -rf /usr/src/wordpress/readme.html /usr/src/wordpress/xmlrpc.php

# meta
RUN bash -c 'echo -e "remove_action(\"wp_head\",\"wp_generator\");" >> /usr/src/wordpress/wp-content/themes/twentynineteen/functions.php'
RUN bash -c 'echo -e "foreach ( array( \"rss2_head\", \"commentsrss2_head\", \"rss_head\", \"rdf_header\",    \"atom_head\", \"comments_atom_head\", \"opml_head\", \"app_head\" ) as \$action ) {    if ( has_action( \$action, \"the_generator\" ) )        remove_action( \$action, \"the_generator\" );}" >> /usr/src/wordpress/wp-content/themes/twentynineteen/functions.php'

# バージョン消す
RUN bash -c 'echo -e "function vc_remove_wp_ver_css_js(\$src){if(strpos(\$src,\"ver=\".get_bloginfo(\"version\")))\$src=remove_query_arg(\"ver\",\$src);return \$src;}" >> /usr/src/wordpress/wp-includes/functions.php'
RUN bash -c 'echo -e "add_filter(\"style_loader_src\", \"vc_remove_wp_ver_css_js\", 9999);" >> /usr/src/wordpress/wp-includes/functions.php'
RUN bash -c 'echo -e "add_filter(\"script_loader_src\", \"vc_remove_wp_ver_css_js\", 9999);" >> /usr/src/wordpress/wp-includes/functions.php'

# ヘッダの設定をする
RUN bash -c 'echo "Header append X-XSS-Protection: \"1; mode=block\"" >> /etc/apache2/apache2.conf'
RUN bash -c 'echo "Header append X-Content-Type-Options: nosniff" >> /etc/apache2/apache2.conf'
RUN bash -c 'echo "Header append X-Frame-Options: SAMEORIGIN" >> /etc/apache2/apache2.conf'
RUN bash -c 'a2enmod headers'
```

```yml:docker-compose.yml
version: '3'
services:
  wordpress:
    build: .
    ports: 
      - "3000:80"
    environment:
      WORDPRESS_DB_NAME: wordpress
      WORDPRESS_DB_USER: mysql_user
      WORDPRESS_DB_PASSWORD: mysql_pw
    restart: always
    depends_on:
      - mysql
  mysql:
    image: mysql:5.7
    environment: 
      MYSQL_ROOT_PASSWORD: root_pw
      MYSQL_DATABASE: wordpress
      MYSQL_USER: mysql_user
      MYSQL_PASSWORD: mysql_pw
    restart: always
    volumes:
      - mysql_data:/var/lib/mysql
volumes:
    mysql_data:
```

なにか「もっとこうした方がいいよ」「もっとここは対策したほうがいいよ」などありましたら教えていただけると助かります！！！！

# Special Thanks

- [ストレスの原因、ネガティブコメントやスパムを「Disable Comments」プラグインで無効化する](https://www.vektor-inc.co.jp/post/disable-comments/)
- [徳丸さんの「セキュリティの都市伝説を暴く」というスライドの42pページあたり](https://www.slideshare.net/ockeghem/uxvssec20170609)
- @togana さん（検証のときにDockerがわからないわたしを助けてくれた同僚）
- @328 さん（同じく検証のときにDockerがわからないわたしを助けてくれた勉強会仲間）

# さいごに

6日目の記事で対応が間に合わなかったものについて、じつはしれっとDockerfileに書いてあります。
一応アドベントカレンダーの記事内で対応を残せてよかったです（開発研修受けてた頃の自分がわかるように書く、という目標は途中で箱にしまって片付けました）。
ただ、普段さわらないDockerでの検証に取り組んだのは、開発経験値の低いわたしにはハードルが高かったようで、Special Thanksなお二人には大変お世話になりました。
時間もオーバーして遅刻更新になってしまいましたが、とてもいい機会を得られました。
Dockerともちょっと仲良くなれた気がするし、WordPressに抱いていた苦手意識はだいぶなくなりました。
よいクリスマス、よい新年をお迎えください。