「SenderEmailAddress」を信じるな。Outlook VBAで構築する、ヘッダー解析による堅牢ななりすまし検知エンジン
業務自動化の現場において、多くのエンジニアが陥る致命的な罠がある。それは、「表示されている送信元アドレスを、真実だと過信すること」だ。
「送信元が `ceo@yourcompany.com` になっているから安全だ」――そんな思考停止したロジックで書かれたコードは、高度ななりすまし攻撃(Spoofing)の前では、ただの無力なスクリプトに過ぎない。
本記事では、単なる「振り分けツール」の次元を超え、メールのメタデータ、すなわちインターネット・メッセージ・ヘッダーを直接解析することで、認証情報(SPF/DKIM/DMARC)の成否を判定し、リスクのあるメールを即座に隔離する「防御型自動化エンジン」の設計思想と実装を伝授する。
1. 設計思想:なぜ「既存のプロパティ」では不十分なのか
多くの初級者が書くコードは、`MailItem.SenderEmailAddress` をチェックするものだ。しかし、攻撃者はメールの「表示名」や「Envelope From」を容易に偽装できる。
プロフェッショナルが狙うべきは、メールの「真実」が刻印されているInternet Message Headersである。現代のメールセキュリティにおいて、その真実とは以下の3点に集約される。
1. SPF (Sender Policy Framework): 送信元IPアドレスが、ドメインの許可リストにあるか。
2. DKIM (DomainKeys Identified Mail): メールの内容が、公開鍵によって正当に署名されているか。
3. DMARC: 上記の認証が失敗した際、ドメイン所有者がどう振る舞うよう指示しているか。
これらはメールヘッダー内の `Authentication-Results` フィールドに集約されている。我々が構築すべきは、このフィールドを読み取り、`spf=pass` や `dkim=pass` といった文字列をスキャンするロジックである。
2. アーキテクチャ:堅牢性を担保する3つの原則
プロダクション環境で動かすコードには、以下の3原則が不可欠だ。
① `NewMailEx` イベントの採用
`NewMail` イベントは、複数のメールが同時に着信した際に、イベントが間引かれたり、どのメールに対して処理すべきかを見失う(Race Condition)リスクがある。
一方、`NewMailEx(EntryID)` は、着信した各メールの固有識別子(EntryID)を引数として渡す。「EntryIDを起点にオブジェクトを取得する」。これが、バグを寄せ付けない鉄則だ。
② PropertyAccessorによる低レイヤーへのアクセス
`MailItem` の標準プロパティには、ヘッダーの全容は格納されていない。MAPIプロパティのスキーマ(`http://schemas.microsoft.com/mapi/proptag/0x007D001E`)を叩き、生のヘッダー文字列を取得する必要がある。
③ 失敗を前提としたエラーハンドリング
メールには、暗号化されたもの、特殊なエンコードが施されたもの、あるいは破損しているものが存在する。これらを無視して処理を続行すれば、VBAは即座にクラッシュする。「解析に失敗した場合は、安全側に倒して隔離する」。これがセキュリティエンジニアの思考だ。
3. 実装:なりすまし検知・隔離エンジンのソースコード
以下に、実務に投入可能なレベルのコード例を示す。このコードは、`ThisOutlookSession` モジュールに記述することを想定している。
' ======================================================================================
' Module: ThisOutlookSession
' Description: 高度なヘッダー解析によるなりすましメール検知・隔離エンジン
' Author: Chief Architect
' ======================================================================================
Option Explicit
' MAPIプロパティタグ: Internet Message Headers
Private Const PR_INTERNET_MESSAGE_HEADERS As String = "http://schemas.microsoft.com/mapi/proptag/0x007D001E"
' 隔離用フォルダ名
Private Const ISOLATION_FOLDER_NAME As String = "【隔離】なりすまし疑い"
''' <summary>
''' 受信イベントハンドラ
''' </summary>
''' <param name="EntryID">着信したアイテムの固有識別子</param>
Private Sub Application_NewMailEx(ByVal EntryID As String)
Dim objItem As Object
Dim objMail As Outlook.MailItem
On Error GoTo ErrorHandler
' EntryIDからアイテムを直接取得(Race Conditionを回避する設計)
Set objItem = Application.Session.GetItemFromID(EntryID)
' メールアイテム以外(会議出席依頼など)は無視
If Not TypeOf objItem Is Outlook.MailItem Then Exit Sub
Set objMail = objItem
' 1. ヘッダー解析によるリスク判定
If IsSpoofingDetected(objMail) Then
' 2. リスクが確認された場合、隔離処理を実行
MoveToIsolationFolder objMail
End If
Exit Sub
ErrorHandler:
' 本番環境では、エラーログをファイルやDBに書き出す設計を推奨
Debug.Print "Critical Error in NewMailEx: " & Err.Description
End Sub
''' <summary>
''' ヘッダー内のAuthentication-Resultsを解析し、なりすましの疑いがあるか判定する
''' </summary>
''' <param name="mail">解析対象のメールアイテム</param>
''' <returns>なりすましの疑いがある場合は True</returns>
Private Function IsSpoofingDetected(ByVal mail As Outlook.MailItem) As Boolean
Dim strHeaders As String
Dim isSafe As Boolean
' 初期値は「安全ではない」とする(Fail-Safe設計)
IsSpoofingDetected = True
On Error GoTo HeaderError
' PropertyAccessorを使用して生のヘッダーを取得
strHeaders = mail.PropertyAccessor.GetProperty(PR_INTERNET_MESSAGE_HEADERS)
' 判定ロジック: Authentication-Results 内の spf, dkim, dmarc の成否を確認
' 少なくとも一つでも "pass" が含まれていれば、暫定的に安全とみなす(要件に応じて調整)
' ※ 実際には、送信ドメインとの一致確認などの高度なロジックが必要
If InStr(1, strHeaders, "spf=pass", vbTextCompare) > 0 And _
InStr(1, strHeaders, "dkim=pass", vbTextCompare) > 0 And _
InStr(1, strHeaders, "dmarc=pass", vbTextCompare) > 0 Then
isSafe = True
Else
isSafe = False
End If
' 安全な場合は False を返す
If isSafe Then IsSpoofingDetected = False
Exit Function
HeaderError:
' ヘッダーが取得できない、あるいは解析不能な場合は「疑わしい」として扱う
Debug.Print "Header Analysis Failed for: " & mail.Subject
IsSpoofingDetected = True
End Function
''' <summary>
''' 指定したメールを隔離フォルダへ移動する
''' </summary>
''' <param name="mail">移動対象のメール</param>
Private Sub MoveToIsolationFolder(ByVal mail As Outlook.MailItem)
Dim targetFolder As Outlook.Folder
Dim ns As Outlook.NameSpace
On Error GoTo MoveError
Set ns = mail.GetStore
' 隔離フォルダの存在確認と取得
Set targetFolder = GetOrCreateFolder(ns.GetDefaultFolder(olFolderInbox), ISOLATION_FOLDER_NAME)
' 移動実行
mail.Move targetFolder
Debug.Print "Mail Isolated: " & mail.Subject
Exit Sub
MoveError:
Debug.Print "Failed to move mail: " & Err.Description
End Sub
''' <summary>
''' フォルダの存在を確認し、なければ作成する(堅牢なフォルダ管理)
''' </summary>
Private Function GetOrCreateFolder(ByVal parentFolder As Outlook.Folder, ByVal folderName As String) As Outlook.Folder
Dim subFolder As Outlook.Folder
On Error Resume Next
Set subFolder = parentFolder.Folders(folderName)
On Error GoTo 0
If subFolder Is Nothing Then
Set subFolder = parentFolder.Folders.Add(folderName)
End If
Set GetOrCreateFolder = subFolder
End Function
4. プロフェッショナルのための補足:運用における注意点
このツールを実務に投入する際、以下の3点に留意せよ。
データベース連携の検討
もし、あなたが「特定のドメインからのなりすましをブラックリスト化したい」と考えるなら、VBA内にハードコードしてはいけない。外部のCSVファイル、あるいはSQLiteなどの軽量データベースを参照する設計にせよ。これにより、コードを書き換えることなく、セキュリティポリシーの更新が可能になる。
誤検知(False Positive)への対策
セキュリティを強めすぎると、正当なメールまで隔離されてしまう。
- ホワイトリストの実装: 社内ドメインや、信頼できるパートナー企業のドメインは、ヘッダー解析をスキップするロジックを必ず組み込むこと。
- 隔離通知: 隔離した事実は、管理者にログとして残すか、あるいはユーザーに「怪しいメールを隔離しました」と通知する仕組みを検討せよ。
パフォーマンスの重み
`PropertyAccessor` は強力だが、大量のメールを一度に処理しようとするとオーバーヘッドが発生する。`NewMailEx` を使い、着信の都度、ピンポイントで処理を行う今の設計は、パフォーマンスの観点からも理にかなっている。
結論
「動けばいい」というコードは、現場では「負債」でしかない。
真に価値のある自動化とは、「例外的な事態を予測し、それに対する振る舞いを設計に組み込んでいるもの」だ。
今回紹介したヘッダー解析のアプローチは、その第一歩に過ぎない。しかし、この「メタデータを疑い、真実を掴みに行く」という姿勢こそが、エンジニアとしての格の違いを決めるのである。
