カテゴリー
Linux Mac サイト構築

自動でmod_securityのルール作って不正アクセスを防ぐようにしてみた

Mod security

 なぜか最近、いまさら「Windows Vista」をネタにした記事がずっとアクセス上位に来るので調べたら不正アクセスの兆候でした。

 最新バージョンのWordPressを使っているので実害はないですが、ランキングがおかしくなるのは嫌なので、mod_securityで対策しました。

 あとMacにも入ってるAWKでのルール自動生成もやってみた(とか書いてMacユーザーの興味を惹いてみる)

 ちなみに今回の内容はレンタルサーバーなどでroot権限がたぶん必要になります。あと一時間に一回Apacheサービスを再起動(正確にはグレースフルリスタート)するという荒技も使っているので自己責任でお願いします。

mod_securityのルールの書き方も(特にID)正しいかわからないので、自分でも調べてみてくださいね。

謎のログが多数

こんな感じ

https://a-tak.com/blog/個別記事のURL/#comment-7326+Result:+chosen+nickname+%22jvsyvgpy25%22;+success+%28from+first+page%29;

 こんなのが複数の所から多数来るので順位が上がってしまっています。なんで、この記事だけ?という気もしますが、リクエストに含まれている「chosen+nickname」で調べたら、海外の掲示板にも同じ事例が挙がってました。

http - Strange request URI with lot of + (spaces) and "chosen nickname" - Information Security Stack Exchange
2) it targets a specific page on my site. ...

 Chromeの翻訳によると、特定のページを対象にしているとのこと。他にも1日わずか40-60回程度のアクセスでDoS攻撃でもないと言っています。うちもまったく同じ現象です。

 読み進めていくとExploit(エクスプロイト。脆弱性を狙うプログラム。)の痕跡とか、いやXRumerというスパムソフトウェアの痕跡とか出ています。一番しつこい奴のIPを調べたら、たしかにProject Honey Potに該当のIPが掲載されていました。パキスタンか。

 邪魔なので、こいつのアクセス自体をWordPressが処理する前に拒否してしまおうとWAFの一つであるmod securityを入れてみることにしました。

WAFって?

 Web Application Firewallの略でWAFです。ざっくり言うと通常のファイアウォールだと接続元のIPやポートなどのTCP/IPレベルの情報しかアクセス拒否の条件に使えませんが、WAFの場合はTCP/IPより上位のアプリケーション層の情報を使ってフィルタが出来ます。WebのHTTPプロトコルもアプリケーション層です。

 これができると例えばURLに特定の文字が入っていたり、POSTで特定の情報が送られてきた場合に拒否するなんてことができます。

 というか、個人サイトにWAFまで普通入れるか?と思ったんですが、最近はあちこちのレンタルサーバーでWAF入ってるんですね。しらんかった。

mod_securityをインストール

yum install mod_security -y

 これでいけた。

rpm -ql mod_security

で、インストール先を確認。
/etc/httpd/modsecurity.d/activated_rules にルールを入れるみたいです。日本語の情報がなかなかヒットしないので手探り&英語と大格闘です(笑)

デフォルトのルールをインストール

yum install mod_security_crs -y

 これでデフォルトのルールが入ります。

service httpd graceful

で設定が反映されますが、WordPress動きません(笑)

 結構、元々のルールが厳しめのようなので、しょうが無いですが、原因になっているルールを取り除いて動くようにします。

/var/log/httpd/modsec_audit.log あたりにログが出ているはずです。こんな感じで↓

Message: Access denied with code 403 (phase 2). Operator EQ matched 0 at REQUESTHEADERS. [file "/etc/httpd/modsecurity.d/activatedrules/modsecuritycrs40genericattacks.conf"] [line "182"] [id "950000"] [rev "1"] [msg "Session Fixation"] [data "Matched Data: phpsessid found within REQUESTHEADERS: 0"] [severity "CRITICAL"] [ver "OWASPCRS/2.2.6"] [maturity "1"] [accuracy "7"] [tag "OWASPCRS/WEBATTACK/SESSIONFIXATION"] [tag "WASCTC/WASC-37"] [tag "OWASPTOP_10/A3"] [tag "PCI/6.5.7"]

「file 」で始まっているところの「/etc/httpd/modsecurity.d/activatedrules/modsecuritycrs40generic_attacks.conf」が引っかかったルールです。こいつを消してApache再起動すると、このルールは無効になります。まぁ、仕方ないでしょう。

 うちの場合、トップページがそもそも出なくて、投稿の時にも別のルールでエラーになってました。

 ログをよく見るとPCI/6.5.7って出てるけど、PCI-DSS要件の項番なのかな?

しかしchosen+nicknameの文字列での拒否はできず

 さっそくさっきのchosen+nicknameがURLにあれば接続拒否しようと思ったのですが、結局これ使えませんでした。というのも、この文字が「#」の後ろにあるからだと思うのですが、modsecurityのREQUESTURIに入ってこないんですね。たしかに、#は同じページ内の移動なので、サーバーの動きに関係ないといえばないんですが…。

 バージョン2.8以上だとFULL_REQUESTというのが使えるみたいなので、これだったらできるかもしれません。

結局IPで拒否

 結局、普通にIPで拒否することにしました。だったら普通のファイアウォールの設定でいいじゃんという気もしたのですが、せっかく入れたのでmod_securityで設定。

 さきほどのactivated_rulesの中にファイルを作って、

SecRule REMOTE_ADDR "@ipMatch  192.187.xxx.xxx" "block,msg:'Wordpress Exploit access',id:150013,severity:'2'"

こんな感じでIP指定しました。複数ある場合は複数行指定が必要ですが、id:の後ろの数値は被らないようにする必要があります。service httpd gracefulしたときにエラーが出ます。

めんどうなので自動化する

 かなり荒技なのでお勧めはしませんがIP追加していくのが面倒なので自動化しました。下手すると自分もトラップに引っかかるかもしれないし、正常なアクセスもトラップするかもしれないので要注意。

いきなりですがこんなスクリプトを組んで/etc/cron.hourlyに突っ込んでchmod +xしました。

#!/bin/bash
cat /var/log/httpd/access_log | grep -E "chosen\+nickname" | awk -F " " '{print $1}' | sort | uniq -c | awk -F " " '{if ($1>1) print "SecRule REMOTE_ADDR \"@ipMatch "$2"\" \"block,msg:\047Wordpress Exploit access\047,id:"15000+NR",severity:\0472\047\""}' > /etc/httpd/modsecurity.d/activated_rules/modsecurity_chosen_nickname.conf;service httpd graceful

[追記]
Twitterでawkの正式な使い方教えて頂きました!

#!/bin/bash
awk -F " " '/chosen\+nickname/ {print $1}' /var/log/httpd/access_log | sort | uniq -c | awk -F " " '{if ($1>1) print "SecRule REMOTE_ADDR \"@ipMatch "$2"\" \"block,msg:\047Wordpress Exploit access\047,id:"15000+NR",severity:\0472\047\""}' > /etc/httpd/modsecurity.d/activated_rules/modsecurity_chosen_nickname.conf;service httpd graceful

AWKはパターンと処理をセットで書くのが基本とのことで、こんな感じで別でgrepしてパイプで渡していた条件をawkの中に入れ込みました。うむ、シンプルになった。

これが実行されるとこんな内容のファイルが自動でできあがります。

SecRule REMOTE_ADDR "@ipMatch 66.117.9.90" "block,msg:'Wordpress Exploit access',id:15037,severity:'2'"
SecRule REMOTE_ADDR "@ipMatch 66.117.9.99" "block,msg:'Wordpress Exploit access',id:15038,severity:'2'"
SecRule REMOTE_ADDR "@ipMatch 68.64.166.2" "block,msg:'Wordpress Exploit access',id:15039,severity:'2'"
SecRule REMOTE_ADDR "@ipMatch 74.91.23.100" "block,msg:'Wordpress Exploit access',id:15041,severity:'2'"

簡単にやってることを書くと…

  1. Apachのログからchosen+nicknameの行を抜き出し
  2. awkでスペースで一行を分割して一番目($1)にあるIPを抽出
  3. sort と uniqでIP毎のアクセス回数を出す
  4. 再度awkで回数($1)が1より大きい(つまり2回来やがった場合やっつける)IP($2)だけ抜き出し
  5. awkのprintでmod_securityのルール文字列を生成
  6. activated_rulesの中にルールファイルを上書き
  7. service httpd gracefulで設定反映(割と乱暴)

こんな感じです。awk便利!

awkの中で使っているNRは行番号で、これを使って被らないIDを採番しています。「\047」はシングルクォーテーションです。

ちなみにawkとかMacのターミナルで普通に使えるので、使いこなせるとテキスト処理とか便利ですよ。

 cron.hourlyに突っ込んで実行権限を与えることで、これが一時間に一回実行されてどんどんスパム業者がフィルタされていきます。今の所、うまく動いて続々とフィルタに追加されていってます。しめしめ(笑)

 Apacheのログファイルは1日でローテションされて、まっさらになるので、一日ごとにこのルールはまっさらになります。とりあえずこれで運用して、イマイチだったら、一週間毎のクリアするなどに変えようと思います。

この記事をアップするときにエラー(笑)

 今回の記事をMarsEditからアップロードしようとしたら

Request body no files data length is larger than the configured limit 131072).. Deny with code (413)

 って止められた(笑)

 ファイルを除いたデータが131072バイトを超えたかららしい。厳しいなぁ(笑)

/etc/httpd/conf.d/mod_security.conf

RequestBodyNoFilesLimit 131072

 ここを変更した。

この記事を書いた人: A-tak

A-tak.com(えいたっく どっとこむ)の管理人。
Apple野郎なおっさんでしたが、ちょっと最近のAppleには飽き気味。
A-tak.comは2002年2月から運営(前身のサイトは1999年3月から)。今年で18年目!

Twitter
Mastodon
Facebook