【上級プロフェッショナル向け】マジックバイトとGetExtensionNameの不一致から不審な「拡張子偽装」を特定するセキュリティスクリプト
レガシーシステムがいまだ根強く稼働する企業インフラにおいて、最も巧妙かつ看過できない脅威の一つが「拡張子の偽装(Masquerading)」である。
攻撃者は、攻撃用バイナリ(`.exe`, `.scr`, `.pif` 等)を `.txt`, `.jpg`, `.pdf` といった無害に見える拡張子に偽装し、ファイルサーバーやWebアップロード経由で組織内へ持ち込もうとする。
VBScriptの標準機能である `FileSystemObject (FSO)` の `GetExtensionName` は、あくまで「文字列としての拡張子」を返すに過ぎない。つまり、`malware.exe` を `malware.jpg` にリネームすれば、FSOは平然と `”jpg”` という文字列を返す。この欺瞞を見破るためには、ファイルシステムのレイヤーに降りて「マジックバイト(ファイルの先頭シグネチャ)」を直接読み解くしかない。
本稿では、VBScriptの限界領域に踏み込み、ADODB.Streamを用いてバイナリを直接解析し、ファイルの実体と拡張子の乖離を検出するエンタープライズ向けセキュリティ監査スクリプトの全貌を解説する。
—
1. アーキテクチャの設計思想:なぜ「FSO単体」では無力なのか
VBScriptにおけるファイル操作は `Scripting.FileSystemObject` に依存しがちだが、FSOはテキスト処理やメタデータの取得には長けていても、バイトストリームの直接操作能力を持たない。
真のファイル形式を特定するためには、ファイル先頭の数バイト(マジックバイト)を16進数(Hex)として抽出し、既知のシグネチャデータベースと突合する必要がある。
さらに、エンタープライズ環境のファイルサーバー監査において、何万ものファイルを走査する際のパフォーマンスは死活問題である。オブジェクトの不必要な生成・破棄(メモリリーク)を防ぎ、I/Oのボトルネックを最小限に抑える設計が求められる。
主要なファイルのマジックバイト(シグネチャ)一覧
| ファイル形式 | 拡張子例 | マジックバイト(Hex) |
| :— | :— | :— |
| JPEG | .jpg, .jpeg | `FF D8 FF` |
| PNG | .png | `89 50 4E 47 0D 0A 1A 0A` |
| PDF | .pdf | `25 50 44 46` (`%PDF`) |
| ZIP / Office(OpenXML) | .zip, .xlsx, .docx | `50 4B 03 04` |
| Windows Executable | .exe, .dll | `4D 5A` (`MZ`) |
—
2. 実装:拡張子偽装検知・監査スクリプト
以下のコードは、指定したディレクトリを再帰的に走査し、拡張子と実際のマジックバイトを比較して不一致(偽装の疑い)がある場合にログ出力、あるいは隔離を行うプロフェッショナル向けスクリプトである。
‘ ==============================================================================
‘ Script Name: Audit-FileMasquerading.vbs
‘ Description: マジックバイトとFSO拡張子の乖離を検出し、拡張子偽装を特定するセキュリティ監査スクリプト
‘ Author: Chief Architect (Legacy Systems & Automation)
‘ ==============================================================================
Option Explicit
Const ForReading = 1
Const OverwriteExisting = True
‘ 監査対象ディレクトリ
Dim targetDir
targetDir = “C:\EnterpriseData\SharedStorage”
‘ ログファイルのパス
Dim logPath
logPath = “C:\EnterpriseData\SecurityAudit_” & FormatDate(Now) & “.log”
‘ オブジェクトの事前宣言(スコープの明確化とGC対策)
Dim fso, logFile
Set fso = CreateObject(“Scripting.FileSystemObject”)
Set logFile = fso.OpenTextFile(logPath, 8, True, 0) ‘ 8: ForAppending, 0: ASCII
logFile.WriteLine “=== Security Audit Started: ” & Now & ” ===”
‘ 再帰的監査の実行
AuditDirectory fso.GetFolder(targetDir), logFile, fso
logFile.WriteLine “=== Security Audit Completed: ” & Now & ” ===”
logFile.Close
Set logFile = Nothing
Set fso = Nothing
WScript.Echo “Audit completed. Check log: ” & logPath
‘ ==============================================================================
‘ 関数: ディレクトリ再帰走査
‘ ==============================================================================
Sub AuditDirectory(objFolder, objLog, objFso)
Dim objFile, subFolder
‘ フォルダ内のファイルを監査
For Each objFile In objFolder.Files
ProcessFile objFile, objLog, objFso
Next
‘ サブフォルダを再帰処理
For Each subFolder In objFolder.SubFolders
AuditDirectory subFolder, objLog, objFso
Next
End Sub
‘ ==============================================================================
‘ 関数: 単一ファイルのバイナリ検査と偽装判定
‘ ==============================================================================
Sub ProcessFile(objFile, objLog, objFso)
On Error Resume Next
Dim filePath, declaredExt, actualType, magicBytes
filePath = objFile.Path
declaredExt = LCase(objFso.GetExtensionName(filePath))
‘ ファイルサイズが小さすぎる場合はスキップ(ヘッダーが読めないため)
If objFile.Size < 8 Then Exit Sub
' 先頭8バイトのマジックバイトをHEX文字列として取得
magicBytes = GetMagicBytes(filePath, 8)
If Err.Number <> 0 Then
objLog.WriteLine “[ERROR] Failed to read: ” & filePath & ” (Error: ” & Err.Description & “)”
Err.Clear
Exit Sub
End If
‘ マジックバイトから真のファイルタイプを推測
actualType = IdentifyFileType(magicBytes)
‘ 宣言された拡張子と実体の乖離を検証
If IsMasquerading(declaredExt, actualType) Then
objLog.WriteLine “[WARNING] Potential Masquerading Detected! Path: ” & filePath & _
” | Declared Ext: .” & declaredExt & _
” | Actual Magic Bytes: ” & magicBytes & _
” | Inferred Type: ” & actualType
End If
On Error GoTo 0
End Sub
‘ ==============================================================================
‘ 関数: ADODB.Streamを用いた高速バイナリ読み取り
‘ ==============================================================================
Function GetMagicBytes(filePath, byteCount)
Dim stream, buffer, i, hexStr, bVal
Set stream = CreateObject(“ADODB.Stream”)
stream.Type = 1 ‘ adTypeBinary
stream.Open
stream.LoadFromFile filePath
‘ 指定バイト数を読み出し
buffer = stream.Read(byteCount)
stream.Close
Set stream = Nothing
‘ バイト配列をHEX文字列に変換
hexStr = “”
Dim adoCon
Set adoCon = CreateObject(“ADODB.Recordset”)
‘ VBScript単体でバイナリ(VT_UI1 | VT_ARRAY)を安全に走査するためADODBの内部ユーティリティを利用するか、
‘ ループで処理する。ここでは確実性を重視してStream経由のByte変換を行う。
‘ ※VBScriptの制限を回避するため、ADODB.StreamをTextモードに切り替えての読み込みは行わず、
‘ バイナリ配列を直接Hex化する
Dim binaryStream
Set binaryStream = CreateObject(“ADODB.Stream”)
binaryStream.Type = 1
binaryStream.Open
binaryStream.Write buffer
binaryStream.Position = 0
binaryStream.Type = 2 ‘ adTypeText
binaryStream.Charset = “iso-8859-1” ‘ 1バイト文字としてマッピング
Dim textData
textData = binaryStream.ReadText
binaryStream.Close
Set binaryStream = Nothing
For i = 1 To Len(textData)
bVal = Asc(Mid(textData, i, 1))
If bVal < 0 Then bVal = bVal + 256
Dim h
h = Hex(bVal)
If Len(h) = 1 Then h = "0" & h
hexStr = hexStr & h
Next
GetMagicBytes = hexStr
End Function
' ==============================================================================
' 関数: マジックバイトのシグネチャマッチング
' ==============================================================================
Function IdentifyFileType(hex)
If Left(hex, 6) = "FFD8FF" Then
IdentifyFileType = "jpg"
ElseIf Left(hex, 16) = "89504E470D0A1A0A" Then
IdentifyFileType = "png"
ElseIf Left(hex, 8) = "25504446" Then
IdentifyFileType = "pdf"
ElseIf Left(hex, 8) = "504B0304" Then
IdentifyFileType = "zip" ' Office文書(.xlsx, .docx等)もここに含まれる
ElseIf Left(hex, 4) = "4D5A" Then
IdentifyFileType = "exe"
Else
IdentifyFileType = "unknown"
End If
End Function
' ==============================================================================
' 関数: 拡張子偽装の判定ロジック
' ==============================================================================
Function IsMasquerading(ext, actual)
IsMasquerading = False
' 不明なファイル形式はここでは偽装判定せずスルー(誤検知防止)
If actual = "unknown" Then Exit Function
' 例1: 実体がEXEであるにもかかわらず、画像やテキストを装っている場合(致命的)
If actual = "exe" And ext <> “exe” And ext <> “dll” And ext <> “scr” Then
IsMasquerading = True
Exit Function
End If
‘ 例2: 実体がZIP/Office文書であるのに、別拡張子を名乗っている場合
If actual = “zip” And ext <> “zip” And ext <> “xlsx” And ext <> “docx” And ext <> “pptx” And ext <> “jar” Then
IsMasquerading = True
Exit Function
End If
‘ 例3: 画像やPDFを標榜しながら、中身が全く異なる場合(厳密チェック)
If (actual = “jpg” Or actual = “png” Or actual = “pdf”) And ext <> actual Then
‘ jpgとjpegの揺れは許容
If Not (actual = “jpg” And ext = “jpeg”) Then
IsMasquerading = True
Exit Function
End If
End If
End Function
‘ ==============================================================================
‘ 補助関数: 日付フォーマット
‘ ==============================================================================
Function FormatDate(d)
FormatDate = Year(d) & _
Right(“0” & Month(d), 2) & _
Right(“0” & Day(d), 2) & “_” & _
Right(“0” & Hour(d), 2) & _
Right(“0” & Minute(d), 2) & _
Right(“0” & Second(d), 2)
End Function
—
3. チーフアーキテクトが解説するコードの急所と最適化の極意
A. `ADODB.Stream` による高速バイナリハンドリング
VBScriptでバイナリを扱う際、FileSystemObjectの `OpenAsTextStream` を使うのは悪手である。テキストエンコーディングの自動変換やBOMの干渉により、バイナリの先頭数バイトが破損・改変されるからだ。
本スクリプトでは、COMコンポーネントである `ADODB.Stream` を `Type = 1 (adTypeBinary)` でインスタンス化し、OSのメモリ空間上にダイレクトにバイナリをロードしている。これにより、巨大なファイルであっても先頭の数バイトのみを効率的に切り出し、I/O負荷を極限まで抑制している。
B. ガベージコレクションとCOMオブジェクトの明示的解放
VBScriptのランタイム(`cscript.exe` / `wscript.exe`)は、スクリプト終了時にCOMオブジェクトを自動解放する仕様になっているが、数千〜数万のファイルをループ処理するプロセスにおいて、これを放置すると確実にメモリリーク(Memory Leak)を引き起こす。
特に `ADODB.Stream` は内部でアンマネージドリソースを消費するため、ループの反復ごとに `Set stream = Nothing` を明示的に実行し、OSへメモリを即座に返還する設計が不可欠である。
C. 誤検知(False Positive)の排除とエンタープライズ耐性
セキュリティスクリプトにおいて、正当なファイルを「悪意あり」と判定する誤検知は業務を停止させる大罪である。
例えば、Office Open XML形式(`.xlsx` や `.docx`)の本質はZIPアーカイブ(マジックバイト `50 4B 03 04`)であるため、単に「ZIPだから拡張子は `.zip` のみ」と判定すると、すべてのExcelファイルが偽装認定されてしまう。
そのため、`IsMasquerading` 関数では、`zip` 実体に対して `.xlsx`, `.docx`, `.pptx` などの正当な派生拡張子をホワイトリスト形式で許容する高度な判定ロジックを組み込んでいる。
—
4. 運用への組み込みとさらなる高みへ
このVBScriptは、Windows Serverのタスクスケジューラに登録し、夜間バッチとして社内ファイルサーバーの共有フォルダを巡回させることで、EDR(Endpoint Detection and Response)やアンチウイルスがすり抜けた未知の持続的脅威(APT)の早期発見に寄与する。
もし、さらなるスケーラビリティやマルチスレッド処理(並列化)が求められるフェーズに移行する場合は、VBScriptの枠組みを脱却し、PowerShell(`.ps1`)や .NET Framework / .NET Core への移行を検討すべきである。しかし、レガシーなインフラ環境や、追加のランタイムインストールが制限される極限のセキュリティポリシー下においては、このVBScriptによるアプローチは今なお最強かつ唯一無二の防衛網として機能し続ける。
