メールの「真実」を剥き出しにする:Outlook VBAによるヘッダー・フォレンジックとなりすまし検知の極致
エンジニア諸君、よく聞いてほしい。
「`MailItem.SenderEmailAddress` が `example.com` だから安全だ」などという甘い認識でシステムを組んでいる者がいるなら、その設計思想は今すぐ破棄すべきだ。それは、玄関の表札だけを見て、中に入ってきたのが正当な住人か、それとも仮面を被った侵入者かを判断しているに等しい。
真のセキュリティ、そして真の自動化エンジニアリングとは、提示された「表示情報」ではなく、データそのものが内包する「証跡(Provenance)」を解析することにある。
本稿では、Outlook VBAを用いて、メールの生ヘッダー(MAPIプロパティ)を直接解析し、SPF/DKIM/DMARCといった認証情報の成否を判定、なりすまし疑いのあるメールを隔離する、極めて堅牢な監視ロジックの構築手法について詳述する。これは単なるスクリプトではない。レガシーな環境においても、メモリ効率と決定論的な動作を両立させるための、アーキテクチャの設計指針である。
1. 信頼の階層:なぜ `SenderEmailAddress` では不十分なのか
標準的なVBAプロパティは、ユーザーに親切な「解釈済みのデータ」を提供してくれる。しかし、攻撃者はこの解釈プロセスを容易に欺くことができる。
我々がアクセスすべきは、SMTP通信の生の結果が刻印された `PR_TRANSPORT_MESSAGE_HEADERS` である。ここには、メールが通過してきたサーバーの履歴、そして `Authentication-Results` ヘッダーが含まれている。ここにこそ、送信ドメイン認証(SPF/DKIM/DMARC)の結果という「動かしようのない事実」が眠っているのだ。
2. 実装の核心:MAPIプロパティへのダイレクトアクセス
通常の `MailItem` オブジェクトのプロパティではなく、`PropertyAccessor` を使用して、MAPIのスキーマプロパティに直接アクセスする。これがプロフェッショナルの手法だ。
必須となるMAPIプロパティ・タグ
- `http://schemas.microsoft.com/mapi/proptag/0x007D001E` : `PR_TRANSPORT_MESSAGE_HEADERS`
このタグを用いることで、メールの全ヘッダー情報を一括で取得できる。
3. 堅牢な実装例:SecurityMonitor Module
以下に、実戦投入に耐えうる設計思想に基づいたコードを示す。
単に「動く」コードではない。オブジェクトのライフサイクルを管理し、メモリリークを防ぎ、例外発生時にもシステムを停止させないための防御的プログラミングを施している。
' ==============================================================================
' Module: SecurityMonitor
' Description: 高度なヘッダー解析によるなりすましメール検知・隔離システム
' Author: Chief Architect
' ==============================================================================
Option Explicit
' Windows API: 高精度なパフォーマンス計測(処理時間の監視用)
' 実際の運用では、検知ロジックの遅延がOutlookのUIスレッドをブロックしないか監視するために使用する
Private Declare PtrSafe Function GetTickCount Lib "kernel32" () As Long
' MAPI Property Tags
Private Const PR_TRANSPORT_MESSAGE_HEADERS As String = "http://schemas.microsoft.com/mapi/proptag/0x007D001E"
' 隔離用フォルダ名
Private Const QUARANTINE_FOLDER_NAME As String = "⚠️_Security_Quarantine"
''' <summary>
''' NewMailExイベントハンドラ。
''' NewMailイベントではなくNewMailExを使用することで、EntryIDを確実に取得し、
''' 高速なメッセージ処理を実現する。
''' </summary>
Public Sub Application_NewMailEx(ByVal EntryIDCollection As String)
Dim entryIDs() As String
Dim i As Long
Dim olItem As Object
Dim mailItem As Outlook.MailItem
Dim startTime As Long
startTime = GetTickCount()
' 複数のEntryIDがカンマ区切りで渡される可能性があるため分割
entryIDs = Split(EntryIDCollection, ",")
For i = LBound(entryIDs) To UBound(entryIDs)
Set olItem = Application.Session.GetItemFromID(entryIDs(i))
' オブジェクト型チェック(会議出席依頼などを除外するため)
If TypeOf olItem Is Outlook.MailItem Then
Set mailItem = olItem
' 解析プロセス開始
ProcessSecurityCheck mailItem
' 【メモリ最適化】オブジェクトの明示的な解放
' ループ内でのCOMオブジェクトの残留は、大規模運用時にメモリリークの主因となる
Set mailItem = Nothing
Set olItem = Nothing
End If
Next i
' 処理時間のログ出力(デバッグ・パフォーマンス監視用)
' Debug.Print "Processing Time: " & (GetTickCount() - startTime) & " ms"
End Sub
''' <summary>
''' メールのヘッダーを解析し、リスク判定を行うコアロジック
''' </summary>
Private Sub ProcessSecurityCheck(ByRef mailItem As Outlook.MailItem)
On Error GoTo ErrorHandler
Dim headerText As String
Dim isSuspicious As Boolean
Dim propertyAccessor As Outlook.PropertyAccessor
Set propertyAccessor = mailItem.PropertyAccessor
' 1. 生ヘッダーの取得
headerText = propertyAccessor.GetProperty(PR_TRANSPORT_MESSAGE_HEADERS)
' 2. 認証結果の解析 (Authentication-Results ヘッダーをスキャン)
' 判定ロジック: spf=fail, dkim=fail, dmarc=fail のいずれかが含まれているか
' 実際には、より厳密な正規表現(VBScript.RegExp)を用いるべきだが、
' パフォーマンスを考慮し、ここでは高速なInStrによるパターンマッチングを採用する
isSuspicious = False
' SPF/DKIM/DMARC の失敗パターンを検知
If InStr(1, headerText, "spf=fail", vbTextCompare) > 0 Or _
InStr(1, headerText, "dkim=fail", vbTextCompare) > 0 Or _
InStr(1, headerText, "dmarc=fail", vbTextCompare) > 0 Then
isSuspicious = True
End If
' 3. 追加の検知ロジック: 送信元ドメインと表示名の一致確認(簡易版)
' ※ ここに高度なドメイン検証ロジックを組み込むことが可能
' 4. 隔離アクション
If isSuspicious Then
MoveToQuarantine mailItem
End If
ExitSub:
Set propertyAccessor = Nothing
Exit Sub
ErrorHandler:
' ログ出力(実運用ではファイルやEvent Logへ書き出すこと)
Debug.Print "Error in ProcessSecurityCheck: " & Err.Description
Resume ExitSub
End Sub
''' <summary>
''' メールの隔離実行
''' </summary>
Private Sub MoveToQuarantine(ByRef mailItem As Outlook.MailItem)
Dim ns As Outlook.NameSpace
Dim quarantineFolder As Outlook.Folder
Dim targetFolder As Outlook.Folder
Dim found As Boolean
Set ns = mailItem.GetStore
found = False
' 隔離フォルダの探索
For Each targetFolder In ns.Folders
If targetFolder.Name = QUARANTINE_FOLDER_NAME Then
Set quarantineFolder = targetFolder
found = True
Exit For
End If
Next targetFolder
' フォルダが存在しない場合は作成(権限に注意)
If Not found Then
On Error Resume Next
Set quarantineFolder = ns.Folders.Add(QUARANTINE_FOLDER_NAME)
On Error GoTo 0
End If
' 隔離実行
If Not quarantineFolder Is Nothing Then
mailItem.Move quarantineFolder
End If
End Sub
4. アーキテクトの視点:運用における極限の知見
このシステムを実環境にデプロイする際、シニアエンジニアが考慮すべきは「コードの正しさ」だけではない。
オブジェクトのライフサイクル管理
VBAにおけるCOMオブジェクト(`MailItem` や `PropertyAccessor`)は、スコープを抜けてもメモリ上に残り続ける性質がある。特に `NewMailEx` のようなイベント駆動型プロシージャでは、大量のメールが短時間に流入した場合、参照が残っているだけでOutlookのメモリ使用量が指数関数的に増大し、最悪の場合、プロセスがクラッシュする。
必ず `Set obj = Nothing` を明示的に行い、オブジェクトの生存期間を最小化せよ。
レガシー環境への配慮
社内システムでは、依然として古いバージョンのOutlookや、制限された権限を持つユーザー環境が混在している。
`ns.Folders.Add` が権限不足で失敗するケース、あるいはプロパティへのアクセスがセキュリティソフトによってブロックされるケースを想定し、必ず `On Error GoTo` による堅牢な例外処理を組み込むこと。
パフォーマンスのトレードオフ
`VBScript.RegExp` を用いた正規表現によるヘッダー解析は強力だが、CPUサイクルを消費する。数千通のメールをリアルタイムに処理する場合、`InStr` による高速な文字列検索と、複雑な正規表現による精密な解析を、重要度に応じて使い分ける設計思想(Tiered Analysis)が求められる。
結言
なりすましメールの検知は、単なる文字列比較ではない。それは、プロトコルが提供する「証跡」をいかに正確に、かつ低負荷で抽出するかという、データエンジニアリングの戦いである。
このコードをベースに、君たちの環境に最適化された、真に「守れる」アーキテクチャを構築してほしい。
Build it robust, or don’t build it at all.
