Docker公式イメージで立てたWordpressにOWASP ZAPで動的スキャンをかけて対応してみた

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

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

# はじめに

弊社では脆弱性診断の際にプロキシツールとして[Port Swigger社](https://portswigger.net/)のBurp Suite Professionalを使用しています。
そこで、昨年のアドベントカレンダーでは[Docker公式イメージでたてたWordPressにBurpでActiveScanをかけて、結果に対していくつか対策をしてみた](https://qiita.com/asami-H-Ishi/items/4566b3c72c97e4d8c512)という記事を公開しました。

脆弱性診断で使われるプロキシツールには、Burpの他に[OWASP ZAP](https://www.owasp.org/index.php/OWASP_Zed_Attack_Proxy_Project)があります。
こちらは通常の診断業務では使用したことがないのですが、無料のツールで日本語対応していることもあり、業務外でいつか触ってみたいなと思っていました。

そこで昨年やったことをZAPでやってみようと思いました！

# この記事の概要

1. Docker公式イメージでWordpressを立てる
2. OWASP ZAPで対象サイトをクロール
3. OWASP ZAPで動的スキャン実行
4. OWASP ZAPの「アラート」を確認、対応すべき対象を確認
5. 対策したymlファイルを公開

予想ですが、Burpとほぼ変わらない結果が出るはずなので、ZAPの扱い方のメモみたいになるかと思います。。。
よろしければ最後までお付き合いください。

# 1.Docker公式イメージでWordpressを立てる
[Docker公式のWordpressイメージ](https://hub.docker.com/_/wordpress)から、下記ymlファイルを作成します。
（去年とちょっと変わってますね）

```docker-compose.yml
version: '3.1'

services:

  wordpress:
    image: wordpress
    restart: always
    ports:
      - 8080:80
    environment:
      WORDPRESS_DB_HOST: db
      WORDPRESS_DB_USER: exampleuser
      WORDPRESS_DB_PASSWORD: examplepass
      WORDPRESS_DB_NAME: exampledb
    volumes:
      - wordpress:/var/www/html

  db:
    image: mysql:5.7
    restart: always
    environment:
      MYSQL_DATABASE: exampledb
      MYSQL_USER: exampleuser
      MYSQL_PASSWORD: examplepass
      MYSQL_RANDOM_ROOT_PASSWORD: '1'
    volumes:
      - db:/var/lib/mysql

volumes:
  wordpress:
  db:
```

ターミナルで下記コマンドを叩きます（バックグラウンドで実行されます）。

```
docker-compose up -d
```

ブラウザで下記にアクセスします。

```
http://localhost:8080
```

すると、WordPressのインストール画面が表示されます。
WordPressのインストールとログインが完了すると、WordPressのダッシュボードに入ることができます。
せっかくなので、ダッシュボードから何か新規投稿して見てみましょう。
管理者としてログインしている状況になるので、ログアウトしてあらためて `http://localhost:8080` にアクセスしてみてください。
<img width="637" alt="スクリーンショット 2019-11-30 16.11.55.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/148952/aeb9b908-0875-4c22-6287-9cc59242fa2e.png">
こんな感じで表示されてるかと思います（サイト名を適当に「AAA」にしたのでこんなんなってます）。

ここまでで、Docker公式イメージでWordpressを立てるところまで終わりました。

# 2. OWASP ZAPで対象サイトをクロール

次に、OWASP ZAPを立ち上げます。
<img width="972" alt="スクリーンショット 2019-11-30 17.00.44.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/148952/8386454b-7628-ed72-81e5-bfe7594b747b.png">
こんな感じ。

OWASP ZAPのインストールやプロキシ設定等は[Mac版のOWASP ZAPで脆弱性チェックの設定](https://doruby.jp/users/r357_on_rails/entries/Mac_OWASP_ZAP)が参考になります。

わたしは通常業務で下記の設定をしているので、そこに合わせてZAPとブラウザの設定をしました。

Chromeでの設定
<img width="931" alt="スクリーンショット 2019-12-04 10.09.54.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/148952/4b13414d-5cb2-83bf-28ae-5edb051b8241.png">


ZAPでのプロキシ設定の手順
<img width="1124" alt="スクリーンショット 2019-12-04 10.01.44.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/148952/52cfd9d6-0573-35ce-39b0-525d027b16b6.png">
。

プロキシ設定を済ませたブラウザから `http://localhost:8080` にアクセスしようとすると、なぜかサイト一覧に表示されなかったので（ここの調査は時間ないのでしませんでした！）、ifconfigでローカルIPアドレスを調べて、`Wordpress管理画面の設定＞一般`で`WordPress アドレス (URL)`と`サイトアドレス (URL)`の`localhost`部分をローカルIPアドレスに書き換えました。
そうすると、ブラウザから`http://<ローカルIPアドレス>:8080`でアクセスし、サイト内の遷移をした時にすべてローカルIPアドレスでURLを取得することができます。

さて、気を取り直して、プロキシ設定を済ませたブラウザから`http://<ローカルIPアドレス>:8080`でアクセスすると、ZAPのサイトタブ一覧にURLが表示される＝ちゃんとZAPで通信がとれていることを確認します。
そのまま、サイトをクロールします。
すべてのページにアクセスし終わったら、クロール完了です。
（ZAPのスパイダー機能で済ませようかと思いましたが、Wordpressの`?p=1`みたいなURLはスパイダーでは取得できないようなので、通常の脆弱性診断と同様クロールする形にしました）

この後、今クロールした診断対象URLたちに動的スキャンを実施したいのですが、Wordpress公式のページや、gravatarのページ（いずれも本番サイト）への遷移もあったので、動的スキャンの対象をローカルで立てたWordpressのみに絞る必要があります。
そこで、サイト一覧の `http://<ローカルIPアドレス>:8080` を右クリックして`コンテキストに含める＞New Context`を押します（画像のとおり）。
<img width="645" alt="スクリーンショット 2019-11-30 17.23.49.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/148952/f2c2b526-c6dc-6e25-8c0b-5c0af7ad7b96.png">

セッション・プロパティ画面が出てくるので、「OK」を押すと、対象のドメインをコンテキストに追加することができます。

コンテキスト一覧に `http://<ローカルIPアドレス>:8080` が含まれていることを確認したら、動的スキャンに進みます。

# 3.OWASP ZAPで動的スキャン実行

OWASP ZAPの下半分に「履歴」「検索」「アラート」「アウトプット」と並び、右端に緑の十字ボタンがあります。
そこを押すと隠れている機能があるので、そこから`動的スキャン`を押します。
<img width="975" alt="スクリーンショット 2019-11-30 17.56.02.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/148952/b8166b67-a994-6544-b5b6-b1d786a2749f.png">

①「新規スキャン」ボタンを押す
②動的スキャンのスコープ画面が出てくるので、「参照」ボタンを押してノード選択
③コンテキストに設定した対象ドメインを選択
④選択ボタンを押す
⑤「スキャンを開始」ボタンを押す
<img width="1291" alt="スクリーンショット 2019-12-04 19.01.23.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/148952/a3d83856-cca8-5529-7e90-f13e0e7f6ca4.png">

これで動的スキャンを実施できます。
結果は「アラート」タブで確認できます。

# 4. OWASP ZAPの「アラート」を確認、対応すべき対象を確認

今回の結果はこんな感じでした。
<img width="1281" alt="スクリーンショット 2019-12-04 20.07.57.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/148952/6dcd0d4c-863b-9df7-df89-7a526ca640fa.png">

ひとつずつ見ていきます。

### X-Frame-Optionsヘッダーの欠如
説明部分には

> 「クリックジャッキング」攻撃を防止するためのX-Frame-OptionsヘッダーがHTTPレスポンスに含まれていません。

と書かれています。
これに対応するにはX-Frame-Optionヘッダの設定が必要です。
WordPressの機能にはSAMEORIGINで設定されているので、同様にサイト全体に対して設定します。

### CookieのHttpOnly属性が未設定
説明部分には

> cookieにHttpOnly属性が設定されていないため、JavaScriptによるcookieへのアクセスが可能です。
悪意のあるスクリプトが本ページで実行可能な場合、cookieにアクセスして別のサイトへcookieを送信することができます。
もしスクリプトがアクセス可能なcookieがセッション管理用である場合、セッションハイジャック攻撃が成立する可能性があります。

と書かれていました。
ただ、今回のWordpressに関しては管理画面を利用するユーザ以外にログイン機能を利用する想定ではないため、対応外とします。

### WebブラウザのXSS防止機能が有効になっていません。
説明部分には

> Web ブラウザのXSS防止機能が有効になっていない、またはWebサーバからのHTTPレスポンスヘッダ 'X-XSS-Protection' が無効になっています。

と書かれていました。
これは対応しておく必要があります。

### X-Content-Type-Optionsヘッダの設定ミス

説明がなぜかこれだけ英語でしたｗ

> The Anti-MIME-Sniffing header X-Content-Type-Options was not set to 'nosniff'. This allows older versions of Internet Explorer and Chrome to perform MIME-sniffing on the response body, potentially causing the response body to be interpreted and displayed as a content type other than the declared content type. Current (early 2014) and legacy versions of Firefox will use the declared content type (if one is set), rather than performing MIME-sniffing.

X−Content−Type−Optionsヘッダの設定をnosniff　にしましょう、という内容になります（ざっくり）。

上記はいずれも、[Docker公式イメージでたてたWordPressにBurpでActiveScanをかけて、結果に対していくつか対策をしてみた](https://qiita.com/asami-H-Ishi/items/4566b3c72c97e4d8c512)で対応した内容になりますので、よかったらこちらの記事も見てみてください！

# 5. 対策したymlファイルを公開

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

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

```
X-XSS-Protection: 1; mode=block
X-Content-Type-Options: nosniff
X-Frame-Option: SAMEORIGIN
```

ヘッダ系の指摘は脆弱性診断をしていて必ず出てくるものなので、WordPressをカスタマイズする過程で先んじて対応しておきたいです。
設定は、Dockerfileに書いておいて、WordPressを立ち上げる時にその設定で立ち上がるようにします。
下記が設定全部載せのDockerfileとymlファイルです。
ご参考になれば幸いです。

```dockerfile
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'
```

```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:
```

# さいごに

検証の時間が足りず、去年の対応と全く同じ形になってしまいましたが、ご参考になれば幸いです。
また、もしかすると最新のWordpressではもっといい設定があるかもしれないので、何かご存知の方がいらしたらご教示いただけると嬉しいです！（時間に余裕があれば、クリスマスまでに自分でも検証してこっそり追記したいと思います）

よいクリスマス、よい年末年始をお過ごしください！