【極限の知見】FSOを用いたネットワーク共有の権限監査:Permission Deniedを「検知すべき脆弱性」へ昇華させる設計論
エンタープライズ環境において、ファイルサーバーの権限管理は常に「不完全な動態」にある。管理者が意図した権限設定と、実際のアクセス制御リスト(ACL)の乖離、そして何より「誰がどこにアクセスできず、どこにアクセスできてしまうのか」という境界線の不透明さ。これらを可視化することは、セキュリティ監査の第一歩である。
本稿では、レガシーかつ強力なツールであるVBScriptとFileSystemObject (FSO)を用い、ネットワーク共有上の全ディレクトリを走査し、実行ユーザーがアクセス拒否(Permission Denied)されるパスを精密に抽出する、プロフェッショナル向けの監査スクリプトの設計思想と実装を解説する。
単なる「フォルダ巡回スクリプト」を書くのではない。オブジェクトのライフサイクル、エラーハンドリングの極致、そして大規模環境でのパフォーマンスを考慮した「堅牢な監査エンジン」を構築することが我々の目的である。
1. 監査における設計思想:エラーは「例外」ではなく「データ」である
一般的なプログラマは、`Err.Number = 70` (Permission Denied) を「処理を中断すべきエラー」と捉える。しかし、セキュリティ監査エンジニアにとって、このエラーこそが「調査すべき重要な情報(シグナル)」そのものである。
アクセス権限のないディレクトリに触れた際、スクリプトを停止させるのではなく、そのパスを「アクセス拒否リスト」として記録し、静かに次のノードへ遷移する。この「エラーをデータとして扱う」設計が、監査の完遂率を決定づける。
攻略すべき技術的障壁
1. 再帰処理のスタック管理: 深いディレクトリ構造における再帰呼び出しの制御。
2. COMオブジェクトの破棄: 大規模走査時のメモリリーク防止(明示的な `Nothing` 割り当て)。
3. ネットワーク遅延への耐性: I/O待ちによるプロセスのハングアップ回避。
2. 実装:高信頼性セキュリティ監査スクリプト
以下に、プロフェッショナルな要件を満たす実装例を示す。このコードは、エラーをトラップし、ログに記録し、オブジェクトを確実に解放することに特化している。
'**
' Script Name: SecurityAudit_FSO_PermissionScanner.vbs
' Description: ネットワーク共有内のアクセス拒否パスを抽出するセキュリティ監査エンジン
' Author: Legendary Chief Architect
'**
Option Explicit
' --- 定数定義 ---
Const ERR_PERMISSION_DENIED = 70 ' Permission Denied
Const LOG_FILE_PATH = "C:\Audit_Reports\Access_Denied_Report.txt"
' --- グローバル変数 ---
Dim objFSO, objLogFile
Dim strRootPath
' --- メインエントリーポイント ---
Sub Main()
' 引数からルートパスを取得(例: \\FileServer\Shared)
If WScript.Arguments.Count = 0 Then
WScript.Echo "Usage: cscript //nologo SecurityAudit_FSO_PermissionScanner.vbs <RootPath>"
WScript.Quit 1
End If
strRootPath = WScript.Arguments(0)
' 1. 初期化
Set objFSO = CreateObject("Scripting.FileSystemObject")
' ログファイルの準備(追記モード)
On Error Resume Next
Set objLogFile = objFSO.OpenTextFile(LOG_FILE_PATH, 8, True)
If Err.Number <> 0 Then
WScript.Echo "Error: Cannot create log file. " & Err.Description
WScript.Quit 1
End If
On Error GoTo 0
objLogFile.WriteLine "--- Security Audit Started: " & Now & " ---"
objLogFile.WriteLine "Target Path: " & strRootPath
objLogFile.WriteLine "--------------------------------------------------"
' 2. 走査開始
WScript.Echo "Auditing started... Please wait."
ScanDirectory strRootPath
' 3. 終了処理
objLogFile.WriteLine "--------------------------------------------------"
objLogFile.WriteLine "--- Audit Completed: " & Now & " ---"
objLogFile.Close
' オブジェクトの明示的解放(重要)
Set objLogFile = Nothing
Set objFSO = Nothing
WScript.Echo "Audit completed. Report saved to: " & LOG_FILE_PATH
End Sub
'**
' Sub ScanDirectory
' Description: 再帰的にディレクトリを走査するコアエンジン
'**
Sub ScanDirectory(strPath)
Dim objFolder, objSubFolder, colSubFolders
' エラーハンドリングの開始
On Error Resume Next
' フォルダオブジェクトの取得試行
Set objFolder = objFSO.GetFolder(strPath)
' --- 重要:エラーチェック ---
If Err.Number <> 0 Then
If Err.Number = ERR_PERMISSION_DENIED Then
' アクセス拒否が発生した場合、ログに記録してこのパスの探索をスキップ
WriteToLog "[DENIED] " & strPath
Else
' その他のエラー(パスが存在しない、ネットワーク切断等)
WriteToLog "[ERROR] " & strPath & " (Code: " & Err.Number & " - " & Err.Description & ")"
End If
Err.Clear
Set objFolder = Nothing
Exit Sub
End If
On Error GoTo 0 ' 通常の動作に戻す
' サブフォルダの列挙
On Error Resume Next
Set colSubFolders = objFolder.SubFolders
If Err.Number <> 0 Then
' SubFoldersプロパティへのアクセス自体が拒否されるケース
WriteToLog "[DENIED_SUB] " & strPath
Err.Clear
Set objFolder = Nothing
Exit Sub
End If
On Error GoTo 0
' 各サブフォルダに対して再帰呼び出し
For Each objSubFolder In colSubFolders
' 再帰呼び出し
ScanDirectory objSubFolder.Path
' ループ内でのオブジェクト解放(メモリ最適化)
' 大規模走査において、COM参照カウントの蓄積を防ぐために極めて重要
Set objSubFolder = Nothing
Next
' 現在のフォルダオブジェクトを解放
Set colSubFolders = Nothing
Set objFolder = Nothing
End Sub
'**
' Sub WriteToLog
' Description: ログへの書き込み
'**
Sub WriteToLog(strMessage)
On Error Resume Next
If Not objLogFile Is Nothing Then
objLogFile.WriteLine strMessage
End If
On Error GoTo 0
End Sub
' 実行
Main
3. アーキテクトの深掘り:なぜこの設計なのか
3.1. オブジェクトのライフサイクル管理 (Deterministic Destruction)
VBScriptはガベージコレクションに依存しているが、COMオブジェクト(FSOなど)の解放は、参照カウントがゼロにならない限りメモリ上に残り続ける。
特に大規模なファイルサーバーを走査する場合、再帰呼び出しの深さに応じて `objFolder` や `objSubFolder` がスタック上に積み上がり、メモリ消費が指数関数的に増大するリスクがある。
本スクリプトでは、`Set objFolder = Nothing` を各ステップで明示的に実行している。これは単なる作法ではなく、「メモリリークによるスクリプトの異常終了を防ぐための防衛策」である。
3.2. エラーハンドリングの二段構え
`On Error Resume Next` を乱用するのは素人のすることだ。本設計では、以下の二段階の戦略をとっている。
1. `GetFolder` 時のエラー: フォルダそのものへのアクセス権がない場合。ここで `Exit Sub` し、再帰の枝を切り落とす。
2. `SubFolders` プロパティへのアクセスエラー: フォルダ自体は見えても、その配下のリストを取得する権限がない場合。これもセキュリティ監査において極めて重要な「不透明な領域」である。
3.3. 再帰とスタックオーバーフローへの考察
VBScriptの再帰は、ディレクトリ階層が数百レベルに及ぶ極端な環境ではスタックオーバーフローを引き起こす可能性がある。実務上のファイルサーバーにおいて、これほど深い階層は稀であるが、もし発生する場合は「再帰」ではなく、`Stack`(配列を用いた非再帰的なループ処理)にアルゴリズムを書き換える必要がある。シニアエンジニアであれば、常にこの「限界点」を意識すべきだ。
4. 実務への展開:さらなる高度化に向けて
本スクリプトは基礎的な監査エンジンであるが、実運用では以下の拡張を検討せよ。
- Windows APIとの連携: `GetFileSecurity` や `GetNamedSecurityInfo` を呼び出すことで、単なる「アクセス可否」だけでなく、具体的にどのSID(Security Identifier)が拒否されているのかを特定する。
- CSV/JSON形式での出力: 監査結果をSIEM(SplunkやMicrosoft Sentinel等)へ投入するため、構造化データとして出力する。
- 並列処理の検討: VBScript単体では困難だが、複数のワーカープロセスを起動し、共有ディレクトリごとに担当を分けることで、I/O待ち時間を大幅に短縮できる。
結論
VBScriptは古い。しかし、そのシンプルさと、Windows OSのコアであるCOMへの直感的なアクセス性は、特定の目的において依然として強力な武器となる。
「Permission Denied」をエラーとして無視するのではなく、それをセキュリティの境界を定義する貴重なデータとして捉えること。その視点の転換こそが、真のシステムアーキテクトへの道である。
