照明制御(DMX512 / Art-Net)を45年やっている組込み屋です。自分のサイト tkurume.work を Vultr の VPS(1vCPU / 1GB / Ubuntu 24.04)で運用しています。
1日の実訪問者は数人という、どこにでもある個人サイトです。
アクセス解析を作り込んでいる最中に、ふとログイン失敗のログを数えてみました。
332,668
1か月分です。 これは何かの間違いだろうと思って先月分も数えました。
335,467
間違いではありませんでした。この記事は、その実測値と、何をどう塞いだかの記録です。ハマりどころとして sshd_config の読み込み順の罠と、締め出されないための手順を書きます。そこだけ読みたい方は 6章・7章 からどうぞ。
1. まず数字
| 項目 | 実測値 |
|---|---|
| SSH ログイン失敗(2026年7月・27日間) | 332,668 回 |
| 同・6月(30日間) | 335,467 回 |
| 1日あたり | 約 12,000 回 |
| 攻撃元 IP 数(SSH) | 864 |
| Web の機密ファイル探索 | 6,085 回 |
| Web スキャン元 IP 数 | 3,533 |
| 侵入に成功した数 | 0 |
実訪問者は1日数人です。人間1人に対して機械が2,000〜3,000回ログインを試している計算になります。
2. 数え方
解釈の余地がないように、生の数え方を書きます。
SSH のログイン失敗
Linux は失敗したログインを /var/log/btmp にバイナリで記録します。lastb で読めます。
# 現在のローテーション期間(このサーバーは月次)の失敗件数
lastb | wc -l
# => 332668
# 狙われたユーザー名ランキング
lastb | awk '{print $1}' | sort | uniq -c | sort -rn | head -12
btmp は logrotate で月次ローテートされるので、/var/log/btmp が今月、btmp.1 が先月です。ファイルを指定すれば先月も数えられます。
lastb -f /var/log/btmp.1 | wc -l
# => 335467
btmp のサイズにも注意してください。うちは btmp + btmp.1 で 245MB ありました。ディスクを食っているのが「攻撃の記録」というのは、なかなか間抜けな話です。
Web 側の探索
nginx の access.log を、gz も含めて横断して数えます。
LOGS() {
for f in /var/log/nginx/access.log /var/log/nginx/access.log.1 /var/log/nginx/access.log.*.gz; do
[ -e "$f" ] || continue
case "$f" in *.gz) zcat "$f";; *) cat "$f";; esac
done
}
# 機密ファイル狙いの探索を数える
LOGS | grep -aiE 'GET [^ ]*(\.env|\.git/|id_rsa|\.ssh|wp-login|wp-admin|xmlrpc|phpmyadmin|\.aws|\.sql|passwd)' \
| awk '{print $9}' | sort | uniq -c | sort -rn
grep に -a を付けているのは、ログにバイナリが混じると grep が黙ってしまうからです。攻撃者は平気で不正なバイト列を送ってくるので、これは付けておいたほうがいいです。
3. 何が狙われていたか
ユーザー名ランキングが、攻撃の性格をよく表しています。
| ユーザー名 | 回数 | 狙う理由 |
|---|---|---|
root |
134,555 | 最高権限 |
admin |
16,671 | 機器の既定管理者名 |
user |
6,987 | 初期設定のまま |
ubuntu |
6,638 | クラウドイメージの既定 |
test |
3,790 | 作って消し忘れた検証用 |
debian |
3,572 | 同上 |
deploy |
3,088 | CI/CD 用アカウント |
ftpuser |
2,024 | 古い FTP 運用の名残 |
postgres |
1,917 | DB の既定ユーザー |
oracle |
1,636 | 業務システム狙い |
pi |
1,613 | Raspberry Pi の既定ユーザー名 |
pi が1,600回以上というのが個人的にはいちばん刺さりました。現場に Raspberry Pi を置いて外から見られるようにしている設備は、私の業界でも珍しくありません。初期ユーザー名・初期パスワードで外につないだら、数時間で見つかります。
そしてこのランキングには、私が実際に使っているユーザー名も 6,039 回含まれていました。ドメインの登録情報から推測されています。「珍しい名前だから」は通用しません。
攻撃元
864 IP に分散していましたが、whois を引くと傾向がはっきりします。
| 回数 | 国 | 素性 |
|---|---|---|
| 5,531 | 香港 | クラウド事業者 |
| 5,443 | ドイツ | 大手ホスティング |
| 4,454 | 中国 | 公的機関名義の回線 |
| 2,672 / 2,529 | フランス | クラウド事業者 |
| 1,994 / 1,991 / 1,989 … | オランダ | 同一事業者の連続アドレス |
| 1,700 | アンドラ | クラウド事業者 |
ほぼ全部が正規のクラウド事業者です。攻撃者が自分の回線から来ることはまずありません。乗っ取られたサーバーか、使い捨てで借りたインスタンスです。
「怪しい国を GeoIP で弾く」という対策があまり効かないのはこれが理由です。ドイツとオランダとフランスを弾く運用は現実的ではありません。
中国の1件が公的機関名義だったのは示唆的でした。当人が攻撃しているというより、その組織のサーバーが乗っ取られて加害側に回っていると見るのが自然です。守れていない機器は、いつのまにか他人を攻撃する道具になります。
Web と SSH は同じ相手が叩いている
# Web に来た IP と SSH に来た IP の積集合
comm -12 <(sort -u web_ips.txt) <(sort -u ssh_ips.txt) | wc -l
# => 34
34 アドレスが両方を叩いていました。片方だけ守っても意味がない、ということです。
4. 余談: AI クローラでアクセス解析が壊れる
攻撃とは別に、大量の巡回プログラムが来ています。
| 名前 | 回数 |
|---|---|
| Bytespider (ByteDance) | 797 |
| Applebot | 472 |
| meta-externalagent | 359 |
| Googlebot | 337 |
| GPTBot / ChatGPT-User / OAI-SearchBot | 727 |
| ClaudeBot / Claude-User / anthropic-ai | 687 |
| bingbot | 229 |
| Amazonbot | 197 |
| PerplexityBot | 179 |
これらは攻撃ではありませんし、AI 検索に載るためにはむしろ来てほしいものです。ただしアクセス解析としては致命的でした。
素朴に「bot っぽい UA を除外」しても、AI クローラの UA は次々増えるので追いつきません。結局、1×1 の GIF ビーコンを </body> 直前に置いて、それを読み込んだリクエストだけを人間として数える方式に変えました。
// hit.php — Content-Length は strlen で出すこと(ここで一度ハマった)
$gif = base64_decode('R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7');
header('Content-Type: image/gif');
header('Content-Length: ' . strlen($gif)); // 42。43 と決め打ちして nginx にエラーを吐かせた
echo $gif;
これで数えたところ、ログ上の「bot ではない PV」と、実際にビーコンを読んだ人数は桁が違いました。レンダリングするクローラ(プレビュー生成や AI のブラウジング)も混ざるので、さらに逆引き(PTR)でデータセンター由来を分離しています。
「1日◯◯アクセス」という数字は、2026年現在ほとんど意味を持ちません。
5. なぜ33万回叩かれて平気だったか
結論から言うと、特別なことは何もしていません。
- SSH が公開鍵認証(当てるべきパスワードが存在しない)
-
CMS を使っていない(静的 HTML 中心。狙われている
/wp-admin/がそもそも無い) - 設定とデータを公開ディレクトリの外に置いている
- ufw で 22/80/443 だけ開けている
- 自作で動かしている PHP がアクセスカウンターだけ
攻撃の内訳を見ればわかりますが、全部「ありがちな設定ミスがある家」を探す作業です。賢いことは何もしていません。逆に言えば、上のどれか1つが欠けていれば33万回のうちどれかが当たります。
6. ハマりどころ: sshd_config は「最初に見つけた値が勝つ」
ここからが本題です。
さすがにパスワード認証は止めようと思い、いつもの通り drop-in を置きました。
# /etc/ssh/sshd_config.d/99-hardening.conf のつもりで書いた
PasswordAuthentication no
PermitRootLogin prohibit-password
片方しか効きませんでした。
sshd_config の man にはこう書いてあります。
Unless noted otherwise, for each keyword, the first obtained value will be used.
後勝ちではなく、先勝ちです。 そして Ubuntu の /etc/ssh/sshd_config はこうなっていました。
$ grep -vE '^\s*#|^\s*$' /etc/ssh/sshd_config | head -3
PermitRootLogin yes # ← 1行目
Include /etc/ssh/sshd_config.d/*.conf # ← 2行目
KbdInteractiveAuthentication no
PermitRootLogin yes が Include より前にあります。つまり sshd_config.d/ に何を書いても PermitRootLogin は絶対に上書きできません。
さらに sshd_config.d/50-cloud-init.conf にはこれが入っています。
PasswordAuthentication yes
99- で始まるファイルは 50- より後に読まれるので、これも先勝ちで負けます。
正しくはこうする
# 1. drop-in は 50-cloud-init.conf より先に読ませる(00- で始める)
# ★ファイル名を変えると設定が効かなくなるので、その旨をファイル内にコメントで書いておく
cat > /etc/ssh/sshd_config.d/00-hardening.conf <<'EOF'
PasswordAuthentication no
KbdInteractiveAuthentication no
PermitEmptyPasswords no
PubkeyAuthentication yes
PermitRootLogin prohibit-password
EOF
chmod 600 /etc/ssh/sshd_config.d/00-hardening.conf
# 2. Include より前にある PermitRootLogin をコメントアウトする
sed -i 's|^PermitRootLogin yes|# PermitRootLogin yes (00-hardening.conf へ移動)|' /etc/ssh/sshd_config
必ず sshd -T で実効値を確認してください。 ファイルを置いただけで満足すると、「設定したつもりで一切効いていない」状態になります。
$ sshd -T | grep -E '^(permitrootlogin|passwordauthentication|pubkeyauthentication)'
permitrootlogin without-password
pubkeyauthentication yes
passwordauthentication no
sshd -T は設定ファイルを読んだ結果であって、動いているデーモンの状態ではありません。reload するまで反映されません。ここも取り違えやすいところです。
7. 締め出されないための手順
SSH の設定変更で最悪なのは、自分が入れなくなることです。うちは踏み台も社内 LAN もない個人サーバーなので、真面目に段取りを組みました。
① 変更前にバックアップと復旧スクリプトを置く
mkdir -p /root/ssh_backup_20260727
cp -a /etc/ssh/sshd_config /etc/ssh/sshd_config.d /root/ssh_backup_20260727/
cat > /root/ssh_revert.sh <<'EOF'
#!/bin/bash
cp -a /root/ssh_backup_20260727/sshd_config /etc/ssh/sshd_config
rm -f /etc/ssh/sshd_config.d/00-hardening.conf
cp -a /root/ssh_backup_20260727/sshd_config.d/. /etc/ssh/sshd_config.d/
systemctl reload ssh
EOF
chmod +x /root/ssh_revert.sh
② 自動巻き戻しを仕掛けてから作業する(これが本命)
systemd-run --on-active=10min --unit=ssh-autorevert /root/ssh_revert.sh
10分後に設定が勝手に元へ戻ります。 成功を確認できたら解除します。
systemctl stop ssh-autorevert.timer
at でも同じことはできますが、systemd-run は追加パッケージなしで使えて、systemctl list-timers で残り時間が見えるのが良いです。
$ systemctl list-timers ssh-autorevert.timer --no-pager
NEXT LEFT UNIT ACTIVATES
Mon 2026-07-27 12:22:50 JST 9min ssh-autorevert.timer ssh-autorevert.service
この保険があるだけで、SSH 設定の変更が「怖い作業」でなくなります。 手が滑っても10分待てば戻ります。
③ restart ではなく reload、その前に sshd -t
sshd -t || { echo "文法エラー。適用中止"; exit 1; }
systemctl reload ssh
reload なら確立済みの接続は切れませんし、万一新しい設定に問題があっても動いているデーモンはそのままです。restart を打つ理由はまずありません。
④ 別の新規接続で確認してから、元の接続を閉じる
私は自動化ツール経由なので毎回新規接続になりますが、手作業の場合は今のターミナルを絶対に閉じずに、別のターミナルから接続確認してください。
拒否されることの確認も忘れずに。
$ ssh -o PubkeyAuthentication=no -o PreferredAuthentications=password root@example
root@example: Permission denied (publickey). # ← パスワードの入口が閉じている
⑤ 最後の砦を確保しておく
VPS なら Web コンソール(VNC)があります。SSH が完全に死んでも入れます。ただし root にパスワードが設定されていないと、そのコンソールでもログインできません。 事前に確認してください。
grep -q '^root:[^*!]' /etc/shadow && echo "コンソールから入れる" || echo "★入れない。要対処"
8. fail2ban
攻撃自体は止められないので、失敗が続く相手を自動遮断します。
# /etc/fail2ban/jail.local
[DEFAULT]
# ★自分の回線を必ず除外する。ここを忘れると自分が締め出される
ignoreip = 127.0.0.1/8 ::1 203.0.113.0/24
findtime = 10m
maxretry = 5
bantime = 1h
# 繰り返す相手ほど長くする
bantime.increment = true
bantime.factor = 2
bantime.maxtime = 1w
backend = systemd
[sshd]
enabled = true
port = ssh
Ubuntu 24.04 では journald が正なので backend = systemd にしておきます。適用前に文法チェックできます。
fail2ban-client -t && systemctl reload fail2ban
fail2ban-client get sshd ignoreip # 除外が効いているか必ず確認
遮断機能が本当に動くかは、予約アドレスで試すのが安全です。
fail2ban-client set sshd banip 203.0.113.1 # TEST-NET-3。実在しない
nft list ruleset | grep -A3 f2b # ルールが入ったか目視
fail2ban-client set sshd unbanip 203.0.113.1
Ubuntu 24.04 の fail2ban は iptables ではなく nftables アクションを自動選択します。iptables -S | grep f2b が空でも壊れているとは限りません。私はこれで一瞬あせりました。
侵入防止の効果もありますが、実際にいちばん効くのは ログが減ることです。月33万件の失敗に埋もれていると、本当に見るべき異常が見えません。
9. そして自分の穴が見つかった
ここまで「うちは大丈夫」という前提で調べていたのですが、実際には穴がありました。
$ find /var/www -maxdepth 3 \( -name '.env' -o -name '.git' -o -name '*.conf' \)
/var/www/html/artifacts/tkurume.work.conf
去年サーバー移行したときの nginx 設定ファイルのコピーが、公開ディレクトリに残っていました。 URL を叩けば誰でも読めます。
$ curl -o /dev/null -w '%{http_code}\n' https://example/artifacts/tkurume.work.conf
200
中身にパスワードは無く、アクセス記録も外部からは0件でしたが、あってはならない状態です。即削除しました。
高度な攻撃で破られたのではありません。穴は自分の置き忘れから開きます。 攻撃者が6,085回も .env を探しているのは、実際に置き忘れる人がいるからです。
一度これを実行してみることをおすすめします。
find /var/www -maxdepth 4 \( -name '.env' -o -name '.git' -o -name '*.sql' \
-o -name '*.conf' -o -name '*.bak' -o -name '*~' -o -name '*.orig' \) 2>/dev/null
10. 制御機器をインターネットにつなぐ方へ
私の本業は照明や舞台の制御装置の設計です。近年「遠隔で監視したい」「スマホから操作したい」という要望が確実に増えました。
そのとき必ずお伝えしていることがあります。
制御機器をグローバル IP に直接晒さないでください。
Art-Net、Modbus、各種の独自プロトコルの多くは認証という概念を持っていません。閉じたネットワークで使う前提で設計されているからです。外から届く場所に置けば、見つけた者が操作できます。パスワードを破る必要すらありません。
この記事の数字が示しているのは、「うちみたいな小さいところは狙われない」が完全に間違っているということです。攻撃は有名かどうかで来るのではなく、全アドレスを機械が順番に舐めた結果として来ます。1日数人しか来ないサイトに1日1万2千回です。
- 制御用ネットワークは分離する(外に出す必要があるのは監視情報だけで、制御そのものではない)
- 遠隔操作が要るなら VPN か踏み台経由にする
- 初期ユーザー名・初期パスワードのまま外につながない(
piが1,613回狙われている現実) - 設置後に一度ログを見る。何が来ているか見れば優先順位はすぐ決まる
制御プロトコル側の事情については、自分のサイトに書いたものがあります。
- 照明制御の規格をやさしく解説 — 位相制御 / 0-10V / DALI / DMX512 / Art-Net / PWM の違いと、それぞれが何を前提に設計されているか
- 照明制御の方式変換・特殊制御 — 方式の違う設備どうしをつなぐ話
- DMX512 / Art-Net トラブルシューティング — 「DMXは大概動く。だから怖い」
まとめ
- 1日数人の個人サイトに1か月33万回のログイン試行。サーバーを立てた瞬間から始まる
- 攻撃元864 IP、ほとんどが乗っ取られた正規クラウド。GeoIP 遮断は効きにくい
-
sshd_configは先勝ち。Includeより前の設定は drop-in で上書きできない。sshd -Tで必ず実効値を見る -
systemd-run --on-activeの自動巻き戻しを仕掛けてから SSH をいじる。これがあるだけで作業の性質が変わる - Ubuntu 24.04 の fail2ban は nftables を使う。
iptablesを見て空でも慌てない -
穴は攻撃ではなく自分の置き忘れから開く。
find /var/wwwを一度実行してみてください
おわりに
33万回という数字を見たときは正直ぎょっとしましたが、内訳を数えてみると**やっていることは全部「開いている家探し」**でした。高度な攻撃は1件も来ていません。来ていたのは、初期パスワードと置き忘れのファイルを探す機械だけです。
だからこそ、基本を外していなければ通らないし、1つ外していれば通るという、身も蓋もない結論になります。私自身、大丈夫だと思って調べ始めて、9章の置き忘れが出てきました。
同じ内容を、コマンド抜きの一般向けにも書きました。数字は同じです。
📊 個人サイトが1か月で33万回攻撃されていた — 実測データと対策
普段は照明・舞台の制御装置を個人で設計しています。調光卓、DMX512 / Art-Net / DALI、LEDシャンデリアの深絞り調光、カレンダー連動の演出制御など、**「既製品では届かないところ」**を主にやっています。
過去の記事や実績はこちらにまとめてあります。
🔗 tkurume.work
- 💡 LED照明がちらつく・暗くできない — 症状から引ける形にまとめたもの
- 🇫🇮 Helvar の DALI システムと DMX512 をつなぐ — 254段階・1200bps という壁
- 📈 LED時代の調光カーブ設計 — 消え際をどう作るか
- 🕺 歌舞伎町ディスコ史と電光ニュースの時代 — 1980年代、現場にいた制御屋の記録
方式の選定や特殊制御でお困りのことがあれば、サイトからご相談いただけます。