【実務・中級編】【機密情報マスク処理】スクリプト引数や設定ファイルのパスワードをログ出力時に自動伏字化するセキュア設計 – VBScript (Visual Basic Scripting Edition)解析バイブル

スポンサーリンク

【機密情報マスク処理】スクリプト引数や設定ファイルのパスワードをログ出力時に自動伏字化するセキュア設計

開発現場でよく見かける光景がある。
「動かない原因を究明するため」と称して、WScript.Argumentsで渡された引数や、INI・JSONといった設定ファイルの中身を、ご丁寧にそのままログファイル(あるいは標準出力)に書き出すVBScript。

……ちょっと待ってほしい。そのログ、誰が読むのか?
もしそのログがCI/CDのビルド成果物として保存されたり、監視サーバーに吸い上げられたり、トラブルシューティングのために社内共有フォルダに放置されたりしたらどうなるか。
パスワードやAPIトークン、接続文字列(Connection String)が「平文のまま」世界に放り出されているという厳然たる事実テロに、君は気づいているだろうか。

今回は、VBScriptとWSH(Windows Script Host)の環境において、ログ出力時の機密情報を完全に無力化(マスク)し、監査要件をもクリアする「セキュア・ロギング設計」の極意を伝授する。

なぜ従来のVBScriptロギングはセキュリティホールになるのか

VBScriptは手軽で強力だ。Windows環境であれば追加のランタイムなしで動作するため、今なお多くの業務自動化・キッティングツール・バッチ処理の裏方として現役で稼働している。

しかし、手軽であるがゆえに「セキュリティ意識の欠如したコード」が量産されやすい。

1. 「とりあえず全部出力」の悪習

デバッグの初期段階で `WScript.Echo` や `FileSystemObject` (FSO) を使って変数を丸ごとファイルに吐き出すコードを書いたことはないだろうか。

‘ 【やってはいけないアンチパターン】
Sub WriteLog(msg)
Dim fso, stream
Set fso = CreateObject(“Scripting.FileSystemObject”)
Set stream = fso.OpenTextFile(“C:\Logs\app.log”, 8, True)
stream.WriteLine Now & ” – ” & msg ‘ ← ここにパスワードやトークンがそのまま流し込まれる
stream.Close
End Sub

この実装では、呼び出し元がうっかり `WriteLog “Connecting with pass=” & password` などと書いた瞬間に、機密情報がログへ永続化される。

2. 引数の直接参照のリスク

タスクスケジューラからバッチを起動する際、引数にクレデンシャルを渡す設計にしている場合、`WScript.Arguments` をそのままログに残すと、プロセス引数のスナップショット(タスクマネージャーやWMI経由)とログの両方から情報が漏洩する二重のリスクを負う。

堅牢なセキュア・ロギングのアーキテクチャ

この問題を根本から解決するためには、「ログ出力の咽元(のど元)にマスクフィルターを常設する」という設計思想が必要だ。

プログラミングにおける「関心の分離」の原則に基づき、ログを書き込む関数(インターフェース)の内部で、必ず正規表現(`VBScript.RegExp`)を用いた伏字化フィルタを通過させる。

設計のポイント

1. ブラックリスト方式とホワイトリスト方式の併用

  • パスワード、secret、token、api_key、credential などのキーワードを含む値を動的に検知する。

2. 正規表現による動的マスキング

  • `key=value` 形式や JSON風の `”password”:”…”` 形式をパターンマッチさせ、値の部分だけを “ に置換する。

3. 一元管理されたロガーオブジェクト

  • ログ出力処理をクラス化(あるいは共通関数化)し、開発者が個別にマスク処理を意識しなくても勝手に安全になる仕組みを強制する。

【実装コード】プロダクション品質のセキュア・ロギングスクリプト

以下のコードは、開発現場でそのままコピー&ペーストして利用できる、実用性と堅牢性を兼ね備えたVBScriptのモジュールだ。FSOを用いたファイル出力と、動的な正規表現マスクを実装している。

‘ ==============================================================================
‘ Script Name: SecureLogger.vbs
‘ Description: 機密情報の自動マスク機能を持つセキュア・ロギングクラス
‘ ==============================================================================

Option Explicit

‘ メイン処理のシミュレーション
Call Main()

Sub Main()
Dim logger
Set logger = New SecureLogger

‘ ログファイルの初期化
logger.LogFilePath = “C:\Logs\SecureOperation.log”

‘ 通常のログ出力
logger.Info “バッチ処理を開始します。”

‘ 【重要】パスワードやトークンを含む危険なログ出力のシミュレーション
‘ 開発者がうっかり平文でログを出力しようとしても、自動的にマスクされる
logger.Info “接続パラメータ設定: Server=db01.corp.local; User=admin; Password=SecretPassword123!; Timeout=30”
logger.Info “API連携実行: token=Bearer_xyz9876543210abcdef&mode=production”
logger.Warn “設定ファイルから取得した api_key=’pk_live_999888777a’ の検証に失敗しました。”

logger.Info “バッチ処理を正常終了しました。”

WScript.Echo “処理が完了しました。ログを確認してください。”
End Sub

‘ ==============================================================================
‘ SecureLogger クラス定義
‘ ==============================================================================
Class SecureLogger
Private m_LogPath
Private m_RegExList

‘ 初期化処理(クラスコンストラクタ相当)
Private Sub Class_Initialize()
m_LogPath = “C:\Logs\default.log”

‘ 検知・マスク対象の正規表現パターンを配列として定義
‘ メンテナンス性を考慮し、大文字小文字を区别しないパターンを設定
Set m_RegExList = CreateObject(“System.Collections.ArrayList”)

Dim re

‘ パターン1: key=value または key: value 形式のパスワード・トークン等
‘ 例: password=xxx, api_key: “yyy”
Set re = CreateObject(“VBScript.RegExp”)
re.Pattern = “(password|passwd|secret|token|api_key|credential)\s([=:]\s[‘””]?)([^””‘\s&]+)([‘””]?)”
re.IgnoreCase = True
re.Global = True
m_RegExList.Add re

‘ パターン2: 接続文字列内の Password=xxx;
Set re = CreateObject(“VBScript.RegExp”)
re.Pattern = “(Password\s=\s)([^;]+)(;?)”
re.IgnoreCase = True
re.Global = True
m_RegExList.Add re
End Sub

‘ ログファイルパスの設定・取得プロパティ
Public Property Let LogFilePath(ByVal path)
m_LogPath = path
End Property
Public Property Get LogFilePath()
LogFilePath = m_LogPath
End Property

‘ 情報ログ出力
Public Sub Info(ByVal message)
Me.Write “INFO”, message
End Sub

‘ 警告ログ出力
Public Sub Warn(ByVal message)
Me.Write “WARN”, message
End Sub

‘ エラーログ出力
Public Sub [Error](ByVal message)
Me.Write “ERROR”, message
End Sub

‘ コア書き込みメソッド(マスク処理を強制適用)
Public Sub Write(ByVal level, ByVal message)
Dim safeMessage
safeMessage = MaskSensitiveData(message)

‘ 実際のファイル書き込み処理
Call WriteToFile(level, safeMessage)

‘ デバッグ用にコンソールにも出力する場合(必要に応じてコメントアウト)
‘ WScript.Echo “[” & level & “] ” & safeMessage
End Sub

‘ 機密情報マスクエンジン
Private Function MaskSensitiveData(ByVal rawMessage)
Dim currentMsg, re
currentMsg = rawMessage

‘ 定義されたすべての正規表現パターンを順次適用
For Each re In m_RegExList
‘ パターン1へのマッチングと置換
‘ ※正規表現のグループ構造に依存した置換処理
If InStr(LCase(re.Pattern), “password\s=”) > 0 And InStr(LCase(currentMsg), “password”) > 0 Then
‘ 接続文字列用パターンマッチ
currentMsg = re.Replace(currentMsg, “$1$3”)
Else
‘ 一般的な key=value 用パターンマッチ (Group 1: キー, Group 2: 区切り, Group 3: 値, Group 4: 引用符)
‘ 安全のため、値の部分を に置換する
currentMsg = re.Replace(currentMsg, “$1$2$4”)
End If
Next

MaskSensitiveData = currentMsg
End Function

‘ ファイルシステムオブジェクトを使用した安全な追記処理
Private Sub WriteToFile(ByVal level, ByVal message)
On Error Resume Next

Dim fso, folder, parentDir, stream
Set fso = CreateObject(“Scripting.FileSystemObject”)

‘ ログ保存先のディレクトリが存在しない場合は自動作成
parentDir = fso.GetParentFolderName(m_LogPath)
If Not fso.FolderExists(parentDir) Then
fso.CreateFolder(parentDir)
End If

‘ ファイルを開く(ForAppending = 8, Create = True)
Set stream = fso.OpenTextFile(m_LogPath, 8, True)
stream.WriteLine FormatDateTime(Now, 0) & ” [” & level & “] ” & message
stream.Close

If Err.Number <> 0 Then
‘ ログ書き込み失敗時は最低限コンソールに逃がす(無限ループ防止のためFSOは使わない)
WScript.Echo “CRITICAL: ログの書き込みに失敗しました – ” & Err.Description
Err.Clear
End If

Set stream = Nothing
Set fso = Nothing

On Error GoTo 0
End Sub
End Class

チーフアーキテクトからの実務アドバイス

このコードを現場に導入するにあたり、以下のポイントを心に刻んでおいてほしい。

1. 「ログが出ないこと」のジレンマに陥らない設計
あまりに厳格にマスクしようとして、デバッグに必要なパラメータの構造(「何番目の引数が不正だったか」など)まで消し去ってしまうと、今度は障害切り分けができなくなる。
今回のコードのように「キー名を残し、バリュー(値)だけを `` に潰す」という粒度を保つことが、セキュアかつ実用的なロギングの黄金律である。

2. FSOの排他制御の限界を知る
VBScriptの `Scripting.FileSystemObject` によるファイル出力は、複数プロセスから同時に書き込まれた場合に競合エラー(書き込み権限エラー)を起こす。
エンタープライズ環境で並列実行されるバッチタスクに組み込む場合は、ログファイル名に日付やプロセスIDを付与するなどの工夫を必ず併用してほしい。

3. セキュリティは「水物」ではない
「社内ネットワークの中だから大丈夫」「テスト環境だからパスワードをベタ書きしても平気」という甘えが、ランサムウェアやサプライチェーン攻撃の踏み台を生む。
VBScriptというレガシーな技術を使ざるを得ない環境であるならばなおのこと、コードの端々にプロフェッショナルとしての矜持と、鉄壁のセキュリティ思想を宿らせてほしい。

自動化とは、単に作業をラクにすることではない。「人間のミス(うっかりログ保存)が起きる余地そのものを構造的に排除すること」。これこそが、一流のエンジニアリングである。

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