mirror of
https://github.com/PowerShell/PowerShell
synced 2026-06-08 12:12:50 +00:00
Co-authored-by: Patrick Meinecke <SeeminglyScience@users.noreply.github.com>
873 lines
36 KiB
C#
873 lines
36 KiB
C#
// Copyright (c) Microsoft Corporation.
|
|
// Licensed under the MIT License.
|
|
|
|
//
|
|
// Application white listing policies such as AppLocker and DeviceGuard UMCI are only implemented on Windows OSs
|
|
//
|
|
#if !UNIX
|
|
|
|
using System.Diagnostics;
|
|
using System.Diagnostics.CodeAnalysis;
|
|
using System.Management.Automation.Internal;
|
|
using System.Management.Automation.Runspaces;
|
|
using System.Management.Automation.Tracing;
|
|
using System.Runtime.InteropServices;
|
|
|
|
namespace System.Management.Automation.Security
|
|
{
|
|
/// <summary>
|
|
/// System wide policy enforcement for a specific script file.
|
|
/// </summary>
|
|
public enum SystemScriptFileEnforcement
|
|
{
|
|
/// <summary>
|
|
/// No policy enforcement.
|
|
/// </summary>
|
|
None = 0,
|
|
|
|
/// <summary>
|
|
/// Script file is blocked from running.
|
|
/// </summary>
|
|
Block = 1,
|
|
|
|
/// <summary>
|
|
/// Script file is allowed to run without restrictions (FullLanguage mode).
|
|
/// </summary>
|
|
Allow = 2,
|
|
|
|
/// <summary>
|
|
/// Script file is allowed to run in ConstrainedLanguage mode only.
|
|
/// </summary>
|
|
AllowConstrained = 3,
|
|
|
|
/// <summary>
|
|
/// Script file is allowed to run in FullLanguage mode but will emit ConstrainedLanguage restriction audit logs.
|
|
/// </summary>
|
|
AllowConstrainedAudit = 4
|
|
}
|
|
|
|
/// <summary>
|
|
/// How the policy is being enforced.
|
|
/// </summary>
|
|
// Internal Note: Current code that consumes this enum assumes that anything but 'Enforce' means
|
|
// that the script is allowed, and that a system lockdown policy that is anything but 'None' means
|
|
// that the API should be called again for individual files. If any elements are added to this enum,
|
|
// callers of the GetLockdownPolicy() should be reviewed.
|
|
public enum SystemEnforcementMode
|
|
{
|
|
/// Not enforced at all
|
|
None = 0,
|
|
|
|
/// Enabled - allow, but audit
|
|
Audit = 1,
|
|
|
|
/// Enabled, enforce restrictions
|
|
Enforce = 2
|
|
}
|
|
|
|
/// <summary>
|
|
/// Support class for dealing with the Windows Lockdown Policy,
|
|
/// Device Guard, and Constrained PowerShell.
|
|
/// </summary>
|
|
public sealed class SystemPolicy
|
|
{
|
|
private SystemPolicy()
|
|
{
|
|
}
|
|
|
|
/// <summary>
|
|
/// Writes to PowerShell WDAC Audit mode ETW log.
|
|
/// </summary>
|
|
/// <param name="context">Current execution context.</param>
|
|
/// <param name="title">Audit message title.</param>
|
|
/// <param name="message">Audit message message.</param>
|
|
/// <param name="fqid">Fully Qualified ID.</param>
|
|
/// <param name="dropIntoDebugger">Stops code execution and goes into debugger mode.</param>
|
|
internal static void LogWDACAuditMessage(
|
|
ExecutionContext context,
|
|
string title,
|
|
string message,
|
|
string fqid,
|
|
bool dropIntoDebugger = false)
|
|
{
|
|
string messageToWrite = message;
|
|
|
|
// Augment the log message with current script information from the script debugger, if available.
|
|
context ??= LocalPipeline.GetExecutionContextFromTLS();
|
|
bool debuggerAvailable = context is not null &&
|
|
context._debugger is ScriptDebugger;
|
|
|
|
if (debuggerAvailable)
|
|
{
|
|
var scriptPosMessage = context._debugger.GetCurrentScriptPosition();
|
|
if (!string.IsNullOrEmpty(scriptPosMessage))
|
|
{
|
|
messageToWrite = message + scriptPosMessage;
|
|
}
|
|
}
|
|
|
|
PSEtwLog.LogWDACAuditEvent(title, messageToWrite, fqid);
|
|
|
|
// We drop into the debugger only if requested and we are running in the interactive host session runspace (Id == 1).
|
|
if (debuggerAvailable && dropIntoDebugger &&
|
|
context._debugger.DebugMode.HasFlag(DebugModes.LocalScript) &&
|
|
Runspace.DefaultRunspace?.Id == 1 &&
|
|
context.DebugPreferenceVariable.HasFlag(ActionPreference.Break) &&
|
|
context.InternalHost?.UI is not null)
|
|
{
|
|
try
|
|
{
|
|
context.InternalHost.UI.WriteLine();
|
|
context.InternalHost.UI.WriteLine("WDAC Audit Log:");
|
|
context.InternalHost.UI.WriteLine($"Title: {title}");
|
|
context.InternalHost.UI.WriteLine($"Message: {message}");
|
|
context.InternalHost.UI.WriteLine($"FullyQualifedId: {fqid}");
|
|
context.InternalHost.UI.WriteLine("Stopping script execution in debugger...");
|
|
context.InternalHost.UI.WriteLine();
|
|
|
|
context._debugger.Break();
|
|
}
|
|
catch
|
|
{ }
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets the system lockdown policy.
|
|
/// </summary>
|
|
/// <returns>An EnforcementMode that describes the system policy.</returns>
|
|
public static SystemEnforcementMode GetSystemLockdownPolicy()
|
|
{
|
|
if (s_systemLockdownPolicy == null)
|
|
{
|
|
lock (s_systemLockdownPolicyLock)
|
|
{
|
|
s_systemLockdownPolicy ??= GetLockdownPolicy(path: null, handle: null);
|
|
}
|
|
}
|
|
else if (s_allowDebugOverridePolicy)
|
|
{
|
|
lock (s_systemLockdownPolicyLock)
|
|
{
|
|
s_systemLockdownPolicy = GetDebugLockdownPolicy(path: null, out _);
|
|
}
|
|
}
|
|
|
|
return s_systemLockdownPolicy.Value;
|
|
}
|
|
|
|
private static readonly object s_systemLockdownPolicyLock = new object();
|
|
private static SystemEnforcementMode? s_systemLockdownPolicy = null;
|
|
private static bool s_allowDebugOverridePolicy = false;
|
|
private static bool s_wldpCanExecuteAvailable = true;
|
|
|
|
/// <summary>
|
|
/// Gets the system wide script file policy enforcement for an open file.
|
|
/// Based on system WDAC (Windows Defender Application Control) or AppLocker policies.
|
|
/// </summary>
|
|
/// <param name="filePath">Script file path for policy check.</param>
|
|
/// <param name="fileStream">FileStream object to script file path.</param>
|
|
/// <returns>Policy check result for script file.</returns>
|
|
public static SystemScriptFileEnforcement GetFilePolicyEnforcement(
|
|
string filePath,
|
|
System.IO.FileStream fileStream)
|
|
{
|
|
SafeHandle fileHandle = fileStream.SafeFileHandle;
|
|
SystemEnforcementMode systemLockdownPolicy = GetSystemLockdownPolicy();
|
|
|
|
// First check latest WDAC APIs if available.
|
|
if (systemLockdownPolicy is SystemEnforcementMode.Enforce
|
|
&& s_wldpCanExecuteAvailable
|
|
&& TryGetWldpCanExecuteFileResult(filePath, fileHandle, out SystemScriptFileEnforcement wldpFilePolicy))
|
|
{
|
|
return GetLockdownPolicy(filePath, fileHandle, wldpFilePolicy);
|
|
}
|
|
|
|
// Failed to invoke WldpCanExecuteFile, revert to legacy APIs.
|
|
if (systemLockdownPolicy is SystemEnforcementMode.None)
|
|
{
|
|
return SystemScriptFileEnforcement.None;
|
|
}
|
|
|
|
// WldpCanExecuteFile was invoked successfully so we can skip running
|
|
// legacy WDAC APIs. AppLocker must still be checked in case it is more
|
|
// strict than the current WDAC policy.
|
|
return GetLockdownPolicy(filePath, fileHandle, canExecuteResult: null);
|
|
}
|
|
|
|
private static SystemScriptFileEnforcement ConvertToModernFileEnforcement(SystemEnforcementMode legacyMode)
|
|
{
|
|
return legacyMode switch
|
|
{
|
|
SystemEnforcementMode.None => SystemScriptFileEnforcement.Allow,
|
|
SystemEnforcementMode.Audit => SystemScriptFileEnforcement.AllowConstrainedAudit,
|
|
SystemEnforcementMode.Enforce => SystemScriptFileEnforcement.AllowConstrained,
|
|
_ => SystemScriptFileEnforcement.Block,
|
|
};
|
|
}
|
|
|
|
private static bool TryGetWldpCanExecuteFileResult(string filePath, SafeHandle fileHandle, out SystemScriptFileEnforcement result)
|
|
{
|
|
try
|
|
{
|
|
string fileName = System.IO.Path.GetFileNameWithoutExtension(filePath);
|
|
string auditMsg = $"PowerShell ExternalScriptInfo reading file: {fileName}";
|
|
|
|
int hr = WldpNativeMethods.WldpCanExecuteFile(
|
|
host: PowerShellHost,
|
|
options: WLDP_EXECUTION_EVALUATION_OPTIONS.WLDP_EXECUTION_EVALUATION_OPTION_NONE,
|
|
fileHandle: fileHandle.DangerousGetHandle(),
|
|
auditInfo: auditMsg,
|
|
result: out WLDP_EXECUTION_POLICY canExecuteResult);
|
|
|
|
PSEtwLog.LogWDACQueryEvent("WldpCanExecuteFile", filePath, hr, (int)canExecuteResult);
|
|
|
|
if (hr >= 0)
|
|
{
|
|
switch (canExecuteResult)
|
|
{
|
|
case WLDP_EXECUTION_POLICY.WLDP_CAN_EXECUTE_ALLOWED:
|
|
result = SystemScriptFileEnforcement.Allow;
|
|
return true;
|
|
|
|
case WLDP_EXECUTION_POLICY.WLDP_CAN_EXECUTE_BLOCKED:
|
|
result = SystemScriptFileEnforcement.Block;
|
|
return true;
|
|
|
|
case WLDP_EXECUTION_POLICY.WLDP_CAN_EXECUTE_REQUIRE_SANDBOX:
|
|
result = SystemScriptFileEnforcement.AllowConstrained;
|
|
return true;
|
|
|
|
default:
|
|
// Fall through to legacy system policy checks.
|
|
Debug.Assert(false, $"Unknown policy result returned from WldCanExecute: {canExecuteResult}");
|
|
break;
|
|
}
|
|
}
|
|
|
|
// If HResult is unsuccessful (such as E_NOTIMPL (0x80004001)), fall through to legacy system checks.
|
|
}
|
|
catch (Exception ex) when (ex is DllNotFoundException or EntryPointNotFoundException)
|
|
{
|
|
// Fall back to legacy system policy checks.
|
|
s_wldpCanExecuteAvailable = false;
|
|
PSEtwLog.LogWDACQueryEvent("WldpCanExecuteFile_Failed", filePath, ex.HResult, 0);
|
|
}
|
|
|
|
result = default;
|
|
return false;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets lockdown policy as applied to a file.
|
|
/// </summary>
|
|
/// <returns>An EnforcementMode that describes policy.</returns>
|
|
public static SystemEnforcementMode GetLockdownPolicy(string path, SafeHandle handle)
|
|
{
|
|
SystemScriptFileEnforcement modernMode = GetLockdownPolicy(path, handle, canExecuteResult: null);
|
|
Debug.Assert(
|
|
modernMode is not SystemScriptFileEnforcement.Block,
|
|
"Block should never be converted to legacy file enforcement.");
|
|
|
|
return modernMode switch
|
|
{
|
|
SystemScriptFileEnforcement.Block => SystemEnforcementMode.Enforce,
|
|
SystemScriptFileEnforcement.AllowConstrained => SystemEnforcementMode.Enforce,
|
|
SystemScriptFileEnforcement.AllowConstrainedAudit => SystemEnforcementMode.Audit,
|
|
SystemScriptFileEnforcement.Allow => SystemEnforcementMode.None,
|
|
SystemScriptFileEnforcement.None => SystemEnforcementMode.None,
|
|
_ => throw new ArgumentOutOfRangeException(nameof(modernMode)),
|
|
};
|
|
}
|
|
|
|
private static SystemScriptFileEnforcement GetLockdownPolicy(
|
|
string path,
|
|
SafeHandle handle,
|
|
SystemScriptFileEnforcement? canExecuteResult)
|
|
{
|
|
SystemScriptFileEnforcement wldpFilePolicy = canExecuteResult
|
|
?? ConvertToModernFileEnforcement(GetWldpPolicy(path, handle));
|
|
|
|
// Check the WLDP File policy via API
|
|
if (wldpFilePolicy is SystemScriptFileEnforcement.Block or SystemScriptFileEnforcement.AllowConstrained)
|
|
{
|
|
return wldpFilePolicy;
|
|
}
|
|
|
|
// Check the AppLocker File policy via API
|
|
// This needs to be checked before WLDP audit policy
|
|
// So, that we don't end up in Audit mode,
|
|
// when we should be enforce mode.
|
|
var appLockerFilePolicy = GetAppLockerPolicy(path, handle);
|
|
if (appLockerFilePolicy == SystemEnforcementMode.Enforce)
|
|
{
|
|
return ConvertToModernFileEnforcement(appLockerFilePolicy);
|
|
}
|
|
|
|
// At this point, LockdownPolicy = Audit or Allowed.
|
|
// If there was a WLDP policy, but WLDP didn't block it,
|
|
// then it was explicitly allowed. Therefore, return the result for the file.
|
|
if (s_cachedWldpSystemPolicy is SystemEnforcementMode.Audit or SystemEnforcementMode.Enforce
|
|
|| wldpFilePolicy is SystemScriptFileEnforcement.AllowConstrainedAudit)
|
|
{
|
|
return wldpFilePolicy;
|
|
}
|
|
|
|
// If there was a system-wide AppLocker policy, but AppLocker didn't block it,
|
|
// then return AppLocker's status.
|
|
if (s_cachedSaferSystemPolicy is SaferPolicy.Disallowed)
|
|
{
|
|
return ConvertToModernFileEnforcement(appLockerFilePolicy);
|
|
}
|
|
|
|
// If it's not set to 'Enforce' by the platform, allow debug overrides
|
|
GetDebugLockdownPolicy(path, out SystemScriptFileEnforcement debugPolicy);
|
|
return debugPolicy;
|
|
}
|
|
|
|
[SuppressMessage("Microsoft.Reliability", "CA2001:AvoidCallingProblematicMethods",
|
|
MessageId = "System.Runtime.InteropServices.SafeHandle.DangerousGetHandle")]
|
|
private static SystemEnforcementMode GetWldpPolicy(string path, SafeHandle handle)
|
|
{
|
|
// If the WLDP assembly is missing (such as windows 7 or down OS), return default/None to skip WLDP validation
|
|
if (s_hadMissingWldpAssembly)
|
|
{
|
|
return s_cachedWldpSystemPolicy.GetValueOrDefault(SystemEnforcementMode.None);
|
|
}
|
|
|
|
// If path is NULL, see if we have the cached system-wide lockdown policy.
|
|
if (string.IsNullOrEmpty(path))
|
|
{
|
|
if ((s_cachedWldpSystemPolicy != null) && (!InternalTestHooks.BypassAppLockerPolicyCaching))
|
|
{
|
|
return s_cachedWldpSystemPolicy.Value;
|
|
}
|
|
}
|
|
|
|
try
|
|
{
|
|
WLDP_HOST_INFORMATION hostInformation = new WLDP_HOST_INFORMATION();
|
|
hostInformation.dwRevision = WldpNativeConstants.WLDP_HOST_INFORMATION_REVISION;
|
|
hostInformation.dwHostId = WLDP_HOST_ID.WLDP_HOST_ID_POWERSHELL;
|
|
|
|
if (!string.IsNullOrEmpty(path))
|
|
{
|
|
hostInformation.szSource = path;
|
|
|
|
if (handle != null)
|
|
{
|
|
IntPtr fileHandle = IntPtr.Zero;
|
|
fileHandle = handle.DangerousGetHandle();
|
|
hostInformation.hSource = fileHandle;
|
|
}
|
|
}
|
|
|
|
uint pdwLockdownState = 0;
|
|
int result = WldpNativeMethods.WldpGetLockdownPolicy(ref hostInformation, ref pdwLockdownState, 0);
|
|
PSEtwLog.LogWDACQueryEvent("WldpGetLockdownPolicy", path, result, (int)pdwLockdownState);
|
|
if (result >= 0)
|
|
{
|
|
SystemEnforcementMode resultingLockdownPolicy = GetLockdownPolicyForResult(pdwLockdownState);
|
|
|
|
// If this is a query for the system-wide lockdown policy, cache it.
|
|
if (string.IsNullOrEmpty(path))
|
|
{
|
|
s_cachedWldpSystemPolicy = resultingLockdownPolicy;
|
|
}
|
|
|
|
return resultingLockdownPolicy;
|
|
}
|
|
else
|
|
{
|
|
// API failure?
|
|
return SystemEnforcementMode.Enforce;
|
|
}
|
|
}
|
|
catch (DllNotFoundException ex)
|
|
{
|
|
s_hadMissingWldpAssembly = true;
|
|
PSEtwLog.LogWDACQueryEvent("WldpGetLockdownPolicy_Failed", path, ex.HResult, 0);
|
|
return s_cachedWldpSystemPolicy.GetValueOrDefault(SystemEnforcementMode.None);
|
|
}
|
|
}
|
|
|
|
private static SystemEnforcementMode? s_cachedWldpSystemPolicy = null;
|
|
|
|
private const string AppLockerTestFileName = "__PSScriptPolicyTest_";
|
|
private const string AppLockerTestFileContents = "# PowerShell test file to determine AppLocker lockdown mode ";
|
|
|
|
private static SystemEnforcementMode GetAppLockerPolicy(string path, SafeHandle handle)
|
|
{
|
|
SaferPolicy result = SaferPolicy.Disallowed;
|
|
|
|
// If path is NULL, we're looking for the system-wide lockdown policy.
|
|
// Since there is no way to get that from AppLocker, we will test the policy
|
|
// against a random non-existent script and module. If that is allowed, then there is
|
|
// no AppLocker script policy.
|
|
if (string.IsNullOrEmpty(path))
|
|
{
|
|
if ((s_cachedSaferSystemPolicy != null) && (!InternalTestHooks.BypassAppLockerPolicyCaching))
|
|
{
|
|
result = s_cachedSaferSystemPolicy.Value;
|
|
}
|
|
else
|
|
{
|
|
//
|
|
// Temp path can sometimes be deleted. While many places in PowerShell depend on its existence,
|
|
// this one can crash PowerShell.
|
|
// A less sensitive implementation will be possible once AppLocker allows validation of files that
|
|
// don't exist.
|
|
//
|
|
|
|
string testPathScript = null;
|
|
string testPathModule = null;
|
|
try
|
|
{
|
|
// Start with the current profile temp path.
|
|
string tempPath = IO.Path.GetTempPath();
|
|
|
|
int iteration = 0;
|
|
while (iteration++ < 2)
|
|
{
|
|
bool error = false;
|
|
|
|
try
|
|
{
|
|
if (!IO.Directory.Exists(tempPath))
|
|
{
|
|
IO.Directory.CreateDirectory(tempPath);
|
|
}
|
|
|
|
testPathScript = IO.Path.Combine(tempPath, AppLockerTestFileName + IO.Path.GetRandomFileName() + ".ps1");
|
|
testPathModule = IO.Path.Combine(tempPath, AppLockerTestFileName + IO.Path.GetRandomFileName() + ".psm1");
|
|
|
|
// AppLocker fails when you try to check a policy on a file
|
|
// with no content. So create a scratch file and test on that.
|
|
string dtAppLockerTestFileContents = AppLockerTestFileContents + Environment.TickCount64;
|
|
IO.File.WriteAllText(testPathScript, dtAppLockerTestFileContents);
|
|
IO.File.WriteAllText(testPathModule, dtAppLockerTestFileContents);
|
|
}
|
|
catch (IO.IOException)
|
|
{
|
|
if (iteration == 2)
|
|
{
|
|
throw;
|
|
}
|
|
|
|
error = true;
|
|
}
|
|
catch (UnauthorizedAccessException)
|
|
{
|
|
if (iteration == 2)
|
|
{
|
|
throw;
|
|
}
|
|
|
|
error = true;
|
|
}
|
|
catch (System.Security.SecurityException)
|
|
{
|
|
if (iteration == 2)
|
|
{
|
|
throw;
|
|
}
|
|
|
|
error = true;
|
|
}
|
|
|
|
if (!error)
|
|
{
|
|
break;
|
|
}
|
|
|
|
// Try again with the AppData\LocalLow\Temp path using known folder id:
|
|
// https://msdn.microsoft.com/library/dd378457.aspx
|
|
Guid AppDatalocalLowFolderId = new Guid("A520A1A4-1780-4FF6-BD18-167343C5AF16");
|
|
tempPath = GetKnownFolderPath(AppDatalocalLowFolderId) + @"\Temp";
|
|
}
|
|
|
|
// Test policy.
|
|
result = TestSaferPolicy(testPathScript, testPathModule);
|
|
}
|
|
catch (System.IO.IOException)
|
|
{
|
|
// If we fail to test the policy, assume the default.
|
|
result = SaferPolicy.Disallowed;
|
|
}
|
|
catch (System.UnauthorizedAccessException)
|
|
{
|
|
// This can happen during thread impersonation if the profile temp paths are not accessible.
|
|
// Allow policy if impersonated, otherwise disallow.
|
|
result =
|
|
(System.Security.Principal.WindowsIdentity.GetCurrent().ImpersonationLevel == System.Security.Principal.TokenImpersonationLevel.Impersonation) ?
|
|
SaferPolicy.Allowed : SaferPolicy.Disallowed;
|
|
}
|
|
catch (ArgumentException)
|
|
{
|
|
// This is for IO.Path.GetTempPath() call when temp paths are not accessible.
|
|
result =
|
|
(System.Security.Principal.WindowsIdentity.GetCurrent().ImpersonationLevel == System.Security.Principal.TokenImpersonationLevel.Impersonation) ?
|
|
SaferPolicy.Allowed : SaferPolicy.Disallowed;
|
|
}
|
|
finally
|
|
{
|
|
// Ok to leave the test scripts in the temp folder if they happen to be in use
|
|
// so that PowerShell will still startup.
|
|
PathUtils.TryDeleteFile(testPathScript);
|
|
PathUtils.TryDeleteFile(testPathModule);
|
|
}
|
|
|
|
s_cachedSaferSystemPolicy = result;
|
|
}
|
|
|
|
if (result == SaferPolicy.Disallowed)
|
|
{
|
|
return SystemEnforcementMode.Enforce;
|
|
}
|
|
else
|
|
{
|
|
return SystemEnforcementMode.None;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// We got a path. Return the result for that path.
|
|
result = SecuritySupport.GetSaferPolicy(path, handle);
|
|
if (result == SaferPolicy.Disallowed)
|
|
{
|
|
return SystemEnforcementMode.Enforce;
|
|
}
|
|
|
|
return SystemEnforcementMode.None;
|
|
}
|
|
}
|
|
|
|
private static SaferPolicy? s_cachedSaferSystemPolicy = null;
|
|
|
|
private static string GetKnownFolderPath(Guid knownFolderId)
|
|
{
|
|
IntPtr pszPath = IntPtr.Zero;
|
|
try
|
|
{
|
|
int hr = WldpNativeMethods.SHGetKnownFolderPath(knownFolderId, 0, IntPtr.Zero, out pszPath);
|
|
if (hr >= 0)
|
|
{
|
|
return Marshal.PtrToStringAuto(pszPath);
|
|
}
|
|
|
|
throw new System.IO.IOException();
|
|
}
|
|
finally
|
|
{
|
|
if (pszPath != IntPtr.Zero)
|
|
{
|
|
Marshal.FreeCoTaskMem(pszPath);
|
|
}
|
|
}
|
|
}
|
|
|
|
private static SaferPolicy TestSaferPolicy(string testPathScript, string testPathModule)
|
|
{
|
|
SaferPolicy result = SecuritySupport.GetSaferPolicy(testPathScript, null);
|
|
if (result == SaferPolicy.Disallowed)
|
|
{
|
|
result = SecuritySupport.GetSaferPolicy(testPathModule, null);
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
private static SystemEnforcementMode GetDebugLockdownPolicy(string path, out SystemScriptFileEnforcement modernEnforcement)
|
|
{
|
|
s_allowDebugOverridePolicy = true;
|
|
|
|
// Support fall-back debug hook for path exclusions on non-WOA platforms
|
|
if (path != null)
|
|
{
|
|
// Assume everything under SYSTEM32 is trusted, with a purposefully sloppy
|
|
// check so that we can actually put it in the filename during testing.
|
|
if (path.Contains("System32", StringComparison.OrdinalIgnoreCase))
|
|
{
|
|
modernEnforcement = SystemScriptFileEnforcement.Allow;
|
|
return SystemEnforcementMode.None;
|
|
}
|
|
|
|
// No explicit debug allowance for the file, so return the system policy if there is one.
|
|
modernEnforcement = s_systemLockdownPolicy switch
|
|
{
|
|
SystemEnforcementMode.Enforce => SystemScriptFileEnforcement.AllowConstrained,
|
|
SystemEnforcementMode.Audit => SystemScriptFileEnforcement.AllowConstrainedAudit,
|
|
SystemEnforcementMode.None => SystemScriptFileEnforcement.None,
|
|
_ => SystemScriptFileEnforcement.None,
|
|
};
|
|
|
|
return s_systemLockdownPolicy.GetValueOrDefault(SystemEnforcementMode.None);
|
|
}
|
|
|
|
// Support fall-back debug hook for system-wide policy on non-WOA platforms
|
|
uint pdwLockdownState = 0;
|
|
object result = Environment.GetEnvironmentVariable("__PSLockdownPolicy", EnvironmentVariableTarget.Machine);
|
|
if (result != null)
|
|
{
|
|
pdwLockdownState = LanguagePrimitives.ConvertTo<uint>(result);
|
|
SystemEnforcementMode policy = GetLockdownPolicyForResult(pdwLockdownState);
|
|
modernEnforcement = ConvertToModernFileEnforcement(policy);
|
|
return policy;
|
|
}
|
|
|
|
// If the system-wide debug policy had no preference, then there is no enforcement.
|
|
modernEnforcement = SystemScriptFileEnforcement.None;
|
|
return SystemEnforcementMode.None;
|
|
}
|
|
|
|
private static bool s_hadMissingWldpAssembly = false;
|
|
|
|
/// <summary>
|
|
/// Gets lockdown policy as applied to a COM object.
|
|
/// </summary>
|
|
/// <returns>True if the COM object is allowed, False otherwise.</returns>
|
|
internal static bool IsClassInApprovedList(Guid clsid)
|
|
{
|
|
// This method is called only if there is an AppLocker and/or WLDP system wide lock down enforcement policy.
|
|
if (s_cachedWldpSystemPolicy.GetValueOrDefault(SystemEnforcementMode.None) != SystemEnforcementMode.Enforce)
|
|
{
|
|
// No WLDP policy implies only AppLocker policy enforcement. Disallow all COM object instantiation.
|
|
return false;
|
|
}
|
|
|
|
// WLDP policy must be in system wide enforcement, look up COM Id in WLDP approval list.
|
|
try
|
|
{
|
|
WLDP_HOST_INFORMATION hostInformation = new WLDP_HOST_INFORMATION();
|
|
hostInformation.dwRevision = WldpNativeConstants.WLDP_HOST_INFORMATION_REVISION;
|
|
hostInformation.dwHostId = WLDP_HOST_ID.WLDP_HOST_ID_POWERSHELL;
|
|
|
|
int pIsApproved = 0;
|
|
int result = WldpNativeMethods.WldpIsClassInApprovedList(ref clsid, ref hostInformation, ref pIsApproved, 0);
|
|
|
|
if (result >= 0)
|
|
{
|
|
if (pIsApproved == 1)
|
|
{
|
|
// Hook for testability. If we've got an environmental override, say that ADODB.Parameter
|
|
// is not allowed.
|
|
// 0000050b-0000-0010-8000-00aa006d2ea4 = ADODB.Parameter
|
|
if (s_allowDebugOverridePolicy)
|
|
{
|
|
if (string.Equals(clsid.ToString(), "0000050b-0000-0010-8000-00aa006d2ea4", StringComparison.OrdinalIgnoreCase))
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
catch (DllNotFoundException)
|
|
{
|
|
// Hook for testability. IsClassInApprovedList is only called when the system is in global lockdown mode,
|
|
// so this wouldn't be allowed in regular ConstrainedLanguage mode.
|
|
// f6d90f11-9c73-11d3-b32e-00c04f990bb4 = MSXML2.DOMDocument
|
|
if (string.Equals(clsid.ToString(), "f6d90f11-9c73-11d3-b32e-00c04f990bb4", StringComparison.OrdinalIgnoreCase))
|
|
{
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
}
|
|
|
|
private static SystemEnforcementMode GetLockdownPolicyForResult(uint pdwLockdownState)
|
|
{
|
|
if ((pdwLockdownState & WldpNativeConstants.WLDP_LOCKDOWN_UMCIAUDIT_FLAG) ==
|
|
SystemPolicy.WldpNativeConstants.WLDP_LOCKDOWN_UMCIAUDIT_FLAG)
|
|
{
|
|
return SystemEnforcementMode.Audit;
|
|
}
|
|
else if ((pdwLockdownState & WldpNativeConstants.WLDP_LOCKDOWN_UMCIENFORCE_FLAG) ==
|
|
WldpNativeConstants.WLDP_LOCKDOWN_UMCIENFORCE_FLAG)
|
|
{
|
|
return SystemEnforcementMode.Enforce;
|
|
}
|
|
else
|
|
{
|
|
return SystemEnforcementMode.None;
|
|
}
|
|
}
|
|
|
|
internal static string DumpLockdownState(uint pdwLockdownState)
|
|
{
|
|
string returnValue = string.Empty;
|
|
|
|
if ((pdwLockdownState & WldpNativeConstants.WLDP_LOCKDOWN_DEFINED_FLAG) == WldpNativeConstants.WLDP_LOCKDOWN_DEFINED_FLAG)
|
|
{
|
|
returnValue += "WLDP_LOCKDOWN_DEFINED_FLAG\r\n";
|
|
}
|
|
|
|
if ((pdwLockdownState & WldpNativeConstants.WLDP_LOCKDOWN_SECUREBOOT_FLAG) == WldpNativeConstants.WLDP_LOCKDOWN_SECUREBOOT_FLAG)
|
|
{
|
|
returnValue += "WLDP_LOCKDOWN_SECUREBOOT_FLAG\r\n";
|
|
}
|
|
|
|
if ((pdwLockdownState & WldpNativeConstants.WLDP_LOCKDOWN_DEBUGPOLICY_FLAG) == WldpNativeConstants.WLDP_LOCKDOWN_DEBUGPOLICY_FLAG)
|
|
{
|
|
returnValue += "WLDP_LOCKDOWN_DEBUGPOLICY_FLAG\r\n";
|
|
}
|
|
|
|
if ((pdwLockdownState & WldpNativeConstants.WLDP_LOCKDOWN_UMCIENFORCE_FLAG) == WldpNativeConstants.WLDP_LOCKDOWN_UMCIENFORCE_FLAG)
|
|
{
|
|
returnValue += "WLDP_LOCKDOWN_UMCIENFORCE_FLAG\r\n";
|
|
}
|
|
|
|
if ((pdwLockdownState & WldpNativeConstants.WLDP_LOCKDOWN_UMCIAUDIT_FLAG) == WldpNativeConstants.WLDP_LOCKDOWN_UMCIAUDIT_FLAG)
|
|
{
|
|
returnValue += "WLDP_LOCKDOWN_UMCIAUDIT_FLAG\r\n";
|
|
}
|
|
|
|
return returnValue;
|
|
}
|
|
|
|
// Overrides for features that should only be enabled in debug mode
|
|
internal static bool XamlWorkflowSupported { get; set; }
|
|
|
|
/// <summary>
|
|
/// Native constants for dealing with the lockdown policy.
|
|
/// </summary>
|
|
internal static class WldpNativeConstants
|
|
{
|
|
internal const uint WLDP_HOST_INFORMATION_REVISION = 0x00000001;
|
|
|
|
internal const uint WLDP_LOCKDOWN_UNDEFINED = 0;
|
|
internal const uint WLDP_LOCKDOWN_DEFINED_FLAG = 0x80000000;
|
|
internal const uint WLDP_LOCKDOWN_SECUREBOOT_FLAG = 1;
|
|
internal const uint WLDP_LOCKDOWN_DEBUGPOLICY_FLAG = 2;
|
|
internal const uint WLDP_LOCKDOWN_UMCIENFORCE_FLAG = 4;
|
|
internal const uint WLDP_LOCKDOWN_UMCIAUDIT_FLAG = 8;
|
|
}
|
|
|
|
/// <summary>
|
|
/// The different host IDs understood by the lockdown policy.
|
|
/// </summary>
|
|
internal enum WLDP_HOST_ID
|
|
{
|
|
WLDP_HOST_ID_UNKNOWN = 0,
|
|
WLDP_HOST_ID_GLOBAL = 1,
|
|
WLDP_HOST_ID_VBA = 2,
|
|
WLDP_HOST_ID_WSH = 3,
|
|
WLDP_HOST_ID_POWERSHELL = 4,
|
|
WLDP_HOST_ID_IE = 5,
|
|
WLDP_HOST_ID_MSI = 6,
|
|
WLDP_HOST_ID_MAX = 7,
|
|
}
|
|
|
|
/// <summary>
|
|
/// Host information structure to contain the lockdown policy request.
|
|
/// </summary>
|
|
[StructLayoutAttribute(LayoutKind.Sequential)]
|
|
internal struct WLDP_HOST_INFORMATION
|
|
{
|
|
/// DWORD->unsigned int
|
|
internal uint dwRevision;
|
|
|
|
/// WLDP_HOST_ID->_WLDP_HOST_ID
|
|
internal WLDP_HOST_ID dwHostId;
|
|
|
|
/// PCWSTR->WCHAR*
|
|
[MarshalAsAttribute(UnmanagedType.LPWStr)]
|
|
internal string szSource;
|
|
|
|
// HANDLE->IntPtr
|
|
internal IntPtr hSource;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Options for WldpCanExecuteFile method.
|
|
/// </summary>
|
|
[Flags]
|
|
internal enum WLDP_EXECUTION_EVALUATION_OPTIONS
|
|
{
|
|
WLDP_EXECUTION_EVALUATION_OPTION_NONE = 0x0,
|
|
WLDP_EXECUTION_EVALUATION_OPTION_EXECUTE_IN_INTERACTIVE_SESSION = 0x1
|
|
}
|
|
|
|
/// <summary>
|
|
/// Results from WldpCanExecuteFile method.
|
|
/// </summary>
|
|
internal enum WLDP_EXECUTION_POLICY
|
|
{
|
|
WLDP_CAN_EXECUTE_BLOCKED = 0,
|
|
WLDP_CAN_EXECUTE_ALLOWED = 1,
|
|
WLDP_CAN_EXECUTE_REQUIRE_SANDBOX = 2
|
|
}
|
|
|
|
/// <summary>
|
|
/// Powershell Script Host.
|
|
/// </summary>
|
|
internal static readonly Guid PowerShellHost = new Guid("8E9AAA7C-198B-4879-AE41-A50D47AD6458");
|
|
|
|
/// <summary>
|
|
/// Native methods for dealing with the lockdown policy.
|
|
/// </summary>
|
|
internal static class WldpNativeMethods
|
|
{
|
|
/// <summary>
|
|
/// Returns a WLDP_EXECUTION_POLICY enum value indicating if and how a script file
|
|
/// should be executed.
|
|
/// </summary>
|
|
/// <param name="host">Host guid.</param>
|
|
/// <param name="options">Evaluation options.</param>
|
|
/// <param name="fileHandle">Evaluated file handle.</param>
|
|
/// <param name="auditInfo">Auditing information string.</param>
|
|
/// <param name="result">Evaluation result.</param>
|
|
/// <returns>HResult value.</returns>
|
|
[DefaultDllImportSearchPathsAttribute(DllImportSearchPath.System32)]
|
|
[DllImportAttribute("wldp.dll", EntryPoint = "WldpCanExecuteFile")]
|
|
internal static extern int WldpCanExecuteFile(
|
|
[MarshalAs(UnmanagedType.LPStruct)]
|
|
Guid host,
|
|
WLDP_EXECUTION_EVALUATION_OPTIONS options,
|
|
IntPtr fileHandle,
|
|
[MarshalAs(UnmanagedType.LPWStr)]
|
|
string auditInfo,
|
|
out WLDP_EXECUTION_POLICY result);
|
|
|
|
/// Return Type: HRESULT->LONG->int
|
|
/// pHostInformation: PWLDP_HOST_INFORMATION->_WLDP_HOST_INFORMATION*
|
|
/// pdwLockdownState: PDWORD->DWORD*
|
|
/// dwFlags: DWORD->unsigned int
|
|
[DefaultDllImportSearchPathsAttribute(DllImportSearchPath.System32)]
|
|
[DllImportAttribute("wldp.dll", EntryPoint = "WldpGetLockdownPolicy")]
|
|
internal static extern int WldpGetLockdownPolicy(
|
|
ref WLDP_HOST_INFORMATION pHostInformation,
|
|
ref uint pdwLockdownState,
|
|
uint dwFlags);
|
|
|
|
/// Return Type: HRESULT->LONG->int
|
|
/// rclsid: IID*
|
|
/// pHostInformation: PWLDP_HOST_INFORMATION->_WLDP_HOST_INFORMATION*
|
|
/// ptIsApproved: PBOOL->BOOL*
|
|
/// dwFlags: DWORD->unsigned int
|
|
[DefaultDllImportSearchPathsAttribute(DllImportSearchPath.System32)]
|
|
[DllImportAttribute("wldp.dll", EntryPoint = "WldpIsClassInApprovedList")]
|
|
internal static extern int WldpIsClassInApprovedList(
|
|
ref Guid rclsid,
|
|
ref WLDP_HOST_INFORMATION pHostInformation,
|
|
ref int ptIsApproved,
|
|
uint dwFlags);
|
|
|
|
[DllImport("shell32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
|
|
internal static extern int SHGetKnownFolderPath(
|
|
[MarshalAs(UnmanagedType.LPStruct)]
|
|
Guid rfid,
|
|
int dwFlags,
|
|
IntPtr hToken,
|
|
out IntPtr pszPath);
|
|
}
|
|
}
|
|
}
|
|
|
|
#endif
|