22番ポート狙いのボットを完全遮断する、SSHポート変更(36878番)手順書

ようやく、セキュリティでの効果的な一歩である「SSHポート変更」を行いましたので、そのメモです。

例によって前置きは長いです。

変えようとしたきっかけ:怪しいログ

かなり怪しいログを見つけたというのがきっかけ。

不審に思ったログ

198.51.100.120 - - [09/Jun/2026:08:10:12 +0900] "GET /projects/zettel/knowledgebase/categories?tag=Linux%2CAnsible%2Cnginx HTTP/1.1" 200 41768 "https://example.com" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/145.0.0.0 Safari/537.36"
203.0.113.185 - - [09/Jun/2026:08:10:15 +0900] "GET /projects/zettel/knowledgebase/categories?tag=cron%E8%A8%AD%E5%AE%9A%2CUbuntu HTTP/1.1" 200 41212 "https://example.com" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/145.0.0.0 Safari/537.36"

なぜこれが怪しいと思ったのかを述べる前に、普通のログを示します。

通常のアクセスログ

203.0.113.50 - - [09/Jun/2026:08:05:43 +0900] "GET /issues/96 HTTP/1.1" 200 50538 "https://example.com/" "Mozilla/5.0 (Linux; Android 10; K) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/148.0.0.0 Mobile Safari/537.36"
203.0.113.50 - - [09/Jun/2026:08:05:44 +0900] "GET /plugin_assets/theme/style.css?1754 HTTP/1.1" 200 22610 "https://example.com/issues/96" "Mozilla/5.0 (Linux; Android 10; K) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/148.0.0.0 Mobile Safari/537.36"
203.0.113.50 - - [09/Jun/2026:08:05:44 +0900] "GET /images/logo.png HTTP/1.1" 200 18871 "https://example.com/issues/96" "Mozilla/5.0 (Linux; Android 10; K) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/148.0.0.0 Mobile Safari/537.36"
  • 普通のアクセスログ:
    • HTML(1行目)を読み込んだ直後の1秒以内に、ページに必要なCSSやロゴ画像(2〜3行目)を自動で一斉にロードしています。
  • 美しいリファラ・チェーン:
    • 2行目以降の画像などのリファラ(リンク元)に、直前に開いたページログにあります。

それに対して、怪しいログは以下の通りです。

  • 数秒ごとに「IPアドレスが変わる」: 1行目は 198.51.100.120、その3秒後の2行目は 203.0.113.185 です。
    • プロキシのプールから「一般人のIP」を次々に切り替えながらリクエストを送っているため、多くのWAFが取り入れているIP制限をすり抜けてきます。
  • アセットの完全無視:
    • HTML(カテゴリページ)だけにアクセスし、付随する画像やCSS、JavaScriptなどは1文字もダウンロードしません(プログラムにとって不要だからです)。
  • リファラ(リンク元)の不自然さ:
    • どちらのリファラもトップページになっています。何もないトップ画面から、いきなり長大な検索クエリ(?tag=…)へ直接ジャンプする人間はいません。ヘッダーを「それっぽく偽装」した結果、逆にガタ(不自然さ)が出ています。

そして、問題は、この怪しいアクセスログはAbusedIPDB(筆者解説記事) でも普通の一般のISPしか出てきません。

この理由を調べたところ、Residential Proxyの存在が浮かび上がりました。

Residential Proxy(住宅用プロキシ)とは?

一言で言うと、「一般家庭のインターネット回線(光回線やスマホのキャリア回線など)のIPアドレスを借りて、そこを経由してWebにアクセスする仕組み」です。

通常、ハッカーやスクレイピング(自動データ収集)ボットは、データセンター(AWSやGCP、あるいは筆者が用いているようなVPSなど)のIPアドレスから攻撃を仕掛けます。しかし、データセンターのIPは「ボットっぽい」として一発でブロックされやすいという弱点があります。(事実、筆者もこれを利用してブロックしています)

そこで、「一般家庭のPCやスマホ、ルーターなどを踏み台にすれば、普通の人が家からアクセスしているように偽装できるのでは?」という発想で生まれたのがResidential Proxyです。

なぜ一般人の回線が使われてしまうのか?

一般家庭の回線をが利用されるのか。主に以下の2つのパターンがあります。

合法的な報酬・同意(ユーザーが知ってて提供)

「このアプリを入れると、あなたの余った帯域(通信量)をお金やギフト券に換えます」というお小遣い稼ぎアプリ(Pawn.streamやHoneygainなど)を一般人が自らインストールしているケース。

マルウェアや不適切なアプリ(ユーザーが知らずに加担)

無料のVPNアプリや、海賊版ソフト、スマート家電の脆弱性を突くマルウェアなどに「プロキシの中継プログラム」が仕込まれており、本人が気づかないうちに「攻撃の踏み台(中継サーバー)」に仕立て上げられているケース。

私は過日、ASNとは何か?インターネットの“住所録”を支える番号と「盗人宿」の把握

という記事で、

なぜなら、多くの攻撃者は海外の規制が緩いプロバイダ/組織を隠れ蓑にしています。

という見解を述べました。Residential Proxyはこれの超・拡大解釈版です。

どうやって対策していくか? SSHのアクセスポートの変更

規制が緩いVPSはその大本のアクセス元を断ってしまえば対策は可能。しかし、一般人向けのISPとなると話は違います。

  • IPアドレスが極めて多く
  • そのISPからのアクセスを断ってしまうと他の善良なアクセス者が利用できない

という、利用者を人質に取った隠れ蓑です。(尤も、筆者は不正アクセスがあった瞬間、そのIPは二度目のアクセスをさせないのですが)

なので、手っ取り早く「SSHのアクセスポート(22番)」の変更から始めることになりました。

いくらFail2banにより不審なアクセスを弾き、鍵交換形式でセキュリティは担保されると言ったところで:

「攻撃者のアクセス元が数千倍に膨れ上がってきた」のでは話は別です。

Linuxのデフォルトである「22番ポート」を開放し続けることは、要塞の本丸の門の前に、毎日数百万人のゾンビ(ボット)が群がってドアノブをガチャガチャ回し続けているのと同じ状態です。

これがサーバーに与える実害は、単に「ハッキングのリスク」だけではありません。物理的なリソース(パフォーマンス)を極悪なまでに食いつぶします。

  • 暗号計算によるCPUの無駄遣い
    • SSHの接続要求が来ると、OSは律儀に重たい暗号処理や鍵の検証、認証チェックを走らせます。ボットが何重にも同時アタックしてくるだけで、CPU・メモリ・ディスクI/Oを継続的に消費します。
  • メモリの圧迫 SSHは接続が来るたびに、個別の sshd 子プロセスをメモリ上に生成します。
    • ボットの同時訪問により、メモリ空間がジワジワと侵食されていきます。
  • ストレージ(I/O)のボトルネック化 ボットが認証に失敗するたび、システムは「認証失敗(Failed password…)」のログをディスクに猛烈な勢いで書き込み続けます。
    • この連続的な書き込み処理(ディスクI/O)のせいで、本来のWebサーバーのためのディスクの読み書き速度が奪われます。
  • セキュリティを高める
  • パフォーマンスを上げる

の一挙両得を狙っていきます。

作業の前の注意点:

  • これはSSHのポートを変更する作業です。
  • そして、SSHのサービスを再起動します。

つまり、心臓に悪い作業です。下手したら自分自身がアクセスできなくなる事態も十分発生します。

  • 実サーバの場合は実機で作業する
  • VPSの場合はシリアルコンソールで接続する

が必須です。この環境が用意できない方はここから先は作業をしないでください。

※筆者が上手くいったという手順です。参考にする方は必ず裏取りを取ってください※

環境

  • XServer VPSを利用
  • Ubuntu 24.04
  • UFW導入済み
  • Fail2ban導入済み

サクッとはしているけど心臓に悪い手順

  1. UFWが開放するポートを別に変えます。(ここでは22→36878)
  2. XServer側のパケットフィルターを変更します。
  3. Fail2banの設定を変更します。
  4. SSHの待ち受けを変更します。
  5. systemd SSHソケットアクティベーションの書き換えを行います。
  6. 設定を反映して別ポートに変更します。
  7. サーバそのものを再起動して再確認を行います。

Step 0. 推奨:ディスクイメージのバックアップ

何かあったときのために、ディスクそのもののバックアップ(スナップショット)を取っておくことを強く推奨します。

Step 1. OS内ファイアウォール(UFW)の解放

※ここからは実機/シリアルコンソールで作業を行います※
※平行して自分の端末とSSHセッションを張っておきます※

まずはOSの内部で、新ポート(36878)を通すようにルールを追加しました。

他のポートとバッティングしないものを選んでください。

  • 36878番ポート(TCP)へのアクセスを許可
sudo ufw allow 36878/tcp comment 'SSH新ポート'
  • 古い22番ポートの許可ルールを削除
sudo ufw delete allow 22/tcp
  • 設定を反映
sudo ufw reload
  • 設定反映確認
sudo ufw verbose

22/tcpが消えていることと、以下のようなメッセージがあることを確認します。

36878/tcp                  ALLOW       Anywhere SSH新ポート

自端末から新しいSSHセッションを立ち上げ、端末とSSH通信ができないことを確認します。

この段階では既存SSHセッションを絶対に閉じないでください

Step 2. インフラ(XServer)側パケットフィルターの変更

OSにボットのパケットが届く前に、XServerのインフラレベルで遮断するための設定です。

  1. XServer VPSパネル > パケットフィルター設定 を開く。
  2. [ +パケットフィルター設定を追加する ] をクリック。
  3. 以下の内容で追加:
  • フィルター: 手動で設定
  • プロトコル: TCP
  • ポート番号: 単一ポート / 36878
  • 許可する送信元IPアドレス: 全て許可
  • メモ: SSHのポート変更 等を付与。
  1. [ 追加する ] を押して確定。
  2. 既存のルール一覧から、古い SSH TCP 22 の横にある [ 削除 ] ボタンを押し、不要なポートを完全に塞ぐ。

Step 3. Fail2ban(検知ツール)の設定変更

ポートが変わっても、Fail2banが正しく不正アクセスを監視・検知できるように設定を追従させました。

  • バックアップ取得
sudo cp /etc/fail2ban/jail.local /path/to/backup/directory/jail.local.$(date +%Y%m%d)

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

  • バックアップ取得確認
diff -u /path/to/backup/directory/jail.local.$(date +%Y%m%d) /etc/fail2ban/jail.local 

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

  • /etc/fail2ban/jail.local を編集し、[sshd] セクションのポートを書き換え:
[sshd]
enabled=true
filter=sshd
mode=normal
#port=22                     
#Port変更 
port=36878                   
protocol=tcp
  • 編集後確認
diff -u /path/to/backup/directory/jail.local.$(date +%Y%m%d) /etc/fail2ban/jail.local 
- port=22                     
+ #port=22                     
+ #Port変更 
+ port=36878     

Step4. SSHコンフィグの変更

  • バックアップ
sudo cp -pi /etc/ssh/sshd_config /path/to/backup/sshd_config.$(date +%Y%m%d)
  • バックアップ取得確認
diff -u /path/to/backup/sshd_config.$(date +%Y%m%d) /etc/ssh/sshd_config 

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

  • ファイル編集(筆者例)
#Port 22
Port 36878

編集後に保存。

  • 差分確認
diff -u /path/to/backup/sshd_config.$(date +%Y%m%d) /etc/ssh/sshd_config 
- Port 22
+ #Port 22
+ Port 36878

Step 5. systemd SSHソケットアクティベーションの書き換え

筆者が一番詰まったところです。

これをやらなかったためにSSH接続できませんでした。

Ubuntuには1/init が22番ポートを掴んでしまう仕様があります。

それを回避し、IPv4/IPv6の両方で新しく設定したポート(36878番)を待ち受けさせるための確実な設定です。

  • 設定上書き用のディレクトリを作成:
sudo mkdir -p /etc/systemd/system/ssh.socket.d
  • 設定ファイル /etc/systemd/system/ssh.socket.d/listen.conf を新規作成し、以下の 4行 を記述:
[Socket]
ListenStream=
ListenStream=36878
ListenStream=0.0.0.0:36878

※ ポート番号は自分が設定したものです。

ListenStream=(右側空欄)で、デフォルトの22番の待ち受けを一度綺麗にクリアするのがポイントです。

Step 6. 設定の反映と確認

  • systemdの設定をリロード
sudo systemctl daemon-reload
  • ssh再起動
sudo systemctl stop ssh.service
  • 新しい設定でソケットとFail2banを再起動
sudo systemctl restart ssh.socket
sudo systemctl restart fail2ban
  • UFW(ファイアウォール)を有効化
sudo ufw enable
sudo netstat -lntp | grep 36878

以下のように、36878 番ポートが 1/init によって正常に LISTEN されていれば成功です。

tcp6       0      0 :::36878                :::* LISTEN      1/init
  • 自環境内でのSSH接続確認
ssh -p 36878 localhost

自分の端末からSSH接続できることを確認します。

  • サーバ再起動
sudo reboot

この段階でサーバそのものを再起動します。なぜなら、ここで「SSH接続できるはず」としても、不慮の再起動で設定が違うなどがあり得るからです。

Step 7. 設定反映確認

※ここからはSSH接続クライアントからの接続確認です。※

SSH接続クライアントの接続ポートを22から設定したポート(上記例では36878)に変更して接続します。

接続できれば設定完了です。

まとめ

この作業はサーバ初期設定からやっておくべきものでしたが、ようやく心臓に悪い作業から解放です。

ポート変更後、筆者環境ではWebサーバのレスポンス改善を確認したました。

これは「22番だから侵入される」という話ではありません。

日々押し寄せる大量の自動化ボットによるSSH接続試行そのものをOSへ到達させないことで、限られたサーバ資源を本来のサービス提供へ集中させられたためと考えています。

なお:半ば筆者のVPSは公開情報ではありますが、上記設定とは異なるポート番号で設定されていることは補足しておきます。

AdGuardの管理画面をSSL化するための手順

前回の続き、導入したAdGuardの管理画面を常時SSL化していく作業です。

自宅内NWに立てることを前提としていても

「SSL化しておかないと寝覚めが悪い」

という性分のため、これを実施します。

環境

  • Ubuntu 26.04
  • Apache 2.4
  • AdGuard導入済み(ポートを8080に変更)

そもそもの問題として

「なんでリバースプロキシーなのにnginxじゃあないのか」

という方がいるかと思いますが、

「apacheでもこの設定は十分可能」

という例示のためです。

前提

  • AdGuardの管理画面に紐付くDNS設定が完了している。
  • そのドメインに即した証明書がある。

さっくりとした手順

  1. Apacheのプロキシモジュールを有効化します。
  2. Apacheのログディレクトリを作成します。
  3. AdGuard管理画面用のバーチャルサイトを作成します。
  4. ログのローテーション設定を行います。
  5. 設定を反映させます。
  6. 動作を確認します。

Apacheのプロキシモジュールを有効化する

まずはApacheがポート:8080を待ち受けてリバースプロキシサーバーとして動けるように、必要なモジュールを有効化します。

sudo a2enmod proxy
sudo a2enmod proxy_http
sudo a2enmod rewrite
sudo a2enmod ssl

有効化したら、一度Apacheを再起動しておきます。

sudo systemctl restart apache2

ログディレクトリの作成

この段階でログディレクトリを作成する理由は

「この段階でやっておかないと忘れるから」

に尽きます。ログは障害の切り分けとして極めて重要です。特にAdGuardは自宅内のNWをほぼ司ります。このときに何か異常が無いかを調べるためにも今の段階で作ります。

  • ログディレクトリの作成
sudo mkdir /var/log/adguard

※名前は自分が管理しやすい者に変更してください。

  • ログディレクトリの所有者変更
sudo chown -R www-data:www-data /var/log/adguard

これは筆者の好みの問題です。(ログローテートの際にwww-dataが参照できるようにするため)

  • ログディレクトリの所有者変更確認
ls -ld /var/log/adguard

所有者とグループがwww-dataを確認します。

管理画面用のバーチャルサイト設定ファイル作成

/etc/apache2/sites-available 内に、adguard.conf等の分かりやすい名前のファイルを管理者権限で作成します。

※ドメイン名は確実に自分の環境に合わせてください

<VirtualHost *:80>
    ServerName adguard.example.com
    # HTTPでアクセスされた場合は自動的にHTTPSへリダイレクト
    RewriteEngine On
    RewriteCond %{HTTPS} off
    RewriteRule ^(.*)$ https://%{HTTP_HOST}%{REQUEST_URI} [R=301,L]
</VirtualHost>

<VirtualHost *:443>
    ServerName adguard.example.com

    #ログ設定
    ErrorLog /var/log/adguard/adguard_ssl_error.log
    CustomLog /var/log/adguard/adguard_ssl_access.log combined

    # SSL設定
    SSLEngine on
    SSLCertificateFile    /etc/certs/あなたの証明書.crt
    SSLCertificateKeyFile /etc/private/あなたの秘密鍵.key
    # 中間証明書(CA Bundle)がある場合は、下の行のコメントアウトを解除してパスを指定してください
    # SSLCertificateChainFile /etc/certs/中間証明書.crt

    # プロキシ設定(Apacheが受けて後ろのAdGuardに流す)
    ProxyPreserveHost On
    ProxyPass / http://127.0.0.1:8080/
    ProxyPassReverse / http://127.0.0.1:8080/
</VirtualHost>

ログローテーションファイルの作成

/etc/logrotate.d/配下にadguard等の名前をつけて、以下の通り設定します。

※ログディレクトリは設定したディレクトリです。以下は一例なので好みに合わせて設定ください。

/var/log/adguard/*.log {
    daily
    missingok
    rotate 10
    compress
    copytruncate
    notifempty 
    su www-data www-data
}

設定反映

  • バーチャルサイト設定ファイルの登録
sudo a2ensite adguard.conf

※作成したファイル名です

  • ファイルの整合性確認
sudo apache2ctl configtest

Syntax OKを確認します。

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

active(running)を確認します。

動作確認

自分のブラウザで

https://adguard.example.com

など、設定したURLにアクセスします。

  1. 設定した管理画面にドメインだけで入れること(ポート番号の付与などが必要ないこと)
  2. SSLで通信できること

を確認します。

ログの所有者変更

これは、筆者の設定の問題。www-dataにしている人向け。

なぜなら、サイトの設定反映後、基本的に/var/log/adguard配下で作成されたログはroot権限で作成されます。しかし、ログローテーションはwww-dataでローテーションするようになっています。これを合わせます。

sudo chown www-data:www-data /var/log/adguard/*.log

以上で「AdGuardを本格的に運用する準備」が整いました。

AdGuard HomeをUbuntu 26.04にインストールしたときのメモと詰まった所。

AdGuard Homeの仕組み:「通信の黒幕」を先回りして止める

通常のブラウザ拡張機能(uBlock Originなど)が「ダウンロードされたページから広告を非表示にする」のに対し、AdGuard Homeは「広告がダウンロードされる前に、接続そのものを遮断する」という方法をとっています。

これを実現しているのがDNSという仕組みのコントロールです。

インターネット上のWebサイトは、すべて「203.0.113.x」のような数字のIPアドレスで動いています。私たちが「google.com」のような分かりやすい名前(ドメイン)を入力したとき、それを数字の住所に変換してくれる案内人がDNSサーバーです。

AdGuard Homeは、家の中のネットワークでの「案内人」の役割を買って出ます。

広告が消える4ステップ

  1. リクエスト: スマホやPCがWebサイトを開こうとすると、ページ内に含まれる広告サーバー(例: ads.example.com)の住所をAdGuard Homeに問い合わせます。
  2. 照合: AdGuard Homeは、あらかじめ登録されている「広告ブロックリスト(ブラックリスト)」と、その問い合わせを照合します。
  3. 遮断: もしリストに載っている広告サーバーだったら、AdGuard Homeは正しい住所を教えず、「そんな住所はありません(NXDOMAIN)」と嘘の返事をします。
  4. 結果: スマホ側は広告のデータをダウンロードすることすらできなくなり、結果として広告が表示されなくなります。

もっと有り体に言うと:ルータ前に位置する『キング・クリムゾン』

本来、スマホやPCがWebサイトを開くときは、以下のような「過程」を踏んでいます。

  • 【通常の過程】
サイトを読み込む → 広告の存在に気づく → 広告サーバーにデータを貰いに行く → 広告が表示される

しかし、AdGuard Homeが介入すると、この時間が消し飛びます。

  • 【AdGuard Home発動】
サイトを読み込む → ×(通信を消し飛ばす)× → 広告の無い「結果」だけが画面に残る!

スマホやアプリからすれば、広告を取得しようとした認識すら残らず、気づいたときには「すでに広告が消滅した快適なページが開いている」という状態になります。

広告サーバー側から見れば、エピタフ(未来予知)で先回りされて、最初から攻撃(広告配信)を無効化されているようなものです。

これを入れるときのメモ

Ubuntu 26.04で実施しました。

注意点

これは、ホームネットワークのDNSを書き換えるための作業です

このヤバさが分からない方は、ここより先は読まない方が破滅から逃れられます。

公式インストーラーを実行します。

curl -s -S -L https://raw.githubusercontent.com/AdguardTeam/AdGuardHome/master/scripts/install.sh | sh -s -- -v

この後、rootパスワードが聞かれるのでそれを入力します。

Ubuntuはデフォルトで systemd-resolved という仕組みが53番ポート(DNS)を占有しています。

更に、AdGuardはDNSを「置き換えます」

これが罠でした。つまり、

  1. OSは「このドメインのIPアドレスは?」とDNSに問い合わせる。
  2. DNSサーバーは必要に応じて上流のDNSへ問い合わせを行う。
  3. 最終的に正しいIPアドレスが返され、通信先が決定される。

仕組みを取っています。

しかし、この、DNSが置き換えられた状態で自分のサイトのドメイン(hoge.example.com)をローカルアドレス127.0.0.1と登録してしまうと、家のネットワーク内にあるクライアントから

「hoge.example.comを探せ」と命令すると、「ああ、ローカルアドレスだね、127.0.0.1だよ」と返します。そうするとクライアントは「OK。127.0.0.1へ接続するんだ」と判断し、自分自身へ接続しようとします。

という、「終わりがないのが終わり」というレクイエムを喰らいます。特に、サーバ内に他のWebサイトを探していたりSSH接続でドメインを指定していると更に致命的です。

これを防ぐためのおまじないをかけておきます。

  • hostsファイルの書き換え
/etc/hosts

に、以下のように入力します。

# ローカルアドレス
127.0.0.1 localhost
# 自分のサーバのドメイン(自分の環境に合わせます)
192.168.1.5 hoge.example.com 
# サーバ内で運用している別サイトのドメイン
192.168.1.5 site.example.com 

というのも、Linuxでは通常、DNSより先にhostsファイルが参照されます。

厳密には名前解決順序はNSS設定に従いますが、Ubuntuの標準構成ではhostsが先に評価されるため、サーバ自身のドメインをここへ記載しておくことで名前解決の事故を避けられます。

ざっくり言うと、まず hosts が参照され、見つからなければ設定されたDNSへ問い合わせが進みます。

なので、これをやっておかないと「サービスを起動した瞬間にSSHからはじき出される」状態も起こり得ます。

  • AdGuard Home用の設定ディレクトリを作成します。
sudo mkdir -p /etc/systemd/resolved.conf.d
  • 競合を避けるための設定ファイルを管理者権限で作成します。
/etc/systemd/resolved.conf.d/adguardhome.conf
[Resolve]
DNS=127.0.0.1
DNSStubListener=no

/etc/resolv.conf の確認

AdGuard Homeを導入した後は、OSが参照するDNSサーバーが正しく設定されているか確認します。

cat /etc/resolv.conf

私の環境では以下のようになりました。

nameserver 127.0.0.1
nameserver 8.8.8.8
nameserver 1.1.1.1
search .

ただし、このファイルは systemd-resolved や NetworkManager によって自動生成される場合があります。

環境によっては直接編集しても再起動時に上書きされるため、設定変更を行う場合は現在の管理方法を確認してから作業してください。

注意点

  • 「お前が最初に見に行くDNSはこれだ!」を決める、Linuxにおける神像のようなファイルです。
  • ワンミス即死があり得ます。
  • そのため、筆者が推奨するバックアップのやりかたは敢えて省いています。

「なあに 心配するな しくじってもたかが死ぬだけよ」

ぐらいの精神で、「自分がネットワークに接続できなくなる」の精神を持ちましょう。

ブラウザから初期設定を行う

ここがApacheが入っている環境での重要なポイントです。

  1. 同じネットワーク内のPCのブラウザから、http://[UbuntuのIPアドレス]:3000 にアクセスします。
  2. 設定ウィザードが始まります。

設定時の重要チェックポイント

  • 管理インターフェース(管理画面のポート)
  • デフォルトでは 80 になっていますが、絶対に 80 のまま進めないでください(すでにApacheが80番ポートを使っているため衝突します)。
  • ここでは一旦、80803000 など、Apacheが使っていない別のポートを指定してください。
  • DNSサーバー(DNSのポート)
  • こちらは 53 番ポートのままでOKです。
  1. ユーザー名とパスワードを設定し、ウィザードを完了させます。

これで無事にAdGuard Homeが起動し、指定したポート(例: http://[UbuntuのIPアドレス]:8080)で管理画面にログインできる状態になりました。

これから

  1. この管理画面の常時SSL化
  2. 肝心要のAdGuardの使い方
  3. そこで詰まった所

などは改めて書きます

2026年の紫陽花。

最も好きな花の一つである紫陽花。名所を2つほど。

飛鳥山公園

王子公園、飛鳥山。

こちらは電車沿線との無機質な取り合わせが素晴らしいです。

本土寺

千葉北西部の名刹、本土寺。

今度は寺の広い境内を活かした密度が魅力。

曇り空だからこそ似合う花があるから梅雨は楽しみまで見えます。

Nextcloudにプロキシサーバを設定。

xtcloudサーバのあるネットワークがプロキシー配下にあったため

  • Nextcloudの管理画面がサーバやアプリのアップデートを検知できない
  • ネットと通信するアプリがNextcloudと通信できない

状況が発生。それを修正します。

やることは単純です。

「configファイルの修正」

OSはUbuntuです。

手順

config.php の場所を確認する

Nextcloudのインストール方法によって、設定ファイルの場所が異なります。一般的な場所は以下の通りです。

  • 手動インストール(Apache/Nginx)の場合:
    • /var/www/nextcloud/config/config.php
    • 筆者環境 /home/www-data/nextcloud/config/config.php
  • Snapでインストールした場合:
    • /var/snap/nextcloud/current/nextcloud/config/config.php

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

  • 設定ファイルバックアップ
sudo cp -pi /home/www-data/nextcloud/config/config.php /path/to/backup/config.php.$(date +%Y%m%d)
  • 設定ファイルのバックアップ確認
sudo diff -u /path/to/backup/config.php.$(date +%Y%m%d) /home/www-data/nextcloud/config/config.php

エラーがないことを確認します。ここで~sudo~をつけるのは、一般権限では読み込みすらできないからです。

3. プロキシ設定を追加する

上記ファイルを$CONFIG = array ( の中に、以下の3行(プロキシの設定)を追記します。既存の設定項目の末尾() の手前)に追加してください。 当然ながら管理者権限が必要です。

  'proxy' => '192.168.1.50:8080', // 自分の環境に合わせます。
  'proxyuserpwd' => '', // もしプロキシに認証(ユーザー名:パスワード)が必要ならここに記述
  'noproxy' => array('localhost', '127.0.0.1'), // プロキシを通さないローカルアドレス

【設定例】全体のイメージ

<?php
$CONFIG = array (
  'instanceid' => 'ocxxxxxxxxxx',
  'passwordsalt' => 'xxxxxxxxxxxxxxxxxxxxxxxxxx',
  // ・・・(中略)・・・
  'trusted_domains' => 
  array (
    0 => 'localhost',
  ),
  'datadirectory' => '/var/www/nextcloud/data',
  'dbtype' => 'mysql',

  // ここに追記します
  'proxy' => '192.168.1.50:8080',
  'proxyuserpwd' => '',
  'noproxy' => array('localhost', '127.0.0.1'),
);

設定後、ファイルを保存します

  • 設定ファイルの修正確認
sudo diff -u /path/to/backup/config.php.$(date +%Y%m%d) /home/www-data/nextcloud/config/config.php
+  'proxy' => '192.168.1.50:8080',
+  'proxyuserpwd' => '',
+  'noproxy' => array('localhost', '127.0.0.1'),

など、追加したプロキシ設定が追加されていることを確認します。

4. Webサーバー(またはPHP)の再起動

設定を反映させるため、Webサーバーを再起動します。

  • Apacheの場合:
sudo systemctl restart apache2
  • Nginx / Apache + PHP-FPMの場合:
sudo systemctl restart nginx
sudo systemctl restart php8.x-fpm  
  • Snapインストールの場合:
sudo snap restart nextcloud

接続テスト

設定完了後、Nextcloudの管理者画面にブラウザからログインし、以下の項目を確認してください。

  1. 「管理者設定」>「概要」 を開き、インターネット接続に関するエラー(「このサーバーにはインターネット接続がありません…」など)が消えているか確認。
  2. 「アプリ」画面 を開き、外部のアプリストアから新しいアプリの一覧が正常にロードされるか確認。

UbuntuServerでのプロキシ設定をコマンドラインで設定する。

Ubuntu Server(GUIなし環境)において、PACファイル(プロキシ自動設定ファイル)が導入されている環境でネットワーク通信(主にAPTパッケージ管理)を有効化するための作業メモです。

背景と注意点

割と頻出する状況だと思います。

  • 検証環境をローカルNWに立てたい
  • でも、社内ポリシーでプロキシーを通す必要がある
  • Windows端末等はGUIで操作できるが、Linuxはそうでない

Ubuntu ServerのCLI環境(apt コマンドなど)は、デフォルトではJavaScriptで記述されたPACファイルを直接解釈できません。

そのため、「PACファイルの内容を確認し、そこに記載されている実際のプロキシサーバーのIPとポートを直接設定する」方法が確実です。

手順1: PACファイルから実際のプロキシ情報を抽出する

まず、サーバー上で curl コマンドを使用してPACファイルの内容を読み込み、転送先となっているプロキシサーバーの「IPアドレス」と「ポート番号」を特定します。

curl -s http://192.168.1.10/proxy.pac

出力例の確認

表示例

function FindProxyForURL(url, host) {
        var clientIP = myIpAddress();
        if (
                isInNet(host, "10.0.0.0", "255.0.0.0") ||
                isInNet(host, "192.168.0.0", "255.255.0.0")
        ) {
                return "DIRECT"; // ローカル通信はプロキシを通さない
        }
        else { 
                // 外部インターネット通信用のプロキシサーバー情報
                return "PROXY 192.168.1.50:8080"; 
        }
}

確認ポイント:

return "PROXY ..." の部分に書かれている情報(上記例では 192.168.1.50:8080)を控えます。

手順2: APT(パッケージ管理)用プロキシの設定

特定したプロキシサーバー情報を、APTの設定ファイルに書き込みます。

設定ファイルの作成・編集

教義・信仰に沿ったエディタを用いて、以下のファイルを編集/作成します。

/etc/apt/apt.conf.d/99proxy

ファイル内に以下の2行を記述します(手順1で確認したIPとポートに置き換えてください)。

Acquire::http::Proxy "http://192.168.1.50:8080/";
Acquire::https::Proxy "http://192.168.1.50:8080/";

gsettingswpad+ などの不要な記述が残っている場合は、すべて削除して上記のみにします。

手順3: 接続テスト(設定の確認)

設定が正しく反映され、インターネット経由でパッケージ情報が取得できるかテストします。

sudo apt update

「無視(Ignored)」や「タイムアウト」のエラーが多発せず、ヒット(Hit)や取得(Get)が進み、最後に 読み込み完了(Reading package lists… Done)と表示されれば設定完了です。

(参考)一般コマンド用(環境変数)の一時設定

もし curlwget など、apt 以外の一般コマンドでもインターネット通信を行いたい場合は、現在のターミナルセッションに対して一時的に以下の環境変数を適用してください。

export http_proxy="http://192.168.1.50:8080"
export https_proxy="http://192.168.1.50:8080"
export no_proxy="localhost,127.0.0.1"

吠えなかった犬の挙動。(POSTに隠されたRedTail)

はじめに

「吠えなかった犬の推理」

という言葉があります。シャーロック・ホームズ『白銀号事件』にある有名なやりとり、

「その他何か私の注意すべきことはないでしょうか?」
「あの晩の犬の不思議な行動に御注意なさるといいでしょう」
「犬は全然何もしなかったはずですが」
「そこが不思議な行動だと申すのです」

から来た、「一見すると不自然ではない(何も起きていない)ことが、状況を踏まえて考えれば極めて不自然であること」という、ミステリの定理とも言えるロジック。

筆者は公開しているVPSで不審なエラーログ(攻撃の検知ログ)は毎日のように見ていますが、先日、エラーではなく通常のアクセスログに、極めて不審な(というか完全にアウトな)一行を発見しました。

今回は、そのログの正体と、その裏に隠された攻撃者の意図について解説します。

見つかった「不自然な1行」のログ

見つかったのは、以下のようなログです(※IPアドレスやホストなどの情報はダミーに無害化しています)。

203.0.113.45 - - [02/Jun/2026:05:23:14 +0900] "POST /cgi-bin/.%2e/.%2e/.%2e/.%2e/.%2e/.%2e/.%2e/.%2e/.%2e/.%2e/bin/sh HTTP/1.1" 404 3097 "-" "libredtail-http"

Webサイトの運用経験がある方なら、この1行を見ただけでいくつか異様な点に気づくかと思います。

  • 見覚えのない海外IPからのアクセス
  • /.%2e/.%2e/ という怪しげな文字列の連続
  • 通常のブラウザでは使われない libredtail-http というUser-Agent
  • そして、何より/bin/sh(OSのシェル)に対して「POST」リクエストを送っているという異常さ

幸い、ステータスコードは 404(Not Found) なので、攻撃はサーバー側で弾かれています。では、この攻撃者は一体何をやろうとしていたのでしょうか?

このログの正体:パストラバーサル攻撃(CVE-2021-41773)

このリクエストは、過去に広く報道された Apache HTTP Serverの脆弱性(CVE-2021-41773など) を狙った自動スキャン(攻撃)ツールによるものです。

URLに含まれる .%2e をURLデコードすると、親ディレクトリを指す .. になります。
攻撃者は、公開フォルダ(cgi-bin)から強制的に外へ飛び出し、サーバーのルートにあるOSの実行ファイル(/bin/sh:シェル)に直接アクセスしようとしていたのです。古典的ですが強力なパストラバーサル(ディレクトリトラバーサル)攻撃です。

なぜ「GET」ではなく「POST」なのか?

「情報を盗み見るだけなら GET なのでは?」と思いましたが、調べてみると攻撃者の意図が浮かび上がりました。

① サーバー上で「コマンドを実行」させるため(RCE)

攻撃者の最終目的は、ファイルを覗き見ることではなく、サーバーを乗っ取ることです。
POST リクエストの「ボディ(本文)」部分に、実行させたい悪意あるLinuxコマンド(マルウェアのダウンロード命令など)を乗せて送信するのが真の目的です。

② 痕跡(ログ)を隠すため

GET メソッドの場合、実行したいコマンドをURLの後ろ(クエリパラメータ)に付ける必要があります。しかし、それだとアクセスログのURL部分に攻撃コマンドが丸見えになってしまいます。

Webサーバーの標準ログ設定は「ヘッダー」しか記録せず、「ボディ」は記録しません(パスワードやカード情報などの機密情報が含まれるため)。
攻撃者はこれを利用し、「POST/bin/sh を叩こうとした」という最低限の事実だけをログに残し、肝心の悪意ある命令(ボディ)をログから隠蔽しているのです。

攻撃の背景にあった、巨大な暗号資産マイニングマルウェア

さらにこのログの User-Agent にある libredtail-http を調べると、明確な犯行の背景が浮かび上がってきました。

これは、感染したサーバーのパワーを勝手に使って暗号資産(仮想通貨)を強制採掘させるマルウェア「RedTail」の拡散ツールです。

もし、サーバーに脆弱性が存在し、この POST が実行(200 OK)されてしまっていた場合、以下のような身の毛もよだつシナリオが進行していました。

  • マルウェア(RedTail)の強制インストール:
    • バックドアが設置され、サーバーが完全に乗っ取られます。
  • CPU使用率が「100%」に張り付く:
    • 裏でマイニングツール(XMRig)が暴走し、サイトが激重になります。クラウドの場合は莫大な従量課金が請求されます。
  • 報酬の隠蔽(マイニングプロキシの中継):
    • 採掘データを直接プールに送らず、攻撃者が用意した中継サーバーを経由させる。これによりウォレットアドレス(足跡)を隠蔽し、リサーチ会社や捜査の手から逃れる工作まで行っています。
      次の攻撃の「踏み台」にされる:
    • 被害者であると同時に加害者になり得ます。世界中の他のサーバーへ向けて、同様の不審な POST リクエストを無差別に送り始めます。

なぜ「犬は吠えなかった」のか?

筆者のVPSには、強力なWAFである ModSecurity を導入して不審な攻撃をシャットアウトしていますが、不思議なことに、今回この件に関するエラーログ(拒否ログ)は現れませんでした。

「攻撃が届いているのに、なぜWAFは吠えなかったのか?」

その理由は、以下の2つの可能性が考えられます。

1. 表面上は正しいドメイン名とヘッダを指定していた

筆者の環境では、ModSecurityで以下のようなカスタムルールを設けています。

# IPアドレス直打ちアクセス対策
SecRule REQUEST_HEADERS:Host "@rx ^[\d.]+(:\d+)?$" \
    "id:10001,\
    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'"

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

「IPアドレス直打ち」や「Hostヘッダが無い」という、通常のWebブラウズではまず存在しないアクセスは、このルールで瞬殺(phase 1でブロック)されます。自動スキャナーの多くはこれで引っかかります。

この攻撃者は、筆者のサーバーの防御手段を見抜いていたのか、ご丁寧に正しいドメイン名をHostヘッダーに指定して、この網を通り抜けてきたと考えられます。

2. WAFが動く前に、Apacheのコア機能が処理を終わらせていた

もう一つの有力な可能性は、Apache自体の挙動です。
ModSecurityがURLの中身を深く精査(パース)するよりも前の段階で、Apacheのコア機能がURLのパスを処理した結果、「物理的にファイルが存在しない(404 Not Found)」と判断して処理を終了したパターンです。

エラー(拒否)ではなく、純粋に「そんなファイルは無いよ」として正常に(?)404を返したため、WAFの検知ログには残らなかった、というわけです。

Apacheにおいて、URLの文字列をデコードして物理的なファイルパスにマッピングする処理(ap_directory_walk)は、ModSecurityの phase:1(ヘッダー検査)と phase:2(ボディ検査)の間、あるいはその手前で行われます。

【Apacheの処理フロー】
1. リクエスト受信
2. ModSecurity (phase:1)  ← Hostヘッダーチェック(ドメインが正しいので通過!)
3. Apacheコア (パスの解決) ← 「/.%2e/」を解析しようとするが、そんなCGIは存在しない(404確定)
4. ModSecurity (phase:2)  ← 実行される前に、Apacheが「404」として即レスポンスを返して終了

つまり、攻撃者はドメインチェック(貴方のカスタムルール10001)を賢くすり抜けたものの、Apache自体のパス解決の壁に激突して、WAFが本格的に牙を剥く前に死んでいたわけです。

 3. 攻撃者が「POST」を選んだもう一つの邪悪な理由

それは「GETの文字数制限を回避するため」です。

RedTailのようなマルウェアは、侵入成功と同時に「Base64で難読化した巨大なシェルスクリプト」を送り込んできます。URLの末尾(GET)にこれを付けると、Apacheの最大URL長制限(LimitRequestLine:通常8KB)に引っかかり、コマンドが途中で切れて実行できません。 そのため、数万文字の「汚い攻撃コード」を確実に一発で流し込むために、ボディ制限が緩い POST を選択せざるを得ないのです。

まとめ:たまには「正常系のログ」も見ましょう

今回、攻撃の予兆に気づけたのは、「運良く」アクセスログを見ていた結果です。

自分の身やデータを守るため、そして自分が踏み台(加害者)にならないためにも、以下の基本を徹底しましょう。

  • Webサーバー(ApacheやNginxなど)を常に最新バージョンにアップデートしておく
  • 不要なCGI設定(cgi-binなど)は無効化・削除しておく
  • もはや必須となっているWAF(ModSecurityなど)の導入
  • クローラーや無駄なアクセスを拾わないログの整理

「エラーがない=安全」とは限らない。
攻撃があった兆候は、静かに普通のアクセスログにも現れる、というお話でした。

休日のソロプレイ『ヌースフィヨルド』プレイ結果。

今回はプレイに比して余り得点が出なかったというのが正直なところ。

『タラデッキ』を用いて31点。

  • 無駄な手を一手取ってしまったのが最後まで響いた。
  • 長老により魚の収入が狂った

が主なところです。

とはいえ、ミスのリカバリーもできていたのでそこは評価しておきたいです。

ボードゲーム『真打 POCKET TRPG編』感想。

元々大好きな作品『真打』のソロプレイ可能版が、さらに凄まじいブラッシュアップを遂げていました。

プレイヤーは若手の落語家となり、ライバルとネタを出し合います。季節に応じた8回の落語会の中で、

  • 噺(はなし)を高座にかけ
  • 称号を集め

寄席を最も盛り上げた者が勝者となります。

一見、風流なテーマゲームに見えますが、その実態はガチガチの条件戦が絡み合うトリックテイキング(トリテ)。しかも、本来ソロプレイが成立しないはずのこのジャンルを、見事な仕掛けで「1人極上パズル」へと昇華させていました。

このゲームの好きなところ

1. 落語という衣をまとった「トリックテイキング」

本作の最大の魅力は、「トリテの複雑なシステムが、落語のテーマと完璧にシンクロしている点」に尽きます。

カードの数値はすべてユニークで、それぞれに「滑稽噺」「人情噺」「怪談」といったスート(色)が存在します。落語の大ネタほど数値が高く、前座噺は低いため、「『牛ほめ』と『芝浜』だったら、どっちがトリ(勝利)を取るか」が直感的にわかります。初心者には見えにくい「勝ちどころ」が、テーマのおかげで自然と見えてきます。

さらに、以下の要素がトリテのシステムと見事に合致しています。

  • 夏の怪談噺は+1点 =「トリックボーナス」の概念
  • 季節の演目で称号獲得 =「マストフォロー」への動機付け
  • ジャンルを揃えるとボーナス =「セットコレクション」

初心者が忌み嫌い、上級者が好むトリテの「複雑な条件戦」を、落語というテーマが見事に受け止めています。

2. 戦略としての「意味がある負け方」

普通のトリテ以上に、「負けること」に強い意味があるのも特徴的です。
トリを取った(トリックの勝者)プレイヤーほどカードの補充が後回しになるシステムのため、「ここぞという本番で勝つために、今はあえて美しく負ける」という場作りが重要になります。

これがソロプレイ(変則ドラフト)になると、さらにカウンティングによる「勝敗のコントロール」が肝となり、脳のメモリをフル活用させられます。

3. トリックで負けても勝てる「セットコレクション」

ボーナス要素である「称号」の獲得条件には、以下のようなものが含まれます。

  • 特定の噺のジャンルを演じる(スートを集める)
  • 点数の低い噺をあえてかける(弱いカードを出す)

これにより、「トリック(勝負)には負けているのに、全体の得点(評価)では勝っている」という不思議な状況がゲームをさらに面白くさせています。

「成立しないはずのソロゲーム」を成立させている異様さ

トリックテイキングとは、本質的に「相手の手札と意思決定を読む」ゲームです。相手が何を持っているか、どのタイミングで勝ちに来るかという「他者との心理戦」が核にあるため、本来はソロプレイが最も成立しにくいジャンルです。

ところが本作は、その読み合いの代替物を「変態的(褒め言葉)」な2つの仕掛けで成立させています。

① オートマ(AI)ではなく“スコアアタック”で縛る

ソロモードは、ライバルに勝つこと以上に「自分の得点をどこまで伸ばせるか」という、自分自身との極限の戦いになります。「どの落語会でどの演目を出すか」「季節ボーナスをどう拾うか」が複雑に絡み合うため、「このシナリオでは全力で勝ちに行く」「ここは勝ち数をコントロールする」というジレンマが、1人プレイでも完璧に再現されています。

② 『TRPG落語編』がもたらす、不確実性の「特殊効果」

前作のPocketにはなかった新要素が、プレイヤーの計算を狂わせます。

  • 『インタラクティブ死神』:出目が奇数ならカード交換
  • 『インスマウス長屋』:配置済みカードと入れ替え、全落語会の勝敗を再判定
  • 『積みゲー幽霊』:4以上で手札交換

これらにより、対人戦の「相手が何をしてくるか分からない不確実性」を、ランダム性+強制効果で見事に再現しています。

現に私がプレイした際、「よし、年末に満を持して『芝浜』を出したぞ!」と思った瞬間、『インタラクティブ死神』が発動。相手に『芝浜』を奪われて演じられ、理不尽に負けるというドラマが起きました。この、ほぼ必勝のパターンが引っくり返される異様さ(理不尽さ)こそが、最高に巧妙で、ゾクゾクしました。

このゲームの少し残念なところ

ルールブックが同梱されていない(Web参照)

コストカットやパッケージを極限まで抑えるためとはいえ、ルールを読むためにインターネット環境が必要なのは少々残念です(とはいえ、10分ほど読み込めばすっと頭に入っていく美しい導線ですが)。

スリーブとパッケージのジレンマ

カードが主体のコンポーネントゆえの宿命ですが、カードを保護するためにスリーブに入れると、元々のコンパクトなパッケージに収まらなくなってしまいます。(尤も、カードサイズが特殊すぎた初代『真打』の苦労に比べれば、贅沢な悩みではあるのですが……!)

まとめ:1人で編み上げる、完璧な「一席」

元々あった「落語の要素」を見事に抽象化した粋なイラストや、ルールそのもののシンプルさはシリーズ共通の魅力として健在です。

その上で、

  • ソロプレイ可能なトリックテイキングという特異性
  • 追加コンポーネント(ダイス)のノイズを感じさせないゲーム性
  • 数々の条件戦がもたらす深い戦略性

が見事に融合した、実に入り組んだ、そして見事な作品でした。

トリテに悲喜こもごもがあるかた、そして「制限の中で最適解を叩き出すパズル」が好きな方には刺さる作品です。

設定したApacheによる動きの解説

先ほどのエントリーの続きです。

「実際に筆者が施している」

Apache設定を元に「どういう動きをしているのか」を紹介します。

サンプル例

サイト名やIPアドレスなどはダミーにしています。

# ============================================================
# 【HTTP: 80番ポート】常時HTTPS(SSL/TLS)へのリダイレクト
# ============================================================
<VirtualHost *:80>
    ServerName example.com

    RewriteEngine On
    RewriteCond %{HTTPS} off
    RewriteRule ^(.*)$ https://%{HTTP_HOST}%{REQUEST_URI} [R=301,L]
</VirtualHost>

# ============================================================
# 【HTTPS: 443番ポート】要塞化されたメインサーバー設定
# ============================================================
<VirtualHost *:443>
    ServerName example.com
    DocumentRoot /var/www/html/public

    # --------------------------------------------------------
    # 1. アクセスログの間引き(ノイズカット)設定
    # --------------------------------------------------------
    # 自身の監視用IPなど、ログに記録させたくないIPを指定
    SetEnvIf Remote_Addr "192.168.1.100" dontlog
    SetEnvIf Remote_Addr "10.0.0.1" dontlog

    # 検索エンジンのクローラー(健全なBot)もログから除外してノイズを減らす
    SetEnvIfNoCase User-Agent "Googlebot" dontlog
    SetEnvIfNoCase User-Agent "GoogleOther" dontlog

    # 【外部ファイル連携】定期的・自動的に更新する悪質ボットのリストを読み込む
    # ※ファイル内で「SetEnv bad_bot 1」などのフラグを立てる想定
    Include /etc/apache2/conf-available/blacklist-bots.txt

    # dontlogフラグが付いたアクセスは記録しない
    CustomLog /var/log/apache2/example_access.log combined env=!dontlog
    ErrorLog /var/log/apache2/example_error.log

    # --------------------------------------------------------
    # 2. メインディレクトリ制御(mod_rewrite × 環境変数)
    # --------------------------------------------------------
    <Directory /var/www/html/public>
        Options -MultiViews
        AllowOverride All

        # 大量の拒否アクセスによるエラーログ肥大化を抑制(重大エラーのみ記録)
        LogLevel authz_core:crit

        <IfModule mod_rewrite.c>
            RewriteEngine On

            # 【攻撃検知】特定の動的ページに対する「大量のタグ連結」などのボット挙動を迎撃
            # 例:特定のURLパスにアクセスがあり、かつクエリにURLエンコードされたカンマ等が大量に含まれる場合
            SetEnvIf Request_URI "^/search/tags" is_target_page
            SetEnvIf Query_String "tag=.*(%2c|,|%e3%80%81).*(%2c|,|%e3%80%81).*(%2c|,|%e3%80%81)" bad_tag_stacking

            # 条件に合致したら、ログを残さず「404 Not Found(存在しない)」を返して虚無へ葬る
            RewriteCond %{ENV:is_target_page} 1
            RewriteCond %{ENV:bad_tag_stacking} 1
            RewriteRule ^ - [E=dontlog:1,R=404,L]

            # 【外部ファイル連携】スパムIPアドレスのブラックリストを読み込んで404を返す
            Include /etc/apache2/conf-available/spam-ips.txt
        </IfModule>

        # 【アクセス拒否】上記の設定や外部リストで「bad_bot」と判定された通信を遮断
        <RequireAll>
            Require not env bad_bot
            Require all granted
        </RequireAll>
    </Directory>

    # 403 Forbidden(拒否)を返すと攻撃者に「防御されている」とバレるため、
    # 403の際も「404 Not Found(最初から何も無い)」として処理する
    ErrorDocument 403 /404.html

    # --------------------------------------------------------
    # 3. セキュリティヘッダーの強化(mod_headers)
    # --------------------------------------------------------
    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"

    # --------------------------------------------------------
    # 4. Nepenthes(ウツボカズラ)トラップ設定(mod_alias)
    # --------------------------------------------------------
    # 攻撃者が盲目的にスキャンしてくる「脆弱性のあるパス」を、ダミーの静的ファイルへ吸い込ませる
    <IfModule mod_alias.c>
        # WordPressを使っていない(または構成が違う)のに狙われるパス
        Alias /wp-login.php /var/www/nepenthes/dummy_login.html
        Alias /wp-admin     /var/www/nepenthes/dummy_login.html
        Alias /wordpress    /var/www/nepenthes/dummy_login.html

        # 漏洩すると致命的な .git ディレクトリへのスキャン対策
        Alias /.git         /var/www/nepenthes/dummy_git.html

        # robots.txtで「巡回禁止」にしているにもかかわらず、
        # 行儀悪くスクラップ(収集)しにくるAI自動巡回エージェント用のトラップ
        Alias /assets-archive /var/www/nepenthes/dummy_login.html

        # トラップ用ディレクトリへのアクセスを許可
        <Directory /var/www/nepenthes>
            Require all granted
        </Directory>
    </IfModule>

    # --------------------------------------------------------
    # 5. SSL/TLS暗号化プロトコル設定
    # --------------------------------------------------------
    SSLEngine on
    Protocols h2 http/1.1

    # 安全性の低い古いプロトコルを徹底排除(TLS 1.2 / 1.3 のみ許可)
    SSLProtocol all -SSLv3 -TLSv1 -TLSv1.1 -TLSv1.2
    SSLCipherSuite ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES256-GCM-SHA384
    SSLHonorCipherOrder off
    SSLSessionTickets off

    SSLCertificateFile    /etc/ssl/certs/example.com.crt
    SSLCertificateKeyFile /etc/ssl/private/example.com.key

    # --------------------------------------------------------
    # 6. WAF(ModSecurity)のチューニング
    # --------------------------------------------------------
    <IfModule security2_module>
        SecRuleEngine On

        # ファイルアップロードの容量制限を引き上げ(必要に応じて調整)
        SecRequestBodyInMemoryLimit 524288000
        SecRequestBodyLimit         524288000
        SecRequestBodyNoFilesLimit  524288000

        # --- 偽陽性(誤検知)の除外ルール ---
        # ※システム(RedmineやWordPressなど)の特性に応じて、正常な通信が遮断されないよう調整
        SecRuleRemoveById 920420 # 特定JavaScriptの誤検知対策
        SecRuleRemoveById 200004 # マルチパート解析エラーの除外
        SecRuleRemoveById 920300 # HTTPプロトコル違反の除外
        SecRuleRemoveById 920260
        SecRuleRemoveById 920270
        SecRuleRemoveById 920240

        # --- 記憶抹消刑(Damnatio Memoriae)システム ---
        # あらかじめリスト化した凶悪なIP(negativelist.txt)からのアクセスは、
        # 404エラーを返しつつ、Apacheのログ(auditlog等含む)に一切記録させず存在を抹消する
        SecRule REMOTE_ADDR "@pmFromFile negativelist.txt" "phase:1,id:2,status:404,msg:'Damnatio Memoriae',nolog,noauditlog"
    </IfModule>
</VirtualHost>

では、各設定の意図は後回しにして、「どういう動きなのか」を見てみましょう。

パターン1: http→httpsへの変更

http(80番)で来たアクセスを強制的にhttps(443)に転送します。

sequenceDiagram autonumber participant Client as ユーザー / 攻撃者 participant Apache80 as Apache (ポート80) Client->>Apache80: HTTPでアクセス (http://example.com) Note over Apache80: RewriteEngine On<br/>RewriteCond %{HTTPS} off Apache80-->>Client: 301 Moved Permanently (https://...)

パターン2: 通常アクセス

悪意を持たないアクセスには正常に通信します。

sequenceDiagram autonumber participant User as 通常ユーザー (または Googlebot) participant Apache443 as Apache (ポート443) participant WebPage as メインコンテンツ (/public) User->>Apache443: HTTPSでアクセス (正常なリクエスト) alt Googlebot や 監視IP の場合 Note over Apache443: SetEnvIf で「dontlog」フラグを付与 Note over Apache443: アクセスログ(example_access.log)へは記録しない else 一般の通常ユーザーの場合 Note over Apache443: 通常通りアクセスログに記録 end Apache443->>WebPage: コンテンツ読み込み WebPage-->>Apache443: ページデータ返却 Note over Apache443: セキュリティヘッダーを付与<br/>(HSTS / X-Content-Type-Options / X-Frame-Options / X-XSS) Apache443-->>User: 200 OK (安全な通信でWebページを表示)

パターン3:攻撃者、ボットなど

スクレイピングや事前に設定していた悪質なボットにどう立ち向かうのかがこちらです。

sequenceDiagram autonumber participant Attacker as 攻撃者 / 悪質ボット participant Apache443 as Apache (ポート443) participant Trap as トラップエリア (/nepenthes) alt パターンA:特定の動的ページへの大量タグ攻撃 Attacker->>Apache443: /search/tags に大量のカンマを含むクエリでアクセス Note over Apache443: bad_tag_stacking 検知 Note over Apache443: E=dontlog:1 (ログを残さない) Apache443-->>Attacker: 404 Not Found (虚無へ葬る) else パターンB:ブラックリスト(blacklist-bots.txt / spam-ips.txt)に該当 Attacker->>Apache443: スパムIPや悪質Botからのアクセス Note over Apache443: Require not env bad_bot で拒否 Note over Apache443: ErrorDocument 403 /404.html Apache443-->>Attacker: 404 Not Found (403を隠蔽して最初から何も無いと騙す) else パターンC:ウツボカズラ(Nepenthes)トラップへの盲目スキャン Attacker->>Apache443: /wp-login.php や /.git へのアクセス Note over Apache443: mod_alias でダミーファイルへ転送 Apache443->>Trap: dummy_login.html などを読み込み Trap-->>Apache443: ダミーデータ Apache443-->>Attacker: 200 OK (ダミーファイルを返して時間を稼ぐ) else パターンD:凶悪なIPリスト(negativelist.txt)に該当 [WAF制御] Attacker->>Apache443: 凶悪リストに載っているIPからのアクセス Note over Apache443: ModSecurity: 'Damnatio Memoriae' 発動 Note over Apache443: nolog, noauditlog (全てのログ・監査ログから存在を抹消) Apache443-->>Attacker: 404 Not Found (記憶抹消刑) end

と、このように、

「“アクセス”は通す
 “攻撃”は阻止する
 ミドルウェアで“両方”やるというのは
 そう難しいことじゃあないな」

と言えるのが「個人VPSでApacheを使う理由」と言えます。

次は、それらを可能にする仕組みについて解説します。

Page 1 of 294

Powered by WordPress & Theme by Anders Norén