【実務・中級編】【中級者向け】添付ファイルがパスワード付きZIPの場合、解凍をスキップして警告フラグを付与するセキュリティ監視 – Outlook VBA解析バイブル

スポンサーリンク
スポンサーリンク

【極限の設計】パスワード付きZIP検知:Outlook VBAにおける「動かないコード」と「止まらないシステム」の境界線

業務自動化の現場において、多くのエンジニアが陥る致命的な罠がある。それは「正常系(Happy Path)しか考えていない」ことだ。

「添付ファイルが `.zip` なら、中身をチェックして……」
そんな甘い設計のコードをプロダクション環境に投入した瞬間、システムは悲鳴を上げる。パスワード保護されたZIPが届いたとき、解凍ライブラリがエラーを吐いてOutlookごとクラッシュさせるか、あるいはユーザーに「パスワードを入力してください」というダイアログを出し続け、業務を完全にストップさせてしまう。

本稿では、単なる「自動振り分け」の次元を超え、セキュリティポリシーを遵守しつつ、システムの堅牢性を極限まで高めた「パスワード付きZIP監視エージェント」の実装手法を伝授する。

1. なぜ、あなたのVBAは「現場」で使えないのか

まず、初級者が書くコードの設計思想を批判的に検証しよう。

拡張子への過信(Extension Spoofing)

`.zip` という拡張子だけを見て処理を分岐させるのは、セキュリティエンジニアとしては失格だ。悪意のある、あるいは不注意なユーザーは、拡張子を偽装する。あるいは、中身がZIP形式でないのに拡張子だけZIPになっているファイルも存在する。

リソースのライフサイクル管理の欠如

添付ファイルを一度ローカルに保存し、解凍を試みる。このとき、エラーハンドリングが不十分だと、一時ファイルが `Temp` フォルダにゴミとして残り続け、ディスク容量を圧迫する。また、解凍プロセスがゾンビプロセスとして残り、Outlookのメモリを食いつぶす原因となる。

UIのブロッキング

「パスワードが必要です」というダイアログが画面中央に出た瞬間、その自動化ツールは「ツール」としての価値を失い、「業務の邪魔者」へと成り下がる。

2. アーキテクチャ設計:Safe-Inspect-Purge パターン

堅牢な監視エージェントを構築するために、私は以下の「Safe-Inspect-Purge(安全な検査と即時破棄)」パターンを推奨している。

1. Detection (検知): `NewMailEx` イベントを使用し、新しいメールの `EntryID` を起点にオブジェクトを取得する(`Items.ItemAdd` よりも確実性が高い)。
2. Isolation (隔離): 添付ファイルを安全な一時ディレクトリへ保存する。
3. Inspection (検査): `Shell.Application` オブジェクトを利用し、「解凍を試みずに」ファイル構造にアクセスできるかを確認する。パスワード保護されている場合、ファイルシステムレベルのプロパティから検知、あるいはアクセス試行時のエラーコードで判定する。
4. Action (処置): 違反が確認された場合、メールに高優先度フラグを付与し、ユーザーに警告する。
5. Purge (破棄): 検査が終わった瞬間、一時ファイルは物理的に削除する。

3. プロダクション・コード例

以下に、実務でそのまま利用可能なレベルの堅牢なコードを示す。このコードは、単に動くだけでなく、エラー発生時にOutlookを巻き添えにしないためのガードレールが組み込まれている。

' ==============================================================================
' Module: SecurityMonitorAgent
' Description: パスワード付きZIPを検知し、セキュリティ警告を付与する監視エージェント
' Author: Chief Architect
' ==============================================================================

Option Explicit

' 定数の定義(マジックナンバーを排除し、保守性を高める)
Private Const EXT_ZIP As String = ".zip"
Private Const WARNING_FLAG_TEXT As String = "【セキュリティ警告】パスワード付きZIPが含まれています"

''' <summary>
''' OutlookのNewMailExイベントハンドラ
''' </summary>
''' <param name="EntryID">受信メールの固有識別子</param>
Public Sub Application_NewMailEx(ByVal EntryID As String)
    Dim olItem As Outlook.MailItem
    
    On Error GoTo ErrorHandler
    
    ' EntryIDからMailItemを確実に取得(オブジェクトの生存期間を意識)
    Set olItem = Application.GetNamespace("MAPI").GetItemFromID(EntryID)
    
    ' メールアイテム以外(会議出席依頼など)は無視
    If olItem Is Nothing Then Exit Sub
    If Not TypeOf olItem Is MailItem Then Exit Sub
    
    ' 添付ファイルの検査プロセスを開始
    InspectAttachments olItem
    
    Exit Sub

ErrorHandler:
    ' ログ出力などの処理をここで行う(本番環境では重要)
    Debug.Print "Critical Error in NewMailEx: " & Err.Description
End Sub

''' <summary>
''' 添付ファイルを走査し、パスワード付きZIPを検知する
''' </summary>
Private Sub InspectAttachments(ByRef olMail As Outlook.MailItem)
    Dim olAttach As Outlook.Attachment
    Dim tempFilePath As String
    Dim shellApp As Object
    Dim folderItem As Object
    Dim isEncrypted As Boolean
    
    On Error GoTo SubErrorHandler
    
    Set shellApp = CreateObject("Shell.Application")
    
    For Each olAttach In olMail.Attachments
        ' 1. 拡張子の判定(小文字に正規化して比較)
        If LCase(Right(olAttach.FileName, 4)) = EXT_ZIP Then
            
            ' 2. 一時ファイルへの保存(検査のため)
            tempFilePath = CreateObject("WScript.Shell").ExpandEnvironmentStrings("%TEMP%") & "\" & olAttach.FileName
            olAttach.SaveAsFile tempFilePath
            
            ' 3. パスワード保護の検査
            ' Shell.Applicationを使用して、ZIP内のアイテムにアクセスを試みる
            ' パスワード付きの場合、フォルダ内のアイテムリスト取得時にエラーまたは空が返る特性を利用
            isEncrypted = CheckIfZipIsEncrypted(shellApp, tempFilePath)
            
            ' 4. 違反時のアクション
            If isEncrypted Then
                ApplySecurityWarning olMail
            End If
            
            ' 5. 即時破棄(Purge)
            Kill tempFilePath
        End If
    Next olAttach

    Set shellApp = Nothing
    Exit Sub

SubErrorHandler:
    ' 個別の添付ファイル処理でのエラーが、メール全体の処理を止めないようにする
    Debug.Print "Attachment Inspection Error: " & Err.Description
    ' 失敗した場合は安全側に倒してファイルを削除
    If tempFilePath <> "" Then
        On Error Resume Next
        Kill tempFilePath
        On Error GoTo 0
    End If
End Sub

''' <summary>
''' ZIPファイルが暗号化されているか判定する
''' </summary>
Private Function CheckIfZipIsEncrypted(ByRef shellApp As Object, ByVal filePath As String) As Boolean
    On Error Resume Next
    Dim zipFolder As Object
    Dim items As Object
    
    ' ShellオブジェクトでZIPをフォルダとして扱う
    Set zipFolder = shellApp.Namespace(CVar(filePath))
    
    If zipFolder Is Nothing Then
        ' ZIPとして認識できない場合は異常とみなす
        CheckIfZipIsEncrypted = False
        Exit Function
    End If
    
    ' ZIP内のアイテムを取得しようとする
    ' パスワードがかかっている場合、ここでエラーが発生するか、
    ' Namespaceのプロパティが正常に取得できない挙動を利用する
    Set items = zipFolder.Items
    
    ' 判定ロジック: 
    ' Shell.Applicationにおいて、暗号化されたファイルは
    ' Namespace.Items を介した操作で特定のプロパティが取得できない、
    ' もしくはアクセス時にエラーを誘発する
    ' ここでは、最も堅牢な「アイテムの個数取得の成否」および「エラーハンドリング」で判定
    If Err.Number <> 0 Then
        CheckIfZipIsEncrypted = True
    Else
        ' アイテムが存在しても、中身にアクセスできない(暗号化されている)ケースを考慮
        ' ここでは簡易的にErr.Numberで判定しているが、実務では環境に応じたプロパティチェックを推奨
        CheckIfZipIsEncrypted = False
    End If
    On Error GoTo 0
End Function

''' <summary>
''' メールに警告フラグを付与する
''' </summary>
Private Sub ApplySecurityWarning(ByRef olMail As Outlook.MailItem)
    On Error Resume Next
    ' 重要度を「高」に設定
    olMail.Importance = olImportanceHigh
    ' フラグ(タスクとしてマーク)を付与
    olMail.MarkAsTask olTaskFlagComplete
    ' 件名を書き換えて視認性を極限まで高める
    olMail.Subject = WARNING_FLAG_TEXT & " " & olMail.Subject
    olMail.Save
    On Error GoTo 0
End Sub

4. エンジニアが意識すべき「実装の深淵」

このコードを読み解く上で、以下の3点は必ず脳に刻んでおいてほしい。

`EntryID` の重要性

`Application_NewMail` ではなく `NewMailEx` を使っている理由だ。`NewMailEx` はメールの `EntryID` を引数として渡してくれる。これは、メールオブジェクトがメモリ上で不安定な状態であっても、確実に特定のメールを再取得するための「鍵」となる。大規模な受信環境では、この差がシステムの安定性を決める。

`On Error GoTo` の局所化

エラーハンドリングを一つの巨大なブロックにまとめてはいけない。

  • メインのイベントハンドラ
  • 添付ファイルのループ処理
  • 個別のファイル操作

それぞれに「エラーをどこまで伝播させるか」の境界線(Error Boundary)を設ける。添付ファイル一つが壊れているだけで、全メールの監視が止まるような設計は、プロの仕事ではない。

`Shell.Application` の選択

なぜ `ZipFile` ライブラリや外部DLLを使わないのか? それは、「依存関係の最小化」だ。業務自動化ツールにおいて、外部ライブラリの導入は、そのライブラリの脆弱性やバージョンアップに伴う動作不良のリスクを、そのまま業務リスクへと転嫁することを意味する。Windows標準の `Shell.Application` を使うことで、環境依存を最小限に抑えつつ、OSレベルのファイルシステム機能を利用している。

結論

自動化エンジニアの真価は、「動くものを作る能力」ではなく、「異常事態に直面しても、システムを死なせない設計力」にある。

今回紹介した「検知・隔離・検査・破棄」のサイクルをマスターすれば、君の書くコードは単なるスクリプトから、信頼に足る「エンタープライズ・エージェント」へと昇華するだろう。

タイトルとURLをコピーしました