13
5

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

シーエー・アドバンスAdvent Calendar 2018

Day 15

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

Posted at

シーエー・アドバンス Advent Calendar 2018 15日目の記事です。

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

はじめに

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

準備するもの(わたしの環境)

  • Docker for Mac
  • ターミナル
  • テキストエディタ(VSCode)
  • Burp Suite Professional v1.7.37

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

  1. 下記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:
  1. ターミナルでymlファイルを置いているディレクトリまでcdコマンドで移動したら、 docker-compose up -d でWordPressを立ち上げます。

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

  3. ifconfig en0 でWordPressを立ち上げているローカル環境の内部IPを確認します。
    inet のところのIPがローカル環境の内部IPです。

  4. ブラウザでアクセスした 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の結果

めっちゃ出た( Д ) ゚ ゚

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

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」プラグインで無効化する

4. Password field with autocomplete enabled
Severity:  Low/Confidence:  Certain

徳丸さんの「セキュリティの都市伝説を暴く」というスライドの42pページあたりから触れられていますが、オートコンプリートは無効にできなくなっているので特に対策しません。

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` ヘッダの設定をします。

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

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

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

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

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

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

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

  1. 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とymlファイルです。
ご参考になれば幸いです。

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

さいごに

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

13
5
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
13
5

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?