0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

1日数人の個人サイトが1か月で33万回攻撃されていた — ログを数えて塞ぐまで

0
Last updated at Posted at 2026-07-27

照明制御(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.1245MB ありました。ディスクを食っているのが「攻撃の記録」というのは、なかなか間抜けな話です。

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万回叩かれて平気だったか

結論から言うと、特別なことは何もしていません

  1. SSH が公開鍵認証(当てるべきパスワードが存在しない)
  2. CMS を使っていない(静的 HTML 中心。狙われている /wp-admin/ がそもそも無い)
  3. 設定とデータを公開ディレクトリの外に置いている
  4. ufw で 22/80/443 だけ開けている
  5. 自作で動かしている 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 yesInclude より前にあります。つまり 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回狙われている現実)
  • 設置後に一度ログを見る。何が来ているか見れば優先順位はすぐ決まる

制御プロトコル側の事情については、自分のサイトに書いたものがあります。

まとめ

  • 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

方式の選定や特殊制御でお困りのことがあれば、サイトからご相談いただけます。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?