Django製の Mezzanine で構築されたブログに対して、脆弱性を検証してみる


最近 OWASP ZAP を使用する機会がありましたので、
自ブログにも、OWASP ZAP を使い脆弱性を検証してみたいと思います。


OWASP ZAP の 参考記事

OWASP ZAP については、以下のサイトが参考になりました。


前提

環境情報

ブログは以下の環境で動作しています。

  • OS
    CentOS release 6.7 (Final)

  • Python Version
    Python 2.7.8

  • Package (必要そうなものだけ抜粋)
    Django (1.10)
    Mezzanine (4.2.0)

検証の内容

  • OWASP ZAP は デフォルト設定で、プロキシ設定なしの、標準モードで実行
    インストールして起動すると表示されるクイックスタートに、
    ブログのドメインを入力するだけです。
    OWASP ZAP クイックスタート

  • Mezzanine は、公開画面 ドメイン指定での検証と、 管理ログイン画面の検証 を行う 1 [1] ブログ投稿画面は、ゴミ投稿データが大量にできそうなので見送りました。


ドメイン直下指定で、検証した結果

以下の警告が出力されました。
ドメイン直下指定だと、sitemap.xml 記載のページから、
各記事、検索ページなども辿って検証しているらしく、検証結果出力に1-2時間程度かかりました。
また、負荷もそれなりにかかるかと思いますので、自身管理下のサイト以外は、実行しないことをお勧め致します。

  • X-Frame-Options ヘッダの欠如

  • アプリケーションエラーの開示

  • Cross-Domain JavaScript Source File Inclusion

  • WebブラウザのXSS防止機能が有効になっていません。

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

  • Incomplete or No Cache-control and Pragma HTTP Header Set

  • Secure Pages Include Mixed Content


管理画面指定で、検証した結果

以下の警告が出力されました。 2 [2] どうも、OWASP ZAPは、管理画面を URL 指定した際、sitemap.xmlから、各記事ページの URL を拾っているらしく、記事ページの警告も以下には含まれています。 ただ、検証対象にしているのは、管理画面のみだったので、こちらはあまり実施に時間を要しませんでした。

  • X-Frame-Options ヘッダーの欠如

  • アプリケーションエラーの開示

  • Cross-Domain JavaScript Source File Inclusion

  • Web ブラウザの XSS 防止機能が有効になっていません。

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

  • Incomplete or No Cache-control and Pragma HTTP Header Set

  • Cookie No HttpOnly Flag

  • Cookie Without Secure Flag

  • Password Autocomplete in Browser


各警告に対する対処

警告の説明は以下の記事がわかりやすいです。
OWASP ZAP スキャンポリシーの検査項目一覧(Release版) - yukisovのメモ帳

X-Frame-Options ヘッダーの欠如

対処しました。
クリックジャッキング対策ができてないという警告です。
Clickjacking Protection | Django documentation | Django に記載がありますが、
Django.middleware.clickjacking.XFrameOptionsMiddleware の記載はあります。 3 static な js,css で出力されているので、Apacheにも、静的ファイルを対象にヘッダを追加する設定を行いました。

<Directory /var/static>
    Header set X-Frame-Options SAMEORIGIN
</Directory>

[3] mezzanine-project で 雛形を作ると、デフォルトでXFrameOptionsMiddlewareは記載されています。

アプリケーションエラーの開示

対処しませんでした。
対象ページは404ではなく、ブログ記事中にエラーを示す文言を記載してしまっていたため、
検出されていましたので、問題はないと判断しました。

Cross-Domain JavaScript Source File Inclusion

対処しませんでした。 外部ドメインのjavascriptの読み込みがあるページで発生していました。
はてなブックマークのjs など明示的に読み込んでいるもの達でしたので、許容しました。

WebブラウザのXSS防止機能が有効になっていません。 と、 X-Content-Type-Optionsヘッダの設定ミス

対処しました。 Django では、SecurityMiddleware というミドルウェアで対処できるので、settings.pyに追加します。 4 * settings.py

MIDDLEWARE_CLASSES = (
    ...
    "Django.middleware.security.SecurityMiddleware", 
    ...
)
SECURE_CONTENT_TYPE_NOSNIFF = True
SECURE_BROWSER_XSS_FILTER = True
設定値の内容は以下記事が参考になりました。
Django 1.8で追加されたSecurityMiddlewareについて - 偏った言語信者の垂れ流し Apache にも静的ファイルを対象にヘッダを追加する設定を行いました。

<Directory /var/static>
    Header set X-XSS-Protection "1; mode=block"
    Header set X-Content-Type-Options "nosniff"
</Directory>

[4] mezzanine-project で 雛形を作ると、デフォルトでSecurityMiddlewareは記載されません。必要であれば追加したほうがいいかもしれません。

Incomplete or No Cache-control and Pragma HTTP Header Set

対処しました。 レスポンスヘッダに no-cache 等がない場合、レスポンスヘッダのPRAGMAに、”no-cache” がない場合、発生するとのことなので、
設定します。

Django post request data caching - Stack Overflow と、 Fighting client-side caching in Django - Stack Overflow を参考にNoCacheMiddleware を作成して、settings.py に追記しました。
Django.utils.cache.add_never_cache_headers だと、privateがつかず、QWASP ZAP の警告が消えなかったので、
Django.utils.cache.patch_cache_control で、設定を行うようにしました。

  • NoCacheMiddleware.py
    from Django.utils.cache import patch_cache_control
    
    class NoCacheMiddleware(object):
    
        def process_response(self, request, response):
            response['Pragma'] = 'no-cache'
            patch_cache_control(response, no_cache=True, no_store=True, must_revalidate=True, private=True)
            return response
    
  • settings.py XFrameOptionsMiddleware の直下あたりに記載しました。
    MIDDLEWARE_CLASSES = (
        ....
        "Django.middleware.clickjacking.XFrameOptionsMiddleware",
        "mezzanine_extentions.middleware.NoCacheMiddleware",
        ....
    )
    
    static な js,css で出力されているので、Apacheにも、静的ファイルを対象にヘッダを追加する設定を行いました。
        <Directory /var/www/site/blog/static>
            Header append Cache-Control "private"                                                                                                                   
        </Directory>
    

Secure Pages Include Mixed Content

対処しませんでした。
SSL の際にリンク先が、http の場合発生します。
数が多いのと、リンク先が、https に対応していない場合があったりするためです。

ログイン画面で出力された警告です。
対処しました。
crsf token に http only 属性がついていないために警告が出ていたので、
settings.py に CSRF_COOKIE_HTTPONLY = True を追加しました。
Mezzanine では mezzanine/core/middleware.py でのみ、crsf token を使っていましたが、
plugin 等で、Javascriptからアクセスしている可能性はあるので、もしかしたらエラーとなるかもしれません。

ログイン画面で出力された警告です。
対処しました。
crsf token に secure 属性がついていないために警告が出ていたので、
settings.py に CSRF_COOKIE_SECURE = True を追加しました。5
[5] デフォルト値がFalseです。

Password Autocomplete in Browser

ログイン画面で出力された警告です。
対処しませんでした。
調べていると、ただ、autocomplete=off つければ、いいというものでもなさそうに思いました。
個人的には、以下 2つの理由で対処しませんでしたが、
サイトによっては、使用するべき場合はあるかもしれません。

  • ブラウザによっては効果がないことも多い。

  • そもそも個人ブログなので、管理画面でログインしようとする人はいるかもしれないが、基本自分一人。
    且つ、autocomplete されるのは、自宅PCなので、あまりリスクにならない。

ブラウザのパスワード保存と自動フィルイン - Cybozu Inside Out | サイボウズエンジニアのブログ input type = password autocomplete = off は使ってはいけない - aiaru’s blog


再実施

設定変更後、 OWASP ZAP を再実行したところ、
トータルの警告は以下のようになりました。
修正対象外、許容した警告と、
どうもまだ、静的ファイルの設定に取りこぼしがあり、一部のjs、css等で警告が出力されていますが、 ファイル総数としては、だいぶ減らすことができました。 6
[6] HTTP サーバ側の設定が、手抜き設定になっているのが原因かと思います。そのうち機会があれば、見直してみようかと

  • アプリケーションエラーの開示

  • Cross-Domain JavaScript Source File Inclusion

  • Password Autocomplete in Browser

  • Secure Pages Include Mixed Content

  • Incomplete or No Cache-control and Pragma HTTP Header Set

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


何の役に立つかはわかりませんが、Mezzanine のcookie について
調べていましたので、記載しておきます。
管理画面を表示する、ログインすると、以下の cookie 値が発行されます。

  • __utma
    Google Analytics の cookie 値です。
    サイトに導入しているので、存在しています。
    ログイン画面自体は、計測対象にはなっていない。 また、当たり前だと思いますが、メタタグで、 <meta name="robots" content="NONE,NOARCHIVE" /> が設定されています。
    管理画面で発行されるわけではないですが、いるので記載。

  • csrftoken 'Django.middleware.csrf.CsrfViewMiddleware' の発行する cookie です。
    CSRF_COOKIE_NAME で名称は変えることができます。
    サイトで使ってるフレームワーク特定されたくない場合、
    変更したほうがいいかと思います。

  • mezzanine-admin-toolbar Mezzanine が 発行しているセッション cookie (ブラウザに保存されないやつ)
    editable.js という javascript 内で使っているものです。Http Only 属性はついていません。
    管理画面の制御に使っているように思います。

  • sessionid ログイン成功時に払い出されるキー値です。
    Django の Django.contrib.sessions の設定値で、名称と、cookie の secure 属性がコントロールできます。
    SESSION_COOKIE_SECURE のデフォルト値は False だったので、
    SESSION_COOKIE_SECURE = True に変更しました。
    Settings | Django documentation | Django


まとめ

以下、まとめます。

  • 公開部については、Mezzanine 側の基本的なセキュリティ対策なされていると思った。7
    [7] ブログ投稿などの編集画面については、試しておりません。

  • 設定値でコントロールするようなところは、デフォルト設定では、secure ではない場合がある。(HTTPS/HTTP でも動作するようになっている)
    HTTPS 強制のサイトの場合は、設定値の見直しは必要。

  • 設定変更は、HTTP サーバ側の設定と、Django の双方必要。

  • Sitemap.xml から、ページ辿り攻撃とか OWASP ZAP 怖い。

なんとなくやってみましたが、結構穴があいていて、焦りました。
以上です。

コメント

カテゴリー