最近 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
は デフォルト設定で、プロキシ設定なしの、標準モードで実行
インストールして起動すると表示されるクイックスタートに、
ブログのドメインを入力するだけです。
-
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
の直下あたりに記載しました。
static な js,css で出力されているので、Apacheにも、静的ファイルを対象にヘッダを追加する設定を行いました。MIDDLEWARE_CLASSES = ( .... "Django.middleware.clickjacking.XFrameOptionsMiddleware", "mezzanine_extentions.middleware.NoCacheMiddleware", .... )
<Directory /var/www/site/blog/static> Header append Cache-Control "private" </Directory>
Secure Pages Include Mixed Content
対処しませんでした。
SSL の際にリンク先が、http の場合発生します。
数が多いのと、リンク先が、https に対応していない場合があったりするためです。
Cookie No HttpOnly Flag
ログイン画面で出力された警告です。
対処しました。
crsf token に http only 属性がついていないために警告が出ていたので、
settings.py に CSRF_COOKIE_HTTPONLY = True
を追加しました。
Mezzanine では mezzanine/core/middleware.py でのみ、crsf token を使っていましたが、
plugin 等で、Javascriptからアクセスしている可能性はあるので、もしかしたらエラーとなるかもしれません。
Cookie Without Secure Flag
ログイン画面で出力された警告です。
対処しました。
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値 について
何の役に立つかはわかりませんが、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
怖い。
なんとなくやってみましたが、結構穴があいていて、焦りました。
以上です。
コメント