更新履歴(7件・最終更新 2026年08月02日)
この記事に加えた変更の記録です。アーカイブした更新前のバージョンは、DOI付きの固定URLから読めます。
- 記事の冒頭に「この記事の知識マップ」節を追加しました。本文で扱っている概念とその関係を、要約・図・詳細ページへのリンクにまとめたものです。本文の主張は変えていません。
- 外部レビュー(1283件)への対応として本文を更新しました。個々の変更内容は、この下の履歴を参照してください。
- トランスクリプト共有のACLが、記録そのものを止める設定になっていたのを直しました。`CREATOR OWNER`に`AD,RA,REA`だけを与えていましたが、Windowsで「追記しかできない」が成立するのは書き手が`FILE_APPEND_DATA`を単独で指定して開いたときだけで、`Start-Transcript`はそう開きません。PowerShellの実装はまず`FileAccess.ReadWrite`、失敗時に`FileMode.Append`+`FileAccess.Write`で開き、.NETはこれを`GENERIC_READ`/`GENERIC_WRITE`へ写します。`FILE_GENERIC_WRITE`には`FILE_WRITE_DATA`や`SYNCHRONIZE`が含まれるため、この`CreateFile`はアクセス拒否になります。読み書きは許して`DE`(削除)を与えない形に改め、この構成で守れるのは「他人の記録に触れない」「自分の記録も消せない」までで、自分の記録の上書きはACLでは塞げないことを明記しました。上書きまで塞ぐ手段として、JEAの`TranscriptDirectory`(Local Systemが書き、標準ユーザーはアクセスを持たない)、イベント転送・SIEM、追記専用で開く独自収集プロセスの3つを挙げています。
- トランスクリプト共有の権限設定で、既存フォルダーを流用したときに直接付与されたACEが残る問題を直しました。`SetAccessRuleProtection($true, $false)`が外すのは継承してきたACEだけで、そのフォルダーに直接付いているACEはそのまま残ります。`New-Item -Force`は既存フォルダーでも成功するため、以前に`Domain Users`や`Everyone`へ変更権限を直接付けてあったなら、以降で`CREATOR OWNER`を絞ってもその付与だけが生き残り、利用者は他人の記録を上書き・削除できました。直接付与を一度すべて落としてから、必要なものだけを同じACLに入れて1回で適用する形にしています(空にする瞬間を作らないため)。
- トランスクリプト共有の権限設計に`OWNER RIGHTS`のACEを追加しました。ファイルを作った利用者はそのファイルの所有者になり、Windowsは所有者に`READ_CONTROL`と`WRITE_DAC`を暗黙で与えます。そのため`WD`も`DE`も配っていなくても、所有者は自分でDACLを書き換えて上書き・削除の権限を付け直せてしまい、「乗っ取られたあとも記録が残る」という目的が達成できていませんでした。`OWNER RIGHTS`(`S-1-3-4`)のACEがあるとき、システムは所有者への暗黙の権限を無視します。あわせて、ACLで守れるのは同じ共有を使う利用者どうしまでで、監査証跡として守るなら利用者が触れない収集側へ流す必要があることも明記しました。
- トランスクリプトの保存先の権限設定を見直しました。利用者への許可に継承を付けて書き込みを配ると、ファイル名さえ分かれば他人の記録を上書きできてしまうため、フォルダーへの作成権限と、自分が作ったファイルへの追記だけに分ける形に直しています。
- 対象環境と前提条件を冒頭の表にまとめ、JEAを扱う7章だけはリモート処理が前提になる旨を章の冒頭に明示しました。あわせてトランスクリプトの集約先を「書けるが消せない」共有にする手順、設定が効いたかの見極め方、多層防御の関係図、言語モードの表の拡充を追加しています。
- 初版公開
この記事を引用する(DOI(登録済みアーカイブ): 10.5281/zenodo.21547462)
以下のDOIは過去に登録されたアーカイブを指しており、現在の本文とは一致しない場合があります。現在の本文を参照するときは、このページのURLを使用してください。
小村 豪(2026)「PowerShellのセキュリティ強化 ── ログ・AMSI・言語モード・JEA」合同会社小村ソフト. https://comcomponent.com/blog/powershell-security-hardening-jea/
- DOI(登録済みアーカイブ)
- 10.5281/zenodo.21547462
- DOI(前回登録した版)
- 10.5281/zenodo.21733171
PowerShellを全面禁止するのではなく、実行内容を記録し、検査・実行範囲・権限をそれぞれ絞る。これが本記事の方針です。ログを残す機能と、実行前に検査する機能、管理作業を限定して任せる機能は、互いの代わりにはなりません。
PowerShellを止めると正規の管理業務や自動化が止まる一方、攻撃側は同等の操作を別の手段で行えます。「何が実行されたかを追えるか」「古い実行経路が残っていないか」「利用者へ必要以上の権限を渡していないか」を順に確認するほうが現実的です。
この記事では、社内のWindows環境を対象に、ログの設定 → 記録の保全 → AMSIと古いエンジン → 言語モード → JEAの設計と構成 → 展開確認の順に説明します。実行ポリシーと署名の詳細は「PowerShellの実行ポリシーとスクリプト署名」で扱い、本記事はその先を整理します。
目的別の読み方
| 確認したいこと | 読む章 | 確認の着地点 |
|---|---|---|
| 何から始めるべきか | 1章: 結論・導入順序 | まず可視化し、必要な制限を段階的に重ねる |
| 5.1と7でログを残したい | 2章: ログの設定と確認 | 新しいセッションの4104と、ログサイズを確認する |
| 集約した記録を守りたい | 3章: トランスクリプトの保全 | 他人の記録を守ることと、自分の記録の上書きを防ぐことを分ける |
| 検査や実行制限を整えたい | 4章: AMSIと古いエンジン、5章: 実行ポリシー・言語モード | 設定名だけでなく、検査・制限を支える仕組みを確認する |
| サービス再起動だけを委任したい | 6章: JEAの設計、7章: JEAの構成と接続 | 何を許すかを決めてから、サーバーへ専用の接続先を登録する |
| 展開前の抜けを確認したい | 8章: チェックリスト | 設定値ではなく、記録・保護・制限の実際の動作を見る |
コードは本番へ一括実行する手順ではありません。パス・サーバー名・グループ名は例です。自社の値へ読み替え、必要な権限と操作する端末を確認して検証機で試してください。ログ集約共有は集約サーバーで、JEAは操作を委任する対象サーバーで構成します。
対象環境・前提条件
| 項目 | 内容 |
|---|---|
| 対象OS | Windows 10/11、Windows Server。AMSIはWindows 10以降が前提です1 |
| 対象バージョン | Windows PowerShell 5.1とPowerShell 7の両方を対象にします。ポリシーの設定キーもログの記録先も5.1と7で別なので、両方が入っている端末では両方に設定してください(第2章・第4章) |
| 必要な権限 | 第2章〜第4章のログ設定・PowerShell 2.0の無効化、第7章のJEAエンドポイント登録は、いずれも管理者権限が必要です。実運用では1台ずつ手で入れるのではなくグループポリシーで配布します |
| ローカル/リモート | 第2章〜第5章は対象の端末・サーバー内で完結します。第6〜7章のJEAだけは、PowerShellリモート処理(WinRM)が有効であることが前提です(第6章の前提条件を参照) |
| 扱わないこと | 実行ポリシーと署名の詳細は別記事(PowerShellの実行ポリシーとスクリプト署名)で扱います。本記事では位置づけだけを整理します(第5章) |
図の実線は常に成り立つ関係、破線は条件付きの関係です(成立条件は詳細ページの各関係の説明に記載)。関係すべての一覧(全24件、根拠・確度つき)と主要概念の定義は知識マップ詳細ページにまとめています。データ: JSON-LD / Turtle
1. まず結論 ── 対策の役割と導入順序を分ける
最優先はスクリプトブロックログによる可視化です。PowerShell 5.0以降には、ログ、AMSI、言語モード、JEAという異なる役割の機能が揃っています。ログは難読化されたコードも展開後の形で残し、AMSIは実行直前の内容をウイルス対策製品へ渡します。言語モードは使える言語要素を、JEAは任せる操作を限定します。2134
flowchart TB
accTitle: PowerShellを守る4つの役割
accDescr: 実行内容の記録、内容の検査、言語要素の制限、操作の委任は互いの代替ではなく別の役割を担う。
A["PowerShellを安全に使う"]
A --> B["記録:実行内容を残す"]
A --> C["検査:AMSIへ渡す"]
A --> D["制限:言語要素を絞る"]
A --> E["委任:JEAで操作を渡す"]
図1: 4つの機能は別々の役割を持ち、この並びは導入順序ではない。
1.1 記録を残してから、検査・権限・実行範囲を整える
記録は「後から追える状態」を作り、委任は利用者へ渡す権限を小さくします。検査と制限は、実行の手前で働く仕組みです。導入の優先順位は、次の表で考えます。
| 段階 | やること | 効果 |
|---|---|---|
| 1. 可視化 | スクリプトブロックログ、トランスクリプション | 何が起きたか分かる。事後調査が可能になる |
| 2. 基礎的な無害化 | PowerShell 2.0の無効化、最新版の維持、AMSIの確認 | 検査を迂回する経路を塞ぐ |
| 3. 権限の限定 | JEAによる委任、管理者権限の削減 | 被害の範囲を限定する |
| 4. 実行の制限 | WDAC/AppLocker + 制約付き言語モード | 未承認コードの実行自体を止める |
多くの現場では、可視化と権限の限定だけでも状況が大きく改善します。WDAC/AppLockerによる実行制限は導入コストが高いため、業務影響を検証しながら進める領域です。
1.2 設定名だけで「守れた」と判断しない
ログを有効にするだけでなく、容量と保存先まで整えます。4104と入出力を含むトランスクリプションを併用し、既定サイズのまま短期間で上書きされないようにします。2 トランスクリプト共有で守れるのは、他人の記録へのアクセスと自分の記録の削除までです。自分の記録の上書きまで防ぐ方法は3章で分けて説明します。
AMSIはPowerShell 5.0以降・Windows 10以降で利用できますが、古い2.0エンジンが残っていれば、検査とログのない経路が残ります。15 また、実行ポリシーはセキュリティ境界ではなく、手動の言語モード設定も防御の主軸にしません。63 特定の管理作業を任せるなら、利用者に管理者権限を配るのではなくJEAで操作を限定します。4
flowchart TB
accTitle: 設定後に確かめるもの
accDescr: ログの有効化、AMSI対応、JEAの定義は設定の存在だけでなく目的を達成しているか確認する。
A["設定を入れる"] --> B["ログが実際に残るか"]
B --> C["残した記録を守れるか"]
C --> D["古い実行経路がないか"]
D --> E["必要な操作だけ任せたか"]
図2: 設定の有無だけでなく、記録・保全・実行経路・委任範囲まで確認する。
2. ログを残す ── 5.1と7の設定・記録先・容量を確認する
この章は、種類を選ぶ → 設定する → 新しいセッションで記録を確認する → 容量を確認するという手順です。トランスクリプションの出力先へ共有を指定する前に、3章の権限設計で集約先を準備してください。
2.1 何を残すかでログを選ぶ
PowerShellでは、モジュールログ・スクリプトブロックログ・トランスクリプションの3種類を使い分けます。イベントに残すものとファイルに残すもの、さらにバージョンごとの記録先を分けて押さえます。2
| 種類 | 記録内容 | イベントID |
|---|---|---|
| モジュールログ | 指定モジュールのパイプライン実行の詳細 | 4103 |
| スクリプトブロックログ | 実行されたコードのテキスト(難読化解除後) | 4104 |
| 記録先のログ | 5.1は Microsoft-Windows-PowerShell/Operational、7は PowerShellCore/Operational |
─ |
| トランスクリプション | セッションの入出力をテキストファイルに記録 | ─(ファイル出力) |
flowchart TB
accTitle: ログの保存先を分けて確認する
accDescr: スクリプトブロックログはバージョンごとのイベントログへ、トランスクリプションはファイルへ記録する。
A["実行内容を記録する"] --> B["スクリプトブロックログ"]
A --> C["トランスクリプション"]
B --> D["5.1/7のイベントログ"]
C --> E["準備したファイル保存先"]
図3: イベントログとトランスクリプトは保存先が異なり、どちらも実際の記録を確認する。
2.2 設定先を確認してから有効にする
Windows PowerShell 5.1は、グループポリシーの「コンピューターの構成 > 管理用テンプレート > Windows コンポーネント > Windows PowerShell」で設定します。2 PowerShell 7は「管理用テンプレート > PowerShell Core」側です。テンプレートも別なので、5.1側だけを設定して終わりにしないでください。7
以下は、レジストリで直接設定する例です。通常の社内展開ではGPOで配布します。前半はスクリプトブロックログ、後半はトランスクリプションで、それぞれ5.1用と7用を設定しています。出力先の共有名は例であり、先に準備した集約先へ読み替えます。
# スクリプトブロックログを有効化する(要管理者権限。通常はGPOで配布する)
$key = 'HKLM:\SOFTWARE\Policies\Microsoft\Windows\PowerShell\ScriptBlockLogging'
New-Item -Path $key -Force | Out-Null
# -Type はレジストリプロバイダーが追加する動的パラメーター。値の型(DWord)を明示できる
Set-ItemProperty -Path $key -Name 'EnableScriptBlockLogging' -Value 1 -Type DWord
# PowerShell 7(pwsh)は別のポリシーキーを見る。両方に設定する
$key = 'HKLM:\SOFTWARE\Policies\Microsoft\PowerShellCore\ScriptBlockLogging'
New-Item -Path $key -Force | Out-Null
Set-ItemProperty -Path $key -Name 'EnableScriptBlockLogging' -Value 1 -Type DWord
# トランスクリプションを有効化し、書き込み専用の共有へ集約する
$key = 'HKLM:\SOFTWARE\Policies\Microsoft\Windows\PowerShell\Transcription'
New-Item -Path $key -Force | Out-Null
Set-ItemProperty -Path $key -Name 'EnableTranscripting' -Value 1 -Type DWord
Set-ItemProperty -Path $key -Name 'OutputDirectory' -Value '\\logsrv\pstranscripts$'
Set-ItemProperty -Path $key -Name 'EnableInvocationHeader' -Value 1 -Type DWord
# トランスクリプションもPowerShell 7側は別キー。同じ値を書く
$key = 'HKLM:\SOFTWARE\Policies\Microsoft\PowerShellCore\Transcription'
New-Item -Path $key -Force | Out-Null
Set-ItemProperty -Path $key -Name 'EnableTranscripting' -Value 1 -Type DWord
Set-ItemProperty -Path $key -Name 'OutputDirectory' -Value '\\logsrv\pstranscripts$'
Set-ItemProperty -Path $key -Name 'EnableInvocationHeader' -Value 1 -Type DWord
この例では、5.1は Windows\PowerShell 配下、7は PowerShellCore 配下へ設定します。スクリプトブロックログ・トランスクリプションとも、両方のキーに同じ値を書きます。GPOで配布する場合も、それぞれのテンプレートを確認してください。
PowerShell CoreのGPOにはWindows PowerShell側の設定を使用する選択肢もありますが、5.1の設定を入れただけで7側まで有効になったとは判断しません。採用した設定方法にかかわらず、両方の実行環境で記録を確認することが要点です。7
flowchart TB
accTitle: 5.1と7の両方でログ設定を確認する
accDescr: Windows PowerShellとPowerShell Coreの設定先を区別し、それぞれの実行環境で記録まで確認する。
A["利用する実行環境を確認"] --> B["Windows PowerShell 5.1"]
A --> C["PowerShell 7"]
B --> D["5.1側の設定とログを確認"]
C --> E["Core側の設定とログを確認"]
D --> F["それぞれで記録を試す"]
E --> F
図4: 設定を共用する方式を選ぶ場合も、5.1と7の両方で記録を確かめる。
2.3 新しいセッションで、記録されたことを確認する
スクリプトブロックログの価値は、難読化への耐性にあります。Base64でエンコードされたコマンドや、文字列連結で組み立てられたコードであっても、実行時点で展開された内容が記録されます。2 攻撃の調査で「何が実行されたか」を再現できるかどうかは、この設定の有無で決まります。
記録の確認は Get-WinEvent です(「Get-WinEventでイベントログを実務的に調べる」)。
# 直近1日のスクリプトブロックログを確認する。
# Windows PowerShell(5.1)とPowerShell 7では書き込まれるログが別なので、
# 両方を対象にする
Get-WinEvent -FilterHashtable @{
LogName = 'Microsoft-Windows-PowerShell/Operational', 'PowerShellCore/Operational'
ID = 4104
StartTime = (Get-Date).AddDays(-1)
} -ErrorAction SilentlyContinue |
Select-Object TimeCreated, LogName, @{ n='Script'; e={ $_.Message } } -First 20
有効化できたかの見極めは、次の3点で行います。
- 設定を入れたあと、新しいPowerShellセッションを開く。スクリプトブロックログは、有効化以降に開始されたセッションから記録されます。2 すでに開いているウィンドウで試して「出ない」と判断しないでください。
- 目印になるコマンドを新しいセッションで実行し、それが4104として返るかを見る。上のクエリで、いま実行したコード片を含むイベントが返れば有効です。1件も返らなければ、
Get-ItemPropertyでポリシーキーの値(EnableScriptBlockLoggingが1)と、5.1側/7側のどちらのキーに書いたかを確認します。 - ログの記録先を間違えていないか。
pwshで実行したなら記録先はPowerShellCore/Operationalです。Microsoft-Windows-PowerShell/Operationalだけを見て「記録されていない」と誤判定するのが、この設定でいちばん多い勘違いです。
上のクエリは -ErrorAction SilentlyContinue を指定しています。何も返らないことだけを、有効化失敗や「実行がなかった」証拠にはしないでください。まず各ログの存在と、どちらのセッションで確認したかを切り分けます。PowerShell 7でイベントプロバイダーが未登録なら、Microsoftの案内に沿ってPowerShell 7の管理者セッションで $PSHOME\RegisterManifest.ps1 により登録します。8
flowchart TB
accTitle: 4104が出ないときの確認順序
accDescr: 新しいセッションで目印のコマンドを実行し、対応するログを確認する。値が返らなければ設定先やログの存在とプロバイダー登録を切り分ける。
A["新しいセッションを開く"] --> B["目印のコマンドを実行"]
B --> C["対応するログで4104を確認"]
C --> D{"見つかったか"}
D -->|"はい"| E["実行した内容と照合"]
D -->|"いいえ"| F["設定先・ログの存在を確認"]
F --> G["7のプロバイダー登録も確認"]
図5: 空の結果だけで記録の有無を決めず、設定先と確認先を切り分ける。
2.4 ログサイズを設定し、変更後の値を確かめる
有効化したら必ずログサイズを見直してください。既定サイズのままだと数時間〜数日で一巡し、いざというときに残っていません。
Get-WinEvent -ListLog 'Microsoft-Windows-PowerShell/Operational', 'PowerShellCore/Operational' |
Select-Object LogName, MaximumSizeInBytes, RecordCount
wevtutil sl Microsoft-Windows-PowerShell/Operational /ms:1073741824 # 1GBに拡張する例
wevtutil sl PowerShellCore/Operational /ms:1073741824 # PowerShell 7側も忘れずに
# 反映されたかを確認する。MaximumSizeInBytes が 1073741824 になっていれば成功
Get-WinEvent -ListLog 'Microsoft-Windows-PowerShell/Operational', 'PowerShellCore/Operational' |
Select-Object LogName, MaximumSizeInBytes
上のコードは、現在値の確認 → 1GBへの変更例 → 変更後の値の確認という一組です。wevtutil sl は成功しても何も表示しないため、表示の有無ではなく MaximumSizeInBytes を設定前後で見比べます。管理者権限で実行していないと変更が拒否されるので、値が変わらないときはまずそこを疑ってください。
flowchart TB
accTitle: ログ容量の変更は値で確認する
accDescr: ログサイズは現在値、変更、変更後の値を確認し、コマンドが何も表示しなかったことを成功判定に使わない。
A["現在の最大サイズを取得"] --> B["必要なサイズへ変更"]
B --> C["変更後の最大サイズを取得"]
C --> D["想定した値と比較"]
図6: 容量変更はコマンドの表示ではなく、設定前後の値で確認する。
3. 記録を守る ── トランスクリプト共有の権限と限界
3.1 先に、守れる範囲を決める
ここでいう集約先は、他人の記録を自由に読み書きできる共有ではありません。また、本文やコードでいう「書き込み専用」は、自分の記録に対する上書きまで禁止する意味ではありません。JEAの TranscriptDirectory とは書き手が違うため、同じ権限設計をそのまま当てはめないでください。
トランスクリプトの出力先は、利用者が書き込みはできて削除できない共有にするのが要点です。ローカルに置くと、侵害された端末では消されてしまいます。
権限は、共有アクセス許可とNTFSのアクセス許可の両方を設定します(実効権限は両者の厳しいほうになります)。要点は、利用者に「フォルダーへのファイル作成」と「自分が作ったファイルの読み書き」だけを与え、削除(DE)とサブフォルダー・ファイルの削除(DC)を与えないことです。
先に、この構成で何が守れて何が守れないかをはっきりさせておきます。守れるのは「他人の記録には一切触れない」と「自分の記録も消せない」の2点です。守れないのは「自分の記録の上書き」で、これはACLだけでは塞げません(理由は後述します)。それでも、乗っ取られたアカウントから他人の証跡を消せなくなるだけで、被害の切り分けは大きく変わります。1台の端末が落ちても、他の端末の記録は残ります。
flowchart TB
accTitle: 共有ACLで守れる範囲
accDescr: この共有ACLは他人の記録へのアクセスと自分の記録の削除を防ぐが、自分の記録の上書きは防げない。
A["利用者が書く記録用共有"] --> B["他人の記録に触れさせない"]
A --> C["自分の記録も削除させない"]
A -.-> D["自分の記録の上書きは残る"]
D --> E["より強い保全は3.4節へ"]
図7: 削除を防ぐことと、上書きを防ぐことは別の条件になる。
3.2 共有とNTFSの両方で権限を設定する
このコードは、指定したフォルダーの既存のアクセス許可を外して入れ直します。業務用の共有へそのまま適用せず、ログ用の検証フォルダーで試してください。EXAMPLE\Domain Users、EXAMPLE\ログ管理者、保存パスは自社の値へ読み替えます。
# ログ集約サーバー側で実行する(要管理者権限)
$path = 'D:\PSTranscripts'
$null = New-Item -Path $path -ItemType Directory -Force
# 共有を作る。利用者側は変更(書き込み)まで、管理はログ担当者だけ
New-SmbShare -Name 'pstranscripts$' -Path $path `
-ChangeAccess 'EXAMPLE\Domain Users' -FullAccess 'EXAMPLE\ログ管理者'
# 親フォルダーからの継承を切る(第2引数 $false = 継承していたACEを引き継がない)。
# 継承で降りてくる CREATOR OWNER のフルコントロールが残っていると、
# 「自分が作ったファイルは自分で消せる」状態になり、集約の意味がなくなる。
#
# ただし SetAccessRuleProtection が外すのは「継承してきたACE」だけで、
# そのフォルダーに直接付与されたACEは残ります。上の New-Item -Force は
# 既存フォルダーでも成功するので、以前に Domain Users や Everyone へ
# 変更権限を直接付けてあったなら、それは以降の行をすべて実行しても
# 生き残ります。利用者は他人のトランスクリプトを上書き・削除できたまま。
# そこで、直接付与を一度すべて落としてから、必要なものだけ入れ直します
$acl = Get-Acl -Path $path
$acl.SetAccessRuleProtection($true, $false)
foreach ($ace in @($acl.Access)) { [void]$acl.RemoveAccessRuleSpecific($ace) }
# 落とし切ったうえで、管理側とSYSTEMを同じ $acl に入れてから1回で適用する。
# 「空にする」と「入れ直す」を別々に Set-Acl すると、誰も触れない瞬間ができる
foreach ($id in @('EXAMPLE\ログ管理者', 'SYSTEM')) {
$acl.AddAccessRule([System.Security.AccessControl.FileSystemAccessRule]::new(
$id, 'FullControl', 'ContainerInherit, ObjectInherit', 'None', 'Allow'))
}
Set-Acl -Path $path -AclObject $acl
# 利用者にはフォルダーへの「作成」だけを許可する。(OI) を付けないのが要点で、
# この許可は配下のファイルには継承されない
# WD=ファイルの作成 AD=フォルダーの作成 X=フォルダーの走査 RA=属性の読み取り
# (OI)を付けると「既存ファイルへのデータ書き込み」まで配ってしまう
icacls $path /grant 'EXAMPLE\Domain Users:(CI)(WD,AD,X,RA)'
# 書き手が「自分の作ったファイルだけ」を読み書きできるようにする。
# CREATOR OWNER はファイル作成時に「その作成者」へ置き換わるので、
# 他人のファイルには効かない。DE(削除)は与えない
# RD,WD,AD = データの読み取り・書き込み・追加 RA,WA = 属性の読み書き
# REA,WEA = 拡張属性の読み書き RC = アクセス許可の読み取り
# S = 同期。GENERIC_READ/GENERIC_WRITE に含まれるので落とせない
# AD(追記)だけに絞りたくなるが、それではトランスクリプトが1行も書けない(後述)
icacls $path /grant 'CREATOR OWNER:(OI)(IO)(RD,WD,AD,REA,WEA,RA,WA,RC,S)'
# ここが要点。上の行だけでは足りません。
# ファイルを作った利用者は、そのファイルの「所有者」になります。
# Windows は所有者に READ_CONTROL と WRITE_DAC を暗黙で与えるため、
# WD も DE も配っていないのに、所有者は自分でDACLを書き換えて
# 自分に上書き・削除の権限を付け直せます。つまり、乗っ取られたアカウントは
# 自分の証跡を消せる。OWNER RIGHTS のACEがあるとき、システムは
# 所有者への暗黙の READ_CONTROL / WRITE_DAC を無視します
icacls $path /grant 'OWNER RIGHTS:(OI)(IO)(RA,REA)'
icacls $path # 設定結果を確認する
flowchart TB
accTitle: ACLの設定で確認する3つの点
accDescr: 継承と直接付与の整理、作成と既存ファイルへの権限の分離、所有者による権限の付け直し対策を順に確認する。
A["以前の許可を整理する"] --> B["作成権限とファイル権限を分ける"]
B --> C["OWNER RIGHTSを確認する"]
C --> D["実際の利用者で試す"]
図8: 既存の許可・継承・所有者の権利を、それぞれ確認する。
確認1: 以前のアクセス許可が残っていないか
既存のフォルダーを流用するときは、直接付与されたACEを必ず洗ってください。SetAccessRuleProtection($true, $false) が外すのは継承してきたACEだけで、そのフォルダーに直接付いているACEはそのまま残ります。9 New-Item -Force は既存フォルダーでも成功するので、「前に一時的に Domain Users へ変更権限を付けた」といった経緯があるフォルダーを再利用すると、以降で CREATOR OWNER を絞ってもその直接付与だけが生き残り、利用者は他人のトランスクリプトを上書き・削除できます。上のコードで一度すべて落としてから入れ直しているのはこのためです。新しいフォルダーを作るなら不要ですが、入れておいて損はありません。適用後に icacls $path の出力を目で確認し、意図しない主体が並んでいないかを見てください。
確認2: ファイル作成と既存ファイルへの書き込みを分けているか
ここで (OI) の付け方が対策の成否を分けます。利用者への許可に (OI) を付けて WD を配ると、そのACEが配下の全ファイルに継承され、名前さえ分かれば他人のトランスクリプトを上書き・切り詰めできてしまいます。9 それでは「端末やアカウントが乗っ取られたあとも記録が残る」という目的が達成できません。上のように、フォルダーへの作成権限(継承させない)と、CREATOR OWNER 経由で自分が作ったファイルだけへの読み書きに分けます。
flowchart TB
accTitle: 作成権限を全ファイルへ配らない
accDescr: フォルダーへの作成権限とCREATOR OWNERによる作成者自身のファイルの読み書きを分け、他人のファイルへの書き込みを許さない。
A["フォルダーへファイル作成"] --> B["新しい記録を作る"]
B --> C["CREATOR OWNERで作成者へ"]
C --> D["自分のファイルだけ読み書き"]
A -.-> E["全ファイルへWDを継承しない"]
図9: 新しい記録を作れることを、他人の記録を書き換えられることへ広げない。
確認3: 所有者が自分で削除権限を付け直せないか
そして、OWNER RIGHTS の行を落とさないでください。ファイルを作った利用者は、そのファイルの所有者になります。Windowsは所有者に READ_CONTROL と WRITE_DAC を暗黙で与えるため、10 ACEで DE を配っていなくても、所有者は自分でDACLを書き換えて、自分に削除の権限を付け直せます。「自分の記録は自分で消せる」状態のままでは、CREATOR OWNER を絞った意味がありません。OWNER RIGHTS(S-1-3-4)のACEを置くと、システムは所有者への暗黙の READ_CONTROL / WRITE_DAC を無視するので、ここで初めて「書けるが消せない」が成立します。10
flowchart TB
accTitle: 所有者の権限変更も考慮する
accDescr: ファイル作成者が所有者としてDACLを変更できる経路をOWNER RIGHTSのACEで制限する。
A["ファイル作成者は所有者"] --> B["暗黙の権利でDACLを変更可能"]
B --> C["削除権限の付け直しが問題"]
C --> D["OWNER RIGHTSのACEを適用"]
D --> E["暗黙の権利に頼る変更を制限"]
図10: 削除権限を配らないだけでなく、所有者が自分で付け直す経路も扱う。
3.3 なぜ AD(追記)だけに絞らないのか
この節は、上書きを許している理由を知りたい方向けの補足です。結論だけ先に言うと、Start-Transcript が要求するアクセス権を満たさないと、保護以前に記録できなくなるからです。
「追記専用」はACLの名前ではなく、ファイルの開き方で決まる
「上書きされたくないのだから CREATOR OWNER は AD だけでいい」と考えたくなります。そうすると、トランスクリプトが1行も残りません。
Windowsで「追記しかできない」が成立するのは、書き手が FILE_APPEND_DATA を単独で指定してファイルを開いたときだけです。Microsoftのリファレンスは FILE_APPEND_DATA を「(ローカルファイルでは、FILE_WRITE_DATA を伴わずにこのフラグを指定した場合、書き込み操作は既存のデータを上書きしない)」と定義しています。11 つまり追記専用は開き方の性質であって、ACEに AD を置くだけで実現するものではありません。
Start-Transcript が要求する権利を確認する
そして Start-Transcript の書き込み口は、追記専用では開きません。PowerShellの実装は、まず FileMode.OpenOrCreate + FileAccess.ReadWrite で開き、失敗したときだけ FileMode.Append + FileAccess.Write で開き直します。12 .NET側では FileAccess.Read/Write がそれぞれ GENERIC_READ/GENERIC_WRITE に写され、FileMode.Append も内部で FileMode.OpenOrCreate に置き換えてから末尾へシークするだけです。13 FILE_APPEND_DATA を単独で要求する経路はありません。
FILE_GENERIC_WRITE には FILE_WRITE_DATA・FILE_WRITE_ATTRIBUTES・FILE_WRITE_EA・READ_CONTROL・SYNCHRONIZE が、FILE_GENERIC_READ には FILE_READ_DATA・FILE_READ_ATTRIBUTES・FILE_READ_EA・READ_CONTROL・SYNCHRONIZE が含まれます。11 アクセスチェックは要求したすべての権利を見るので、AD,RA,REA しか持たない利用者のこの CreateFile は拒否されます。共有側で変更(Change)を許していても、NTFS側で落ちます。記録を守るつもりで、記録そのものを止めることになります。
だからこの節のACLは、AD 単独ではなく「読み書きは許すが DE(削除)・WDAC(権限変更)・WO(所有者変更)は与えない」という形にしてあります。所有者は自分のトランスクリプトを上書き・切り詰めできます。ここはACLでは塞げない、と割り切ってください。
flowchart TB
accTitle: 追記だけの許可では記録できない理由
accDescr: Start-Transcriptが要求する読み書きのアクセス権をADだけの許可では満たせず、ファイルを開けない。
A["Start-Transcriptでファイルを開く"] --> B["読み書きの権利を要求"]
B --> C{"すべての要求を満たすか"}
C -->|"ADだけでは不足"| D["アクセス拒否:記録できない"]
C -->|"満たす"| E["記録できるが上書きも可能"]
図11: 追記専用はファイルの開き方で決まり、ACLにADを置くだけでは実現しない。
3.4 上書きも防ぐなら、利用者が触れない収集先へ
上書きまで塞ぎたいなら、書き手が触れない場所へ出すしかありません。選択肢は3つです。
- JEAなら、セッション構成ファイルの
TranscriptDirectoryを使う(7章)。この出力を書くのは接続してきた利用者ではなく Local System で、Microsoftのドキュメントも「標準ユーザーはこのフォルダーへのアクセスを持たないこと」「監査する security 管理者だけに絞ること」を求めています。14 利用者にそもそも権限が要らないので、この節の悩みごと消えます - Windowsイベント転送やSIEMへ取り込む。スクリプトブロックログ(4104)はイベントログに出るので、転送の対象にできます
- 追記専用で開く独自の収集プロセスを立てる。
FILE_APPEND_DATAを単独で要求して開くところまで自分で書けば、ACE側のADが初めて意味を持ちます
なお、ACLで守れるのはあくまで「その共有に書く利用者」からです。ファイルサーバーの管理者は所有権を取得できますし、端末側が完全に掌握されていれば、そもそも共有へ書き出す前に細工できます。この節の権限設計は、「同じ共有を使う利用者どうしで消し合えない」を担保するものだと理解してください。
flowchart TB
accTitle: 利用者が触れない収集先を選ぶ
accDescr: 上書きまで防ぎたい場合はJEAの記録先、イベント転送やSIEM、追記専用の独自収集など、書き手から記録を分離する。
A["書き手から記録を分離したい"] --> B["JEA:Local Systemが記録"]
A --> C["イベント転送/SIEMへ集約"]
A --> D["追記専用の独自収集"]
B --> E["利用者のアクセスを制限"]
C --> E
D --> E
図12: 通常の共有とJEAの記録先では書き手が異なるため、同じACLを当てはめない。
3.5 実際の利用者権限で、書き込み・閲覧・削除を試す
icacls の権限の記号(WD AD DE DC など)と継承指定((OI) (CI) (IO))の意味は、Windowsコマンドのリファレンスに一覧があります。9 なお、この構成は必ず検証機で1回試してから展開してください。トランスクリプトは実行中のセッションが同じファイルへ書き足していくため、権限を絞りすぎると記録そのものが落ちます。「利用者の端末から書けること」「利用者が他人の既存ファイルを読めず、書き換えられないこと」「利用者が自分のファイルを削除できないこと」の3点を、実際に確認するのが確実です。
4. 検査の経路を整える ── AMSIとPowerShell 2.0
4.1 AMSIは実行直前の内容を検査へ渡す
AMSI(Antimalware Scan Interface) により、PowerShell 5.0以降ではスクリプトの内容が実行直前にウイルス対策製品へ渡され、難読化を解除した状態で検査されます。1 Microsoft Defenderを含む対応製品を使っていれば、追加設定なしで機能します。
flowchart TB
accTitle: AMSIと対応製品の役割
accDescr: AMSIが実行直前のスクリプト内容を対応するウイルス対策製品へ渡し、検査する仕組みを示す。
A["PowerShellの実行直前の内容"] --> B["AMSI"]
B --> C["対応するウイルス対策製品"]
C --> D["内容を検査"]
図13: AMSIは内容を検査製品へ渡す仕組みで、対応した実行環境と製品を確認する。
4.2 古いエンジンが残っているかを確認し、無効化する
問題は、古いエンジンにはこの仕組みがないことです。Windows PowerShell 2.0エンジンが有効なままだと、powershell.exe -Version 2 によってログもAMSIも効かない環境に切り替えられる余地が残ります。この機能は非推奨であり、無効化が推奨されています。5
次のコードは、前半が状態確認、後半が無効化です。確認結果を読んでから変更し、全行を無条件に続けて実行しないでください。この手順は2.0のオプション機能が残っている環境のためのもので、機能が見つからない環境へ追加し直す手順ではありません。
# PowerShell 2.0エンジンの状態を確認し、無効化する(要管理者権限・再起動が必要な場合あり)
Get-WindowsOptionalFeature -Online -FeatureName MicrosoftWindowsPowerShellV2* |
Select-Object FeatureName, State
Disable-WindowsOptionalFeature -Online -FeatureName MicrosoftWindowsPowerShellV2Root -NoRestart
flowchart TB
accTitle: 古いエンジンは存在を確認して無効化する
accDescr: PowerShell 2.0のオプション機能が残っている環境を対象に状態を確認し、必要な無効化を行う。存在しない機能を追加し直さない。
A["2.0のオプション機能を確認"] --> B{"残っているか"}
B -->|"有効"| C["無効化し必要な再起動を扱う"]
B -->|"ない/無効"| D["追加・有効化し直さない"]
C --> E["利用する5.1と7を確認"]
D --> E
図14: 古い機能を確認するコードと、無効化するコードは判断を挟んで使う。
4.3 7を導入しても、5.1側の確認を省かない
なお、PowerShell 7(pwsh)を導入している場合、設定キーもログの記録先も5.1とは別です(ポリシーは PowerShellCore 配下、ログは PowerShellCore/Operational)。両方が使われる環境では、設定・ログサイズ・調査クエリのすべてを両方に対して行ってください。バージョンの共存については「Windows PowerShell 5.1とPowerShell 7の違い」を参照してください。
5. 実行範囲を絞る ── 実行ポリシーと言語モードを混同しない
5.1 実行ポリシーは、悪意ある操作を止める境界ではない
この章では、悪意ある操作を止めることと、配布物の完全性を確認することを分けます。
改めて明確にしておきます。実行ポリシーはセキュリティ境界ではありません。公式ドキュメントは、ユーザーが意図せずスクリプトを実行してしまうことを防ぐための安全機能であり、悪意ある操作を防ぐものではないと明記しています。6
とはいえ無価値ではありません。署名運用には「配布したモジュールが改ざんされていないことを確認できる」という別の価値があります(「PowerShellモジュールの社内配布と更新」)。役割を正しく理解して使うことが重要で、「AllSignedにしたから安全」という理解が最も危険です。
flowchart TB
accTitle: 署名の価値と実行制限を分ける
accDescr: 実行ポリシーをセキュリティ境界とは扱わず、署名の完全性確認とアプリケーション制御による実行制限を別の役割として理解する。
A["目的を分ける"] --> B["配布物の完全性を確認する"]
A --> C["コードの実行範囲を制限する"]
B --> D["署名運用の価値"]
C --> E["アプリ制御と言語モード"]
D -.-> F["AllSignedだけで安全とはしない"]
図15: 署名の価値を認めることと、実行ポリシーを防御の主軸にすることは違う。
5.2 制限の強弱だけでなく、用途を比べる
PowerShellのセッションには言語モードがあり、使える言語要素が制限されます。3
| モード | 使えるもの | 実務での位置づけ |
|---|---|---|
| FullLanguage | すべての言語要素(既定) | 通常のセッション |
| ConstrainedLanguage | コマンドレットはすべて動き、ループ・条件分岐・文字列展開・プロパティ参照も使える。ただし使える.NET型が許可リストに限定され、Add-Type は署名済みアセンブリしか読み込めない |
WDAC/AppLocker配下で自動的に切り替わるモード。対話操作や通常の管理作業はおおむねそのまま行える |
| RestrictedLanguage | コマンドは実行できるがスクリプトブロックが使えない。変数は $PSCulture $PSUICulture $true $false $null のみ、比較演算子は -eq -gt -lt のみで、代入・プロパティ参照・メソッド呼び出しは不可 |
モジュールマニフェスト(.psd1)の読み込みに使われるモード。人が対話で使う想定のものではない |
| NoLanguage | スクリプト言語そのものが無効。スクリプトも変数も使えず、コマンドレットとネイティブコマンドの呼び出しだけができる | JEAのセッション構成(RestrictedRemoteServer)の既定。「決められたコマンドを叩くだけ」の窓口を作るためのモード |
3つの制限モードは段階ではなく、用途が違います。ConstrainedLanguageは「人がスクリプトを書いて使う環境を、危険な型の利用だけ止める」もの、NoLanguageは「そもそもスクリプトを書かせない」もので、後者はJEAのような限定的な窓口でしか成立しません。3
flowchart TB
accTitle: 言語モードは用途で選ぶ
accDescr: 制約付き言語モードは通常の管理作業で使える言語要素を絞り、NoLanguageはJEAのような決められた操作の窓口で用いる。
A["利用のしかたを見る"] --> B["スクリプトを書いて管理する"]
A --> C["決めたコマンドだけを使う"]
B --> D["アプリ制御+ConstrainedLanguage"]
C --> E["JEAのNoLanguageセッション"]
図16: 制限モードは単純な強弱の段階ではなく、利用する場面が異なる。
5.3 現在のモードを確認する
現在のモードは次で確認できます。
$ExecutionContext.SessionState.LanguageMode
5.4 モードの表示だけでなく、制限を強制する仕組みを見る
重要なのは設定方法です。制約付き言語モードは、WDAC(Windows Defender Application Control)やAppLockerで許可リスト方式のアプリケーション制御を構成した際に、PowerShellが自動的に切り替わる形で機能します。3 環境変数などで手動設定する方法は回避が容易であり、セキュリティ機能としては機能しません。アプリケーション制御を導入せずに言語モードだけを絞るのは、労力の割に効果がないと理解してください。
flowchart TB
accTitle: 言語モードは強制する仕組みまで確認する
accDescr: モードを表示するだけでなく、WDACやAppLockerによる制御に基づいているかを確認し、手動設定を防御として扱わない。
A["現在のLanguageModeを確認"] --> B["制限を強制する仕組みを確認"]
B --> C["WDAC/AppLockerの構成"]
B -.-> D["手動設定だけには頼らない"]
図17: 言語モードの表示と、回避できない形で制限する仕組みを分けて確認する。
6. JEAを設計する ── 接続先・許可範囲・関数の責任
現実の被害を左右するのは、多くの場合権限の広さです。「ヘルプデスクに管理者権限を渡している」「運用担当が全員Domain Adminに入っている」という状態では、1台の侵害が全社の侵害になります。
JEA(Just Enough Administration)は、管理者権限を渡さずに特定の操作だけを委任する仕組みです。4 「アプリケーションサービスの再起動だけをヘルプデスクに任せる」といった要求に、ぴったり合います。
6章では前提と許可範囲を決め、7章でファイルを作って登録・接続します。「何を許すか」の定義(.psrc)と、「誰に渡してどう実行するか」の定義(.pssc)は別の役割です。
6.1 前提: JEAは専用のリモート接続先を作る
第2章〜第5章がローカルの設定で完結するのに対し、JEAはPowerShellリモート処理(WinRM)の仕組みそのものの上に作られています。15 「JEAで委任する」とは、対象のサーバーに専用の接続先(セッション構成)を登録し、そこへ接続させることです。自社で再現できるかどうかは、まずここで決まります。
必要になるのは次の3点です。
| 前提 | 内容 |
|---|---|
| PowerShellのバージョン | JEAはPowerShell 5.0以降で利用できます15 |
| リモート処理の有効化 | 対象サーバーでPowerShellリモート処理が有効であること。Windows Server 2012以降は既定で有効で、無効な場合は管理者権限のPowerShellで Enable-PSRemoting を実行します15 |
| 配置場所 | 役割機能ファイル(.psrc)は対象サーバー上のモジュールの RoleCapabilities フォルダーに、セッション構成(.pssc)は対象サーバー上で登録します。利用者の端末に置くものではありません4 |
つまり、7章の手順1〜3は委任される側のサーバーで、管理者権限で行う作業です。利用者の端末で必要なのは、そのサーバーへ接続できることだけです。リモート処理そのものの構成と安全な設定は「PowerShell Remoting(WinRM)入門」にまとめています。
以下の例は、配置パスと Get-EventLog に合わせ、対象サーバーのWindows PowerShell 5.1側で構成する例として読みます。PowerShell 7では Get-EventLog は標準のコマンドとして提供されないため、7側のエンドポイントへ無変更で適用できる例ではありません。16 記事全体で5.1と7を扱うことと、個々のサンプルが両方でそのまま使えることは別です。
flowchart TB
accTitle: JEAで渡すのは管理者権限ではなく操作
accDescr: 利用者は専用のエンドポイントへ接続し、仮想アカウント側が許可された管理作業を実行する。
A["管理者ではない利用者"] --> B["対象サーバーの専用接続先"]
B --> C["役割で許可した操作"]
C --> D["仮想アカウント側で実行"]
D --> E["実行内容を記録"]
図18: 対象サーバーのJEA接続先を通じて、必要な操作だけを委任する。
6.2 値の制限だけに頼らず、入力経路を絞る
次の2例は、パラメーター制限だけに頼った構成の問題を説明するもので、導入時に実行する手順ではありません。
VisibleCmdlets の ValidateSet だけでは、状態を変えるコマンドを安全に絞れません。Parameters と ValidateSet による制限は、そのパラメーターが実際に束縛されたときにしか評価されません。Restart-Service は ServiceController をパイプラインで受け取れるため、
Get-Service WinRM | Restart-Service
と書かれると -Name が束縛されず、ValidateSet は素通りします。「KsAppService と Spooler だけ再起動できる」つもりのエンドポイントで、任意のサービスを再起動できてしまうわけです。
同じことは別のパラメーターセットでも起きます。Get-WinEvent に LogName の ValidateSet を付けても、
Get-WinEvent -ProviderName Microsoft-Windows-Security-Auditing -MaxEvents 1
と書かれれば LogName は束縛されず、制限は働きません。JEAセッションは仮想管理者として動くため、この場合Securityログまで読めてしまいます。
そのため7章の構成例では Restart-Service を VisibleCmdlets に入れず、引数でしか対象を受け取らないラッパー関数(Restart-KsAppService)だけを公開しています。入力経路が引数しかなければ、迂回のしようがありません。
Parameters と ValidateSet による制限は、「そのパラメーターが束縛されたとき」にしか働きません。パイプライン入力や別のパラメーターセットで束縛を回避できるコマンドでは、制限そのものが無効になります。引数を絞りたいコマンドは、原則としてラッパー関数で包むと考えてください。
flowchart TB
accTitle: パラメーター制限が働く条件
accDescr: ValidateSetは対象のパラメーターが束縛されたときに評価されるため、パイプライン入力や別のパラメーターセットを持つコマンドは入力経路を確認する。
A["許可する値を絞りたい"] --> B["すべての入力経路を確認"]
B --> C["パイプライン入力"]
B --> D["別のパラメーターセット"]
C --> E["制限した引数が束縛されるか"]
D --> E
E --> F["限定したラッパー関数で受ける"]
図19: 特定の引数への制限が、別の入力経路でも働くとは限らない。
6.3 関数の定義と公開は別々に必要
役割機能ファイルでは、VisibleFunctions は関数を作らない点にも注意してください。名前を挙げただけの関数はセッションに存在せず、利用者には「そんなコマンドはない」と見えます。独自関数は FunctionDefinitions で定義したうえで、VisibleFunctions にも挙げてください。17 数が増えるならスクリプトモジュールに切り出し、そのモジュールの関数を VisibleFunctions で公開するほうが管理しやすくなります。
flowchart TB
accTitle: 関数の定義と利用者への公開を分ける
accDescr: FunctionDefinitionsなどで用意した関数をVisibleFunctionsで公開する。名前を指定するだけでは関数は作られない。
A["FunctionDefinitionsで定義"] --> B["セッションに関数が存在"]
B --> C["VisibleFunctionsで公開"]
C --> D["JEA利用者が呼べる"]
図20: 関数を用意することと、利用者へ見せることの両方が必要になる。
6.4 公開する関数の本体にも責任を持つ
関数の本体はJEAの制約を受けません。17 Select-Object などJEAが差し替える制約付きコマンドを本来の挙動で使いたい場合は、7章の例のように Microsoft.PowerShell.Utility\Select-Object と完全修飾で呼びます。裏を返せば、関数の中では何でもできるということなので、利用者からの入力を Invoke-Expression に渡すような書き方は絶対に避けてください。
flowchart TB
accTitle: JEAの利用者と関数本体の境界
accDescr: JEA利用者の制限と関数本体の実行範囲は異なり、関数側で利用者の入力を安全に扱う必要がある。
A["JEA利用者の入力"] --> B["公開する関数で受ける"]
B --> C["限定した対象だけを渡す"]
C --> D["管理コマンドを呼び出す"]
B -.-> E["関数本体は別途レビュー"]
図21: 利用者を制限しても関数本体の安全性までは保証されないため、入力の扱いを設計する。
7. JEAを構成する ── 定義・登録・接続を順に行う
手順1〜3は対象サーバーの管理者が行い、その後で利用者が専用エンドポイントへ接続します。以下はWindows PowerShell 5.1側の構成例です。役割名、グループ、保存先、公開する操作を6章で決めてから作成してください。
flowchart TB
accTitle: JEA構成ファイルを役割ごとに用意する
accDescr: 役割機能ファイルをモジュールへ配置し、セッション構成から役割名で参照する。登録するのはセッション構成ファイルで、その後で利用者が接続する。
A["psrc:モジュールへ配置"] -->|"役割名で参照"| B["pssc:利用者・実行・記録"]
B --> C["psscをサーバーへ登録"]
C --> D["利用者が専用接続先へ接続"]
図22: psrcはモジュールへ配置してpsscから参照し、サーバーへ登録するのはpsscである。
手順1: 役割機能ファイル(.psrc)で許可する操作を定義する
まず、役割ファイルを置くモジュールを作る
役割機能ファイルは、PowerShellモジュールの RoleCapabilities フォルダーに置かれている必要があります。フォルダーを作っただけではモジュールとして認識されず、後述の RoleDefinitions から名前で解決できません。そのため、まず入れ物となるモジュール(マニフェストを持つフォルダー)を作ります。4
# 役割機能を入れるモジュールを作る(マニフェストが必要)
$moduleRoot = 'C:\Program Files\WindowsPowerShell\Modules\KsJea'
$null = New-Item -Path "$moduleRoot\RoleCapabilities" -ItemType Directory -Force
# モジュールフォルダーには、フォルダーと同名のファイルが1つ以上必要
$null = New-Item -Path "$moduleRoot\KsJea.psm1" -ItemType File -Force
New-ModuleManifest -Path "$moduleRoot\KsJea.psd1" -RootModule 'KsJea.psm1'
# 役割機能ファイルの雛形を作る(ファイル名が役割名になる)。
# 役割名は PSModulePath 上の全モジュールから「名前だけ」で解決されるため、
# 'HelpDesk' のような一般的な名前は他のモジュールの .psrc と衝突しうる。
# 衝突するとどちらが選ばれるかの保証がなく、意図しない権限が与えられる。
# 組織の接頭辞を付けた一意な名前にする
New-PSRoleCapabilityFile -Path "$moduleRoot\RoleCapabilities\KsHelpDesk.psrc"
# モジュールとして見えているかを確認する
Get-Module -Name KsJea -ListAvailable
flowchart TB
accTitle: 役割名は検出可能なモジュールから解決される
accDescr: 役割機能ファイルは検出可能なモジュールのRoleCapabilities配下へ置き、他の役割名と衝突しない一意の名前を選ぶ。
A["対象サーバーのPSModulePath"] --> B["検出可能なKsJeaモジュール"]
B --> C["RoleCapabilitiesフォルダー"]
C --> D["一意のKsHelpDesk.psrc"]
D --> E["RoleDefinitionsで名前を指定"]
図23: 配置先だけでなく、モジュールとして見えることと役割名の一意性を確認する。
次に、作成した役割ファイルへ公開する操作を書く
次は、作成した KsHelpDesk.psrc に記述する内容の抜粋であり、コンソールに貼って実行する設定コマンドではありません。GUID = '....' は省略表記です。雛形が生成した実際のGUIDは残し、公開コマンドと関数の定義を編集してください。
読みどころは、Restart-Service を直接公開せず、許可するサービス名を引数で受け取る Restart-KsAppService だけを公開する点です。値の制限だけに頼らない理由は6章で説明しました。
# KsHelpDesk.psrc(抜粋)── 「何を、どこまで」許可するかを宣言する
@{
GUID = '....'
# 読み取り専用のコマンドはそのまま見せてよい
VisibleCmdlets = @(
'Get-Service',
'Get-EventLog'
)
# 状態を変える Restart-Service は、あえて VisibleCmdlets に入れない(後述)
# VisibleFunctions は「セッションに読み込まれている関数」を絞り込むだけで、
# 関数を定義はしない。独自関数は FunctionDefinitions で中身を書き、
# そのうえで VisibleFunctions にも名前を挙げる(両方必要)
VisibleFunctions = @('Get-KsAppStatus', 'Restart-KsAppService')
FunctionDefinitions = @(
@{
Name = 'Get-KsAppStatus'
ScriptBlock = {
# 関数の本体は既定の言語モードで動くため、JEAの制約を受けない。
# 利用者の入力をそのまま危険なコマンドへ渡さないこと
Get-Service -Name 'KsAppService' |
Microsoft.PowerShell.Utility\Select-Object Name, Status, StartType
}
},
@{
# 再起動は「引数でしか対象を受け取らない」関数として公開する
Name = 'Restart-KsAppService'
ScriptBlock = {
param(
[Parameter(Mandatory)]
[ValidateSet('KsAppService', 'Spooler')]
[string] $Name
)
Microsoft.PowerShell.Management\Restart-Service -Name $Name
}
}
)
VisibleExternalCommands = @()
}
手順2: セッション構成ファイル(.pssc)で、誰にどの役割を割り当てるかを定義する
.psrc が「何を許すか」なのに対し、ここでは誰にその役割を渡し、どのアカウントで実行し、どこへ記録するかを定義します。EXAMPLE\HelpDesk は対象の利用者グループへ読み替えます。
TranscriptDirectory は3章の利用者が直接書く共有とは別です。Local Systemに読み書きの権限があり、標準ユーザーはアクセスできず、監査する管理者だけが読める保存先を用意してください。14
# SessionType: 制限付きリモートサーバー(既定でNoLanguage)
# RunAsVirtualAccount: 仮想管理者アカウントとして実行
# TranscriptDirectory: 実行内容を記録
$pssc = @{
Path = '.\KsHelpDesk.pssc'
SessionType = 'RestrictedRemoteServer'
RunAsVirtualAccount = $true
TranscriptDirectory = 'C:\JeaTranscripts'
RoleDefinitions = @{ 'EXAMPLE\HelpDesk' = @{ RoleCapabilities = 'KsHelpDesk' } }
}
New-PSSessionConfigurationFile @pssc
flowchart TB
accTitle: JEAの記録はLocal Systemが書く
accDescr: JEAのTranscriptDirectoryはLocal Systemが書き込む。標準ユーザーにアクセスを持たせず、監査する管理者を限定する。
A["JEAセッションの実行内容"] --> B["Local Systemが記録"]
B --> C["TranscriptDirectory"]
C --> D["限定した監査管理者が確認"]
C -.-> E["標準ユーザーにアクセスを与えない"]
図24: JEAの保存先には通常の利用者が直接書く共有とは異なる権限を設定する。
手順3: サーバーへ登録する
ここまでのファイルを用意してから、同じ対象サーバーでセッション構成を登録します。ファイルを作っただけでは、利用者の接続先にはなりません。
Register-PSSessionConfiguration -Name 'KsHelpDesk' -Path .\KsHelpDesk.pssc -Force
利用者側: 登録した専用エンドポイントへ接続する
ここからは利用者側の操作です。サーバー名と構成名を指定し、既定の接続先ではなく、作成したJEAの接続先を選びます。
Enter-PSSession -ComputerName 'appsrv01' -ConfigurationName 'KsHelpDesk'
# 許可されたコマンドしか使えない。再起動は公開した Restart-KsAppService で指定サービスのみ
JEAの要点は3つです。4
- 利用者は管理者権限を持たない。実行は仮想アカウント側で行われる
- セッションは制限付きリモートサーバーとして構成される。既定で言語モードが制限され、任意のコードを実行できない
- トランスクリプトで実行内容が記録される。誰が何をしたかが監査できる
flowchart TB
accTitle: 専用の構成名を指定して接続する
accDescr: 対象サーバーへ構成を登録した後、利用者はComputerNameとConfigurationNameでその専用エンドポイントを選ぶ。
A["サーバー:構成を登録"] --> B["利用者:サーバー名を指定"]
B --> C["KsHelpDesk構成を指定"]
C --> D["許可された操作だけを利用"]
図25: サーバー側の登録と利用者側の接続を別の作業として確認する。
接続や役割の解決で困ったときの確認場所
なお、役割機能ファイルは前述のとおりモジュールの RoleCapabilities フォルダー配下にある必要があり、そのモジュールが $env:PSModulePath から見つかることが前提です。RoleDefinitions で指定した役割名が解決できないときは、まず Get-Module -ListAvailable でモジュールが見えているかを確認してください。4
登録したエンドポイントは Get-PSSessionConfiguration で一覧できます。冒頭に書いたとおりJEAはリモート実行の構成が前提なので、接続できないときはJEAの定義ではなくWinRM側から切り分けてください(「PowerShell Remoting(WinRM)入門」)。
8. 展開前の実務チェックリスト
設定値を入れたことと、期待どおり動くことは別です。ログは新しいセッションでの4104と容量、共有は利用者権限での読み書き・削除、JEAは専用エンドポイントと公開する操作を、それぞれの節で確認してからチェックします。
表の「書き込み専用共有」は、3章の権限設計を指します。自分の記録の上書きまで防ぐ構成ではない点を、運用の判断に含めてください。
| 項目 | 優先度 | 状態 |
|---|---|---|
| スクリプトブロックログ(4104)を有効化した | 高 | GPOで全社配布218 |
| PowerShell関連ログのサイズを拡張した | 高 | 既定のままだと数日で消える |
| トランスクリプションを有効化し、書き込み専用共有に集約した | 高 | 端末ローカルに置かない2 |
| PowerShell 2.0エンジンを無効化した | 高 | ログ・AMSIの迂回経路5 |
| ウイルス対策製品がAMSIに対応している | 高 | 既定で有効1 |
| 実行ポリシーをセキュリティ境界と誤解していない | 中 | 署名は別の価値6 |
| 管理者権限の付与範囲を棚卸しした | 高 | 被害範囲を決めるのは権限の広さ |
| 定型作業をJEAで委任した | 中 | 管理者権限の削減に直結4 |
| WDAC/AppLockerを検討した | 中 | 言語モード制限はこれとセット3 |
| PowerShell 7側にもログ設定を配布した | 中 | 5.1と7で設定は別 |
flowchart TB
accTitle: 設定値と動作結果を対にして確認する
accDescr: ログ、保存先、JEAは設定しただけでなく、それぞれ記録・権限・接続と操作を確認してから展開する。
A["検証機で設定する"] --> B["新しいセッションでログ確認"]
B --> C["利用者権限で共有を確認"]
C --> D["専用エンドポイントで操作確認"]
D --> E["業務影響を確認して展開"]
図26: 管理者の設定画面だけでなく、実際に使う側の動作を確かめてから展開する。
9. まとめ
ここまでの判断を、導入時に見直す要点としてまとめます。
- PowerShellの禁止は実効性が低く、業務を止めます。方針は「可視化と制限」です。
- 最優先はスクリプトブロックログ(4104)の有効化。難読化されたコードも展開後の形で記録されます。ログサイズの拡張とセットで実施してください。
- トランスクリプションは、利用者が消せない共有に集約します。ただし、自分の記録の上書きまで防ぐには、JEAの
TranscriptDirectoryやイベント転送など、利用者が触れない収集先を使います。 - PowerShell 2.0エンジンは無効化します。ログもAMSIも効かない経路を残さないためです。
- 実行ポリシーはセキュリティ境界ではありません。署名は改ざん検知として価値がありますが、防御の主軸にはしないでください。
- 権限の広さが被害の広さを決めます。JEAで「必要な操作だけ」を委任すれば、管理者権限を配らずに運用が回せます。
flowchart TB
accTitle: 禁止ではなく可視化と制限を重ねる
accDescr: 実行内容を残し、記録を保全してから古い実行経路をなくし、必要な操作の委任と実行制限を組み合わせる。
A["実行内容を残す"] --> B["残した記録を守る"]
B --> C["古い実行経路を残さない"]
C --> D["必要な操作だけを委任する"]
D --> E["業務影響を見て実行制限を重ねる"]
図27: 記録・保全・検査・委任・制限を、それぞれの役割に合わせて組み合わせる。
サンプルコードのダウンロード
この記事で扱ったコードは、そのまま動かせる形にまとめて配布しています。ログ設定の有効化と、JEAエンドポイントの作成が入っています。
この記事のサンプルは、Windowsやテナントに依存するため実行検証はしていません。構文解析とPSScriptAnalyzerによる静的解析までは全ファイルに対して実施していますが、動作は必ずご自身の検証機で確認してください。
# 構文解析 + 静的解析(Windows以外でも実行できる)
./Invoke-SampleTests.ps1
設定値(パス、サーバー名、テナントIDなど)は例です。そのまま本番環境で実行せず、自社の環境に合わせて読み替えてください。
関連記事
- PowerShellの実行ポリシーとスクリプト署名 ── 「Bypassで蓋をする」運用から卒業する
- Get-WinEventでイベントログを実務的に調べる ── 絞り込みの速さが調査時間を決める
- PowerShell Remoting(WinRM)入門 ── 複数台のWindowsを一括管理する
- PowerShellでの資格情報の安全な扱い ── 平文パスワードをスクリプトから追放する
- Windowsアプリのセキュリティ最低限チェックリスト
- IPA「情報セキュリティ10大脅威 2026」を業務システムの視点で読む
関連する相談領域
合同会社小村ソフトでは、Windows運用環境のセキュリティ設定レビュー、権限委任(JEA)を含む運用設計、監査ログの整備と活用の相談を扱っています。
参考リンク
-
Microsoft Learn, Antimalware Scan Interface (AMSI). AMSIがアプリケーションとサービスから任意のアンチマルウェア製品へコンテンツを渡して検査させる仕組みであること、PowerShellを含むWindowsのスクリプトエンジンが統合されており、難読化されたスクリプトも実行時の内容で検査できることについて。 ↩ ↩2 ↩3 ↩4 ↩5
-
Microsoft Learn, about_Logging_Windows. モジュールログ・スクリプトブロックログ・トランスクリプションの3種類のログ機能、グループポリシーおよびレジストリ(HKLM\SOFTWARE\Policies\Microsoft\Windows\PowerShell配下)による有効化、スクリプトブロックログがイベントID 4104として難読化解除後のコードを記録すること、モジュールログがイベントID 4103として記録されること、トランスクリプションのOutputDirectoryやEnableInvocationHeaderの設定について。 ↩ ↩2 ↩3 ↩4 ↩5 ↩6 ↩7 ↩8
-
Microsoft Learn, about_Language_Modes. FullLanguage・ConstrainedLanguage・RestrictedLanguage・NoLanguageの各モードで利用できる言語要素、$ExecutionContext.SessionState.LanguageModeによる現在のモードの確認、WDACやAppLockerによるアプリケーション制御が有効な環境でPowerShellが制約付き言語モードで動作すること、手動での言語モード設定がセキュリティ機能として意図されていないことについて。 ↩ ↩2 ↩3 ↩4 ↩5 ↩6
-
Microsoft Learn, Just Enough Administration (JEA) の概要. JEAが管理者権限を付与せずに特定の管理作業のみを委任する仕組みであること、役割機能ファイル(.psrc)によるVisibleCmdlets・パラメーターとValidateSetによる制限、セッション構成ファイル(.pssc)のRestrictedRemoteServer・RunAsVirtualAccount・TranscriptDirectory・RoleDefinitions、Register-PSSessionConfigurationによる登録、JEAセッションで言語モードが制限されることについて。役割機能ファイルをPowerShellモジュールのRoleCapabilitiesフォルダーに配置する必要があること(モジュールとして検出できる状態にしておくこと、New-PSRoleCapabilityFileによる作成)はJEA Role Capabilitiesを参照。 ↩ ↩2 ↩3 ↩4 ↩5 ↩6 ↩7 ↩8
-
Microsoft Learn, Windows PowerShell 2.0 の非推奨化. Windows PowerShell 2.0エンジンが非推奨であり、無効化が推奨されること、Windowsのオプション機能として提供され有効・無効を切り替えられることについて。 ↩ ↩2 ↩3
-
Microsoft Learn, about_Execution_Policies. 実行ポリシーがセキュリティ境界ではなく、ユーザーが意図せずスクリプトを実行することを防ぐための安全機能であること、複数の回避手段が存在することについて。 ↩ ↩2 ↩3
-
Microsoft Learn, about_Group_Policy_Settings. PowerShell 7のグループポリシーが「管理用テンプレート > PowerShell Core」に置かれること、管理用テンプレートをインストールする必要があること、Windows PowerShell側のポリシー値を使用する選択肢について。 ↩ ↩2
-
Microsoft Learn, about_Logging_Windows (PowerShell 7). PowerShell 7のイベントログが
PowerShellCore/Operationalであること、Windowsでは書き込み前にイベントプロバイダーの登録が必要であり、管理者権限で$PSHOME\RegisterManifest.ps1を使用することについて。 ↩ -
Microsoft Learn, icacls. ファイルとフォルダーのアクセス制御リストを表示・変更するコマンドであること、/grantによる権限付与と/remove:gによる付与済み権限の削除、権限マスクに指定できる詳細な権限の記号(DE=削除、DC=サブフォルダーとファイルの削除、WD=データの書き込み/ファイルの作成、AD=データの追加/サブフォルダーの作成、WA=属性の書き込み、WEA=拡張属性の書き込み、RA=属性の読み取り、X=実行/走査、F=フルアクセスなど)、継承の指定((OI)=オブジェクト継承、(CI)=コンテナー継承)について。共有の作成とアクセス許可の指定はNew-SmbShare(-FullAccess / -ChangeAccess / -ReadAccess)を、継承の無効化はObjectSecurity.SetAccessRuleProtection(第1引数で継承を保護し、第2引数で継承済みACEを引き継ぐかを指定する)を参照。このメソッドが指定できるのは継承ACEの扱いだけで、そのオブジェクトに直接付与されたACEは対象外です。直接付与を落とすにはObjectSecurity.RemoveAccessRuleSpecificなどで個別に外します。 ↩ ↩2 ↩3
-
Microsoft Learn, 特殊なIDグループ(Special Identity Groups).
OWNER RIGHTS(S-1-3-4)がオブジェクトの現在の所有者を表すグループであり、このSIDを持つACEがオブジェクトに適用されているとき、システムは所有者への暗黙のREAD_CONTROLとWRITE_DACを無視することについて。裏を返すと、このACEが無ければ所有者は暗黙にDACLを書き換えられます。 ↩ ↩2 -
Microsoft Learn, File Access Rights Constants.
FILE_APPEND_DATAが「For a file object, the right to append data to the file. (For local files, write operations will not overwrite existing data if this flag is specified without FILE_WRITE_DATA.)」と定義されており、追記専用の書き込みは「FILE_WRITE_DATAを伴わずにFILE_APPEND_DATAを指定して開いた」ときに成立すること、FILE_WRITE_DATA/FILE_ADD_FILE、FILE_DELETE_CHILDの意味について。ジェネリックアクセス権のマッピング(FILE_GENERIC_WRITE=FILE_APPEND_DATA+FILE_WRITE_ATTRIBUTES+FILE_WRITE_DATA+FILE_WRITE_EA+STANDARD_RIGHTS_WRITE+SYNCHRONIZE、FILE_GENERIC_READ=FILE_READ_ATTRIBUTES+FILE_READ_DATA+FILE_READ_EA+STANDARD_RIGHTS_READ+SYNCHRONIZE)はFile Security and Access Rightsを参照。 ↩ ↩2 -
PowerShell,
TranscriptionOption.FlushContentToDisk(src/System.Management.Automation/engine/hostifaces/MshHostUserInterface.cs). トランスクリプトの書き込み口がnew FileStream(this.Path, FileMode.OpenOrCreate, FileAccess.ReadWrite, FileShare.Read)で開かれ、失敗した場合のみnew FileStream(this.Path, FileMode.Append, FileAccess.Write, FileShare.Read)で開き直されること、FILE_APPEND_DATAを単独で要求する経路が無いことについて。 ↩ -
.NET,
SafeFileHandle.Open(src/libraries/System.Private.CoreLib/src/Microsoft/Win32/SafeHandles/SafeFileHandle.Windows.cs).FileAccess.Read/FileAccess.WriteがそれぞれGENERIC_READ/GENERIC_WRITEに写されること、FileMode.AppendがCreateFileを呼ぶ前にFileMode.OpenOrCreateへ置き換えられるだけで、アクセスマスクにFILE_APPEND_DATA単独が使われることはないことについて。 ↩ -
Microsoft Learn, JEA セッション構成. セッション構成ファイルの
TranscriptDirectoryにフォルダーを指定すると自動でトランスクリプトが記録されること、「Transcripts are written to the folder by the Local System account, which requires read and write access to the directory. Standard users should have no access to the folder. Limit the number of security administrators that have access to audit the transcripts.」(標準ユーザーはこのフォルダーへのアクセスを持つべきではなく、監査する管理者だけに絞る)について。 ↩ ↩2 -
Microsoft Learn, JEA の前提条件. JEAがPowerShell 5.0以降で利用できること、PowerShellリモート処理がJEAの土台であり、JEAを使う前にリモート処理が有効かつ適切に保護されている必要があること、Windows Server 2012以降ではPowerShellリモート処理が既定で有効であり、有効でない場合は管理者権限のPowerShellウィンドウでEnable-PSRemotingを実行して有効化できることについて。 ↩ ↩2 ↩3
-
Microsoft Learn, Differences between Windows PowerShell 5.1 and PowerShell 7.x.
Get-EventLogを含む*-EventLogコマンドレットがPowerShell 7の標準コマンドから削除され、Windowsでのイベントの取得にはGet-WinEventを使用することについて。 ↩ -
Microsoft Learn, JEA Role Capabilities. 独自関数をFunctionDefinitionsで定義したうえでVisibleFunctionsにも名前を挙げる必要があること(「Don’t forget to add the name of your custom functions to the VisibleFunctions field so they can be run by the JEA users.」)、関数の本体(スクリプトブロック)がシステム既定の言語モードで実行されJEAの制約を受けないこと、JEAが差し替える制約付きコマンドを本来の実装で使うには完全修飾名(Microsoft.PowerShell.Utility\Select-Object)が必要なこと、関数が多い場合はスクリプトモジュールに切り出してVisibleFunctionsで公開する方法、モジュールフォルダーにフォルダーと同名のファイルが必要なことについて。 ↩ ↩2
-
Microsoft Learn, Set-ItemProperty. レジストリプロバイダー使用時に-Typeが動的パラメーターとして追加され、レジストリ値のデータ型(String / ExpandString / Binary / DWord / MultiString / QWord など)を指定できることについて。値が存在しない場合に作成されることを含む。 ↩
関連する記事
同じタグを共有する最新の記事です。さらに近い話題で知識を深められます。
Get-WinEventでイベントログを実務的に調べる ── 絞り込みの速さが調査時間を決める
Windowsのイベントログ調査をPowerShellで効率化する方法をまとめます。Where-Objectで絞ると遅い理由、FilterHashtableとXPathの使い分け、再起動やログオン・アプリ異常終了の調査レシピ、複数台からの収集までを解説します。
Windowsセキュリティ監査ポリシーとイベントログ調査の実務 ── 4625を読める情シスになる
「サインイン失敗のログを調べてほしい」に応えるための実務ガイドです。基本と詳細の監査ポリシーの関係、最低限有効化すべきサブカテゴリ、イベントID 4624/4625/4688の読み方、Securityログの容量設計、Get-WinEventでの抽出までを解説します。
Windows LAPS実務ガイド ── 全PC共通のローカル管理者パスワードをやめる
全PC共通のローカル管理者パスワードは、1台の侵害が全台に波及するPass-the-Hash攻撃の温床です。OS標準機能になったWindows LAPSによる自動ローテーションと、AD/Entra IDへの保存設定、運用の落とし穴を解説します。
Windows証明書ストア実務ガイド ── ユーザーとコンピューター、どちらに入れるか
クライアント証明書はユーザーとコンピューターのどちらのストアに入れるべきか。certmgr.mscとcertlm.mscの違い、秘密キーの権限付与、PowerShellでの期限棚卸しまで、証明書の定番事故を体系的に潰す実務ガイドです。
Windowsファイアウォールと業務アプリ ── 受信規則はインストーラーで登録する
Windows業務アプリが客先で通信できないとき、受信規則・待ち受け・プロファイル・管理ポリシーをどう切り分けるか。初回起動の警告に頼らない規則の設計、インストーラーでの登録と更新、ログの読み方を解説します。
関連トピック
このテーマと近いトピックページです。記事を起点に、関連するサービスや他の記事へ進めます。
Windows技術トピック
Windows 開発、不具合調査、既存資産活用の技術トピックをまとめた入口です。
このテーマがつながるサービス
この記事は次のサービスページにつながります。近い入口からご覧ください。
Windowsアプリ開発
業務アプリ、装置連携、通信ツールなどの Windows ソフト開発を支援します。
よくある質問
この記事のテーマについて、相談時によくある質問をまとめています。
- セキュリティ対策として、社内でPowerShellを禁止すべきでしょうか?
- 実効性が低く、副作用が大きいため推奨しません。PowerShellはWindowsの管理基盤そのもので、実体は.NET上の機能です。実行ファイルをブロックしても、同じAPIを別の手段から呼ぶことは可能なため、攻撃側にとっての障害にはなりにくい一方、正規の管理業務と自動化は確実に止まります。現実的な方針は、禁止ではなく「可視化と制限」です。スクリプトブロックログで何が実行されたかを記録し、古いバージョンを無効化し、必要に応じて言語モードやJEAで実行できる範囲を絞ります。
- スクリプトブロックログを有効にすると、ログが大量に出て困りませんか?
- 確かに増えるので、ログのサイズ設定とセットで有効化してください。Microsoft-Windows-PowerShell/Operationalログの最大サイズを既定のままにしておくと、短期間で上書きされて肝心なときに残っていない、という事態になります。運用としては、ログサイズを十分に確保し、必要ならSIEMやイベント転送で集約します。なお、より詳細な「呼び出しの開始・停止」まで記録する設定もありますが、こちらは出力量が非常に多いため、通常は既定のスクリプトブロックログのみを有効にします。
- 実行ポリシーをAllSignedにすれば、セキュリティ対策になりますか?
- 実行ポリシーはセキュリティ境界ではありません。公式ドキュメントも、ユーザーが意図せず危険なスクリプトを実行することを防ぐための仕組みであって、悪意ある操作を止めるものではないと明記しています。回避方法が複数存在するためです。署名運用には、配布物の完全性を確認できるという別の価値がありますが、防御の主軸はログによる可視化、AMSIによる検査、言語モードやJEAによる権限の制限に置いてください。
- 制約付き言語モード(ConstrainedLanguage)は、手動で設定してよいものですか?
- 環境変数などで手動設定するのは推奨されません。回避が容易で、セキュリティ機能として機能しないためです。制約付き言語モードは、WDAC(Windows Defender Application Control)やAppLockerによるアプリケーション制御を構成した結果として、PowerShellが自動的に切り替わる形で使うのが本来の設計です。アプリケーション制御を導入せずに言語モードだけを絞っても、実効性のある防御にはなりません。
- ヘルプデスク担当者に、サーバーの特定サービスの再起動だけを任せたいのですが。
- JEA(Just Enough Administration)がその用途のための仕組みです。役割機能ファイルで「このコマンドの、このパラメーターの、この値だけ許可する」と定義し、セッション構成として登録します。利用者は管理者権限を持たないまま、仮想アカウント経由で許可された操作だけを実行できます。セッションは制限付きリモートサーバーとして構成され、既定では言語モードも制限されるため、任意のコードを実行される余地がありません。実行内容はトランスクリプトとして記録できます。