WAFを導入するメリット
WAFとは、Web Application Firewallの略称であり、Web上でアクセスできるアプリケーション、たとえばWordPressなどを攻撃から保護するための一つのソリューションです。SQLインジェクションやディレクトリトラバーサルなどの脆弱性を利用しようとする不正なアクセスを、PHPなどのプログラム実行レイヤではなくその手前で防ぐことができるため、未修正の脆弱性による被害を受けるリスクを軽減することができます。
過去にも当サイトのWAFで攻撃に備える(。・д・)o┫゙;`;:゙;`;:などで解説しましたが今回はさらに詳しく解説してまいります。
KUSANAGIでWAFを有効にする方法
KUSANAGIでは、標準搭載のWAFとして、nginxではNAXSI、ApacheではModSecurityを利用しています。なお、初期状態では有効となっていませんので、手動で有効化する必要があります。
有効化は以下のコマンド一つで完了します。なお、初回では関連パッケージのインストールも行われます。
kusanagi waf on
うまく行っていれば最後に以下のような文字列が表示されるはずです。
restart completed.
waf completed.
nginxのNAXSIでエラーが出た際に原因を分析する方法
さて、有効化したものの、実際にアクセスしてみたらしばしば403 Forbiddenなどが表示されることがあるかと思います。このような際に原因を分析する方法を解説します。
※なお、KUSANAGIではWordPress向けに最適化したNAXSIルールが標準で含まれているため下記で示すエラーは発生しません。今回は特別にそれを無効化しています。
まず、以下のような画面が出たらそのプロファイルのnginxのエラーログを見てみましょう。ファイルは/home/kusanagi/(プロファイル名)/log/nginx/error.log (HTTPSでエラーが発生している場合はssl_error.log)です。
ログを見てみたところ、以下のようなエラーが発生していることが確認できました:
2023/07/21 08:36:23 [error] 7432#7432: *1 NAXSI_FMT: ip=192.0.2.1&server=example.com.internal&uri=/wp-login.php&vers=1.3&total_processed=1&total_blocked=1&config=block&cscore0=$XSS&score0=16&zone0=HEADERS&id0=1315&var_name0=cookie, client: 50.5.35.41, server: example.com.internal, request: "GET /wp-login.php?action=logout&_wpnonce=facec0ed55 HTTP/1.1", host: "example.com.internal", referrer: "http://example.com.internal/"
まずid0を確認してみてください。1315と書かれていますね。これが今回引っかかったフィルタということです。なお、複数引っかかった場合にはこれがid1、id2…などと増えていきます。さて、では今回示されているIDの1315が何なのか確認しましょう。ベースとなるルールは/etc/opt/kusanagi/nginx/naxsi.d/naxsi_core.rules.confに記載されています。ファイルを開いて1315で絞り込んだところ、以下の行が検出されました。
MainRule "rx:%[23]." "msg:double encoding" "mz:ARGS|URL|BODY|$HEADERS_VAR:Cookie" "s:$XSS:8" id:1315;
ここから導き出せるものとしては、エンコードが二重にされているから危険度のスコアを上げたということです。
続いて、これに対処するためのフィルタを追加します。そのためにどこでエラーが発生したかをさらに調べます。上のエラーログからzone0というものを確認しますと、HEADERSと書かれています。つまりHTTPヘッダになにか異常があったことがわかります。そして、var_name0を確認すると、cookieと書かれています。つまり今回はHTTPヘッダとして送られてきたCookieがおかしいということがわかりましたので、これを除外するものを書きましょう。
今回の場合はこのような構文となります:
BasicRule wl:1315 "mz:$HEADERS_VAR:cookie";
wl(ホワイトリストの意味です)にIDを、mz(マッチしたゾーンの意味です):から先には除外ルールを入れます。今回の場合は、HTTPヘッダのCookieにおいては1315を除外するといったものとなります。
これを/etc/opt/kusanagi/nginx/naxsi.d/wordpress/user.confへ入れればOKです。
入れてkusanagi nginxでnginxを再起動して問題が起きないことを確認できました。
問題が発生するたびにこの作業を繰り返してフィルタの精度を上げていきましょう。
ApacheのModSecurityでエラーが発生した際に原因を分析する方法
KUSANAGIは標準ではnginxを利用していますが、しばしば実運用ではApacheを利用することがあるかと思います。そこで、ApacheにおいてModSecurityを利用する場合のエラー原因の分析法も合わせてお伝えします。
今回のために特別に制作したプログラムを用いてリクエストを送信し、以下のブロック画面を表示させました。
もしModSecurityが不正なリクエストとみなした場合、サーバは以下のような画面を返しリクエストを遮断します。
この際、/home/kusanagi/(プロファイル名)/log/httpd/error.log (HTTPSの場合はssl_error.log)に遮断された理由が出力されます。ログには大量のメッセージが出力されていましたので、かいつまんで説明します。以下にログの一例を示します。
アクセスがブロックされた前後の時間の情報をまず確認します。行の頭あたりにある、[Fri Jul 21 08:36:23.102987 2023]のような文字列のところから確認できます(ちなみに上の場合は2023年7月21日金曜日の午前8時36分23秒に当該事象があったという意味です)。
そして、不正なリクエストと認識された原因は[id “920220”]や[id “920270”]、[id “949110”]それに[id “980130”]などが一例として出力されます。ここまでのことをまとめると、以下のようなエラーログが出ているはずです(一部伏せ字にしてあります):
[Fri Jul 21 08:36:23.102987 2023] [security2:error] [pid *****:tid ***************] [client 192.0.2.1:*****] [client 192.0.2.1] ModSecurity: Warning. Found 1 byte(s) in ARGS:test_query_string outside range: 1-255. [file "/etc/opt/kusanagi/httpd/modsecurity.d/activated_rules/REQUEST-920-PROTOCOL-ENFORCEMENT.conf"] [line "517"] [id "920270"] [msg "Invalid character in request (null character)"] [data "ARGS:test_query_string=************"] [severity "CRITICAL"] [ver "OWASP_CRS/3.3.4"] [tag "application-multi"] [tag "language-multi"] [tag "platform-multi"] [tag "attack-protocol"] [tag "paranoia-level/1"] [tag "OWASP_CRS"] [tag "capec/1000/210/272"] [hostname "example.com.internal"] [uri "/"] [unique_id "***************************"]
詳細は述べませんが、これらのIDのエラーが発生するのは(一般的には)不正な文字列を含むリクエストを送信すると発生されます。これらはソフトウェアがスレッドセーフであれば問題にはなりにくいですが、ノンスレッドセーフなソフトウェアですと問題が発生することがあるのでアクセス制限の解除には注意が必要です。
今回は意図的に発生させたエラーで安全が保証されているので特別に除外ルールに設定してみます。場合によってですが、リクエストされたパスやクエリストリング、リファラ情報なども除外ルール作成時に必要です。これは上の情報と同じ行の末尾近くにあり、[url “/path”]や[data ~]などの形式で出力されます。
さて、これら引っかかった原因引っかかったフィルタのIDを控えておきます。
ただし、通常は解除する際、除外するIDは少ないほうが安全面から理想的です。今回の場合は、不正な文字列が入っていないように見えるメッセージを見て除外します。具体的に言うと、ID: 949110と980130です。どのようなログなのか、参考までに記載しておきます:
[Fri Jul 21 08:36:23.103004 2023] [security2:error] [pid *****:tid ***************] [client 192.0.2.1:*****] [client 192.0.2.1] ModSecurity: Access denied with code 403 (phase 2). Operator GE matched 5 at TX:anomaly_score. [file "/etc/opt/kusanagi/httpd/modsecurity.d/activated_rules/REQUEST-949-BLOCKING-EVALUATION.conf"] [line "153"] [id "949110"] [msg "Inbound Anomaly Score Exceeded (Total Score: 10)"] [severity "CRITICAL"] [ver "OWASP_CRS/3.3.4"] [tag "application-multi"] [tag "language-multi"] [tag "platform-multi"] [tag "attack-generic"] [hostname "example.com.internal"] [uri "/"] [unique_id "***************************"]
さて、その2つを除いてまだ2つが残っていますね。これをもとに除外リストを作ります。できればdataの中から検知対象となったクエリストリングなども控えておくと更に安全に作れます。導入されているプロファイルにもよりますが、/etc/opt/kusanagi/httpd/modsecurity.d/kusanagi_activated_rules/wordpress/user.confに追加すればOKです。今回は上記の事象を元に総合的に判断し、下記のコードを中間行に追記しました。
<If "%{QUERY_STRING} =~ /.*test_query_string=.*/ || %{HTTP_REFERER} =~ /.*test_query_string=.*/">
SecRuleRemoveById 920220
SecRuleRemoveById 920270
</If>
これを保存後、kusanagi httpd
で再起動してデザインが崩れることなくアクセスできることを確認しました。
エラーが解消できなく、真にやむを得ない場合にWAFを無効化する方法
上記の手段でエラーの原因を調べても解消できなく、かつ真にやむを得ない場合はWAFを無効化することもできます。以下のコマンドを実行すればOKです。
kusanagi waf off
まとめ
KUSANAGIに標準搭載されているWAFを利用すると、より安全なウェブサイトをより簡単に運営することができます。たしかに誤検知のリスクももちろんありますが、コツさえ掴めば比較的簡単に修正ができますので、利便性と安全性を天秤にかけて、利用を検討してみてはいかがでしょうか。
参考文献
https://code.google.com/archive/p/naxsi/wikis/BasicRule.wiki