タグ: Ubuntu Page 1 of 26

TLSの矛盾で読み解くエージェント偽装の対応。

筆者はUbuntu環境のApache設定で

  • 不審なIP/NWをブロック
  • 過剰にアクセスしてくるクローラーをエージェントで判別してブロック

というセキュリティ対策を取っています。(詳細)

しかし、これは相手もその辺りの呼吸をわきまえていて、

  • まだブロックされていない/もしくは攻撃者がよく使うASN「ではない」アクセス元から
  • 正常なアクセスを装って
  • 情報を袖手したり攻撃の糸口をつかもうとする

パターンが割とあります。今回、それを検知した時のお話。

不自然に見えたログ

例によってURLとIPアドレスはダミーですが、以下のような奇妙なログを見つけました。

192.0.2.10 - - [27/Mar/2026:10:28:03 +0900] "GET /images/?1770681132 HTTP/1.1" 404 5506 "-" "Mozilla/5.0 (Linux; Android 5.0; SM-G900P Build/LRX21T) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.4601.1280 Mobile Safari/537.36"

一見、ただの404エラーではありますが、強烈な違和感を覚えました。

違和感1「古いAndroid端末」

Android 5.0は、2018年頃に公式セキュリティサポート終了。GooglePlay開発者サービスも2024年7月には終わっています。

違和感2「古いChrome」

同じくChrome60。これも2017年と古いバージョンです。

違和感3「TLS1.3貫通」

そんな古いAndroidが筆者のサイトにアクセスできるということはまず、あり得ません。種を明かしてしまうと、筆者のWebサイトは

#SSL対応
  SSLEngine on
    Protocols h2 http/1.1
SSLProtocol             all -SSLv3 -TLSv1 -TLSv1.1 -TLSv1.2

として、TLS1.3未満のアクセスができないようになっています。 この暗号化形式をサポートするようになったのはAndroid10以降。

結論:エージェント偽装。

古いAndroidが新しいSSL暗号が施されたサイトには、そもそもアクセス不可能です。リクエストの段階でハンドシェイクが拒否されるため、404エラーどころかWebサイトそのものが見られません。

しかし、上記のアクセスログは「古いAndroidのバージョンからTLS1.3のSSLログが残る」。つまり、残る結論はほぼ「エージェントを偽装してくるクローラー」に限られます。

アプリ開発者が Conscrypt(Google製のセキュリティライブラリ)などをアプリに同梱している場合は、Android 4.4以降の古い端末でもアプリ単位でTLS 1.3通信を行える場合がありますが、そんな回りくどい方法はないでしょう

措置:新たなエージェント拒否を追加。

筆者の「厄介なエージェントを拒否する」仕組みがこちら。

  • apache側
(省略)
    # botのアクセスリストを外部ファイルから読み込む
    Include /etc/apache2/conf-enabled/bad-bot-list.conf
(省略)
        <RequireAll>
            # bad_bot変数がセットされていたらアクセスを拒否
            Require not env bad_bot
            # それ以外は許可
            Require all granted
        </RequireAll>
  • bad-bot-list.conf
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の箇所に、以下を加えます。

# Android 10.0未満  を排除する
SetEnvIfNoCase User-Agent "Android [1-9]\." bad_bot dontlog

# 10年以上前の古いOS(Windows XP/Vista等)を装うボットも排除
SetEnvIfNoCase User-Agent "Windows NT [3-5]\." bad_bot dontlog

# その他、不自然な挙動を示すUA群
SetEnvIfNoCase User-Agent "^$" bad_bot dontlog

後は

sudo apache2ctl configtest
sudo systemctl reload apache2.service

で、上記、不自然なログのアクセスはピタリと止まりました。

まとめ

不正アクセスにはユーザーのみならず

  • IPアドレス
  • ドメイン

はもとより、OSやエージェントを偽装してくる輩も多いので。

だが…………
マヌケは見つかったようだな

ぐらいの勢いでアクセスログを観察していきましょうというお話でした。

Grwoi/Apacheリバースプロキシ、セキュリティ判断FからB+まで改善した記録

昨日の続き。

HTTP ObservatoryでRedmineサイトをB-→B+に改善しましたが、

筆者環境の

  • Apache 2.4によるリバースプロキシ
  • Growi v7.4.7

環境のHTTP診断もそのぐらいであろうと思ったらまさかのF判定を食らいました。

Fランクの内訳

最初の診断結果では、以下の項目で派手に減点を食らっていました。

  • CSP (Content Security Policy):
    • 未設定 (-25)
  • Cookies:
    • Secure属性なし (-20)
  • HSTS: 認識されず
    • (-20)
  • X-Frame-Options / X-Content-Type-Options:
    • 認識されず (-25)

特に謎だったのが、「Apache側でHeader設定を入れているはずなのに、認識されない(Failed)」という点でした。

アプリとApacheの二重出力。

原因を調査するため、curl -I コマンドで生のレスポンスヘッダーを確認しました。

curl -I https://growi-site
Strict-Transport-Security: max-age=63072000
Strict-Transport-Security: max-age=15552000; includeSubDomains  # <-- 2重に出ている
X-Content-Type-Options: nosniff
X-Content-Type-Options: nosniff # <-- 2重に出ている
Set-Cookie: connect.sid=...; Path=/; HttpOnly  # <-- Secure属性がない

原因の分析:

リバースプロキシ構成では、「バックエンド(アプリ側)が吐き出すヘッダー」と「Apacheが追加しようとするヘッダー」が両方ブラウザに届いてしまい、重複が発生。

診断ツール側が「どの設定を信頼すべきか判断できない」としてエラーになっていたのです。

これを回避するための手段が以下の通りです。

さっくりとした手順

  1. Apacheの設定ファイルを書き換えます。
  2. 設定を反映します。
  3. 設定反映を確認します。

Apacheの設定ファイルを書き換え

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

.confの名前やバックアップディレクトリは自分の環境に合わせます。

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

エラーがないことを確認します。

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

  • 修正前
    Header always set Strict-Transport-Security "max-age=63072000"
    Header always set X-Content-Type-Options "nosniff"
    Header always set X-Frame-Options "SAMEORIGIN"
    Header always set X-XSS-Protection "1; mode=block"
  • 修正後
        # 重複を防ぐため、通常のレスポンスとProxyレスポンスの両方から一旦削除
        Header unset Strict-Transport-Security
        Header always unset Strict-Transport-Security
        Header always set Strict-Transport-Security "max-age=63072000; includeSubDomains; preload"

        Header unset X-Content-Type-Options
        Header always unset X-Content-Type-Options
        Header always set X-Content-Type-Options "nosniff"

        Header unset X-Frame-Options
        Header always unset X-Frame-Options
        Header always set X-Frame-Options "SAMEORIGIN"

        # 足りなかった重要ヘッダーを新規追加
        Header always set Referrer-Policy "strict-origin-when-cross-origin"

        # CSP: アプリの動作を維持しつつ、安全性を高める(object-src 'none' を追加)
        Header always unset Content-Security-Policy
        Header always set Content-Security-Policy "default-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval'; style-src 'self' 'unsafe-inline'; img-src 'self' data: https:; font-src 'self' data:; connect-src 'self' ws: wss:; object-src 'none';"

        # CookieのSecure属性をApache側で強制付与
        Header edit Set-Cookie ^(.-)$ "$1; Secure; SameSite=Lax"

この「2重出力」を解消し、かつアプリ側で足りない属性を付与するために、Apacheの設定を「既存ヘッダーの完全掃除 + 強制セット」という戦略に変更しました。

  • 修正後の差分確認
diff -u /path/to/backup/growi-site.conf.$(date +%Y%m%d) /etc/apache2/sites-available/growi-site.conf

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

-    Header always set X-Content-Type-Options "nosniff"
-    Header always set X-Frame-Options "SAMEORIGIN"
-    Header always set X-XSS-Protection "1; mode=block"
+        # 重複を防ぐため、通常のレスポンスとProxyレスポンスの両方から一旦削除
+        Header unset Strict-Transport-Security
+        Header always unset Strict-Transport-Security
+        Header always set Strict-Transport-Security "max-age=63072000; includeSubDomains; preload"
+
+        Header unset X-Content-Type-Options
+        Header always unset X-Content-Type-Options
+        Header always set X-Content-Type-Options "nosniff"
+
+        Header unset X-Frame-Options
+        Header always unset X-Frame-Options
+        Header always set X-Frame-Options "SAMEORIGIN"
+
+        # 足りなかった重要ヘッダーを新規追加
+        Header always set Referrer-Policy "strict-origin-when-cross-origin"
+        
+        # CSP: アプリの動作を維持しつつ、安全性を高める(object-src 'none' を追加)
+        Header always unset Content-Security-Policy
+        Header always set Content-Security-Policy "default-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval'; style-src 'self' 'unsafe-inline'; img-src 'self' data: https:; font-src 'self' data:; connect-src 'self' ws: wss:; object-src 'none';"
+
+        # CookieのSecure属性をApache側で強制付与
+        Header edit Set-Cookie ^(.-)$ "$1; Secure; SameSite=Lax"

設定反映を確認します。

  • 構文確認
sudo apache2ctl configtest

Syntax OKを確認します。

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

active (running)を確認します。

設定反映の確認

curl -I https://growi-site
Strict-Transport-Security: max-age=63072000; includeSubDomains; preload
X-Content-Type-Options: nosniff
X-Frame-Options: SAMEORIGIN
X-XSS-Protection: 1; mode=block
Referrer-Policy: strict-origin-when-cross-origin
Content-Security-Policy: default-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval'; style-src 'self' 'unsafe-inline'; img-src 'self' data: https:; font-src 'self' data:; connect-src 'self' ws: wss:; object-src 'none';

など、ヘッダーのダブりがないことを確認します。

HTTP Observe に再度アクセスし、セキュリティ診断を行いました。

再スキャンの結果、主要な項目はすべて Passed となり、無事に B+ を獲得。

  • HSTS / XFO / NoSniff: 重複が解消され、正しく認識された。
  • Cookies: Secure/SameSite属性が付き、満点。
  • Referrer Policy: 合格。

なぜ「A+」ではないのか?

CSPにおける 'unsafe-inline''unsafe-eval' が「安全ではない」として減点対象になっています。しかし、これらを外すとモダンなWebアプリ(Wikiのエディタなど)は動かなくなることが多いため、利便性とのトレードオフとして 「B+」は現実的な落とし所と言えます。

まとめ

  1. 診断ツールで「Recognizedできない」と言われたら、まずは curl -I で重複を疑う。
  2. Apache側で Header always unset してから set することで、バックエンドとの競合を確実に排除できる。
  3. CookieのSecure化もApacheの Header edit で一発解決。

HTTP外部診断で公開用RedmineをB-からB+に向上させたときの記録。

HTTP Observe サイトのセキュリティ診断。

これで、筆者が公開しているRedmineのセキュリティを上げたときのメモです。

どう判断されたか

結果はB-。特に引っかかったのは

  • Content Security Policy(CSP)
    • Content Security Policy (CSP) header not implemented
  • Cookies
    • Session cookie set without the Secure flag, but transmission over HTTP prevented by HSTS.

何が問題なのか

クッキーの「剥き出し」状態(Secure Flag の欠如)

セッション情報(ログイン情報を保持する)が記録されたクッキーにSecureフラグが付与されていなかったため、たとえ常時httpsで通信していたとしても、ブラウザの設定や不慮の事故で通信が発生した差違に、クッキーがそのままNWに漏洩するリスクがあります。

これが漏洩し、奪われると、悪意ある第三者のなりすまし(セッションハイジャック)を可能にします。

CSPの未実装

Content-Security-Policy (CSP) ヘッダーが全く存在していなかったため、ブラウザに対して「どのスクリプトを実行していいか」が確認されていない状態。Redmineそのものや導入しているプラグインに脆弱性があった場合、攻撃者は外部から悪意あるスクリプトを注入し、閲覧者のブラウザ上で実行させるXSSに無防備となります。

個の2点を直していきます。

環境

  • Redmine 5.1
  • Apache 2.4
    • 常時SSL化設定済み。
  • Ruby 3.2.3

さっくりとした手順

  • 設定ファイルを作成します。
  • 設定を反映します。
  • セキュリティ診断でのランクアップを確認します。

設定ファイルの作成

cd /path/to/redmine/config && pwd

自分の環境のRedmineconfigディレクトリに合わせます。(筆者環境/home/www-data/redmine/config)

  • 設定ファイル作成前確認
ls -l ./initializers/additional_environment.rb

ファイルが無いことを確認します。

  • 設定ファイル作成
sudo -u www-data tee ./initializers/additional_environment.rb > /dev/null << 'EOF'
Rails.application.configure do
  # Cookie の Secure フラグを立てます
  config.session_store :cookie_store, key: '_redmine_session', secure: true

  # CSP の設定を追加します
  config.content_security_policy do |policy|
    policy.default_src :self, :https
    policy.font_src    :self, :https, :data
    policy.img_src     :self, :https, :data
    policy.object_src  :none
    # Redmine はインラインのスクリプトとスタイルを多用するため、これを許可します。
    policy.script_src  :self, :https, :unsafe_inline, :unsafe_eval
    policy.style_src   :self, :https, :unsafe_inline
  end

  # ブラウザが違反を報告するだけで、ブロックしないモードです。(デバッグ用)
  # config.content_security_policy_report_only = true
end
EOF

設定ファイル作成前確認

ls -l ./initializers/additional_environment.rb

ファイルがあることを確認します。

設定反映を確認します。

  • 構文確認
sudo apache2ctl configtest

Syntax OKを確認します。

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

active (running)を確認します。

まとめ

HTTP Observe に再度アクセスし、セキュリティ診断を行いました。

結果はB+。

  • Contents Security Policy (CSP)
    • Content Security Policy (CSP) implemented unsafely. This includes 'unsafe-inline' or data: inside script-src, overly broad sources such as https: inside object-src or script-src, or not restricting the sources for object-src or script-src.
    • Remove unsafe-inline and data: from script-src, overly broad sources from object-src and script-src, and ensure object-src and script-src are set.

依然として課題は残るものの、ここを下手にいじると、外部プラグインとの連携などに支障を来します。

これらを狙った脆弱性はWAFで埋めて生きつつ、これらの改善案を図っていくのが今後の課題です。

Nextcloudで大容量ファイルを安定して扱うためのApacheチューニング:RequestReadTimeoutの調整

スマートフォンからの画像を自分のPCに転用する形で用いているNextcloud。

アップロードが途中で失敗したり、特定の条件下で接続が切れたりすることがあります。Apacheビルトインのmod_reqtimeout設定を最適化し、サーバーの安定性と利便性を両立させるためのチューニングを行いました。

環境

  • Nextcloud 32
  • Apache 2.4
  • PHP-FPM8.3
  • Ubuntu 24.04

そもそもmod_reqtimeoutとは?

Slowloris(スローロリス)攻撃への対策

このモジュールがビルトインされたのは、Slowlorisという、非常に低コストで強力な攻撃手法への対抗があります。

通常のDoS攻撃は大量の通信を送りますが、Slowlorisは逆に「極めてゆっくり」通信します。

  1. サーバーに接続を開始する。
  2. リクエスト(ヘッダーやボディ)を、タイムアウトにならないギリギリの遅さで、1バイトずつ小出しに送る。
  3. サーバー側は「まだデータが来るはずだ」と判断し、その接続(スレッドやプロセス)を維持し続ける。

結果 サーバーの同時接続数の上限が攻撃者の「待ち」状態で埋まってしまい、正規のユーザーがアクセスできなくなります。

mod_reqtimeout は、この「嫌がらせ」を許さないための防衛線です。

サーバーリソースの適正管理

Apacheサーバーが1つの接続を維持するには、メモリやCPUリソースを消費します。
もしタイムアウト設定がない、あるいは極端に長い場合、以下のような問題が発生します。

  • ゾンビ接続の蓄積: ネットワークが切れたのにクローズ処理が終わっていない接続が残り続ける。
  • リソースの浪費: 応答の遅いクライアント(意図的か否かに関わらず)のために、サーバーの貴重な作業枠をいつまでも空けておくことになります。

なぜこれがNextcloudで徒になるのか

Nextcloudのデータの性質と通信の仕組みにあります。

巨大なファイルを細切れにする

Nextcloudは、数MBから数GBあるような大きなファイルを送る際、一気に送らずにファイルを分割して、「チャンク(塊)」に分割して送信します。

  • 動作の流れ:
    • チャンクAを送信 → サーバーが処理 → チャンクBを送信……
  • 問題点:
    • チャンクとチャンクの間に、クライアント側でのファイル読み込みやハッシュ計算などで一瞬「無通信」の時間が流れます。
  • 結果:
    • Apacheのmod_reqtimeoutは、この無通信を先のSlowlorisと誤認して、接続をバッサリ切ってしまいます。

モバイル回線の不安定さ

Nextcloudは外出先のスマートフォンから使うことを想定しています。

  • 電波の瞬断:
    • 移動中にWi-Fiから4G/5G回線に切り替わったりする。
  • 上り速度の制限:
    • モバイル回線は「下り」は速くても「上り」が極端に遅いことが多い。

これも、先のSlowloris攻撃と見做されてしまいます。

チューニングの必要性

だからといって、このmod_reqtimeoutを無効にすると、それらの攻撃への備えがなくなります。

Nextcloudの利便性を高めつつ不審な攻撃を守るというのは

「任務」は遂行する「部下」も守る
お前ごときに「両方」やるというのはそう難しいことじゃあないな

ぐらいの精神でやっていきましょう。

さっくりとした手順

  • mod_reatimeoutの設定ファイルのバックアップを取る。
  • mod_reatimeoutの設定ファイルを修正する。
  • サービス再起動を行う。

mod_reatimeoutの設定ファイルのバックアップ

  • 念のためのモジュール確認
sudo apache2ctl -M |grep req

reqtimeout_module (shared)を確認します。(apacheをapt等で入れていれば、まず入っています)

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

/path/to/backup/directoryは自分の環境に合わせます。

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

エラーがないことを確認します。

mod_reatimeoutの設定ファイルを修正

/etc/apache2/mods-available/reqtimeout.confを管理者権限で修正します。

筆者は以下のように行いました。

-RequestReadTimeout body=10,minrate=500
+RequestReadTimeout body=20,minrate=500

具体的には、リクエストボディ(データの送信本体)の読み取り開始を待機する時間を10秒から20秒へ延長しています。

  • body=20:
  • リクエストボディの最初の1バイトを待つ時間を20秒に設定。
  • minrate=500:
  • データが送り始められた後、最低でも秒間500バイトの転送速度を要求する。これより遅い状態が続くとタイムアウトします。
  • ファイルの差分確認
diff -u /path/to/backup/directory/reqtimeout.conf.$(date +%Y%m%d) /etc/apache2/mods-available/reqtimeout.conf 

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

-RequestReadTimeout body=10,minrate=500
+RequestReadTimeout body=20,minrate=500

設定反映

  • 構文チェック
sudo apache2ctl configtest

Syntax OKとなることを必ず確認してください。でないと、apacheサービスが停止したままとなってしまい、サービス断が発生します。

  • Apache サービス再起動
sudo systemctl reload apache2.service
  • Apacheサービス再起動確認
systemctl status apache2.service

active(running)を確認します。

まとめ

Nextcloudサーバーにおいて、RequestReadTimeout のbody待ち時間を延長することは、「モバイル環境や大容量ファイル送信時の安定性」を向上させるために非常に有効な手段です。

もし、ログ(Apacheのerror_log)に The timeout specified has expiredrequest body read timeout といった記録が残っている場合は、この値を調整してみることをお勧めします。

  • 参考コマンド
sudo grep "request body read timeout" /var/log/apache2/error.log

Nextcloud Ver 32にアップデート後、高性能バックエンドのDockerをアップデート。

警告: 実行中のバージョン: 2.0.4~docker; サーバーはこのTalkバージョンの全ての機能をサポートしていません。欠落している機能: chat-relay

と出たので、それに対応していきます。

環境

  • Nextcloud 33.0
  • Dockerを利用して高性能バックエンドサーバ(Signaling Server)を構築。構築手順
  • それ以外はLAMP環境。
    • Apache 2.4
    • MySQL
    • PHP-FPM
    • その上でNextcloud

アップデートは、こちらの手順で、コマンドラインから行いました。

その後、管理画面で上記のエラーが出たという次第です。

やっぱり必要なフェイズ・ゼロ

このNextcloudを個人的に運用しているのならばそのまま行って構いません。しかし、これを組織で運用しているとなると話はまるで違います。

  • NextcloudのアップデートによりDockerコンテナもアップデートが必要。
  • ついてはこの計画でサーバ設定を行う
  • そのため、追加で作業時間をいただきたい
  • 作業時間は○時頃、○分程度で終わる。その間、Nextcloudは使えなくなる
    など、利用者への周知という名の政治交渉が必要になります。この運用者の政治的な立ち位置(担当者/担当部門が強権を振るえるか否か)でも言い方や手段が決まってきます。そこは状況に応じていきましょう。

※ 検証環境を用意できる程度には時間と予算と環境に余裕がある方は、その環境にいることを感謝しつつ、検証を重ねていきましょう。

さっくりとした手順

  1. Nextcloudのメンテナンスモードを有効化します。
  2. Dockerの設定ファイルを修正します。
  3. Dockerコンテナを最多値揚げします。
  4. Nextcloudのメンテナンスモードを無効化します。
  5. エラーの解消を確認します。

メンテナンスモードを有効化

  • Nextcloudのルートディレクトリ移動
cd /path/to/nextcloud/root/directory && pwd

自分の環境に合わせます。(筆者環境/home/www-data/nextcloud)

  • メンテナンスモード有効化
sudo -u www-data php occ maintenance:mode --on
  • メンテナンスモード確認

運用中のNextcloudのURLにアクセスし、メンテナンスモードであることを確認します。

設定ファイル (server.conf) の構成

  • ファイルのバックアップ
sudo cp -pi /hoge/docker/files/nextcloud-signaling/server.conf /path/to/backup/directory/server.conf.$(date +%Y%m%d)

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

  • ファイルのバックアップ確認
diff -u /path/to/backup/directory/server.conf.$(date +%Y%m%d) /hoge/docker/files/nextcloud-signaling/server.conf

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

  • server.confファイル修正

chat-relay 機能を有効にするため、以下のように項目を付け加えます。

  • [chat] セクション(新規追記)
[chat]
enabled = true

追記したら保存を行います。

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

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

+ [chat]
+ enabled = true

Docker アップデート・再起動

これが地味にハマりました。古い docker-compose (v1.29.x等) を使用している環境だと、イメージのメタデータ構造の違いによるエラー(KeyError: 'ContainerConfig')が発生。

それを避けるための手順です。

  • 最新イメージの直接取得

docker-compose を介さず、Docker本体で最新イメージをプルする。

sudo docker pull strukturag/nextcloud-spreed-signaling:latest
sudo docker pull nats:2.9
  • 不完全なコンテナの掃除

作成失敗などで残った残骸を削除し、競合を防ぐ。

sudo docker container prune -f
  • コンテナの起動
sudo docker-compose up -d

Nextcloudのメンテナンスモードの無効化

  • メンテナンスモード無効化
sudo -u www-data php occ maintenance:mode --off
  • メンテナンスモード確認

運用中のNextcloudのURLにアクセスし、管理画面に入ります。

Nextcloud管理画面での反映

Nextcloudの「設定」>「Talk」にある高性能バックエンド設定で、URL横の「チェックマーク(保存)」を押し、chat-relayAvailable features に含まれたことを確認して対処完了です。

改めて思ったこと

Dockerは確かに便利な代物ですが、管理が複雑になっていくというのが難点。それ故、Dockerは最小限にして登録していきたいものです。

緊急対応:Ubuntu搭載PCが不調となったとき(initramfsエラー)の対処。

宅内でのサーバとして立てているUbuntu20.04という完全にEOLを迎えている機体。それがエラーを起こしたときのメモです。

1. 発生した事象

Ubuntu 20.04を入れているミニPCの起動時、画面に以下のメッセージが表示され、デスクトップが立ち上がらずに (initramfs) というプロンプトで停止しました。

  • sgx: disabled by BIOS
  • (initramfs) _ (入力待ち状態)

2. 原因切り分け

以下、Geminiからのアドバイス。

  • SGXのメッセージ: これは単なる「通知」であり、起動不可の直接的な原因ではないことが多い。
  • initramfsでの停止: 真の原因は、ファイルシステムの整合性エラー。強制終了や停電などで、Ubuntuがインストールされているディスク領域(パーティション)が正常に読み込めなくなったために発生します。

3. 対処手順(修復方法)

これに従って対処を行いました。

エラーを起こしたパーティションの特定

(initramfs) プロンプトで exit を入力

エラーの詳細(どのパーティションが壊れているか)を確認するために、まず exit と打ちます。

エラーメッセージからデバイス名を特定

The root filesystem on /dev/sdb2 requires a manual fsck

というメッセージが表示されました(筆者環境)これをメモします。

パーティション修復

特定したデバイス名に対して修復コマンドを実行します。

fsck /dev/sdb2 -y

-y オプションを付けることで、すべての修復箇所を自動で「Yes」として処理します。

修復完了後の再起動

FILE SYSTEM WAS MODIFIED と表示されたら、以下のコマンドで再起動(またはブート続行)を試みます。

reboot

または

exit

結局原因は?

機器が古すぎたためのディスクエラー につきます。

これが仕事でしたら「さっさと取り替えろ」「いや、取り替えないように準備する」なのですが、自分の環境なのでそうはいかず。

なんとか予算と時間を見つけてリプレースをする必要に迫られました。

『ONE OUTS』システム番外:ASNごとIPSetで弾いたときの記録

はじめに

Webサイトを公開していると、必ず遭遇するのが「自動化された嫌がらせ」です。

ログを確認すると、1秒ごとに異なるURLへPOSTリクエストを送り続けてくるボットたちがいます。

今回は、特定のネットワークセグメントから行われた執拗な「PHPUnitのバックドア設置スキャン」に対し、アプリケーション層(ModSecurity)だけでなく、カーネル層(IPset)で物理的に遮断した際のメモです。

前提

以下の手順でipsetを導入していること。

1. ログに残る「執拗なアクセス」

ある日、アクセスログを眺めていると、以下のような文字列が並んでいることに気づきました。

# access.log の例 (ダミーデータ)
192.0.2.239 - - [06/Mar/2026:14:35:07] "POST /lib/phpunit/Util/PHP/eval-stdin.php HTTP/1.1" 404 3067 "-" "Mozilla/5.0..."
192.0.2.239 - - [06/Mar/2026:14:35:08] "POST /lib/phpunit/src/Util/PHP/eval-stdin.php HTTP/1.1" 404 3067 "-" "Mozilla/5.0..."
...

攻撃者は /vendor//lib/ などのディレクトリを総当たりし、脆弱性のあるPHPUnitファイルを探しています。

このアクセス元を調べてみました。

2. ASN(拠点のプロバイダー)を特定する

しかし、調べてIPをブロックするだけでは終わりません。相手は同じネットワーク(ASN)内の別IPから再び攻撃してくるからです。
そこで whois コマンドを用いて、この攻撃者が所属するネットワークの広がりを確認しました。

  • 攻撃者のIPから所属ASNを特定する

※ダミーIPにしています。

whois -h whois.radb.net -- '192.0.2.239'

結果、このIPは特定のホスティング事業者が管理する「広大なネットワーク帯域」の一部であることが判明しました(仮に AS_DUMMY_99 とします)。

3. 「IPset」による物理的遮断

単なるIPの拒否リストでは、数千に及ぶボットのIPを捌ききれません。数千ものIP/NWをufwでブロックすると、たちどころにその膨大なルールでufwは機能不全を起こします。

それが、上述したLinuxカーネルレベルで高速にパケットを破棄できる ipset を使用した形です。

手順:ASNを広域ブロックする

まず、そのASNが保持する全CIDRを取得し、aggregate コマンドで最適なサイズに集約します。

  • ASNからルートを取得し、整理する
whois -h whois.radb.net -- '-i origin AS_DUMMY_99' | grep -E '^route:' | aggregate > ban_list.txt

(aggregateがない場合はsudo apt install aggregateでインストールします)

これにより、手作業では到底管理できない広大なネットワーク帯域を、数行のルールに圧縮できます。最後に、このルールを ipset に投入します。

筆者手順ではこの形。

sudo ipset add ufw-blocklist IPアドレス・NWアドレス
sudo ipset save ufw-blocklist -f /etc/ufw/ipsets.save

まとめ

なぜASNごと切り捨てるのかは「彼らの根城だから」につきます。(そのあたりは筆者コラムに書いています)

そもそも、これらのボットは、海外の法が緩いVPSや出所の怪しいプロバイダに巣喰っています。

こん城ん奴ばら共は糞じゃ
撫で切りぞ
根切りぞ

ぐらいのブロックは必要です。

続:ipsetによるufwブロックの効率化スクリプト。

このスクリプトのver.2といったところです。

#!/bin/bash

# セット名の定義
SET_NAME="ufw-blocklist"
SAVE_FILE="/etc/ufw/ipsets.save"

# 1. root権限チェック
if [[ $EUID -ne 0 ]]; then
   echo "エラー: このスクリプトは sudo または root 権限で実行してください。"
   exit 1
fi

# 保存用関数 (変更があった場合のみ呼び出し)
confirm_and_save() {
    echo "------------------------------------------"
    read -p "変更をファイルに保存しますか? ($SAVE_FILE) (y/n): " confirm
    if [[ "$confirm" =~ ^[Yy]$ ]]; then
        ipset save "$SET_NAME" -f "$SAVE_FILE"
        echo "保存完了しました。"
    else
        echo "保存をスキップしました。"
    fi
}

# ipsetが存在しない場合に作成
if ! ipset list "$SET_NAME" > /dev/null 2>&1; then
    echo "情報: $SET_NAME が見つからないため新規作成します。"
    ipset create "$SET_NAME" hash:net
fi

while true; do
    echo "=========================================="
    echo " ipset 管理メニュー ($SET_NAME)"
    echo "=========================================="
    echo "1) ネットワーク帯/IP を追加 (例: 192.0.2.0/24 203.0.113.5)"
    echo "2) ネットワーク帯/IP を削除"
    echo "3) 現在のリストを表示 (list)"
    echo "q) 終了"
    echo "------------------------------------------"
    read -p "番号を選択してください: " choice

    case $choice in
        1)
            echo "【追加モード】追加したいNW帯を入力してください(空エンターでメニューに戻る)"
            echo "例: 203.0.113.0/24 198.51.100.0/24"
            changed=false
            while true; do
                read -p "追加対象: " targets
                [ -z "$targets" ] && break
                
                for target in $targets; do
                    if ipset add "$SET_NAME" "$target" 2>/dev/null; then
                        echo "  [成功] $target を追加しました。"
                        changed=true
                    else
                        echo "  [失敗] $target は既にあるか、形式が不正です。"
                    fi
                done
            done
            [ "$changed" = true ] && confirm_and_save
            ;;
        2)
            echo "【削除モード】削除したいNW帯を入力してください(空エンターでメニューに戻る)"
            changed=false
            while true; do
                read -p "削除対象: " targets
                [ -z "$targets" ] && break
                
                for target in $targets; do
                    if ipset del "$SET_NAME" "$target" 2>/dev/null; then
                        echo "  [成功] $target を削除しました。"
                        changed=true
                    else
                        echo "  [失敗] $target が見つからないか、形式が不正です。"
                    fi
                done
            done
            [ "$changed" = true ] && confirm_and_save
            ;;
        3)
            echo "--- 現在の登録内容 ---"
            ipset list "$SET_NAME" | grep -A 100 "Members:"
            ;;
        q)
            echo "終了します。"
            exit 0
            ;;
        *)
            echo "無効な選択です。"
            ;;
    esac
    echo ""
done

試作版との違いは

  1. 連続入力に対応。ASNが持つNWを連続で断つことができます。
  2. 既にブロックされているNW/IPアドレスをチェックする機能を追加。

まだ詰められる感じなのでもう少し続けます。

Ubuntu 24.04にapache mod_securityを導入。

ApacheのWAFモジュールであるmod_securityを導入します。

  • AWS 時代から早々とインストールしていた
  • 各種不審なIPアドレスを弾くための盾

として機能している、筆者のVPS運用の核となる技術。その2026年版です。

そもそもWAFとは?

WAFとはWeb Application Firewallの略で、Webアプリケーション層の脆弱性を狙った攻撃を防ぐためのセキュリティシステムです。

  • UFWやFail2banがIPアドレスやポート番号といった家の玄関を監視するのに対し、WAFは、Webサーバーへ届く手紙(HTTPリクエスト)の中身を解析し、悪意あるスクリプトやコマンドの有無をチェックします。
  • UFWでは、通常のポート(80番や443番)を通る攻撃は防げませんが、WAFは攻撃の内容を解析し、独自のルールセットに基づき「このアクセスは許可するが、このコードがアクセスすることは許可しない」という、より柔軟で厳しいセキュリティチェックを施します。

この機能により、Webサーバーやアプリケーション本体に脆弱性が見つかったとしても、WAFが前段の盾としてこれをカバーできます。

ModSecurityとは?

ModSecurityは、IIS/Apache/Nginxといった主要なWebサーバープログラムにモジュールとして組み込みが可能なオープンソースのWAFです。

  • 導入コスト: ライセンス費用が不要であり、既存のWebサーバーと連携する形で容易に組み込めます。
  • 柔軟性: OSSであるため、高い柔軟性を持ち、設定(チューニング)次第でピンポイントの防御や包括的な防御を併せ持つことができます。

備考(バージョンの選択):

本稿で導入するModSecurityは、Ubuntu 24.04系のパッケージ管理で提供されるEOL (End-of-Life) となっているv2ですが、機能性は単一VPSの防御としては十分です。

v3への移行は、セキュリティ強度とメンテナンス性を考慮し、パッケージ化ないしはOSアップデートなどのタイミングでまた後日検討していきます。

環境

  • Ubuntu 24.04 (22.04でも動作確認)
  • Apache 2.4

※ パッケージ管理にaptitudeを用いています。必要に応じてaptに読み替えてください。

さっくりとした手順

  1. mod_securityのインストールを行います。
  2. mod_securityの設定を行います。
  3. Apacheのバーチャルサイトにmod_securityを組み込みます。
  4. 設定を反映して動作を確認します。

mod_securityのインストールを行います。

  • パッケージ全体のアップデート
sudo aptitude update
  • mod_securityインストール
sudo aptitude install libapache2-mod-security2
  • インストール確認
sudo apache2ctl -M |grep security
security2_module (shared)

と表示されていることを確認します。

ModSecurityの設定

  • 設定ファイル書き換え
sudo mv /etc/modsecurity/modsecurity.conf-recommended /etc/modsecurity/modsecurity.conf

推奨ファイルをそのまま設定ファイルとして書き換えます。

OWASP Core Rule Set (CRS)のインストールと設定

  • ディレクトリ移動
cd /usr/share/modsecurity-crs && pwd
  • ルールセットのダウンロード
sudo git clone https://github.com/coreruleset/coreruleset.git
  • ルールセットの設定書き換え
sudo mv /usr/share/modsecurity-crs/coreruleset/crs-setup.conf.example /usr/share/modsecurity-crs/coreruleset/crs-setup.conf

mod_securityモジュールにCRSを読み込む設定を追記

  • ディレクトリ移動
cd /etc/apache2/mods-available/ && pwd
  • ファイルのバックアップ
sudo cp -pi security2.conf /path/to/backup/directory/security2.conf.$(date +%Y%m%d)

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

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

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

  • ファイル追記

/etc/apache2/mods-available/security2.confを、以下の差分になるように教義・信仰に沿ったエディタで編集します。(要root権限)

 <IfModule security2_module>
-       # Default Debian dir for modsecurity's persistent data
-       SecDataDir /var/cache/modsecurity
+    # Default Debian dir for modsecurity's persistent data
+    SecDataDir /var/cache/modsecurity

-       # Include all the *.conf files in /etc/modsecurity.
-       # Keeping your local configuration in that directory
-       # will allow for an easy upgrade of THIS file and
-       # make your life easier
-        IncludeOptional /etc/modsecurity/*.conf
+    # Include all the *.conf files in /etc/modsecurity.
+    # Keeping your local configuration in that directory
+    # will allow for an easy upgrade of THIS file and
+    # make your life easier
+    IncludeOptional /etc/modsecurity/*.conf

-       # Include OWASP ModSecurity CRS rules if installed
-       IncludeOptional /usr/share/modsecurity-crs/*.load
+    # --- OWASP Core Rule Set (CRS) の読み込み ---
+
+    # 1. CRSのセットアップファイルを読み込む(必須)
+    IncludeOptional /usr/share/modsecurity-crs/coreruleset/crs-setup.conf
+    
+    # 2. CRSのルールファイルを読み込む
+    #    パフォーマンス問題を起こすSQLデータ漏洩検知ルールを除外
+    IncludeOptional /usr/share/modsecurity-crs/coreruleset/rules/REQUEST-*.conf
+    IncludeOptional /usr/share/modsecurity-crs/coreruleset/rules/RESPONSE-950-DATA-LEAKAGES.conf
+    IncludeOptional /usr/share/modsecurity-crs/coreruleset/rules/RESPONSE-952-DATA-LEAKAGES-JAVA.conf
+    IncludeOptional /usr/share/modsecurity-crs/coreruleset/rules/RESPONSE-953-DATA-LEAKAGES-PHP.conf
+    IncludeOptional /usr/share/modsecurity-crs/coreruleset/rules/RESPONSE-954-DATA-LEAKAGES-IIS.conf
+    IncludeOptional /usr/share/modsecurity-crs/coreruleset/rules/RESPONSE-959-BLOCKING-EVALUATION.conf
+    IncludeOptional /usr/share/modsecurity-crs/coreruleset/rules/RESPONSE-980-CORRELATION.conf
+    IncludeOptional /usr/share/modsecurity-crs/coreruleset/rules/RESPONSE-999-EXCLUSION-RULES-AFTER-CRS.conf

→ 修正後のsecurity2.conf全文

<IfModule security2_module>
    # Default Debian dir for modsecurity's persistent data
    SecDataDir /var/cache/modsecurity

    # Include all the *.conf files in /etc/modsecurity.
    # Keeping your local configuration in that directory
    # will allow for an easy upgrade of THIS file and
    # make your life easier
    IncludeOptional /etc/modsecurity/*.conf

    # --- OWASP Core Rule Set (CRS) の読み込み ---

    # 1. CRSのセットアップファイルを読み込む(必須)
    IncludeOptional /usr/share/modsecurity-crs/coreruleset/crs-setup.conf

    # 2. CRSのルールファイルを読み込む
    #    パフォーマンス問題を起こすSQLデータ漏洩検知ルールを除外
    IncludeOptional /usr/share/modsecurity-crs/coreruleset/rules/REQUEST-*.conf
    IncludeOptional /usr/share/modsecurity-crs/coreruleset/rules/RESPONSE-950-DATA-LEAKAGES.conf
    IncludeOptional /usr/share/modsecurity-crs/coreruleset/rules/RESPONSE-952-DATA-LEAKAGES-JAVA.conf
    IncludeOptional /usr/share/modsecurity-crs/coreruleset/rules/RESPONSE-953-DATA-LEAKAGES-PHP.conf
    IncludeOptional /usr/share/modsecurity-crs/coreruleset/rules/RESPONSE-954-DATA-LEAKAGES-IIS.conf
    IncludeOptional /usr/share/modsecurity-crs/coreruleset/rules/RESPONSE-959-BLOCKING-EVALUATION.conf
    IncludeOptional /usr/share/modsecurity-crs/coreruleset/rules/RESPONSE-980-CORRELATION.conf
    IncludeOptional /usr/share/modsecurity-crs/coreruleset/rules/RESPONSE-999-EXCLUSION-RULES-AFTER-CRS.conf
</IfModule>

※ なぜここまで除外するか?

この、RESPONSE-9x系のルールは、ページの内容に機密情報(クレジットカードのデータなど)が入っていないかを精査します。

これは重要なものですが、昨今のAIボットによる過剰なクロールが挟むと、サーバそのものへの負荷を強め、更にログの圧迫(実際にサーバ容量120GB全てを食い尽くしました)とサーバダウンにつながります。

こちらは個人サイト、単一VPSの運用を旨としているため、ここに関するデータはオミットです。 その分、他の設定の補強でセキュリティ強度を担保します。

例外ルールを追記

/usr/share/modsecurity-crs/coreruleset/rules && pwd

配下に

REQUEST-900-EXCLUSION-RULES-BEFORE-CRS.confファイルを作成します。

#
# === CRS Exclusions - Before Rules Execution (Organized) ===
#

# ===================================================================
# 1. 共通ルール・汎用ルール (General/Common Rules)
# ===================================================================

# 1-1. 遅い通信(Slowloris)対策
# 矛盾するConnectionヘッダーを持つリクエストを遮断
SecRule REQUEST_HEADERS:Connection "@rx (?i)(?:keep-alive(?:,\sclose|,\skeep-alive)|close(?:,\skeep-alive|,\sclose))" \
    "id:10001,phase:1,t:none,block,msg:'[CUSTOM RULE] Contradictory Connection header, possible Slowloris probe.',tag:'application-attack',tag:'PROTOCOL_VIOLATION/INVALID_HREQ',setvar:'tx.inbound_anomaly_score_pl1=+%{tx.critical_anomaly_score}'"

# 1-2. IPアドレス直打ちアクセス対策
SecRule REQUEST_HEADERS:Host "@rx ^[\d.]+(:\d+)?$" \
    "id:10002,\
    phase:1,\
    deny,\
    status:404,\
    log,\
    msg:'[CUSTOM RULE] Host header is a numeric IP address (incl port). Blocked immediately.',\
    tag:'application-attack',\
    tag:'PROTOCOL_VIOLATION/INVALID_HREQ'"

# 1-3. Hostヘッダーが存在しない場合は即ブロック
SecRule &REQUEST_HEADERS:Host "@eq 0" \
    "id:10003,\
    phase:1,\
    deny,\
    status:404,\
    log,\
    msg:'[CUSTOM RULE] Missing Host Header. Blocked immediately.'"

なぜこの設定が必要なのか?

「雑なスキャナー/クローラーをまとめてブロックする」にあります。

  • 攻撃者は、 Connection: keep-alive, close という通常ではありえないヘッダーでサーバを枯渇させることが非常に多いです。(Slowloris などのDoS攻撃ツール)
  • 攻撃者のほぼ大半は、ドメイン名ではなくIPアドレスを指定。そして、Hostヘッダーを指定せずに無差別にスキャンを行います。

「ブラウザを用いて実際にアクセスする」方が

  • 矛盾したヘッダー
  • IPアドレス直打ち
  • Hostヘッダー抜きのアクセス

はあり得ません。これら雑なスキャナー/クローラーにWAFの計算力を与える必要はありません。

  • 設定追記の整合性を確認
sudo apache2ctl configtest

Syntax OKを確認します。

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

active (running) を確認します。

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

稼働済みのApacheバーチャルサイトの設定ファイルをいじります。バックアップ確認は入念に行ってください。

  • ディレクトリ移動
cd /etc/apache2/sites-available && pwd
  • バーチャルサイトの設定ファイルバックアップ
sudo cp -pi your_site.conf /path/to/backup/directory/your_site.conf.$(date +%Y%m%d)

.confファイルやバックアップディレクトリは自分の環境を指定します。

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

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

  • ファイル追記

/etc/apache2/sites-available/your_site.confを、以下の差分になるように教義・信仰に沿ったエディタで編集します。(要root権限)

# Mod Security

## ModSecurity有効化
SecRuleEngine On
## ModSecurity検知モード
### 検知モードで動かす場合はSecRuleEngine Onをコメントアウトしてこちらを有効化します
#SecRuleEngine DetectionOnly

## ファイルのアップロードをできるようにします。
SecRequestBodyInMemoryLimit 524288000
SecRequestBodyLimit 524288000

## テスト用の検知パラメータを付け加えます。
    SecRule ARGS:modsecparam "@contains test" "id:4321,deny,status:403,msg:'ModSecurity test rule has triggered'"
  • ファイル差分
diff -u /path/to/backup/directory/your_site.conf.$(date +%Y%m%d) your_site.conf
+# Mod Security
+
+## ModSecurity有効化
+SecRuleEngine On
+## ModSecurity検知モード
+### 検知モードで動かす場合はSecRuleEngine Onをコメントアウトしてこちらを有効化します
+#SecRuleEngine DetectionOnly
+ 
+## ファイルのアップロードをできるようにします。
+SecRequestBodyInMemoryLimit 524288000
+SecRequestBodyLimit 524288000
+
+## テスト用の検知パラメータを付け加えます。
+    SecRule ARGS:modsecparam "@contains test" "id:4321,deny,status:403,msg:'ModSecurity test rule has triggered'"
+
  • 設定追記の整合性を確認
sudo apache2ctl configtest

Syntax OKを確認します。

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

active (running) を確認します。

mod_security動作確認

  1. ブラウザで、上記の設定を行ったWebサイトにアクセスし、閲覧できることを確認します。
  2. アドレスバーの末尾に?modsecparam=testを追加してアクセスします。

403 Forbidden

のように、アクセスできないことを確認します。

また、サーバでも

sudo cat /path/to/sites_log/directory/sites_error.log

※ログの格納場所やログの名前は自分の環境に合わせます。

を開き、

ModSecurity: Access denied with code 403 (phase 2). String match "test" at ARGS:modsecparam. [file "/etc/apache2/sites-enabled/your_site.conf"] [line "53"] [id "4321"] [msg "ModSecurity test rule has triggered"] [hostname "host_address"] [uri "/"] [unique_id "xxxxxxx"]

のように、エラーが発生していることを確認します。

備考

WordPress、Redmine等のWebアプリは自身の操作によって「不審なアクセス」として遮断することが極めてよくあります。(偽陽性)

そのため、テストを行った後は

## ModSecurity有効化
#SecRuleEngine On
## ModSecurity検知モード
### 検知モードで動かす場合はSecRuleEngine Onをコメントアウトしてこちらを有効化します
SecRuleEngine DetectionOnly

として検知モードとして動かした方が良いでしょう。

Mod_Securityが検知した「点火テスト」確認

Webサーバを運営していてよかったことの一つは、「悪意ある攻撃」をリアルタイムで見られること、に尽きます。

2026/02/17も、そのようなログを見かけました。どのような攻撃で、どんな悪意があったのかのメモです。

さっくりとした攻撃概要

  1. 攻撃者はNode.js系のサービスが生きているかを確認した。
  2. その上で、サーバ乗っ取りを画策した。

環境・備考

  • Apache 2.4
  • ModSecurity v2 / CRSを利用

以下、ご紹介するログは実際のログですが、

  • IPアドレス
  • どのサイトに来たか

は別のものに置き換えています。これはプライバシー配慮という点ではなく「攻撃者に名前を与えない」から来るものです。

攻撃ログ(抜粋)

攻撃者は、Unix系とWindows系の両方のサーバーを想定し、2回に分けて「点火テスト」を試みています。

A. Unix/Linuxターゲットのペイロード

[Tue Feb 17 03:16:50 2026] [security2:error] [client 192.0.2.55] 
ModSecurity: Access denied with code 403 (phase 2). 
[id "932130"] [msg "Remote Command Execution: Unix Shell Expression Found"]
[id "934100"] [msg "Node.js Injection Attack 1/2"]

[data "Matched Data: var res=process.mainModule.require('child_process').execSync('echo $((40261*44548))').toString().trim(); ..."]

[hostname "sub.example.jp"] [uri "/"]

B. Windowsターゲットのペイロード

[Tue Feb 17 03:16:51 2026] [security2:error] [client 192.0.2.55] 
ModSecurity: Warning. [id "932120"] [msg "Remote Command Execution: Windows PowerShell Command Found"]
[data "Matched Data: var res=process.mainModule.require('child_process').execSync('powershell -c 40261*44548').toString().trim(); ..."]

攻撃の構造解析:何が行われようとしたのか

この攻撃はサーバー乗っ取りを目的としたものです。

「点火テスト」による生存確認

攻撃コードの中にある 40261*44548 という計算式が肝です。

  • 手口:
    • サーバー上でこの計算を実行させ、その結果(1,793,526,428)をレスポンスとして返させようとしています。
  • 意図:
    • もし計算結果が返ってくれば、そのサーバーは「外部からの命令をそのまま実行する状態」にあることが証明され、攻撃者は本格的なマルウェアの設置へ移行します。

Node.jsの深部への侵入

  • 手口:
    • process.mainModule.require('child_process') という、Node.jsの最も強力な権限を持つ命令を呼び出そうとしています。
  • 意図:
    • これが成功すると、サーバー上で任意のOSコマンド(ファイルの削除、パスワードの窃取など)が実行可能になります。

プロトタイプ汚染 (Prototype Pollution)

  • 手口:
    • __proto__ という特殊なプロパティを操作しようとしています。
  • 意図:
    • アプリケーション全体の挙動を「根底から書き換える」手法です。Next.jsの内部ロジックを捻じ曲げ、セキュリティチェックをバイパスしようとしています。

特記事項

今回のログで最も注目すべきは、ModSecurityの異常スコア(Anomaly Score)が 45点〜50点というそこそこ高い数値を叩き出している点です。

  • Unixシェルの構文検知(RCE)
  • Node.jsの危険な関数検知(Injection)
  • プロトタイプ汚染の検知(JavaScript攻撃)
  • PowerShellの使用検知(Windows攻撃)

これら複数のフィルタが同時にに反応したため、ModSecurityはこれは間違いなく脅威であると断定し、即座に遮断しました。

まとめ

攻撃は「いきなり行う」というよりは「どの攻撃が効くか」を確認してからリソースを集中していきます。なので、実際のログを見て「これが攻撃の前段階である」をつかんでおくのは大事。(そのためのログ確認です)

『孫子 虚実篇 六』の

「攻めて必ず取る者は、其の守らざる所を攻むればなり。守って必ず固き者は、其の攻めざる所を守ればなり」

これは、守備側に立たされるサーバ管理者も持っておきたい心構えだと思いました。

Page 1 of 26

Powered by WordPress & Theme by Anders Norén