タグ: apache Page 1 of 4

Mod_Securityチューニングのケーススタディ:「PCRE limits exceeded」エラーへの対処

概要

先日の記事でgitを用いたmod-securrity core rule setのアップデートを行いました。

このアップデートにより新たに検知されたルールの対処を行います。

環境

  • Ubuntu 24.04
  • Apache 2.4
  • Mod_Security 2系
  • OWASP Core Rule Set v4.1.5
  • ApacheのバーチャルファイルごとにModSecurityを制御

事象

OWASP Core Rule Set (CRS) を導入したModSecurityをブロックモード(SecRuleEngine On)に切り替え後、

Apacheのエラーログに、これまで見られなかった Execution error - PCRE limits exceeded (-47) というエラーが大量に記録されていることを発見しました。

  • エラー例(IPアドレスやホスト名は改変済み)
[Sat Jun 14 11:09:05.195039 2025] [security2:error] [pid 28306:tid 28306] [client AAA.BBB.CCC.DDD:59314] [client AAA.BBB.CCC.DDD] ModSecurity: Rule 73f4b9603e90 [id "951190"][file "/usr/share/modsecurity-crs/coreruleset/rules/RESPONSE-951-DATA-LEAKAGES-SQL.conf"][line "246"] - Execution error - PCRE limits exceeded (-47): (null). [hostname "hoge.example.com"] [uri "/path/to/page.html"] [unique_id "aE_NnHj4jZdbb2PH1-4O0QAAABc"]
[Sat Jun 14 11:09:05.195564 2025] [security2:error] [pid 28306:tid 28306] [client AAA.BBB.CCC.DDD:59314] [client AAA.BBB.CCC.DDD] ModSecurity: Rule 73f4b95f9e98 [id "951210"][file "/usr/share/modsecurity-crs/coreruleset/rules/RESPONSE-951-DATA-LEAKAGES-SQL.conf"][line "288"] - Execution error - PCRE limits exceeded (-47): (null). [hostname "hoge.example.com"] [uri "/path/to/page.html"] [unique_id "aE_NnHj4jZdbb2PH1-4O0QAAABc"]

このログから、SQLインジェクション系の情報漏洩を検知するRESPONSE-951-DATA-LEAKAGES-SQL.confの内容に沿ったものと特定。

cat /var/log/apache2/hoge/error.log |grep 951190 |wc -l

としたところ、数時間で2万件近いログを検知。

PCEリミットとは?

複雑な正規表現を、非常に大きなデータ(Webページなど)に対して実行すると、サーバーのCPUやメモリを大量に消費し、サービス拒否(DoS)攻撃に繋がる危険性があります。これを防ぐため、ModSecurityには処理の複雑さや再帰の深さに上限(リミット)が設けられています。

エラーの原因

今回のエラーは、サーバーが返信するHTMLページ(レスポンスボディ)の内容を、情報漏洩がないか確認するルールがスキャンしようとした際に、そのWebページの内容が非常に大きい、または複雑であったため、PCREの処理上限値を超えてしまったことが原因です。(筆者のサイトの長文が災いしています)

対処内容

この、処理上限値を超えてしまったことでエラーが発生したのですからmodsecurity.confSecPcreMatchLimitの値を大きくすると言うのが考えられる対処ではありますが、

運用しているサイトのvpsのリソースを鑑みて、より簡便な「原因となっている特定のルールのみの無効化」を行いました。

さっくりとした手順

  1. Apacheのバーチャルサイト設定(.confファイル)のバックアップを取ります。
  2. .confファイルの修正を行います。
  3. Apache再起動で修正を反映させます。
  4. ログを確認し、設定反映を確認します。

設定ファイルの修正

  • ファイルのバックアップ
sudo cp -ci /etc/apache2/sites-available/hoge.conf /path/to/backup/directory/hoge.conf.$(date +%Y%m%d)

設定ファイルやバックアップディレクトリは自分の環境に合わせます。

  • バックアップ確認
diff -u /path/to/backup/directory/hoge.conf.$(date +%Y%m%d) /etc/apache2/sites-available/hoge.conf

エラー無く、差分も表示されていなければバックアップは成功です。

  • ファイル修正

/etc/apache2/sites-available/hoge.confを以下のように修正していきます。(要root権限)

# PCREリミット超過エラーを起こす、レスポンスボディスキャン系のルール群を無効化
SecRuleRemoveById 951190
SecRuleRemoveById 951210
SecRuleRemoveById 951220
SecRuleRemoveById 951250
  • ファイル修正確認
diff -u /path/to/backup/directory/hoge.conf.$(date +%Y%m%d) /etc/apache2/sites-available/hoge.conf
  • 差分例
+ # PCREリミット超過エラーを起こす、レスポンスボディスキャン系のルール群を無効化
+ SecRuleRemoveById 951190
+ SecRuleRemoveById 951210
+ SecRuleRemoveById 951220
+ SecRuleRemoveById 951250

設定ファイルの設定反映

  • 構文確認
sudo apache2ctl configtest

Syntax OKを確認します。

  • 設定反映
sudo systemctl restart apache2.service
  • Apache稼働確認
systemctl status apache2.service

active(running)を確認します。

動作確認

対処後、ターミナルで

tail -f /var/log/apache2/hoge/error.log |grep "PCRE limits"

を実行し、しばらく観察します。

このエラーが表示されないことを確認し、処置が完了です。

gitによるmod-securrity core rule setのアップデート。

概要

こちらの方法で導入したMod-Security(WAF)。

この防御ルールであるOWASP Core Rule Setの更新を行います。

環境

  • Ubuntu 24.04
  • Apache 2.4
    • バーチャルサイトで個別設定
    • aptでインストールしているため
    • 設定ファイルは/etc/apache2/sites-available配下の.confファイル
    • サービス実行ユーザはwww-data
  • Mod Security ver.2系

注意点

アップデートにより

  • 追加されたルール
  • 削除されたルール
  • 変更された挙動

などがあるため、今までと同じ作業をしていたのにWAFがブロックした(あるいは今までブロックされていたアクセスがスルーした)などは可能性としてあります。

なので、

  1. ModSecurityを検知モードにして作業を実施する
  2. しばらく様子を見て有効化に戻す

という作業を取ります。そのため、WAFの防御が一度無効化される時間が発生します。

環境や予算が許すなら、インターネットに公開された別環境で試してからの方が良いでしょう。

→ テストが済んでいる状態であれば、本番環境で「ModSecurityをオフにする」の作業は不要になります。

さっくりとした手順

  1. 【事前準備】ModSecurityを検知モードにします。
  2. 【事前準備】ルールセットディレクトリのバックアップを行います。
  3. gitで最新リリースを確認します。
  4. OWASP Core Rule Setのアップデートを行います。
  5. Webサービスの再起動でアップデートを反映させます。
  6. 【事後作業】ModSecurityを有効化します。

【事前準備】ModSecurityを検知モードにします。

  • ディレクトリ移動
cd /etc/apache2/sites-available && pwd
  • .confファイルのバックアップ
sudo cp -pi hoge.conf /path/to/backup/directory/hoge.conf.yyyymmdd
  1. 設定ファイル(.conf)は自分の環境に合わせます。
  2. 任意のバックアップディレクトリを指定します。
  • .confファイルのバックアップ確認
diff -u /path/to/backup/directory/hoge.conf.yyyymmdd hoge.conf

差分が無いことを確認します。

通常、筆者は$(date +%Y%m%d)変数を用いてバックアップを行いますが、「検知モードでの試験」は日をまたぐこともあるため、この形式にしています。

  • .confファイルの編集

hoge.confファイルを管理者権限で修正。

SecRuleEngine On

から

SecRuleEngine DetectionOnly

に変更し、保存します。

  • ファイル修正確認
diff -u /path/to/backup/directory/hoge.conf.$(date +%Y%m%d) hoge.conf

以下の差分を確認します。

- SecRuleEngine On
+ SecRuleEngine DetectionOnly
  • 設定ファイル構文確認
sudo apache2ctl configtest

Syntax OKを確認します。

  • Webサービス再起動前確認
systemctl status apache2.service

active(running)を確認します。

  • Webサービス再起動
sudo systemctl restart apache2.service

※設定ファイルの修正のみのためreloadで十分ですが、念を入れてrestartにしています。(以下同じ)

  • Webサービス再起動後確認
systemctl status apache2.service

active(running)を確認します。

【事前準備】ルールセットディレクトリのバックアップを行います。

cd /usr/share/modsecurity-crs && pwd

これは、上記のリンク先で示した通り、ルールセットを/usr/share/modsecurity-crs/corerulesetに配置していた場合のディレクトリです。

必要に応じて自分の環境に合わせます。

  • ルールセットのディレクトリコピー
sudo cp -pir coreruleset /path/to/backup/directory/coreruleset.$(date +%Y%m%d)

任意のバックアップディレクトリを指定します。

  • ディレクトリコピー確認
ls -l /path/to/backup/directory/coreruleset.$(date +%Y%m%d)

ディレクトリ一式があることを確認します。

OWASP Core Rule Setのアップデート

  • ディレクトリ移動
cd /usr/share/modsecurity-crs/coreruleset && pwd
  • リモートリポジトリの確認
sudo git fetch origin
  • 最新バージョンとの差分確認
sudo git status

筆者環境で、以下のように表示されました。

ブランチ main

このブランチは 'origin/main' に比べて172コミット遅れています。fast-forwardすることができます。

  (use "git pull" to update your local branch)


Changes not staged for commit:

  (use "git add/rm <file>..." to update what will be committed)

  (use "git restore <file>..." to discard changes in working directory)

        deleted:    crs-setup.conf.example


no changes added to commit (use "git add" and/or "git commit -a") 

コミット内容が大幅に変わっているので、上述した「検知モードで様子見にする」が活きてきます。

  • gitによるアップデート
sudo git pull

これで、OWASP Core Rule Setが最新版に変更されました。

Webサービスの再起動でアップデートを反映させます。

  • Webサービス再起動前確認
systemctl status apache2.service

active(running)を確認します。

  • Webサービス再起動
sudo systemctl restart apache2.service
  • Webサービス再起動後確認
systemctl status apache2.service

active(running)を確認します。

その後、既存のWebシステムに問題が無いかを確認します。

tail -f /var/log/hoge/hoge_error.log

等として、通常の作業を行い

  1. 既存のサービスに異常は無いか?
  2. 新たな偽陽性は発生していないか?

等を確認し、それに応じて新たな偽陽性の除外などを行っていきます。

【事後作業】ModSecurityを有効化します。

アップデートの影響で既存サービスに問題が無いことが判明したら、再びModSecurityを有効化します。この例では、バックアップから.confファイルを切り戻します。

  • バーチャルサイト(.conf)ファイルのディレクトリに移動
cd /etc/apache2/sites-available && pwd
  • バックアップから切り戻し
sudo cp -pi /path/to/backup/directory/hoge.conf.$(date +%Y%m%d) hoge.conf
  • Webサービス再起動前確認
systemctl status apache2.service

active(running)を確認します。

  • Webサービス再起動
sudo systemctl restart apache2.service
  • Webサービス再起動後確認
systemctl status apache2.service

active(running)を確認します。

その後、既存のWebシステムに問題が無いかを確認します。

有効化前に既存.confファイルを修正した場合

新たに偽陽性を追加した、既存のファイルを削除した、またはそれに伴う設定変更を行ったことにより、バックアップ前と.confファイルが異なるケースはあります。

その場合は、.confファイルの

SecRuleEngine DetectionOnly

SecRuleEngine On

と修正した後で以下の作業を行います。

  • 設定ファイル構文確認
sudo apache2ctl configtest

Syntax OKを確認します。

  • Webサービス再起動前確認
systemctl status apache2.service

active(running)を確認します。

  • Webサービス再起動
sudo systemctl restart apache2.service
  • Webサービス再起動後確認
systemctl status apache2.service

active(running)を確認します。

アクセスログを圧迫するクローラーへの対処。(Apache 2.4と外部ファイル連携によるクローラーのアクセス遮断とログ軽減)

はじめに

Webサイトを外部に公開していると、非常に厄介なクローラーがアクセスログに蓄積。これらは

  • 超高速
  • 超高頻度

で行われるため、本当に必要なアクセスログ(どの検索サイトから来たか、攻撃の兆候となる不審な点はないか)がつかみにくくなっています。

これを解決するため、以下の措置を執りました。

  1. クローラーや悪質なボットのアクセス拒否
  2. それらをアクセスログにすら残さず、きちんとしたアクセスログを追いやすくする

(※こちらの記事を更に強力に推し進めたものです)

環境

  • Ubuntu 24.04
  • Apache 2.4
    • setenvifモジュール
    • authz_core モジュール
    • (特段の理由がない限り、上記2つは標準で組み込まれていることが多いです。)
    • ない場合はsudo a2enmod setenvif等とした後、sudo systemctl restart apache2.serviceとしてモジュールを組み込みます。
  • また、バーチャルサイトによる複数ドメインにも対応しています。

さっくりとした手順

  1. クローラーのリストファイルを作成します。
  2. Apacheバーチャルサイトの.confファイルを修正します。
  3. 設定を反映後、状況を確認します。

クローラーのリストファイルを作成

  • /etc/apache2/sites-enabled/il-vento-d-oro.txt

としてファイルを作成。(ファイル名や格納場所は自分の環境に合わせてください)

SetEnvIfNoCase User-Agent "facebookexternalhit/1\.1" bad_bot dontlog
SetEnvIfNoCase User-Agent "SemrushBot/7~bl" bad_bot dontlog
SetEnvIfNoCase User-Agent "AhrefsBot" bad_bot dontlog
SetEnvIfNoCase User-Agent "MJ12bot" bad_bot dontlog
SetEnvIfNoCase User-Agent "DotBot" bad_bot dontlog
SetEnvIfNoCase User-Agent "Baiduspider" bad_bot dontlog
SetEnvIfNoCase User-Agent "YandexBot" bad_bot dontlog
SetEnvIfNoCase User-Agent "Sogou web spider" bad_bot dontlog
SetEnvIfNoCase User-Agent "Exabot" bad_bot dontlog
SetEnvIfNoCase User-Agent "MegaIndex\.ru" bad_bot dontlog
SetEnvIfNoCase User-Agent "SeznamBot" bad_bot dontlog
SetEnvIfNoCase User-Agent "BLEXBot" bad_bot dontlog
SetEnvIfNoCase User-Agent "Bytespider" bad_bot dontlog
SetEnvIfNoCase User-Agent "DataForSeoBot" bad_bot dontlog
SetEnvIfNoCase User-Agent "serpstatbot" bad_bot dontlog
SetEnvIfNoCase User-Agent "SeekportBot" bad_bot dontlog
SetEnvIfNoCase User-Agent "index\.community crawler" bad_bot dontlog
SetEnvIfNoCase User-Agent "PetalBot" bad_bot dontlog
SetEnvIfNoCase User-Agent "BacklinksExtendedBot" bad_bot dontlog
SetEnvIfNoCase User-Agent "meta-externalagent/1\.1" bad_bot dontlog
SetEnvIfNoCase User-Agent "ICC-Crawler" bad_bot dontlog
SetEnvIfNoCase User-Agent "bingbot" bad_bot dontlog
SetEnvIfNoCase User-Agent "msnbot" bad_bot dontlog
SetEnvIfNoCase User-Agent "Applebot" bad_bot dontlog
SetEnvIfNoCase User-Agent "DuckDuckBot" bad_bot dontlog
SetEnvIfNoCase User-Agent "Majestic-SEO" bad_bot dontlog
SetEnvIfNoCase User-Agent "cognitiveSEO" bad_bot dontlog
SetEnvIfNoCase User-Agent "archive\.org_bot" bad_bot dontlog
SetEnvIfNoCase User-Agent "CCBot" bad_bot dontlog
SetEnvIfNoCase User-Agent "^$" bad_bot dontlog
SetEnvIfNoCase User-Agent "Custom-AsyncHttpClient" bad_bot dontlog
SetEnvIfNoCase User-Agent "ImagesiftBot" bad_bot dontlog

上記は、サイトを運営していく中で

  • robots.txtを無視し
  • あらゆるページにアクセスし
  • 下手をすれば帯域を阻害する

ものを集めています。"^$"は、エージェントを偽装するスクレイピングへの措置です。

この、

  • SetEnvIfNoCase User-Agent "エージェント名"
  • bad_bot
  • dontlog

と、続けて書くことで、厄介なボットへの対処とアクセスログへの無視を決めます。

もちろん、これらのbotが必要というのであれば省いていきます。

apacheの設定ファイルを編集

  • 設定ファイルのバックアップ
sudo cp -pi /etc/apache2/sites-available/hoge.conf /path/to/backup/directory/hoge.conf.$(date +%Y%m%d)

設定ファイルは自分の環境に合わせます。
任意のバックアップディレクトリを指定します。

  • diffによるバックアップ確認
diff -u /path/to/backup/directory/hoge.conf.$(date +%Y%m%d) etc/apache2/sites-available/hoge.conf

差分(エラー)が無いことを確認します。

  • ファイルの修正

以下のように修正していきます。(要管理者権限)

例)

<VirtualHost *:443>
    # ServerName、ErrorLogは自分の環境に合わせます
    ServerName hoge.example.com
    ErrorLog /var/log/apache2/hoge/error.log
    # 1)Robots.txtをすり抜ける悪質なbotのリスト(先ほど作成したリストファイルのパスをフルパスで指定) 
    Include /etc/apache2/sites-enabled/il-vento-d-oro.txt
    # dontlogを付与したエージェントはアクセスログに記録させません
    # CustomLogは自分の環境に合わせます
    CustomLog /var/log/apache2/hoge/access.log combined env=!dontlog

# DocumentRootは自分の環境に合わせます。
DocumentRoot /var/www/html/hoge

# Directoryディレクティブは自分の環境に合わせます。
    <Directory /var/www/html/hoge>
        Options -MultiViews
        AllowOverride All
     <RequireAll>
      # 上記リストに従い、"bad_bot"変数がセットされているクローラーのアクセスを拒否します
      Require not env bad_bot
      # それ以外は許可
      Require all granted
     </RequireAll>
    </Directory>

# Require notでForbiddenにすると、クローラーは「拒否されただけで本体はある」と判断し、Webアクセスを繰り返すパターンがあるため、403に404を返す設定を付け加えます
# Directory
ErrorDocument 403 "Not Found"
  • 差分確認
diff -u /path/to/backup/directory/hoge.conf.$(date +%Y%m%d) etc/apache2/sites-available/hoge.conf
  • 上記、加えた設定が+になっていること
  • ディレクティブの重複や閉じ忘れがないこと

を改めて確認します。

設定反映と確認

  • 別のセッションでアクセスログを確認
tail -f /var/log/apache2/hoge/access.log

※この段階ではクローラーからのアクセスが多々来ています

  • 構文確認
sudo apache2ctl configtest

Syntax Okを確認します

  • Apache再起動前確認
systemctl status apache2.service

active(running)を確認します

  • Apache再起動
sudo systemctl restart apache2.service
  • Apache再起動後確認
systemctl status apache2.service

active(running)を確認します

この段階で、上記、

tail -f /var/log/apache2/hoge/access.log

が止まっていれば設定は成功です。

  • 今までと同じようにアクセスができること
  • 既存のWebサイトに異常が無いこと
  • その場合のアクセスログが表示されること

を確認し、作業は成功です。

作業を切り戻す場合

何か不具合が起きた場合は、バックアップしていた.confを切り戻します。

  • 差分確認
sudo cp -pi /path/to/backup/directory/hoge.conf.$(date +%Y%m%d) etc/apache2/sites-available/hoge.conf

作業時に指定したバックアップディレクトリです。

  • 差分確認
diff -u /path/to/backup/directory/hoge.conf.$(date +%Y%m%d) etc/apache2/sites-available/hoge.conf

差分が ない ことを確認します。

  • 構文確認
sudo apache2ctl configtest

Syntax Okを確認します

  • Apache再起動前確認
systemctl status apache2.service

active(running)を確認します

  • Apache再起動
sudo systemctl restart apache2.service
  • Apache再起動後確認
systemctl status apache2.service

active(running)を確認します

設定前になっていることを確認します。

Let’s Encrypt仕様変更によるSSL Staplingの無効化

新たなSSL証明書を導入したサーバにて、以下のエラーを確認。こちらの解決のメモです。

前提

  • Ubuntu 24.04
  • Apache 2.4
  • Let's Encryptによるワイルドカード証明書を発行

エラー内容

サイトのエラーログに以下を発見しました。

[Thu May 22 14:03:51.270340 2025] [ssl:error] [pid 929:tid 929] AH02218: ssl_stapling_init_cert: no OCSP URI in certificate and no SSLStaplingForceURL set [subject: CN=*.ryza.jp / issuer: CN=E6,O=Let's Encrypt,C=US / serial: 058A8F3B6C32F08B41E5E56BEAD92451D34A / notbefore: May 21 12:08:57 2025 GMT / notafter: Aug 19 12:08:56 2025 GMT]
[Thu May 22 14:03:51.270358 2025] [ssl:error] [pid 929:tid 929] AH02604: Unable to configure certificate atelier.ryza.jp:443:0 for stapling 
  • 意味:
  • ApacheがSSL証明書 (CN=*.ryza.jp) のOCSP Staplingを初期化しようとしましたが、証明書内にOCSPレスポンダのURI(OCSP URI)が見つかりませんでした。また、Apacheの設定で SSLStaplingForceURL も指定されていません。
  • OCSP Staplingとは:
  • SSL証明書の失効状態を効率的に確認するための仕組みです。ウェブサーバーが事前にCA(認証局)に問い合わせて証明書の有効性を確認し、その応答(OCSPレスポンス)を証明書と一緒にクライアントに提供することで、クライアント側の確認負荷を軽減し、表示速度を向上させます。

なお、Apacheの.confファイルには以下の項目があります。

SSLUseStapling On
SSLStaplingCache "shmcb:logs/ssl_stapling(32768)"

つまり、ApacheでSSL Stapling設定されているにもかかわらず、OCSPレスポンスが働いていないことを意味します。

状況確認

現行の証明書の状況

openssl x509 -in /path/to/your/certificate.pem -noout -ocsp_uri

結果は何もなし。証明書内にこれがないことで、先のエラーが発生したようです。

過去にLet's Encryptで発行した証明書の状況

2024年9月に発行したLet's Encryptの証明書で

openssl x509 -in /path/to/your/certificate.pem.202409 -noout -ocsp_uri

を実施したところ、http://e6.o.lencr.orgと返ってきました。

導き出される状況

  • 証明書発行時に何らかのエラーが発生して、このURIが欠落した
  • 筆者は他に複数のドメインを利用し、いずれもLet's Encryptで証明書を発行しています。1つだけならいざ知らず、現行全てのドメインでの証明書でこのURLが欠落しているのは一時的なエラーではないと考えました。

そこで、もう一つの可能性として「Let's Encryptの仕様が変わったのでは?」と情報を探していきます。

答えを発見

Removing OCSP URLs from Certificates from Let's Encrypt

Let’s Encrypt will be removing OCSP URLs from certificates on May 7, 2025 as part of our plan to drop OCSP support and instead support certificate revocation information exclusively via CRLs.
Let's Encryptは、2025年5月7日にOCSPのサポートを停止し、代わりにCRLを介してのみ証明書の失効情報をサポートする計画の一環として、証明書からOCSP URLを削除します。

状況そのもののアナウンスを見つけました。この、OCSPのURLが消えた証明書は、いずれも、2025年5月18日以降に発行したものです。完全に時期的に一致。

対処

ここが分かれば、後は設定変更です。Ubuntuサーバで設定を変更します。

  • 設定ファイルバックアップ
sudo cp -pi /etc/apache2/sites-available/virutal.conf /path/to/backup/directory/virutal.conf.$(date +%Y%m%d)

設定ファイルは自分の環境に合わせます。任意のバックアップディレクトリを指定します。

  • ファイルバックアップ確認
diff -u /path/to/backup/directory/virutal.conf.$(date +%Y%m%d) /etc/apache2/sites-available/virutal.conf

差分がなければバックアップ完了です。

  • ファイル編集

任意の方法で(要管理者権限)以下のように編集します。

# 2025年5月のLet's Encryptの方針によりOCSPが廃止されたため
SSLUseStapling Off
#SSLStaplingCache "shmcb:logs/ssl_stapling(32768)"
  • 編集確認
diff -u /path/to/backup/directory/virutal.conf.$(date +%Y%m%d) /etc/apache2/sites-available/virutal.conf
-SSLUseStapling On
-SSLStaplingCache "shmcb:logs/ssl_stapling(32768)"
+# 2025年5月のLet's Encryptの方針によりOCSPが廃止されたため
+SSLUseStapling Off
+#SSLStaplingCache "shmcb:logs/ssl_stapling(32768)"
  • 整合性確認
sudo apache2ctl configtest

Syntax OKを確認します。

  • Apache再起動前確認
systemctl status apache2.service

active(running)を確認します。

  • Apache再起動
sudo systemctl restart apache2.service

※reloadより確実なrestartを採用しています

  • Apache再起動後確認
systemctl status apache2.service

active(running)を確認します。

修正確認

設定を反映させたサイトを適当に閲覧し、エラーログにて、冒頭のエラーがないことを確認しました。

Redmineのマルチドメインのメモ。

公開用Redmine等で取得しているreisalin.com。今回、新たに「ryza.jp」を取得しました。

そこで、「1つのRedmineサイトに対して、別のドメインからアクセス可能にする」方法を取ったのでメモに残します。

やったこと

の両方からアクセスできるようにする。

環境

  • Ubuntu 24.04
  • Apache 2.4
  • Redmine 5.1
  • (DB等は割愛)

以下のように設定しています。

https://atelier.reisalin.com/projects/zettel/knowledgebase/articles/19

準備

  1. DNS設定で、両方のドメインから同一のIPが引けるように設定を行いました。
  2. ワイルドカード証明書を取得しました。

実施手順

ディレクトリ移動

cd /etc/apache2/sites-available && pwd

ファイルコピー

sudo cp -pi atelier.conf atelier_ryza.jp.conf

ファイル編集

  • 主な編集
-servername atelier.reisalin.com
+servername atelier.ryza.jp
  RewriteEngine On
         RewriteCond %{HTTPS} off
         RewriteRule ^(.*)$ https://%{HTTP_HOST}%{REQUEST_URI} [R=301,L]

-    CustomLog /var/log/atelier/atelier_access.log combined env=!dontlog
+    CustomLog /var/log/atelier_ryza/atelier_access.log combined env=!dontlog

-SSLCertificateFile /etc/certs/reisalin.com.crt
-SSLCertificateKeyFile /etc/private/reisalin.com.key
+SSLCertificateFile /etc/certs/ryza.jp.crt
+SSLCertificateKeyFile /etc/private/ryza.jp.key

かなり単純な編集です。すなわち、コピーした後で

  • servernameを変える
  • ログの出力先を必要に応じて追加する
  • 証明書と秘密鍵をドメインに応じたものに変える

.conf有効化と反映

  • .conf有効化
sudo a2ensite atelier_ryza.jp.conf
  • 整合性確認
sudo apache2ctl configtest

Syntax OKを確認します。

Webサービス再起動

  • サービス再起動前確認
systemctl status apache2.service

active(running)を確認します。

  • サービス再起動
sudo systemctl restart apache2.service && $?

0を確認します。

  • サービス再起動後確認
systemctl status apache2.service

active(running)を確認します。

確認

以下、両方のサイトにアクセスします。

それぞれで同じコンテンツが見えればOKです。

備考

なぜ、このマルチドメインがうまくいったか?

Redmineのプログラムが、内部処理としてURLを参照しないというある種のおおらかさに救われました。また、リンクの参照なども(明示しない限りは)相対パスで記述されるため、混乱も少なかったと思います。

これが、他の(特にLalavelフレームワークのようなPHP環境)だとAPP_URLが関係するため、両立は今回のように上手くいきません。別途、リバースプロキシーを立てるなどの配慮が必要です。

Apacheのサービス再起動と確認スクリプト。

Ubuntu 24.04/Apache環境で

  • 再起動前に稼働しているサイトを確認
  • 構文チェック
  • 再起動
  • 再起動後のサービス状況

を一括で行うスクリプトです。

スクリプト

  • apache_check_restart.sh
#!/bin/bash


# サイト設定ディレクトリ
SITES_DIR="/etc/apache2/sites-enabled"

# スクリプトを root ユーザーで実行しているかチェック
if [ "$EUID" -ne 0 ]; then
    echo "このスクリプトは root 権限で実行する必要があります。"
    exit 1
fi

# 1. /etc/apache2/sites-enabled 配下のファイルとURL表示
echo "==== 有効なサイト設定ファイル ===="
if [ -z "$(ls -A $SITES_DIR)" ]; then
    echo "サイト設定が存在しません。"
else
    for site in "$SITES_DIR"/*; do
        echo "設定ファイル: $(basename "$site")"
        # URL部分を含むServerNameやServerAliasをgrepして表示 (重複行を削除)
        grep -Ei "ServerName|ServerAlias" "$site" | sed 's/^[ \t]*//' | sort | uniq
    done
fi

echo "=================================="

# 2. Apache構文チェック
echo "構文チェック中..."
apachectl configtest
if [ $? -ne 0 ]; then
    echo "構文エラーが検出されました。Apacheを再起動できません。"
    exit 1
fi
echo "構文チェック完了: 問題ありません。"

# 3. Apache再起動の確認
read -p "Apacheを再起動しますか? (y/n): " CONFIRM
if [[ "$CONFIRM" =~ ^[Yy]$ ]]; then
    echo "Apacheを再起動します..."
    systemctl restart apache2
    if [ $? -ne 0 ]; then
        echo "Apacheの再起動に失敗しました。"
        exit 1
    fi
    echo "Apacheが正常に再起動されました。"

    # 4. Apacheステータス確認
    echo "==== Apacheステータス ===="
    systemctl status apache2 --no-pager
else
    echo "Apacheの再起動はキャンセルされました。"
fi

root権限で作成し、

sudo chmod +x apache_check_restart.sh

で実行権限を付与します。

動作例

sudo bash apache_check_restart.sh
==== 有効なサイト設定ファイル ====
設定ファイル: atelier.conf
ServerName atelier.reisalin.com
servername atelier.reisalin.com
設定ファイル: bookstack.conf
ServerName barrel.reisalin.com
servername barrel.reisalin.com
構文チェック中...
Syntax OK
構文チェック完了: 問題ありません。
Apacheを再起動しますか? (y/n): 

Apacheが正常に再起動されました。
==== Apacheステータス ====
● apache2.service - The Apache HTTP Server
     Loaded: loaded (/usr/lib/systemd/system/apache2.service; enabled; preset: enabled)
     Active: active (running) since Wed 2024-12-18 08:23:29 JST; 12ms ago
       Docs: https://httpd.apache.org/docs/2.4/
    Process: 335542 ExecStart=/usr/sbin/apachectl start (code=exited, status=0/SUCCESS)
      Tasks: 11 (limit: 4690)
     Memory: 535.1M (peak: 2.2G swap: 0B swap peak: 67.9M)
        CPU: 510ms
     CGroup: /system.slice/apache2.service
             ├─335551 "Passenger core"
             ├─335577 "PassengerWatchdog (cleaning up...)"
             └─335580 /usr/sbin/apache2 -k start

と表示されます。上記、一連の流れを一括で行うので便利です。

WordPressの設定変更:Confファイルによるセキュリティ設定

apacheのバーチャルファイルにセキュリティ設定を追加します。

バーチャルサイトの設定ファイルバックアップ

  • バックアップ
sudo cp -pi /etc/apache2/sites-avaiable/wordpress.conf /path/to/backup/directory/wordpress.conf.$(date +%Y%m%d)

ファイル及びバックアップディレクトリは自分の環境に合わせます。

  • diffによる差分確認
diff -u /path/to/backup/directory/wordpress.conf.$(date +%Y%m%d) /etc/apache2/sites-avaiable/wordpress.conf

エラーがなければバックアップは成功です。

バーチャルサイトの設定ファイル編集

DocumentRootの項目に、以下のように編集します。/home/www-data/wordpressの部分は自分の環境に合わせます。

ここでは、サーバのリソースを食い潰し、robots.txtを無視するクローラーを止めることにしています。

また、こういったクローラーであることを隠し、ユーザーエージェントを偽装するIPもレンジでブロックしています。

    DocumentRoot /home/www-data/wordpress
    <Directory /home/www-data/wordpress>
        Options -Indexes
        Options FollowSymLinks
        AllowOverride All
        ## スパムクローラーを拒否
        SetEnvIfNoCase User-Agent "facebookexternalhit/1.1" spam_crawler
        SetEnvIfNoCase User-Agent "SemrushBot/7~bl" spam_crawler
        SetEnvIfNoCase User-Agent "AhrefsBot" spam_crawler
        SetEnvIfNoCase User-Agent "MJ12bot" spam_crawler
        SetEnvIfNoCase User-Agent "DotBot" spam_crawler
        SetEnvIfNoCase User-Agent "Baiduspider" spam_crawler
        SetEnvIfNoCase User-Agent "YandexBot" spam_crawler
        SetEnvIfNoCase User-Agent "Sogou web spider" spam_crawler
        SetEnvIfNoCase User-Agent "Exabot" spam_crawler
        SetEnvIfNoCase User-Agent "MegaIndex.ru" spam_crawler
        SetEnvIfNoCase User-Agent "SeznamBot" spam_crawler
        SetEnvIfNoCase User-Agent "BLEXBot" spam_crawler
        SetEnvIfNoCase User-Agent "Bytespider" spam_crawler
        SetEnvIfNoCase User-Agent "DataForSeoBot" spam_crawler
        SetEnvIfNoCase User-Agent "serpstatbot" spam_crawler
        SetEnvIfNoCase User-Agent "SeekportBot" spam_crawler
        SetEnvIfNoCase User-Agent "index.community crawler" spam_crawler
        SetEnvIfNoCase User-Agent "PetalBot" spam_crawler
     <RequireAll>
      Require all granted
      ##スパムクローラーを拒否
      Require not env spam_crawler
        #偽装エージェントを拒否
        Require not ip 190.92.0.0/16
        Require not ip 159.138.0.0/16
        Require not ip 166.108.0.0/16
        Require not ip 124.243.0.0/16
        Require not ip 114.119.0.0/16
        Require not ip 119.8.0.0/16
        Require not ip 110.238.0.0/16
        Require not ip 217.113.194.0/24
        Require not ip 34.123.0.0/16
     </RequireAll>
    ## 設定ファイルのアクセス拒否
     <Files wp-config.php>
      Require all denied
     </Files>
     <Files xmlrpc.php>
      Require all denied
     </Files>
     ## キャッシュを有効
     ExpiresActive On
     ExpiresByType image/jpg "access plus 1 year"
     ExpiresByType image/jpeg "access plus 1 year"
     ExpiresByType image/gif "access plus 1 year"
     ExpiresByType image/png "access plus 1 year"
     ExpiresByType text/css "access plus 1 month"
     ExpiresByType application/pdf "access plus 1 month"
     ExpiresByType text/x-javascript "access plus 1 month"
     ExpiresByType application/x-shockwave-flash "access plus 1 month"
     ExpiresByType image/x-icon "access plus 1 year"
     ExpiresDefault "access plus 2 days"
    </Directory>
     <Files xmlrpc.php>
      Require all denied
     </Files>

は運用に合わせて無効化の可否を決めてください。

  1. モバイルアプリからのリモート投稿ができなくなります。
  2. ピンバックとトラックバックを受け付けることができなくなります。
  3. Jetpackの一部機能が動作しなくなります。
  4. その他、リモート投稿や自動投稿のプラグインが影響を受けます。

編集後、

diff -u /path/to/backup/directory/wordpress.conf.$(date +%Y%m%d) /etc/apache2/sites-avaiable/wordpress.conf

として、上記の差分が出ることを確認します。

設定反映

  • 設定有効
sudo a2ensite wordpress.conf

→ 一時的に無効にしている場合

  • 構文確認
sudo apache2ctl configtest

Syntax OKを確認します。

  • Apache2再起動
sudo systemctl restart apache2.service

その後、Wordpressが正常に閲覧できることを確認します。

確認後、追加のセキュリティ設定を行わないのであれば、

sudo a2dissite wordpress.conf
sudo systemctl restart apache2.service

として、無用な攻撃を防ぎます。

ログ調査時にIPやステータスコードを抜き出すコマンド。

Apacheのアクセスログを調査するとき何かと使うのでメモです。

アクセスログからIPのみを抽出してカウント

 awk 'match($0,/[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+/) { print substr($0, RSTART, RLENGTH) }' "/var/log/apache/access.log" | sort -u 

表示例

3 xxx.xxx.xxx.xxx
2 yyy.yyy.yyy.yyy
1 zzz.zzz.zzz.zzz

と、煩雑なその他のログを探すことなく件数とIPをカウントします。

更にステータスコードも表示

awk 'match($0, /[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+/) { ip = substr($0, RSTART, RLENGTH) } match($0, /" [0-9]{3} /) { status = substr($0, RSTART+2, 3); print ip, status }' /var/log/apache/access.log | sort | uniq -c | sort -nr

表示例

5 xxx.xxx.xxx.xxx 200
3 yyy.yyy.yyy.yyy 404
2 zzz.zzz.zzz.zzz 500

ステータスコードが入るので、アクセス制御の有無やそれを突破してきたIPも調べられます。

Apacheにmod_dosdetector 導入

概要

過剰なWebクローラーによりサーバのパフォーマンスが落ちることがあるため、mod_dosdetectorを入れてみます。

mod_dosdetector は、Apache HTTP Server 用のモジュールで、DoS(Denial of Service)攻撃を検出し、対策を講じるためのものです。このモジュールは、特定のIPアドレスからの過剰なリクエストを監視し、しきい値を超えた場合にそのIPアドレスを一時的にブロックすることで、サーバーのリソースを保護します。

Ubuntu 20.04にはこちらを入れましたが、

  • ufwとの連携でサーバに負荷がかかる
  • 細かい設定が可能

ということで、こっちをUbuntu 24.04に入れてみます。

環境

  • Ubuntu 24.04
  • Apache 2.4
    • Apache2-devがインストールされていることが前提です。

さっくりとした手順

  1. gitでソースをダウンロードします。
  2. Makefileを書き換えた上でインストールします。
  3. 設定ファイルを作成します。
  4. 設定を有効化し、Apacheを再起動します。

mod_dosdetectorのダウンロード

  • ソース格納ディレクトリに移動
cd /usr/local/src && pwd

自分の環境に合わせます。

  • git clone
sudo git clone https://github.com/stanaka/mod_dosdetector.git
  • 展開したディレクトリに移動
cd mod_dosdetector && pwd

Makefileの書き換えとインストール

  • Makefile修正

デフォルトのMakefileは/usr/sbin/apxsとなっているため書き換えます。

sudo sed -i 's|^APXS=.*|APXS=/usr/bin/apxs|' Makefile
  • インストール
sudo make install
  • インストール確認
cat /etc/apache2/mods-available/dosdetector.load

LoadModule dosdetector_module /usr/lib/apache2/modules/mod_dosdetector.soと表示されます。

設定ファイル追加

参考: mod_dosdetectorを使ってみましょうよ。~挫折を乗り越え~

sudo tee /etc/apache2/mods-available/dosdetector.conf > /dev/null <<EOF
<IfModule dosdetector_module>
DoSDetection on
DoSPeriod 60
DoSThreshold 5
DoSHardThreshold 10
DoSBanPeriod 60
DoSTableSize 100
DoSIgnoreContentType ^(image/|application/|text/javascript|text/css)
</IfModule>
EOF
  • DoSDetection on
    • 説明: DoS (Denial of Service) 検出を有効にします。
  • DoSPeriod 60
    • 説明: DoS攻撃を検出するための監視期間を秒単位で指定します。
  • DoSThreshold 5
    • 説明: DoS攻撃と見なすリクエストの閾値を指定します。
  • DoSHardThreshold 10
    • 説明: より厳しい閾値を指定します。
    • 60秒間に同一IPアドレスから10回以上のリクエストがあった場合、即座にそのIPアドレスをブロックします。
  • DoSBanPeriod 60
    • 説明: DoS攻撃と見なされたIPアドレスをブロックする期間を秒単位で指定します。
  • DoSTableSize 100
    • 説明: DoS検出のために保持するIPアドレスの最大数を指定します。
  • DoSIgnoreContentType ^(image/|application/|text/javascript|text/css)
    • 説明: DoS検出から除外するコンテンツタイプを正規表現で指定します。

設定有効化とApache再起動

  • mod有効化
sudo a2enmod dosdetector
  • Webサービス再起動
sudo systemctl restart apache2.service

まずはこれで様子を見てみます。

Mod_Securityの連携。(検知した怪しい挙動とtor出口リストをブロックする)

個別に走らせていたスクリプトを1つにまとめました。

前提

  • Apache / Mod_Security導入済み
  • サイトのログローテーションが毎日であること

スクリプト

スクリプトの動き

  1. Tor出口リストを公式からダウンロードします。
  2. このリストからIPアドレスだけを抽出します。
  3. 変数に従ってエラーログからMod_Securityが検知したIPアドレスだけを抽出します。
  4. これらをMod_Securityのnegativelistに入れ、以下のサイトアクセスを丸ごと弾きます。
    • Tor出口リストに登録されているIPアドレス
    • Mod_Securityが検知したIPアドレス
  5. 自分の偽陽性を防ぐため、自分のゲートウェイなどのIPアドレスからのアクセスは除外します。
  6. リストを反映させるためApacheを再起動します。

スクリプト内容

#!/bin/bash
# このスクリプトは、Tor出口ノードリストをダウンロードし、特定のログファイルからIPアドレスを抽出して処理します。
# 使い方: このスクリプトを実行することで、最新のTor出口ノードリストを取得し、ログファイルから抽出したIPアドレスと照合します。
# 結果は指定されたディレクトリに保存され、Apacheが再起動されます。
# cronでの指定例: 毎日午前2時に実行する場合 -> 0 2 * * * /path/to/this_script.sh

# === 変数の定義 ===

# 出口ノードリストのURL
TOR_EXIT_LIST_URL="https://check.torproject.org/exit-addresses"

# ダウンロード先ファイル名
OUTPUT_FILE="/hoge/script/log/node_list/exit_nodes.$(date +%Y%m%d).txt"

# 各サイトのログディレクトリ
log_dirs=("/var/log/bookstack")  # 各サイトのログディレクトリを指定

# エラーログファイル名
log_file="bs_error.log"

# Tor出口ノードリストのディレクトリ
tor_list_dir="/hoge/script/log/sorted_list"  # tor_list.txtのディレクトリを指定

# Tor出口ノードリストのファイル名
tor_list_file="sorted_ip_addresses$(date +%Y%m%d).txt"

# 除外するIPアドレスをファイルで指定
exclude_ips_file="/hoge/script/log/exclude_ips.txt"

# 共通の出力ログディレクトリ
output_log_dir="/hoge/script/log"

# === Tor出口ノードリストのダウンロードと処理 ===

# curlを使用してリストをダウンロード
curl -o "$OUTPUT_FILE" "$TOR_EXIT_LIST_URL" >/dev/null 2>&1

# ダウンロードが成功したかどうかを確認
if [ $? -eq 0 ]; then
echo "Tor出口ノードリストをダウンロードしました。ファイル: $OUTPUT_FILE"
else
echo "ダウンロード中にエラーが発生しました。"
exit 1
fi

# IPアドレスのみを抽出し、ソートして出力
awk '/^ExitAddress/ {print $2}' "$OUTPUT_FILE" | sort -u | tee "$tor_list_dir/$tor_list_file" >/dev/null 2>&1

# === 各サイトのエラーログからIPアドレスを抽出して処理 ===

for log_dir in "${log_dirs[@]}"; do
cd "$log_dir"
awk '/ModSecurity/ && match($0,/[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+/) { print substr($0, RSTART, RLENGTH) }' "$log_file" | sort -u > "$output_log_dir/suspicious_ip/suspicious_ip.$(date +%Y%m%d)"
done

# 過去のIPアドレスを読み込んで重複を排除し、ファイルに保存
cat "$output_log_dir/suspicious_ip/suspicious_ip."2* | sort -u > "$output_log_dir/suspicious_ip_all.txt"

# 新たにリストに書き起こすと同時に別のログファイルを読み込んで重複を排除し、negativelist.txtに追加
cat "$output_log_dir/suspicious_ip_all.txt" "$tor_list_dir/$tor_list_file" | sort -u > "$output_log_dir/negativelist.txt"

# 除外するIPアドレスをファイルから削除
while IFS= read -r exclude_ip; do
sed -i "/$exclude_ip/d" "$output_log_dir/negativelist.txt"
done < "$exclude_ips_file"

# Apacheを再起動
systemctl reload apache2.service

スクリプトの他に用意するもの

  • exclude_ips.txt
192.168.0.1
nnn.nnn.nnn.nnn

など、エラーログから除外したいIPアドレスのリストを作ります。

  • バーチャルサイトの編集

/etc/apache2/site-available 配下のバーチャルサイトの設定ファイルを以下のように追記します。

    # Mod_Security設定
    SecRuleEngine On

    SecRule REMOTE_ADDR "@pmFromFile negativelist.txt" "phase:1,id:2,deny,msg:'Negativelisted IP address'"
  • スクリプトが生成したファイルへのシンボリックリンク
sudo ln -sf /hoge/script/log/negativelist.txt /etc/apache2/sites-enabled/negativelist.txt
  • cron設定
sudo crontab -e -u root

として、

0 2 * * * /path/to/this_script.sh

等と設定すれば、毎日2時に、その日に検知した怪しい動きをシャットダウンしてくれます。

Page 1 of 4

Powered by WordPress & Theme by Anders Norén