ModSecurityについて
オープンソースのWAFで最もメジャーと思われるWAF(Web Application Firewall)。Apache httpdなんかでCGIなどに処理が渡る前にリクエストをチェックして、必要に応じてブロックしてくれる。
RHELクローンだと以下の関連パッケージが提供されている。
# yum list | grep mod_security
mod_security.x86_64 2.9.6-1.el8 appstream
mod_security-mlogc.x86_64 2.9.6-1.el8 appstream
mod_security_crs.noarch 3.3.4-1.el8 appstream
CRSとは
Core Rule Setの略で、OWASPによってメンテナンスされているらしいModSecurity(および互換WAF)用の汎用ルールセット。
そのまま導入するとやりすぎで不具合が出ることが多いと思うが、これについては今回は詳細を省略。
mlogcとは??
今回のネタ。
# yum info mod_security-mlogc
してみると、ModSecurity Audit Log Collectorの略らしい。
で、これは何をするものか?と調べてみると、リモートサーバにHTTPでログを(ほぼ)リアルタイムで転送するらしい。転送先のサーバでログをAuditConsoleを利用することで、ログの可視化が可能になる。
ちょうどWAFの生ログを見るのが嫌になっていたので、「AuditConsoleを導入したらいいんじゃね?」と考えた。
AuditConsoleについてググって見つけたブログ記事だと、https://www.jwall.org/web/audit/console/からダウンロードできるとあるが、現時点ではウェブサイトがなくなっている様子でダウンロードできない…
いろいろ探してみたけど手に入らなくなっているようなので諦めて、日次でローテートしたWAFログをcronで別サーバに転送、集計して表示するWebアプリを自分で適当に作成した。おしまい。
リブート
Webアプリを作成したものの、遅れがひどいため、リアルタイムに近いログ確認の欲求が再燃。でもAuditConsoleが見つけられてない…
結論:mlogcからログを受け取って表示する仕組みを自前で用意すればいいんじゃね?
mlogcの仕組み
mlogcの詳細について調べようにも、ググっても見当たらない…。とりあえずmlogcの動作の仕組みから調べる。 <- "俺得ニッチ"部分
ModSecurityでログをリクエストごとに指定した場所に個別ファイルとして保存する設定としておき、mlogcを呼ぶことで(以前に転送失敗したログも含め)指定したエンドポイントに送信する様子。(正確にはHTTPリクエストがエラーにならなければ転送したファイルを消す。)
試行錯誤した結果、以下のように設定。あと、パースが楽になるのでJSONにしておく。
- SecAuditLog /var/log/httpd/modsecurity.log
+ SecAuditLog "|/usr/bin/mlogc /etc/mlogc.conf"
+ SecAuditLogType Concurrent
+ SecAuditLogFormat JSON
+ SecAuditLogStorageDir /var/log/mlogc/data
また、/etc/mlogc.conf も ConsoleURI を編集。SensorUsername と SensorPassword はmlogcがAPIエンドポイントの ConsoleURI へのリクエストヘッダにBASIC認証として送信される。
- ConsoleURI "https://CONSOLE_IP_ADDRESS:8888/rpc/auditLogReceiver"
+ ConsoleURI "https://<REMOTE_SERVER>/api/auditlog/"
SensorUsername "SENSOR_USERNAME"
SensorPassword "SENSOR_PASSWORD"
これでModSecurityでログが記録されるたびに https://<REMOTE_SERVER>/api/mlogc/ にリクエストが飛ぶことになる。ModSecurityがパイプでmlogcを呼び出すので、mlogcのデーモンなどはない。
受け側の用意
HTTPリクエストが飛ぶようになっても受ける側が無いと意味がないので、mlogcからのリクエストを受け取る方法をソースを読んだりしながら引き続き調査。
mlogcでは ConsoleURI で指定したエンドポイントに対し、PUTでリクエストを送っている様子。
PHPだとPUTリクエストの内容は php://input から受け取れるので、以下のようなテストコードを書いて動作を確認。実際には適当に処理してDBなんかに突っ込むと思う。
<?php
$data = file_get_contents('php://input');
file_put_contents(time(), $data);
正常に処理が完了した場合はHTTPステータスコード 200を、エラーの場合はHTTPステータスコード 409が返ってくることを期待しているようなので、そのようにしてやる必要がある。「PUTなら201か204だろ!」とかやるとNG。
BASIC認証は apache_request_headers() を使うか、フレームワークに任せる。