更新履歴(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)
この記事はZenodoにアーカイブされています。常に最新版へ解決されるDOIと、いま表示している版に固定されたDOIの両方を下に示します。
小村 豪(2026)「PowerShellのセキュリティ強化 ── ログ・AMSI・言語モード・JEA」合同会社小村ソフト. https://doi.org/10.5281/zenodo.21547462 https://staging.comcomponent.com/blog/powershell-security-hardening-jea/
- DOI(最新版)
- 10.5281/zenodo.21547462
- DOI(この版)
- 10.5281/zenodo.21733171
「侵入されたときにPowerShellが悪用された」という報告が続いた結果、社内でPowerShellの利用を全面禁止しようとする組織があります。しかしこれは、実効性の面でも業務影響の面でも割に合いません。PowerShellはWindowsの管理基盤そのものであり、止めれば運用の自動化が止まります。一方で攻撃側は、同等のことを別の手段で実行できます。
現実的な方針は、禁止ではなく可視化と制限です。幸い、PowerShell 5.0以降には防御側のための機能が揃っています。難読化されたコードでも展開後の形で記録するスクリプトブロックログ、実行前にウイルス対策製品へ内容を渡すAMSI(Antimalware Scan Interface、スクリプトの中身をマルウェア対策製品に検査させるWindowsの仕組み)、実行できる構文自体を制限する言語モード、そして「必要な操作だけを委任する」JEA。これらを組み合わせれば、業務を止めずに監査可能な状態を作れます。
この記事では、社内のWindows環境でPowerShellを安全に使い続けるために設定すべき項目を、効果の高い順にまとめます。実行ポリシーと署名については「PowerShellの実行ポリシーとスクリプト署名」で扱っているので、本記事はその先を扱います。
対象環境・前提条件
| 項目 | 内容 |
|---|---|
| 対象OS | Windows 10/11、Windows Server。AMSIはWindows 10以降が前提です1 |
| 対象バージョン | Windows PowerShell 5.1とPowerShell 7の両方を対象にします。ポリシーの設定キーもログの記録先も5.1と7で別なので、両方が入っている端末では両方に設定してください(第3章・第4章) |
| 必要な権限 | 第3章〜第4章のログ設定・PowerShell 2.0の無効化、第7章のJEAエンドポイント登録は、いずれも管理者権限が必要です。実運用では1台ずつ手で入れるのではなくグループポリシーで配布します |
| ローカル/リモート | 第3章〜第6章は対象の端末・サーバー内で完結します。第7章のJEAだけは、PowerShellリモート処理(WinRM)が有効であることが前提です(第7章の冒頭を参照) |
| 扱わないこと | 実行ポリシーと署名の詳細は別記事(PowerShellの実行ポリシーとスクリプト署名)で扱います。本記事では位置づけだけを整理します(第5章) |
1. まず結論
- 最優先はスクリプトブロックログの有効化です。実行されたコードがイベントログに記録され、難読化されたコードも展開後の形で残ります(イベントID 4104)。2
- トランスクリプションも併せて有効にします。入出力を含むセッションの記録を、書き込み専用の共有に集約できます。2
- ログを有効にしたら、ログのサイズ設定も見直します。既定のままだと短期間で上書きされます。2
- AMSIにより、実行前のスクリプトがウイルス対策製品に渡されます。PowerShell 5.0以降、Windows 10以降で有効です。1
- 古いPowerShell 2.0エンジンは無効化します。残っていると、ログもAMSIも効かない古いエンジンへ切り替えられる余地が残ります。3
- 実行ポリシーはセキュリティ境界ではありません。公式が明記しています。防御の主軸にしないでください。4
- 言語モードはWDAC/AppLockerの結果として使います。手動設定は回避可能で、セキュリティ機能として機能しません。5
- 権限委任にはJEA。「このコマンドの、このパラメーターだけ」を許可し、管理者権限を配らずに運用を回せます。6
この記事の知識マップ
この記事は、PowerShellを禁止するのではなく、可視化・迂回経路の遮断・権限の限定・実行の制限を段階的に組み合わせて安全に使う方法を扱う。スクリプトブロックログとトランスクリプションで実行内容を記録してGet-WinEventで検証し、AMSIと2.0エンジンの無効化で検査を迂回する経路を塞ぐ。トランスクリプトの集約先はOWNER RIGHTSを含むACL設計で他人の記録を守り、制約付き言語モードはWDACやAppLockerを構成した結果として初めて実効性を持つ。最後にJEAで役割機能ファイルとセッション構成ファイルを使い、仮想アカウント経由で管理者権限を配らずに操作を委任する。
flowchart LR
accTitle: PowerShellセキュリティ強化の知識マップ
accDescr: PowerShellのログ・AMSI・言語モード・JEAによる可視化と権限委任の関係、およびトランスクリプト保護のACL設計を示す図。
powershell_security_hardening["PowerShellのセキュリティ強化"]
jea["JEA(Just Enough Administration)"]
script_block_logging["スクリプトブロックログ"]
powershell_transcription["PowerShellトランスクリプション"]
get_winevent["Get-WinEvent"]
transcript_share_acl_hardening["トランスクリプト共有のACL設計"]
log_tampering_by_compromised_account["侵害されたアカウントによるログ改ざん"]
owner_rights_sid["OWNER RIGHTS(S-1-3-4)"]
role_capability_file["役割機能ファイル(.psrc)"]
session_configuration_file["セッション構成ファイル(.pssc)"]
jea_virtual_account["仮想アカウント(RunAsVirtualAccount)"]
jea_transcript_directory["JEAのTranscriptDirectory"]
powershell_remoting["PowerShell Remoting"]
powershell_language_mode["PowerShellの言語モード"]
constrained_language_mode["制約付き言語モード(Constrained Language Mode)"]
app_control_for_business["App Control for Business(旧WDAC)"]
applocker["AppLocker"]
excessive_admin_privilege_grant["管理者権限の過剰付与"]
parameter_validateset_restriction_bypass["ValidateSetによるパラメーター制限の回避"]
jea_wrapper_function_pattern["JEAの引数限定ラッパー関数"]
amsi["AMSI(Antimalware Scan Interface)"]
powershell_v2_engine["Windows PowerShell 2.0エンジン"]
powershell_execution_policy["PowerShellの実行ポリシー"]
powershell_security_hardening -->|"利用する"| script_block_logging
powershell_security_hardening -->|"利用する"| powershell_transcription
script_block_logging -->|"で確認できる"| get_winevent
transcript_share_acl_hardening -.->|"軽減する"| log_tampering_by_compromised_account
transcript_share_acl_hardening -->|"利用する"| owner_rights_sid
jea -->|"利用する"| role_capability_file
jea -->|"利用する"| session_configuration_file
jea -.->|"利用する"| jea_virtual_account
jea -->|"で構成できる"| jea_transcript_directory
jea -->|"前提とする"| powershell_remoting
session_configuration_file -->|"利用する"| powershell_language_mode
powershell_language_mode -->|"利用する"| constrained_language_mode
constrained_language_mode -.->|"前提とする"| app_control_for_business
constrained_language_mode -.->|"前提とする"| applocker
jea -->|"軽減する"| excessive_admin_privilege_grant
role_capability_file -.->|"原因になり得る"| parameter_validateset_restriction_bypass
jea_wrapper_function_pattern -->|"軽減する"| parameter_validateset_restriction_bypass
jea_wrapper_function_pattern -->|"前提とする"| role_capability_file
powershell_security_hardening -->|"利用する"| amsi
powershell_v2_engine -->|"両立しない"| amsi
powershell_v2_engine -->|"両立しない"| script_block_logging
powershell_execution_policy -->|"用いるのは非推奨"| powershell_security_hardening
powershell_transcription -.->|"前提とする"| transcript_share_acl_hardening
jea_transcript_directory -->|"防止する"| log_tampering_by_compromised_account
図の実線は常に成り立つ関係、破線は条件付きの関係です(成立条件は詳細ページの各関係の説明に記載)。関係すべての一覧(全24件、根拠・確度つき)と主要概念の定義は知識マップ詳細ページにまとめています。データ: JSON-LD / Turtle
2. 何を守るのか ── 可視化が先、制限が後
この記事で扱う4つの対策は、それぞれ別の層を担当します。まず全体像です。
flowchart TB
U["管理者・ヘルプデスク・自動化スクリプト<br/>(そして侵入した攻撃者)"]
DEL["【委任】JEA(第7章)<br/>管理者権限を配らず、許可した操作だけを渡す"]
LIM["【制限】言語モード + WDAC/AppLocker(第6章)<br/>実行できるコードと使える構文を絞る"]
SCAN["【検査】AMSI(第4章)<br/>実行直前の内容をウイルス対策製品へ渡す<br/>PowerShell 2.0の無効化で迂回経路を塞ぐ"]
RUN["PowerShellでの操作"]
REC["【記録】スクリプトブロックログ 4104・トランスクリプション(第3章)<br/>難読化を解いた形で残す"]
U --> DEL
DEL --> LIM
LIM --> SCAN
SCAN --> RUN
RUN --> REC
記録は「後から追える状態」を作り、委任は権限そのものを小さくし、検査と制限は実行の手前で止めます。4つは互いの代替ではなく、担当する層が違います。そのうえで、導入の順序には優先順位があります。
| 段階 | やること | 効果 |
|---|---|---|
| 1. 可視化 | スクリプトブロックログ、トランスクリプション | 何が起きたか分かる。事後調査が可能になる |
| 2. 基礎的な無害化 | PowerShell 2.0の無効化、最新版の維持、AMSIの確認 | 検査を迂回する経路を塞ぐ |
| 3. 権限の限定 | JEAによる委任、管理者権限の削減 | 被害の範囲を限定する |
| 4. 実行の制限 | WDAC/AppLocker + 制約付き言語モード | 未承認コードの実行自体を止める |
多くの現場では、1と3をやるだけで状況が大きく改善します。4は導入コストが高いため、業務影響を検証しながら進める領域です。
3. ログを有効にする ── 4104が最重要
PowerShellのログには3つの階層があります。2
| 種類 | 記録内容 | イベントID |
|---|---|---|
| モジュールログ | 指定モジュールのパイプライン実行の詳細 | 4103 |
| スクリプトブロックログ | 実行されたコードのテキスト(難読化解除後) | 4104 |
| 記録先のログ | 5.1は Microsoft-Windows-PowerShell/Operational、7は PowerShellCore/Operational |
─ |
| トランスクリプション | セッションの入出力をテキストファイルに記録 | ─(ファイル出力) |
いずれもグループポリシーの「コンピューターの構成 > 管理用テンプレート > Windows コンポーネント > Windows PowerShell」で設定できます。レジストリで直接設定することも可能です。2
# スクリプトブロックログを有効化する(要管理者権限。通常は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
PowerShell 7は Windows\PowerShell 配下ではなく PowerShellCore 配下のポリシーを見ます。5.1側だけ設定して満足すると、pwshで実行されたスクリプトの記録がまるごと欠落します。スクリプトブロックログ・トランスクリプションとも、両方のキーに同じ値を書いてください(GPOで配布する場合も、それぞれのテンプレートで設定します)。
スクリプトブロックログの価値は、難読化への耐性にあります。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だけを見て「記録されていない」と誤判定するのが、この設定でいちばん多い勘違いです。
有効化したら必ずログサイズを見直してください。既定サイズのままだと数時間〜数日で一巡し、いざというときに残っていません。
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
wevtutil sl は成功しても何も表示しないため、設定前後で MaximumSizeInBytes を見比べるのが唯一の確認手段です。管理者権限で実行していないと変更が拒否されるので、値が変わらないときはまずそこを疑ってください。
トランスクリプトの集約先を「書けるが消せない」共有にする
トランスクリプトの出力先は、利用者が書き込みはできて削除できない共有にするのが要点です。ローカルに置くと、侵害された端末では消されてしまいます。
権限は、共有アクセス許可とNTFSのアクセス許可の両方を設定します(実効権限は両者の厳しいほうになります)。要点は、利用者に「フォルダーへのファイル作成」と「自分が作ったファイルの読み書き」だけを与え、削除(DE)とサブフォルダー・ファイルの削除(DC)を与えないことです。
先に、この構成で何が守れて何が守れないかをはっきりさせておきます。守れるのは「他人の記録には一切触れない」と「自分の記録も消せない」の2点です。守れないのは「自分の記録の上書き」で、これはACLだけでは塞げません(理由は後述します)。それでも、乗っ取られたアカウントから他人の証跡を消せなくなるだけで、被害の切り分けは大きく変わります。1台の端末が落ちても、他の端末の記録は残ります。
# ログ集約サーバー側で実行する(要管理者権限)
$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 # 設定結果を確認する
既存のフォルダーを流用するときは、直接付与されたACEを必ず洗ってください。SetAccessRuleProtection($true, $false) が外すのは継承してきたACEだけで、そのフォルダーに直接付いているACEはそのまま残ります。7 New-Item -Force は既存フォルダーでも成功するので、「前に一時的に Domain Users へ変更権限を付けた」といった経緯があるフォルダーを再利用すると、以降で CREATOR OWNER を絞ってもその直接付与だけが生き残り、利用者は他人のトランスクリプトを上書き・削除できます。上のコードで一度すべて落としてから入れ直しているのはこのためです。新しいフォルダーを作るなら不要ですが、入れておいて損はありません。適用後に icacls $path の出力を目で確認し、意図しない主体が並んでいないかを見てください。
ここで (OI) の付け方が対策の成否を分けます。利用者への許可に (OI) を付けて WD を配ると、そのACEが配下の全ファイルに継承され、名前さえ分かれば他人のトランスクリプトを上書き・切り詰めできてしまいます。7 それでは「端末やアカウントが乗っ取られたあとも記録が残る」という目的が達成できません。上のように、フォルダーへの作成権限(継承させない)と、CREATOR OWNER 経由で自分が作ったファイルだけへの読み書きに分けます。
そして、OWNER RIGHTS の行を落とさないでください。ファイルを作った利用者は、そのファイルの所有者になります。Windowsは所有者に READ_CONTROL と WRITE_DAC を暗黙で与えるため、8 ACEで DE を配っていなくても、所有者は自分でDACLを書き換えて、自分に削除の権限を付け直せます。「自分の記録は自分で消せる」状態のままでは、CREATOR OWNER を絞った意味がありません。OWNER RIGHTS(S-1-3-4)のACEを置くと、システムは所有者への暗黙の READ_CONTROL / WRITE_DAC を無視するので、ここで初めて「書けるが消せない」が成立します。8
なぜ AD(追記)だけに絞らないのか
「上書きされたくないのだから CREATOR OWNER は AD だけでいい」と考えたくなります。そうすると、トランスクリプトが1行も残りません。
Windowsで「追記しかできない」が成立するのは、書き手が FILE_APPEND_DATA を単独で指定してファイルを開いたときだけです。Microsoftのリファレンスは FILE_APPEND_DATA を「(ローカルファイルでは、FILE_WRITE_DATA を伴わずにこのフラグを指定した場合、書き込み操作は既存のデータを上書きしない)」と定義しています。9 つまり追記専用は開き方の性質であって、ACEに AD を置くだけで実現するものではありません。
そして Start-Transcript の書き込み口は、追記専用では開きません。PowerShellの実装は、まず FileMode.OpenOrCreate + FileAccess.ReadWrite で開き、失敗したときだけ FileMode.Append + FileAccess.Write で開き直します。10 .NET側では FileAccess.Read/Write がそれぞれ GENERIC_READ/GENERIC_WRITE に写され、FileMode.Append も内部で FileMode.OpenOrCreate に置き換えてから末尾へシークするだけです。11 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 が含まれます。9 アクセスチェックは要求したすべての権利を見るので、AD,RA,REA しか持たない利用者のこの CreateFile は拒否されます。共有側で変更(Change)を許していても、NTFS側で落ちます。記録を守るつもりで、記録そのものを止めることになります。
だからこの節のACLは、AD 単独ではなく「読み書きは許すが DE(削除)・WDAC(権限変更)・WO(所有者変更)は与えない」という形にしてあります。所有者は自分のトランスクリプトを上書き・切り詰めできます。ここはACLでは塞げない、と割り切ってください。
上書きまで塞ぎたいなら、書き手が触れない場所へ出すしかありません。選択肢は3つです。
- JEAなら、セッション構成ファイルの
TranscriptDirectoryを使う(7章)。この出力を書くのは接続してきた利用者ではなく Local System で、Microsoftのドキュメントも「標準ユーザーはこのフォルダーへのアクセスを持たないこと」「監査する security 管理者だけに絞ること」を求めています。12 利用者にそもそも権限が要らないので、この節の悩みごと消えます - Windowsイベント転送やSIEMへ取り込む。スクリプトブロックログ(4104)はイベントログに出るので、転送の対象にできます
- 追記専用で開く独自の収集プロセスを立てる。
FILE_APPEND_DATAを単独で要求して開くところまで自分で書けば、ACE側のADが初めて意味を持ちます
なお、ACLで守れるのはあくまで「その共有に書く利用者」からです。ファイルサーバーの管理者は所有権を取得できますし、端末側が完全に掌握されていれば、そもそも共有へ書き出す前に細工できます。この節の権限設計は、「同じ共有を使う利用者どうしで消し合えない」を担保するものだと理解してください。
icacls の権限の記号(WD AD DE DC など)と継承指定((OI) (CI) (IO))の意味は、Windowsコマンドのリファレンスに一覧があります。7 なお、この構成は必ず検証機で1回試してから展開してください。トランスクリプトは実行中のセッションが同じファイルへ書き足していくため、権限を絞りすぎると記録そのものが落ちます。「利用者の端末から書けること」「利用者が他人の既存ファイルを読めず、書き換えられないこと」「利用者が自分のファイルを削除できないこと」の3点を、実際に確認するのが確実です。
4. 迂回経路を塞ぐ ── PowerShell 2.0とAMSI
AMSI(Antimalware Scan Interface) により、PowerShell 5.0以降ではスクリプトの内容が実行直前にウイルス対策製品へ渡され、難読化を解除した状態で検査されます。1 Microsoft Defenderを含む対応製品を使っていれば、追加設定なしで機能します。
問題は、古いエンジンにはこの仕組みがないことです。Windows PowerShell 2.0エンジンが有効なままだと、powershell.exe -Version 2 によってログもAMSIも効かない環境に切り替えられる余地が残ります。この機能は非推奨であり、無効化が推奨されています。3
# PowerShell 2.0エンジンの状態を確認し、無効化する(要管理者権限・再起動が必要な場合あり)
Get-WindowsOptionalFeature -Online -FeatureName MicrosoftWindowsPowerShellV2* |
Select-Object FeatureName, State
Disable-WindowsOptionalFeature -Online -FeatureName MicrosoftWindowsPowerShellV2Root -NoRestart
なお、PowerShell 7(pwsh)を導入している場合、設定キーもログの記録先も5.1とは別です(ポリシーは PowerShellCore 配下、ログは PowerShellCore/Operational)。両方が使われる環境では、設定・ログサイズ・調査クエリのすべてを両方に対して行ってください。バージョンの共存については「Windows PowerShell 5.1とPowerShell 7の違い」を参照してください。
5. 実行ポリシーの位置づけを間違えない
改めて明確にしておきます。実行ポリシーはセキュリティ境界ではありません。公式ドキュメントは、ユーザーが意図せずスクリプトを実行してしまうことを防ぐための安全機能であり、悪意ある操作を防ぐものではないと明記しています。4
とはいえ無価値ではありません。署名運用には「配布したモジュールが改ざんされていないことを確認できる」という別の価値があります(「PowerShellモジュールの社内配布と更新」)。役割を正しく理解して使うことが重要で、「AllSignedにしたから安全」という理解が最も危険です。
6. 言語モード ── アプリケーション制御と組み合わせて使う
PowerShellのセッションには言語モードがあり、使える言語要素が制限されます。5
| モード | 使えるもの | 実務での位置づけ |
|---|---|---|
| FullLanguage | すべての言語要素(既定) | 通常のセッション |
| ConstrainedLanguage | コマンドレットはすべて動き、ループ・条件分岐・文字列展開・プロパティ参照も使える。ただし使える.NET型が許可リストに限定され、Add-Type は署名済みアセンブリしか読み込めない |
WDAC/AppLocker配下で自動的に切り替わるモード。対話操作や通常の管理作業はおおむねそのまま行える |
| RestrictedLanguage | コマンドは実行できるがスクリプトブロックが使えない。変数は $PSCulture $PSUICulture $true $false $null のみ、比較演算子は -eq -gt -lt のみで、代入・プロパティ参照・メソッド呼び出しは不可 |
モジュールマニフェスト(.psd1)の読み込みに使われるモード。人が対話で使う想定のものではない |
| NoLanguage | スクリプト言語そのものが無効。スクリプトも変数も使えず、コマンドレットとネイティブコマンドの呼び出しだけができる | JEAのセッション構成(RestrictedRemoteServer)の既定。「決められたコマンドを叩くだけ」の窓口を作るためのモード |
3つの制限モードは段階ではなく、用途が違います。ConstrainedLanguageは「人がスクリプトを書いて使う環境を、危険な型の利用だけ止める」もの、NoLanguageは「そもそもスクリプトを書かせない」もので、後者はJEAのような限定的な窓口でしか成立しません。5
現在のモードは次で確認できます。
$ExecutionContext.SessionState.LanguageMode
重要なのは設定方法です。制約付き言語モードは、WDAC(Windows Defender Application Control)やAppLockerで許可リスト方式のアプリケーション制御を構成した際に、PowerShellが自動的に切り替わる形で機能します。5 環境変数などで手動設定する方法は回避が容易であり、セキュリティ機能としては機能しません。アプリケーション制御を導入せずに言語モードだけを絞るのは、労力の割に効果がないと理解してください。
7. JEA ── 「必要な操作だけ」を委任する
現実の被害を左右するのは、多くの場合権限の広さです。「ヘルプデスクに管理者権限を渡している」「運用担当が全員Domain Adminに入っている」という状態では、1台の侵害が全社の侵害になります。
JEA(Just Enough Administration)は、管理者権限を渡さずに特定の操作だけを委任する仕組みです。6 「アプリケーションサービスの再起動だけをヘルプデスクに任せる」といった要求に、ぴったり合います。
この章だけは前提が違う ── JEAはリモート処理の上に立つ
第3章〜第6章がローカルの設定で完結するのに対し、JEAはPowerShellリモート処理(WinRM)の仕組みそのものの上に作られています。13 「JEAで委任する」とは、対象のサーバーに専用の接続先(セッション構成)を登録し、そこへ接続させることです。自社で再現できるかどうかは、まずここで決まります。
必要になるのは次の3点です。
| 前提 | 内容 |
|---|---|
| PowerShellのバージョン | JEAはPowerShell 5.0以降で利用できます13 |
| リモート処理の有効化 | 対象サーバーでPowerShellリモート処理が有効であること。Windows Server 2012以降は既定で有効で、無効な場合は管理者権限のPowerShellで Enable-PSRemoting を実行します13 |
| 配置場所 | 役割機能ファイル(.psrc)は対象サーバー上のモジュールの RoleCapabilities フォルダーに、セッション構成(.pssc)は対象サーバー上で登録します。利用者の端末に置くものではありません6 |
つまり、以下の手順1〜3は委任される側のサーバーで、管理者権限で行う作業です。利用者の端末で必要なのは、そのサーバーへ接続できることだけです。リモート処理そのものの構成と安全な設定は「PowerShell Remoting(WinRM)入門」にまとめています。
手順1: 役割機能ファイル(.psrc)で許可する操作を定義する
役割機能ファイルは、PowerShellモジュールの RoleCapabilities フォルダーに置かれている必要があります。フォルダーを作っただけではモジュールとして認識されず、後述の RoleDefinitions から名前で解決できません。そのため、まず入れ物となるモジュール(マニフェストを持つフォルダー)を作ります。6
# 役割機能を入れるモジュールを作る(マニフェストが必要)
$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
# 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 = @()
}
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ログまで読めてしまいます。
そのため上の例では Restart-Service を VisibleCmdlets に入れず、引数でしか対象を受け取らないラッパー関数(Restart-KsAppService)だけを公開しています。入力経路が引数しかなければ、迂回のしようがありません。
Parameters と ValidateSet による制限は、「そのパラメーターが束縛されたとき」にしか働きません。パイプライン入力や別のパラメーターセットで束縛を回避できるコマンドでは、制限そのものが無効になります。引数を絞りたいコマンドは、原則としてラッパー関数で包むと考えてください。
ここで2点、はまりやすい仕様があります。1つ目は、VisibleFunctions は関数を作らないことです。名前を挙げただけの関数はセッションに存在せず、利用者には「そんなコマンドはない」と見えます。独自関数は FunctionDefinitions で定義したうえで、VisibleFunctions にも挙げてください。14 数が増えるならスクリプトモジュールに切り出し、そのモジュールの関数を VisibleFunctions で公開するほうが管理しやすくなります。
2つ目は、関数の本体がJEAの制約を受けないことです。14 Select-Object などJEAが差し替える制約付きコマンドを本来の挙動で使いたい場合は、上のように Microsoft.PowerShell.Utility\Select-Object と完全修飾で呼びます。裏を返せば、関数の中では何でもできるということなので、利用者からの入力を Invoke-Expression に渡すような書き方は絶対に避けてください。
手順2: セッション構成ファイル(.pssc)で、誰にどの役割を割り当てるかを定義する
# SessionType: 制限付きリモートサーバー(既定でNoLanguage)
# RunAsVirtualAccount: 仮想管理者アカウントとして実行
# TranscriptDirectory: 実行内容を記録
$pssc = @{
Path = '.\KsHelpDesk.pssc'
SessionType = 'RestrictedRemoteServer'
RunAsVirtualAccount = $true
TranscriptDirectory = 'C:\JeaTranscripts'
RoleDefinitions = @{ 'EXAMPLE\HelpDesk' = @{ RoleCapabilities = 'KsHelpDesk' } }
}
New-PSSessionConfigurationFile @pssc
手順3: 登録する
Register-PSSessionConfiguration -Name 'KsHelpDesk' -Path .\KsHelpDesk.pssc -Force
利用者側は、次のように接続します。
Enter-PSSession -ComputerName 'appsrv01' -ConfigurationName 'KsHelpDesk'
# 許可されたコマンドしか使えない。Restart-Service も指定サービスのみ
JEAの要点は3つです。6
- 利用者は管理者権限を持たない。実行は仮想アカウント側で行われる
- セッションは制限付きリモートサーバーとして構成される。既定で言語モードが制限され、任意のコードを実行できない
- トランスクリプトで実行内容が記録される。誰が何をしたかが監査できる
なお、役割機能ファイルは前述のとおりモジュールの RoleCapabilities フォルダー配下にある必要があり、そのモジュールが $env:PSModulePath から見つかることが前提です。RoleDefinitions で指定した役割名が解決できないときは、まず Get-Module -ListAvailable でモジュールが見えているかを確認してください。6
登録したエンドポイントは Get-PSSessionConfiguration で一覧できます。冒頭に書いたとおりJEAはリモート実行の構成が前提なので、接続できないときはJEAの定義ではなくWinRM側から切り分けてください(「PowerShell Remoting(WinRM)入門」)。
8. 実務チェックリスト
| 項目 | 優先度 | 状態 |
|---|---|---|
| スクリプトブロックログ(4104)を有効化した | 高 | GPOで全社配布215 |
| PowerShell関連ログのサイズを拡張した | 高 | 既定のままだと数日で消える |
| トランスクリプションを有効化し、書き込み専用共有に集約した | 高 | 端末ローカルに置かない2 |
| PowerShell 2.0エンジンを無効化した | 高 | ログ・AMSIの迂回経路3 |
| ウイルス対策製品がAMSIに対応している | 高 | 既定で有効1 |
| 実行ポリシーをセキュリティ境界と誤解していない | 中 | 署名は別の価値4 |
| 管理者権限の付与範囲を棚卸しした | 高 | 被害範囲を決めるのは権限の広さ |
| 定型作業をJEAで委任した | 中 | 管理者権限の削減に直結6 |
| WDAC/AppLockerを検討した | 中 | 言語モード制限はこれとセット5 |
| PowerShell 7側にもログ設定を配布した | 中 | 5.1と7で設定は別 |
9. まとめ
- PowerShellの禁止は実効性が低く、業務を止めます。方針は「可視化と制限」です。
- 最優先はスクリプトブロックログ(4104)の有効化。難読化されたコードも展開後の形で記録されます。ログサイズの拡張とセットで実施してください。
- トランスクリプションは、利用者が消せない共有に集約するのが要点です。
- PowerShell 2.0エンジンは無効化します。ログもAMSIも効かない経路を残さないためです。
- 実行ポリシーはセキュリティ境界ではありません。署名は改ざん検知として価値がありますが、防御の主軸にはしないでください。
- 権限の広さが被害の広さを決めます。JEAで「必要な操作だけ」を委任すれば、管理者権限を配らずに運用が回せます。
サンプルコードのダウンロード
この記事で扱ったコードは、そのまま動かせる形にまとめて配布しています。ログ設定の有効化と、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
-
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 ↩9
-
Microsoft Learn, Windows PowerShell 2.0 の非推奨化. Windows PowerShell 2.0エンジンが非推奨であり、無効化が推奨されること、Windowsのオプション機能として提供され有効・無効を切り替えられることについて。 ↩ ↩2 ↩3
-
Microsoft Learn, about_Execution_Policies. 実行ポリシーがセキュリティ境界ではなく、ユーザーが意図せずスクリプトを実行することを防ぐための安全機能であること、複数の回避手段が存在することについて。 ↩ ↩2 ↩3
-
Microsoft Learn, about_Language_Modes. FullLanguage・ConstrainedLanguage・RestrictedLanguage・NoLanguageの各モードで利用できる言語要素、$ExecutionContext.SessionState.LanguageModeによる現在のモードの確認、WDACやAppLockerによるアプリケーション制御が有効な環境でPowerShellが制約付き言語モードで動作すること、手動での言語モード設定がセキュリティ機能として意図されていないことについて。 ↩ ↩2 ↩3 ↩4 ↩5
-
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
-
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.」(標準ユーザーはこのフォルダーへのアクセスを持つべきではなく、監査する管理者だけに絞る)について。 ↩ -
Microsoft Learn, JEA の前提条件. JEAがPowerShell 5.0以降で利用できること、PowerShellリモート処理がJEAの土台であり、JEAを使う前にリモート処理が有効かつ適切に保護されている必要があること、Windows Server 2012以降ではPowerShellリモート処理が既定で有効であり、有効でない場合は管理者権限のPowerShellウィンドウでEnable-PSRemotingを実行して有効化できることについて。 ↩ ↩2 ↩3
-
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)がその用途のための仕組みです。役割機能ファイルで「このコマンドの、このパラメーターの、この値だけ許可する」と定義し、セッション構成として登録します。利用者は管理者権限を持たないまま、仮想アカウント経由で許可された操作だけを実行できます。セッションは制限付きリモートサーバーとして構成され、既定では言語モードも制限されるため、任意のコードを実行される余地がありません。実行内容はトランスクリプトとして記録できます。