スマートフォン・タブレットからインターネットサーバーオペレーション

APPW.jp
 
.htaccess block / IPv4 · IPv6 · UA

攻撃を、止める
.htaccess による遮断

検出はできた。では、どう止めるか。IPv4 は RewriteCond、IPv6 は RequireAll、UA ブロック—— 「IPを弾く」だけでも、書き方はまるで違います。そして遮断を書いた途端に現れる AH01071 との戦いまで。 Apache 層でリクエストを門前払いする、静的遮断の実装記録です。

Apache 2.4 .htaccess RewriteCond RequireAll AH01071

[ § 0 ] 導入

見つけた。では、どう止めるか

姉妹サイトの記事 「攻撃者は、パターンで浮かぶ」で、 アクセスログから攻撃者を検出するところまで来ました。この記事は、その続きです。 検出した相手を、実際に止める

大事なのは 検出 ≠ 遮断 だということ。検出は「誰を止めるか」を決めるだけ。 実際に門を閉じるのは Apache の仕事です。その第一の道具が .htaccess。 ただし「IPを弾く」という単純な操作ですら、IPv4 / IPv6 / UA で書き方がまるで違います

本記事の IP はすべてマスク済み。 例に出す 203.0.113.x(IPv4)と 2001:db8::/32(IPv6)はドキュメント用の予約レンジで、実在のアクセス元ではありません。
[ § 1 ] 全体像

どこで、何を止めるか

.htaccess による遮断には、大きく3つの軸があります。IPアドレス(v4 / v6)User-Agent。それぞれ別の構文が要ります。共通なのは「アプリに届く前、Apache 層で弾く」こと。 軽くて速い、第一の関門です。

この記事の範囲

ここで扱うのは .htaccess による静的な遮断——「リストに載っている相手」を弾くもの。頻度に応じて動的に弾く(APCu レート制限)のは、次回2本目の担当です。

[ § 2 ] 観点①

IPv4 を弾く — RewriteCond

Apache 2.4 では、古い Deny from ではなく mod_rewriteRequire が主流です。 IPv4 はパターンで書きやすいので、RewriteCondREMOTE_ADDR を見て弾きます。

.htaccess — IPv4 ブロック
RewriteEngine On
# 単一IP/サブネット前方一致で弾く
RewriteCond %{REMOTE_ADDR} ^203\.0\.113\.66$  [OR]
RewriteCond %{REMOTE_ADDR} ^198\.51\.100\.
RewriteRule .* - [F,L]        # 403 Forbidden で即終了

[F] で 403 を返し、[L] でルール評価を打ち切ります。 RewriteRule 方式なら、[OR] で複数条件をまとめたり、前方一致でサブネットを丸ごと弾いたりと、柔軟に組めます。

[ § 3 ] 観点②

IPv6 を弾く — RequireAll

IPv6 は勝手が違います。: を含む表記や省略記法があり、RewriteCond の正規表現では綺麗に書けません。 ここは mod_authzRequire not ip を使い、CIDR で範囲指定するのが素直です。

.htaccess — IPv6 ブロック
<RequireAll>
  Require all granted
  Require not ip 2001:db8::/32     # この範囲を拒否
  Require not ip 2001:db8:dead::/48
</RequireAll>
v4 と v6 で、手法を分けた。 IPv4 はパターンが効くので RewriteCond、IPv6 は CIDR が素直なので Require。 無理に1つの書き方へ統一せず、それぞれが得意な構文を使う——これが実運用で落ち着いた形です。
[ § 4 ] 観点③

User-Agent を弾く — 名乗りで止める

検出の記事で「UAは偽装できる」と書きました。だから UA ブロックは万能ではありません。 でも、正直に名乗る迷惑ボット——スクレイパやスキャナには有効です。既知の名前を軽く弾く用途に向きます。

.htaccess — UA ブロック
RewriteCond %{HTTP_USER_AGENT} (EvilScanner|badbot|scanner) [NC]
RewriteRule .* - [F,L]        # [NC]=大文字小文字を無視
住み分け

UA遮断は「既知の迷惑ボットを軽く弾く」用途。名乗りを偽装する本命の攻撃者は、頻度やパターン——次回2本目の APCu レート制限で止めます。静的リストと動的判定の二枚看板です。

[ § 5 ] 落とし穴

AH01071 との戦い

error.log / AH01071: Got error 'Primary script unknown' — 遮断を書いた前後から、このエラーが目立ち始める。

これは PHP-FPM(FastCGI)が、存在しないスクリプトへのリクエストを受け取ってしまったときに出ます。 スキャナは /wp-login.php のような、このサイトに実在しない .phpを次々に叩きます。 .php を無条件で PHP-FPM に渡す設定だと、その存在しないパスがそのまま FPM に届き、AH01071 を量産します。

回避 — 存在するスクリプトだけ FPM へ渡す

handoff の前に「ファイルが実在するか」を確かめます。実在しない .php は FPM に渡さず、Apache 側で 404 にする。 これで AH01071 は止まります。

.htaccess / vhost — AH01071 回避
<FilesMatch \.php$>
  # 実在するスクリプトだけ PHP-FPM へ
  <If "-f %{REQUEST_FILENAME}">
    SetHandler "proxy:unix:/run/php/php-fpm.sock|fcgi://localhost"
  </If>
</FilesMatch>

順序 — 遮断は FPM 到達より前に効かせる

もう1つの勘所は順序です。遮断ルール([F,L])を、FPM への handoff よりに置く。 [L] で確実に打ち切れば、弾いた相手はそもそも FPM に届きません。「止める」を先、「渡す」を後——この並びが崩れると、 せっかく弾いたつもりのリクエストが FPM を叩いて AH01071 になります。

AH01071 は「遮断」と「FastCGI」の噛み合わせ。 エラーの犯人は遮断そのものではなく、存在チェックの欠落と順序でした。 同じエラーで検索して辿り着いた方は、まず「実在チェック」と「ルール順序」の2点を確認してみてください。
[ § 6 ] 体感デモ

リクエストは、どこで弾かれるか

送信元をプリセットから選んで、リクエストを流します。IPv4 / IPv6 / UA の各関門を通過していき、 該当する関門で 403 [F] として弾かれます。正常なリクエストだけが PHP-FPM に到達します。

Interactive · Request block flow
リクエスト遮断フロー
サーバー通信なし。遮断はプリセットの模擬です。関門は論理的な順序で表示しています(実際の Apache は評価フェーズが異なります)。
🛡️IPv4 チェックRewriteCond %{REMOTE_ADDR}
待機
🛡️IPv6 チェック<RequireAll> Require not ip
待機
🔍UA チェックRewriteCond %{HTTP_USER_AGENT}
待機
⚙️PHP-FPM 到達存在チェック後にhandoff
待機
送信元を選んで「リクエストを送る」を押してください。正常ユーザーは全関門を通過、それ以外はどこかで弾かれます。

正常ユーザーは全関門を素通りして 200。ブロック対象は、それぞれ対応する関門で 403 として止まります。 ここで見えてくるのは静的遮断の限界です——「リストに載っている相手」しか止められない。 まだ弾くと決めていない相手、頻度で初めて怪しくなる相手は、この仕組みでは通ってしまいます。

[ § 7 ] まとめ

静的遮断の、その先へ

.htaccess による静的遮断は、IPv4(RewriteCond)/ IPv6(RequireAll)/ UA(RewriteCond)を使い分けるのが要点でした。 そして AH01071 は遮断と FastCGI の噛み合わせ——実在チェックとルール順序で回避します。

この記事のまとめ

  • IPv4 はパターンが効く RewriteCond %{REMOTE_ADDR} + [F,L]
  • IPv6 は CIDR が素直な <RequireAll> + Require not ip
  • UA ブロックは正直に名乗る迷惑ボット用。偽装する本命は頻度で止める。
  • AH01071 は「存在しない .php を FPM に渡す」のが原因。実在チェックルール順序で回避。
  • 静的遮断は「リストに載った相手」限定。未知・高頻度は次の層へ。

関連記事

『攻撃を、止める — .htaccess による遮断(IPv4 / IPv6 / UA と AH01071)』を公開しました。