mirror of
https://github.com/PowerShell/PowerShell
synced 2026-06-08 12:12:50 +00:00
Merged PR 27698: Block getting help from network locations in restricted remoting sessions
Related work items: #152543
This commit is contained in:
@@ -489,37 +489,6 @@ Function PSGetSerializedShowCommandInfo
|
||||
@"Remove-Item -Path 'function:\PSGetSerializedShowCommandInfo' -Force");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the command to be run to in order to import a module and refresh the command data.
|
||||
/// </summary>
|
||||
/// <param name="module">Module we want to import.</param>
|
||||
/// <param name="isRemoteRunspace">Boolean flag determining whether Show-Command is queried in the local or remote runspace scenario.</param>
|
||||
/// <param name="isFirstChance">Boolean flag to indicate that it is the second attempt to query Show-Command data.</param>
|
||||
/// <returns>The command to be run to in order to import a module and refresh the command data.</returns>
|
||||
internal static string GetImportModuleCommand(string module, bool isRemoteRunspace = false, bool isFirstChance = true)
|
||||
{
|
||||
string scriptBase = "Import-Module " + ShowCommandHelper.SingleQuote(module);
|
||||
|
||||
if (isRemoteRunspace)
|
||||
{
|
||||
if (isFirstChance)
|
||||
{
|
||||
scriptBase += ";@(Get-Command " + ShowCommandHelper.CommandTypeSegment + @" -ShowCommandInfo )";
|
||||
}
|
||||
else
|
||||
{
|
||||
scriptBase += GetSerializedCommandScript();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
scriptBase += ";@(Get-Command " + ShowCommandHelper.CommandTypeSegment + ")";
|
||||
}
|
||||
|
||||
scriptBase += ShowCommandHelper.GetGetModuleSuffix();
|
||||
return scriptBase;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the command to be run in order to show help for a command.
|
||||
/// </summary>
|
||||
|
||||
@@ -1290,6 +1290,16 @@ namespace System.Management.Automation
|
||||
#endif
|
||||
}
|
||||
|
||||
internal static bool PathIsDevicePath(string path)
|
||||
{
|
||||
#if UNIX
|
||||
return false;
|
||||
#else
|
||||
// device paths can be network paths, we would need windows to parse it.
|
||||
return path.StartsWith(@"\\.\") || path.StartsWith(@"\\?\") || path.StartsWith(@"\\;");
|
||||
#endif
|
||||
}
|
||||
|
||||
internal static readonly string PowerShellAssemblyStrongNameFormat =
|
||||
"{0}, Version=3.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35";
|
||||
|
||||
@@ -1547,6 +1557,23 @@ namespace System.Management.Automation
|
||||
|
||||
return oldMode;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns true if the current session is restricted (JEA or similar sessions)
|
||||
/// </summary>
|
||||
/// <param name="context">ExecutionContext.</param>
|
||||
/// <returns>True if the session is restricted.</returns>
|
||||
internal static bool IsSessionRestricted(ExecutionContext context)
|
||||
{
|
||||
CmdletInfo cmdletInfo = context.SessionState.InvokeCommand.GetCmdlet("Microsoft.PowerShell.Core\\Import-Module");
|
||||
// if import-module is visible, then the session is not restricted,
|
||||
// because the user can load arbitrary code.
|
||||
if (cmdletInfo != null && cmdletInfo.Visibility == SessionStateEntryVisibility.Public)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -253,6 +253,17 @@ namespace Microsoft.PowerShell.Commands
|
||||
/// </summary>
|
||||
protected override void ProcessRecord()
|
||||
{
|
||||
#if !UNIX
|
||||
string fileSystemPath = SessionState.Path.GetUnresolvedProviderPathFromPSPath(this.Name);
|
||||
string normalizedName = FileSystemProvider.NormalizePath(fileSystemPath);
|
||||
// In a restricted session, do not allow help on network paths or device paths, because device paths can be used to bypass the restrictions.
|
||||
if (Utils.IsSessionRestricted(this.Context) && (FileSystemProvider.PathIsNetworkPath(normalizedName) || Utils.PathIsDevicePath(normalizedName))) {
|
||||
Exception e = new ArgumentException(HelpErrors.NoNetworkCommands, "Name");
|
||||
ErrorRecord errorRecord = new ErrorRecord(e, "CommandNameNotAllowed", ErrorCategory.InvalidArgument, null);
|
||||
this.ThrowTerminatingError(errorRecord);
|
||||
}
|
||||
#endif
|
||||
|
||||
HelpSystem helpSystem = this.Context.HelpSystem;
|
||||
try
|
||||
{
|
||||
@@ -502,7 +513,7 @@ namespace Microsoft.PowerShell.Commands
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Validates input parameters.
|
||||
/// Validates input parameters.
|
||||
/// </summary>
|
||||
/// <param name="cat">Category specified by the user.</param>
|
||||
/// <exception cref="ArgumentException">
|
||||
|
||||
@@ -101,7 +101,7 @@ namespace Microsoft.PowerShell.Commands
|
||||
/// <returns>
|
||||
/// The path with all / normalized to \
|
||||
/// </returns>
|
||||
private static string NormalizePath(string path)
|
||||
internal static string NormalizePath(string path)
|
||||
{
|
||||
return GetCorrectCasedPath(path.Replace(StringLiterals.AlternatePathSeparator, StringLiterals.DefaultPathSeparator));
|
||||
}
|
||||
|
||||
@@ -187,4 +187,7 @@ To update these Help topics, start PowerShell by using the "Run as Administrator
|
||||
<data name="CircularDependencyInHelpForwarding" xml:space="preserve">
|
||||
<value>ForwardHelpTargetName cannot refer to the function itself.</value>
|
||||
</data>
|
||||
<data name="NoNetworkCommands" xml:space="preserve">
|
||||
<value>Cannot get help from a network location when in a restricted session.</value>
|
||||
</data>
|
||||
</root>
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
# Copyright (c) Microsoft Corporation.
|
||||
# Licensed under the MIT License.
|
||||
|
||||
Import-Module HelpersCommon
|
||||
|
||||
Describe 'Online help tests for PowerShell Cmdlets' -Tags "Feature" {
|
||||
|
||||
# The csv files (V2Cmdlets.csv and V3Cmdlets.csv) contain a list of cmdlets and expected HelpURIs.
|
||||
@@ -61,3 +63,18 @@ Describe 'Get-Help -Online is not supported on Nano Server and IoT' -Tags "CI" {
|
||||
{ Get-Help Get-Help -Online } | Should -Throw -ErrorId "InvalidOperation,Microsoft.PowerShell.Commands.GetHelpCommand"
|
||||
}
|
||||
}
|
||||
|
||||
Describe 'Get-Help should throw on network paths' -Tags "CI" {
|
||||
BeforeAll {
|
||||
$script:skipTest = -not $IsWindows
|
||||
}
|
||||
|
||||
It "Get-Help should throw not on <command>" -Skip:$skipTest -TestCases (Get-HelpNetworkTestCases -PositiveCases) {
|
||||
param(
|
||||
$Command,
|
||||
$ExpectedError
|
||||
)
|
||||
|
||||
{ Get-Help -Name $Command } | Should -Not -Throw
|
||||
}
|
||||
}
|
||||
|
||||
@@ -96,7 +96,6 @@ Describe "JEA session Transcript script test" -Tag @("Feature", 'RequireAdminOnW
|
||||
Unregister-PSSessionConfiguration -Name JEA -Force -ErrorAction SilentlyContinue
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Describe "JEA session Get-Help test" -Tag @("CI", 'RequireAdminOnWindows') {
|
||||
@@ -136,6 +135,34 @@ Describe "JEA session Get-Help test" -Tag @("CI", 'RequireAdminOnWindows') {
|
||||
Remove-Item $RoleCapDirectory -Recurse -Force -ErrorAction SilentlyContinue
|
||||
}
|
||||
}
|
||||
|
||||
It "Get-Help should throw <ExpectedError> on <command>" -TestCases (Get-HelpNetworkTestCases) {
|
||||
param(
|
||||
$Command,
|
||||
$ExpectedError
|
||||
)
|
||||
|
||||
[string] $RoleCapDirectory = (New-Item -Path "$TestDrive\RoleCapability" -ItemType Directory -Force).FullName
|
||||
[string] $PSSessionConfigFile = "$RoleCapDirectory\TestConfig.pssc"
|
||||
$configurationName = 'RestrictedWithNoGetHelpProxy'
|
||||
try
|
||||
{
|
||||
New-PSSessionConfigurationFile -Path $PSSessionConfigFile `
|
||||
-SessionType Empty `
|
||||
-LanguageMode NoLanguage `
|
||||
-ModulesToImport 'Microsoft.PowerShell.Utility', 'Microsoft.PowerShell.Core' `
|
||||
-VisibleCmdlets 'Get-command', 'measure-object', 'select-object', 'enter-pssession', 'get-formatdata', 'out-default', 'out-file', 'exit-pssession', 'get-help'
|
||||
Register-PSSessionConfiguration -Name $configurationName -Path $PSSessionConfigFile -Force -ErrorAction SilentlyContinue
|
||||
$scriptBlock = [scriptblock]::Create("Get-Help -Name $Command")
|
||||
{Invoke-Command -ConfigurationName $configurationName -ComputerName localhost -ScriptBlock $scriptBlock -ErrorAction Stop} |
|
||||
Should -Throw -ErrorId $ExpectedError
|
||||
}
|
||||
finally
|
||||
{
|
||||
Unregister-PSSessionConfiguration -Name $configurationName -Force -ErrorAction SilentlyContinue
|
||||
Remove-Item $RoleCapDirectory -Recurse -Force -ErrorAction SilentlyContinue
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Describe "Remoting loopback tests" -Tags @('CI', 'RequireAdminOnWindows') {
|
||||
@@ -332,6 +359,7 @@ Describe "Remoting loopback tests" -Tags @('CI', 'RequireAdminOnWindows') {
|
||||
$session = New-RemoteSession -ConfigurationName $endPoint
|
||||
try {
|
||||
$result = Invoke-Command -Session $session -ScriptBlock { $Host.Version }
|
||||
Write-Verbose "host version: $result" -Verbose
|
||||
$result | Should -Be $PSVersionTable.PSVersion
|
||||
}
|
||||
finally {
|
||||
|
||||
@@ -38,6 +38,7 @@ FunctionsToExport = @(
|
||||
'Test-TesthookIsSet'
|
||||
'Wait-FileToBePresent'
|
||||
'Wait-UntilTrue'
|
||||
'Get-HelpNetworkTestCases'
|
||||
'Get-PlatformInfo'
|
||||
'Get-WSManSupport'
|
||||
)
|
||||
|
||||
@@ -446,3 +446,96 @@ function Test-IsReleaseCandidate
|
||||
|
||||
return $false
|
||||
}
|
||||
|
||||
function Test-IsWinServer2012R2
|
||||
{
|
||||
if (-not $IsWindows) {
|
||||
return $false
|
||||
}
|
||||
|
||||
$osInfo = [System.Environment]::OSVersion.Version
|
||||
return ($osInfo.Major -eq 6 -and $osInfo.Minor -eq 3)
|
||||
}
|
||||
|
||||
function Get-HelpNetworkTestCases
|
||||
{
|
||||
param(
|
||||
[switch]
|
||||
$PositiveCases
|
||||
)
|
||||
# .NET doesn't consider these path rooted and we won't go to the network:
|
||||
# \\?
|
||||
# \\.
|
||||
# \??
|
||||
|
||||
# Command discovery does not follow symlinks to network locations for module qualified paths
|
||||
$networkBlockedError = "CommandNameNotAllowed,Microsoft.PowerShell.Commands.GetHelpCommand"
|
||||
$scriptBlockedError = "ScriptsNotAllowed"
|
||||
|
||||
$formats = @(
|
||||
'//{0}/share/{1}'
|
||||
'\\{0}\share\{1}'
|
||||
'//{0}\share/{1}'
|
||||
'Microsoft.PowerShell.Core\filesystem:://{0}/share/{1}'
|
||||
)
|
||||
|
||||
if (!$PositiveCases) {
|
||||
$formats += 'filesystem:://{0}/share/{1}'
|
||||
}
|
||||
|
||||
$moduleQualifiedCommand = 'test.dll\fakecommand'
|
||||
$lanManFormat = @(
|
||||
'//;LanmanRedirector/{0}/share/{1}'
|
||||
)
|
||||
|
||||
$hosts = @(
|
||||
'fakehost'
|
||||
'fakehost.pstest'
|
||||
)
|
||||
|
||||
$commands = @(
|
||||
'test.ps1'
|
||||
'test.dll'
|
||||
$moduleQualifiedCommand
|
||||
)
|
||||
|
||||
$variants = @()
|
||||
$cases = @()
|
||||
foreach($command in $commands) {
|
||||
$hostName = $hosts[0]
|
||||
$format = $formats[0]
|
||||
$cases += @{
|
||||
Command = $format -f $hostName, $command
|
||||
ExpectedError = $networkBlockedError
|
||||
}
|
||||
}
|
||||
|
||||
foreach($hostName in $hosts) {
|
||||
# chose the format with backslashes(\) to match the host with blackslashes
|
||||
$format = $formats[1]
|
||||
$command = $commands[0]
|
||||
$cases += @{
|
||||
Command = $format -f $hostName, $command
|
||||
ExpectedError = $networkBlockedError
|
||||
}
|
||||
}
|
||||
foreach($format in $formats) {
|
||||
$hostName = $hosts[0]
|
||||
$command = $commands[0]
|
||||
$cases += @{
|
||||
Command = $format -f $hostName, $command
|
||||
ExpectedError = $networkBlockedError
|
||||
}
|
||||
}
|
||||
|
||||
foreach($format in $lanManFormat) {
|
||||
$hostName = $hosts[0]
|
||||
$command = $moduleQualifiedCommand
|
||||
$cases += @{
|
||||
Command = $format -f $hostName, $command
|
||||
ExpectedError = $scriptBlockedError
|
||||
}
|
||||
}
|
||||
|
||||
return $cases | Sort-Object -Property ExpectedError, Command -Unique
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user