はじめに
PowerShellはWindows管理者にとって欠かせない強力なツールですが、その強力さゆえに、侵入後の攻撃者にとっても最も好まれる「環境寄生型(Living-off-the-Land)」攻撃の足場になります。追加のマルウェアバイナリを持ち込まずとも、OS標準搭載のPowerShellだけで偵察・認証情報窃取・横展開・C2通信までを完結できてしまうためです。
この記事では、PowerShellの「言語モード」による実行機能の制限と、攻撃の痕跡を確実に残すための「ロギング」の2本柱で、PowerShellそのものを要塞化する方法を解説します。
前提条件
- 言語モードの制限を実運用で機能させるには、後述のとおりWDAC(Windows Defender Application Control)またはAppLockerによるアプリケーション制御ポリシーの適用が前提になります。単独では回避が容易なため、セキュリティ境界として過信しないでください。
- 本記事のロギング設定はWindows PowerShell(5.1)を対象とするGPOです。PowerShell 7(pwsh.exe)には別系統の管理用テンプレートが必要で、Windows PowerShell用のGPOは既定では適用されません。
- スクリプトブロックログやモジュールログは環境によって大量のイベントを生成するため、事前にSIEM側の転送・保持容量を見積もってください。
Linuxエンジニア向けの例え
言語モードの制限は、rbash(制限付きbash)やLinuxコンテナでseccompプロファイルを使ってシステムコールを絞り込む発想に近いものです。「使える構文・API」そのものを狭めることで、たとえコードを実行されても被害を最小化します。
ロギングの3本柱も、Linuxの監査基盤と対応づけると理解しやすくなります。
-
モジュールログ(EID 4103) ≒
auditdで特定のバイナリやパスに絞って-wルールを張るイメージ。指定したモジュールの呼び出しだけを狙い撃ちで記録します。 -
スクリプトブロックログ(EID 4104) ≒
auditdのexecve監視で引数を丸ごと記録する設定、あるいはシェルの難読化されたコマンドをデコードしてロギングするイメージ。実行された生のコード内容が残るため、最も検知価値が高いログです。 -
トランスクリプション ≒
scriptコマンドやasciinemaでセッション全体を記録するイメージ。ただし出力はテキストファイルであり、イベントログではない点に注意が必要です。
1. 言語モードとは
PowerShellセッションには4種類の「言語モード」があり、実行時に使える構文やAPIの範囲を制御します。
| 言語モード | 説明 |
|---|---|
| FullLanguage | 既定のモード。すべてのコマンドレット、.NETクラスの直接呼び出し、COMオブジェクトの生成など制約なく利用可能。 |
| ConstrainedLanguage | .NETの型変換やCOMオブジェクトの生成、任意の.NETメソッド呼び出しなどが禁止される。承認済みのコマンドレット・基本的なデータ型操作のみに制限。 |
| RestrictedLanguage | 変数の代入・パイプライン・基本コマンドレットの呼び出し程度に限定され、ループ処理やスクリプトブロックの定義もできない。 |
| NoLanguage | スクリプトテキストの解析自体を行わない、最も制限された状態。 |
攻撃者が悪用するのは主にFullLanguageモードの自由度です。特にAdd-Typeによる任意.NETコードのコンパイルや、COMオブジェクト経由でのAPI呼び出しは、Invoke-MimikatzのようなオフェンシブPowerShellツールキットの多くが依存する機能であり、これらはConstrainedLanguageモードで利用できなくなります。
2. Constrained Language Modeの正しい有効化方法
「やってはいけない」設定方法
Web上には$ExecutionContext.SessionState.LanguageMode = "ConstrainedLanguage"や、環境変数__PSLockdownPolicyを設定する方法が紹介されていることがありますが、どちらも本番のセキュリティ境界として使うべきではありません。
-
$ExecutionContext.SessionState.LanguageModeの変更は、その場のセッション限りの一時的な設定に過ぎず、新しいPowerShellセッションを開けば簡単に無効化されます。 -
__PSLockdownPolicyはMicrosoft自身のデバッグ用途の内部実装に依存した非公式な手段であり、正式にサポートされた制御方法ではありません。
正しい設定方法:WDAC/AppLockerによる強制
ConstrainedLanguageモードを実運用で機能させる唯一の方法は、システム全体のアプリケーション制御ポリシー(WDACまたはAppLocker)を適用することです。WDAC/AppLockerが有効な状態でPowerShellが起動すると、ポリシーで信頼されていないスクリプトやモジュールは自動的にConstrainedLanguageモードで実行され、ポリシーで署名検証済み・許可された対象のみがFullLanguageモードで実行されます。
WDACポリシーの作成・展開手順は本シリーズ第24回(Device Guard/WDAC)で解説したものと同じ流れです。PowerShell向けに追加で意識すべき点は次のとおりです。
- WDACポリシーの「監査モード」段階では、実際に制限がかかる前に、業務スクリプトがConstrainedLanguageモードで意図せず失敗しないか検証してください(
Add-Typeや外部COM連携を使う社内スクリプトは要注意です)。 - 現在の言語モードは、セッション内で以下のコマンドで確認できます。
$ExecutionContext.SessionState.LanguageMode
3. ロギングの3本柱
Windows PowerShell(5.1)には、既定では無効になっている3種類のロギング機能があります。いずれもイベントログ Microsoft-Windows-PowerShell/Operational に記録されます(トランスクリプションのみ、別途指定したフォルダーへのテキストファイル出力です)。
| ロギング種別 | イベントID | 記録内容 | 特徴 |
|---|---|---|---|
| モジュールログ | 4103 | 指定したモジュールに含まれるコマンドレットの呼び出しとパイプライン実行の詳細 | 監視対象モジュールを個別に指定する必要があり、網羅的な検知には不向き |
| スクリプトブロックログ | 4104 | 難読化解除された生のスクリプト内容そのもの | 検知価値が最も高い。32KBを超えるスクリプトは複数の4104イベントに分割される |
| トランスクリプション | (イベントログではなくテキストファイル) | 実行したコマンドと出力結果をまとめたセッション全体の記録 | 監査証跡やコンプライアンス保存に有用。改ざん耐性のため保存先はアクセス制御・転送を徹底する |
なお、Windows 10/Server 2016以降ではスクリプトブロックログを明示的に有効化していなくても、AMSI(Antimalware Scan Interface)が疑わしいと判定したスクリプトブロックは警告レベルのイベントとして自動的に記録される仕組みが備わっています。とはいえ、これはあくまで補助的な検知であり、確実な証跡を残すには明示的な有効化が前提です。
GPOでの設定手順(Windows PowerShell 5.1)
- グループポリシーエディター(
gpedit.msc)を開きます。 - コンピューターの構成 → 管理用テンプレート → Windowsコンポーネント → Windows PowerShell に移動します。
- 「PowerShell スクリプトブロックログを有効にする」 を「有効」に設定します。
-
「モジュールログを有効にする」 を「有効」に設定し、対象モジュール名の一覧に
*を指定するとすべてのモジュールが対象になります。 - 「PowerShellトランスクリプションを有効にする」 を「有効」に設定し、出力先ディレクトリを指定します(既定はユーザーの「マイドキュメント」配下のため、集中管理する場合は共有フォルダーへの変更を推奨)。
対応するレジストリ値は以下のとおりです(GPO展開時にこの下へ書き込まれます)。
HKLM\SOFTWARE\Policies\Microsoft\Windows\PowerShell\ScriptBlockLogging → EnableScriptBlockLogging = 1
HKLM\SOFTWARE\Policies\Microsoft\Windows\PowerShell\ModuleLogging → EnableModuleLogging = 1
HKLM\SOFTWARE\Policies\Microsoft\Windows\PowerShell\ModuleLogging\ModuleNames → * = *
PowerShell 7(pwsh.exe)は別ポリシー
PowerShell 7には$PSHOME配下に専用の管理用テンプレートと導入スクリプト(InstallPSCorePolicyDefinitions.ps1)が同梱されており、これをローカルコンピューターまたは中央ストアにインストールすることで、Windowsコンポーネント → PowerShell Core という別ノードのGPOとして設定できます。Windows PowerShell向けのGPOを設定しただけでは、pwsh.exeのロギングは有効になりません。混在環境では両方の設定を忘れずに行ってください。
4. ログの確認
イベントビューアーで確認する場合のパスは以下のとおりです。
パス: アプリケーションとサービス ログ → Microsoft → Windows → PowerShell → Operational
# 直近のスクリプトブロックログ(EID 4104)を取得し、実行内容を確認
Get-WinEvent -FilterHashtable @{LogName='Microsoft-Windows-PowerShell/Operational'; Id=4104} -MaxEvents 20 |
Select-Object TimeCreated, Id, Message
まとめ:実行の「制限」と「証跡」は両輪で
PowerShellの要塞化は、片方だけでは不十分です。
- 言語モードの制限は、WDAC/AppLockerによるアプリケーション制御ポリシーとセットで初めて意味を持つ(単独設定は回避可能)
- スクリプトブロックログ(EID 4104)は、難読化されたコードの実体を暴く最も価値の高い証跡
- PowerShell 7を併用する環境では、Windows PowerShellとは別のGPOテンプレートの適用を忘れない
実行できる操作を減らす「予防」と、実行された操作を漏れなく記録する「検知」を組み合わせることで、PowerShellを攻撃者にとって使いにくい環境に変えていきましょう。
今すぐ実行すべき確認コマンド
# 1. 現在のセッションの言語モードを確認
$ExecutionContext.SessionState.LanguageMode
# 2. スクリプトブロックログ/モジュールログが有効になっているか、GPO適用結果を確認
Get-ItemProperty -Path 'HKLM:\SOFTWARE\Policies\Microsoft\Windows\PowerShell\ScriptBlockLogging' -ErrorAction SilentlyContinue
Get-ItemProperty -Path 'HKLM:\SOFTWARE\Policies\Microsoft\Windows\PowerShell\ModuleLogging' -ErrorAction SilentlyContinue
# 3. 直近のスクリプトブロックログ(EID 4104)が実際に記録されているか確認
Get-WinEvent -FilterHashtable @{LogName='Microsoft-Windows-PowerShell/Operational'; Id=4104} -MaxEvents 5 |
Select-Object TimeCreated, Id
# 4. WDAC/AppLockerポリシーが端末に適用されているか確認(第24回の内容と対応)
CiTool.exe -lp -json