メールアカウントのパスワード漏洩から大量のスパムメールを送信された話

さくらのVPSで運用しているメールサーバで、メールアカウントのパスワードが漏洩し、大量の迷惑メールが送信される事案が発生しました。

サーバそのものに侵入された形跡はなく、正しいメールアカウントとパスワードを使ってSMTP認証を突破されています。

この記事では、
何が起きたのか/どう発見したか/なぜ既存対策では防げなかったのか/実施した対策
をまとめます。


1. メールサーバの構成

今回のメールサーバは AlmaLinux10 上で運用しており、主な構成は以下です。

  • freenginx:メールProxyサーバ、メール認証用Webサーバ
  • Postfix:SMTPサーバ(送信/受信)
  • Rspamd:送信側ではDKIM/ARC署名/Virusチェックに利用
  • Dovecot:IMAP/POPサーバ(メールボックス)
  • 389 Directory Server:アカウント情報管理サーバ
  • Valkey:認証失敗回数等の管理(認証用phpで利用)

構成図 2要素認証構成

既存の認証制限機能

過去7日間に認証成功履歴がないIPであれば、同一プロトコル(smtp/pop/imap)で短期間に3回失敗した時点で最大30日間そのIPをブロックする。
同一IPからの攻撃には強い、毎回IPを変更する攻撃には効果がない。

2. 何が起きたのか

不正利用されたメールアカウントは A と B の2つでした。
ただし、大量のスパム送信に利用されたのはAのみです。Bについて確認された不正利用は、IMAPによるメールボックスへのアクセスでした。

被害内容

  • メール配送件数:45,078
  • 外部への配送(宛先はユニークで集計)
    • 成功件数(Sent):19,047
    • 一時エラー件数(Deferred):4,922
    • エラー件数(Bounced):21,624
  • ユニーク認証成功クライアントIP数:13,131
クライアントIPの国別件数集計結果を表示する
順位 国名 (国コード) 送信件数
1 ロシア (RU) 8,987
2 インド (IN) 6,108
3 ベネズエラ (VE) 5,645
4 オーストラリア (AU) 2,583
5 イギリス (GB) 2,041
6 パキスタン (PK) 2,004
7 アルゼンチン (AR) 1,953
8 イラン (IR) 1,656
9 韓国 (KR) 1,413
10 スウェーデン (SE) 1,277
11 台湾 (TW) 1,020
12 トルコ (TR) 965
13 イタリア (IT) 697
14 タイ (TH) 590
15 ベトナム (VN) 469
16 香港 (HK) 421
17 フランス (FR) 341
18 カーボベルデ (CV) 334
19 インドネシア (ID) 331
20 マレーシア (MY) 328
21 ポルトガル (PT) 326
22 カナダ (CA) 322
23 イラク (IQ) 321
24 スペイン (ES) 281
25 ルーマニア (RO) 263
26 ガーナ (GH) 237
27 エジプト (EG) 219
28 アラブ首長国連邦 (AE) 209
29 南アフリカ (ZA) 209
30 イスラエル (IL) 202
31 メキシコ (MX) 192
32 シンガポール (SG) 159
33 ドイツ (DE) 149
34 パラグアイ (PY) 145
35 ペルー (PE) 143
36 フィンランド (FI) 142
37 オランダ (NL) 128
38 バングラデシュ (BD) 120
39 ブルガリア (BG) 119
40 タンザニア (TZ) 109
41 ケニア (KE) 102
42 ハンガリー (HU) 98
43 ボリビア (BO) 94
44 日本 (JP) 89
45 ポーランド (PL) 84
46 モーリシャス (MU) 84
47 コロンビア (CO) 79
48 ザンビア (ZM) 74
49 ウズベキスタン (UZ) 70
50 ウクライナ (UA) 62
51 スロバキア (SK) 57
52 スイス (CH) 52
53 カンボジア (KH) 52
54 ブルネイ (BN) 52
55 コスタリカ (CR) 51
56 モロッコ (MA) 49
57 ドミニカ共和国 (DO) 49
58 フィリピン (PH) 47
59 ラオス (LA) 45
60 アルジェリア (DZ) 43
61 チリ (CL) 41
62 ギリシャ (GR) 39
63 アルメニア (AM) 38
64 チェコ (CZ) 38
65 ボスニア・ヘルツェゴビナ (BA) 36
66 オーストリア (AT) 36
67 サウジアラビア (SA) 36
68 デンマーク (DK) 35
69 クウェート (KW) 34
70 ナイジェリア (NG) 34
71 コンゴ共和国 (CG) 30
72 カザフスタン (KZ) 29
73 マカオ (MO) 29
74 ベルギー (BE) 29
75 ノルウェー (NO) 27
76 ベリーズ (BZ) 27
77 ニュージーランド (NZ) 20
78 エクアドル (EC) 19
79 グアドループ (GP) 18
80 北マケドニア (MK) 17
81 セルビア (RS) 17
82 モザンビーク (MZ) 15
83 セントクリストファー・ネイビス (KN) 15
84 プエルトリコ (PR) 14
85 サンピエール島・ミクロン島 (PM) 14
86 レバノン (LB) 13
87 チュニジア (TN) 12
88 モルドバ (MD) 12
89 グアム (GU) 11
90 マルタ (MT) 11
91 スリランカ (LK) 11
92 パナマ (PA) 10
93 アンゴラ (AO) 10
94 スーダン (SD) 10
95 アフガニスタン (AF) 8
96 エチオピア (ET) 8
97 カタール (QA) 7
98 アゼルバイジャン (AZ) 7
99 セネガル (SN) 7
100 リトアニア (LT) 6
101 コソボ (XK) 6
102 アイスランド (IS) 5
103 アルバニア (AL) 5
104 ネパール (NP) 5
105 エストニア (EE) 5
106 サントメ・プリンシペ (ST) 4
107 ルクセンブルク (LU) 4
108 キプロス (CY) 4
109 バルバドス (BB) 4
110 パレスチナ (PS) 3
111 ラトビア (LV) 3
112 クロアチア (HR) 3
113 アメリカ (US) 2
114 エスワティニ (SZ) 2
115 ホンジュラス (HN) 2
116 ヨルダン (JO) 2
117 カメルーン (CM) 2
118 ブラジル (BR) 1
119 エルサルバドル (SV) 1
120 アイルランド (IE) 1
121 ウルグアイ (UY) 1

アメリカ (US) が不自然なほど少ない

3. 攻撃のタイムライン

■ 8月26日〜29日:アカウントAの認証突破、テスト送信

8月26日:20:53 にアカウントAのSMTP AUTHに成功し、特定宛先(u1@frosttree.xyz)へメールを1通送信
8月29日:10:41 - 12:46 に同様に同宛先に3通送信
=> 認証情報が使えるかの確認をしていたと考えられる。

■ 8月29日 22:26:アカウントAを使用して大量送信開始

不特定多数へのスパム送信が本格化。

■ 9月3日:アカウントBの認証突破、不正利用

  • 9月3日 11:59 ベトナムから IMAP 認証成功
  • 9月3日 13:39 ドイツから IMAP 接続
    • 仮想通貨取引サイトからメール受信可否の確認と思われる操作(アカウント作成まではされていない)
    • IMAP接続後、仮想通貨取引サイトからのメールを受信し、受信したメールの削除

メールログとZabbixのトラフィックデータから、既存メールを読み出した形跡はなかった。

Sep  3 11:59:58 alpha dovecot[1206]: imap(*****@ma3ki.net)<269460></uzZWItaVLB/AAAB>: Disconnected: Connection closed (No commands sent) in=0 out=459 deleted=0 expunged=0 trashed=0 hdr_count=0 hdr_bytes=0 body_count=0 body_bytes=0
Sep  3 13:41:59 alpha dovecot[1206]: imap(*****@ma3ki.net)<274178><5pgsvYxayJx/AAAB>: Disconnected: Connection closed (SELECT finished 0.325 secs ago) in=5036 out=499122 deleted=1 expunged=1 trashed=0 hdr_count=107 hdr_bytes=428695 body_count=1 body_bytes=39356

dovecotさん、詳細なログを出してくれてありがとう。

4. 発見の経緯

■ 9月5日 10:00:別作業中にサーバでたまたま ps -ef を実行し、Postfix のプロセスが多いことに気付きました。

プロセスが滞留することに違和感を感じログを確認したところ、すぐに大量送信を発見。

すぐに

  1. Postfix停止
  2. 影響調査
  3. キュー削除
  4. パスワード変更

を実施

パスワードを変更したことで不正送信は停止したが、その後もしばらく認証試行は続き、すべて認証に失敗していることを確認。
9月8日 8:25 を最後にアカウントAへのSMTP認証試行も確認されなくなりました。

発生時に発見できなかった理由は、

  • 7月にメールサーバをRocky Linux9 から AlmaLinux10 にリプレースし、メールログの監視を後回しにしていた
  • アカウントAにはリターンメールが大量に届いていたが、メールの送受信に利用していなかった

5. OS侵入の有無(侵入の痕跡なし)

SSHは公開鍵認証のみ+非標準ポートで運用しており、ログにも侵入の痕跡はありませんでした。
今回の不正利用は SMTP AUTH による悪用であり、OS侵入を示す証拠は見つかってません。

6. 既存の不正アクセス対策が効かなかった理由

  • Nginx Mail Proxy と Valkey を組み合わせたブルートフォース/ディクショナリーアタック対策
    => アカウントA/B 共に、いきなり正しいパスワードで認証成功しているため発動せず

  • Postfix の sender_login_mismatch
    => 認証アカウントと送信されたメールのFromが一致したため発動せず

既存対策は「ブルートフォース/ディクショナリーアタック」「送信者詐称」を前提にした防御だったため、
正しいパスワードを使った不正利用には無力だった。

7. 一次対応

不正アクセスされないための対策

■ 対応1:GeoIP2 による国外アクセス拒否

認証用スクリプトに指定した国のみ認証を許可する機能を追加。
JPからのアクセスのみ認証に進めるように動作を変更。
今回の攻撃はほぼ海外からだったため、この効果が大きい。

■ 対応2:Webmail の認証を2要素認証必須に変更

Webmailからの不正アクセスの形跡は見つかってないが同じアカウントパスワードでログインが可能なため、2要素認証必須に変更。

# config/config.inc.php
$config['oauth_login_redirect'] = true;

WebmailのURLにアクセスするとAutheliaにリダイレクトされ、パスワード認証後にOTPを求められる
詳しくは RoundcubeとAutheliaで2要素認証を試してみた を参照

2要素認証画面

1. アカウント/パスワード認証 2. OTP認証
Authelia_Password Authelia_OTP

不正アクセスされたときの対策

■ 対応3:認証済みメールにも Rspamd の迷惑メール判定を適用

送信メールにも Rspamd を適用し、迷惑メール判定・流量制限(メールアドレス毎に1分間に2通まで)を導入。
認証成功後に送信されるメールもGPTプラグイン(さくらのAIエンジンと連携)などで内容を確認し、スコアが1.0以上の場合は送信拒否。

Rspamd設定例
# /etc/rspamd/local.d/settings.conf
outbound_authenticated {
    priority = 10;
    authenticated = true;

    apply {
        actions {
            reject = 1.0;   # 1.0点以上で送信拒否(露骨なスパムは確実に遮断)
        }

        symbols_disabled = [
            "R_SPF_FAIL",
            "R_SPF_SOFTFAIL",
            "R_SPF_PLUSALL",
            "R_DKIM_REJECT",
            "R_DKIM_PERMFAIL",
            "DMARC_POLICY_REJECT",
            "DMARC_POLICY_QUARANTINE",
            "DMARC_POLICY_SOFTFAIL",
            "MX_INVALID",
            "MX_NONE",
            "ONCE_RECEIVED",
            "ONCE_RECEIVED_STRICT"
        ];
    }
}
# /etc/rspamd/local.d/ratelimit.conf
action = "soft reject";

rates {
    user_strict_limit = {
        selector = "user.lower";
        bucket = [
            {
                burst = 2;
                rate = "2 / 1min";
            }
        ];
    }
}

設定後、試しに下記のメールを gmail.com に送信し、X-GPT-Reason が出力されることを確認

件名: これはテストメールです。
本文: これはテストメールです。

X-GPT-Reason: No malicious content, no suspicious URLs or requests, appears to be a harmless test message.

■ 対応4:Postfix の宛先数制限

今回のアクセスでは1回の SMTP トランザクションで複数宛先のものはなさそうだが、少なめに変更。

smtpd_recipient_limit = 10

8. 恒久対応

■ 対応5:OTP before MailService

対策1のGeoIP2による国外アクセス拒否は、JPを許可している場合、日本国内から正しいパスワードを使って認証された不正アクセスには効果がない。 また、WebmailはAutheliaと連携して2要素認証に対応しているが、通常のメールクライアントは任意のメールサーバのOAuth2/OIDCに対応していないためそう簡単な話ではない。 そこでAutheliaで二要素認証したアカウント・IPからは一定時間MailService(SMTP/POP/IMAP)の認証を許可するようにした。これを「OTP before MailService」と呼ぶことにした。2FA before MailService と少し迷った。

OTP before MailService フローチャート

  flowchart TD
    %% 事前認証のステップ
    A["ブラウザ"] -->|アクセス| B["AutheliaでOTP認証"]
    B -->|認証成功| C["Valkeyにアカウント/IPアドレスを登録<br>(TTL 600秒)"]

    %% メールアクセスのステップ
    D["メールクライアント"] -->|送受信| E["メールプロキシ (freenginx)"]
    E -->|アカウント/IPアドレスを確認| F{"Valkeyに登録されているか?"}

    %% 分岐の結果
    F -->|はい| G{"パスワードは正しいか?"}
    F -->|いいえ| J["通信を即座に拒否"]

    %% 認証の結果
    G -->|はい| H["メールサーバへ通信を中継"]
    G -->|いいえ| J["通信を即座に拒否"]

仕組み

  1. WebでAutheliaへアクセスしてOTP認証を行う。
  2. OTP認証に成功時に、アカウントと接続元IPをValkeyに記録(TTL 600秒)
  3. メールクライアントからSMTP/POP/IMAPなどで接続すると、Mail ProxyにてOTP認証済みかを確認
  4. アカウント/接続元IPがValkeyに登録されていれば、メールアカウントのパスワード認証を行い、各MailServiceへProxyする。

メールクライアント側から見ると、従来のID・パスワード認証のまま利用できる。 メールクライアントで認証成功すると、同じアカウント・IPの組み合わせはGeoIPやOTP認証制限を長時間(TTL 86400秒)回避できる。 定期的に認証するクライアントであれば、一度OTP認証してしまえばIPが変わらない限り何度もOTP認証をする必要はない。

実装

  • Authelia で OTP認証成功したログを syslog に出力するようにする。
    • 認証成功ログはdebug用のログなのでソースを修正してinfoレベルで出力するバイナリを作成
  • 上記ログ出力されたらrsyslog経由から起動する Valkey にアカウント・IP・TTLを登録するプログラムを作成
  • Mail Proxyの認証スクリプトに上に記載したOTP before MailService 機能を追加

ログ

私のメールサーバは間違ったパスワードを入力するとログに入力されたものが出力されます。

#-- 中国からのアクセスを拒否
Oct 11 03:51:42 alpha nginx-mail-proxy[71408]: auth=reject, reason='OTP required', user=job@ma3ki.net, client=192.144.153.60, country=CN, proto=smtp, passwd='12'

#-- 普段利用していないIPから正規のアカウントでのアクセスを拒否
Oct 10 18:43:18 alpha nginx-mail-proxy[60729]: auth=reject, reason='OTP required', user=example@ma3ki.net, client=153.142.225.118, country=JP, proto=imap, passwd='***(redacted_correct_password)***'

#-- AutheliaでのOTP認証ログ
Oct 10 18:45:04 alpha authelia[765]: time="2026-10-10T18:45:04+09:00" level=info msg="Successful TOTP authentication attempt made by user 'example@ma3ki.net'" method=POST path=/api/secondfactor/totp path_raw=/authelia/api/secondfactor/totp remote_ip=153.142.225.118

#-- OTP認証後のIMAPアクセスを許可
Oct 10 18:45:15 alpha nginx-mail-proxy[60657]: auth=successful, access=otp, meth=plain, userid=example@ma3ki.net, client=153.142.225.118, country=JP, proto=imap, proxy=127.0.0.1:143

#-- OTP認証後のIMAPアクセスのあとを許可
Oct 10 19:34:51 alpha nginx-mail-proxy[69564]: auth=successful, access=trusted, meth=plain, userid=example@ma3ki.net, client=153.142.225.118, country=JP, proto=imap, proxy=127.0.0.1:143

9. まとめ

これだけあらゆる企業が不正アクセスを受けているのに自分のパスワードが漏れて不正アクセスをされるとは考えてもいなかった意識の低さが今回の原因です。
そのおかげというのもよくないですが、漏洩したアカウントパスワードを使用された不正アクセスをどう防ぐかを真剣に考えられ、自身が満足できる OTP before MailServiceを思いつき動かすことができました。
一次対応をしてから2週間以上経過しましたが、Google には受信を拒否されたままでしたので、Postmaster Tools にて問い合わせをして無事9/22にメールを受け取ってもらえるようになりました。
今回スパムメールを送信してしまった方々、メールサーバ運用者様、大変申し訳ありませんでした。

Built with Hugo
Theme Stack designed by Jimmy