Skip to content

Commit 3d6e32e

Browse files
TravisEz13adityapatwardhan
authored andcommitted
Block getting help from network locations in restricted remoting sessions (#20593)
1 parent 0e2c97e commit 3d6e32e

File tree

9 files changed

+166
-35
lines changed

9 files changed

+166
-35
lines changed

src/Microsoft.Management.UI.Internal/commandHelpers/ShowCommandHelper.cs

Lines changed: 0 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -489,37 +489,6 @@ private static string GetSerializedCommandScript()
489489
@"Remove-Item -Path 'function:\PSGetSerializedShowCommandInfo' -Force");
490490
}
491491

492-
/// <summary>
493-
/// Gets the command to be run to in order to import a module and refresh the command data.
494-
/// </summary>
495-
/// <param name="module">Module we want to import.</param>
496-
/// <param name="isRemoteRunspace">Boolean flag determining whether Show-Command is queried in the local or remote runspace scenario.</param>
497-
/// <param name="isFirstChance">Boolean flag to indicate that it is the second attempt to query Show-Command data.</param>
498-
/// <returns>The command to be run to in order to import a module and refresh the command data.</returns>
499-
internal static string GetImportModuleCommand(string module, bool isRemoteRunspace = false, bool isFirstChance = true)
500-
{
501-
string scriptBase = "Import-Module " + ShowCommandHelper.SingleQuote(module);
502-
503-
if (isRemoteRunspace)
504-
{
505-
if (isFirstChance)
506-
{
507-
scriptBase += ";@(Get-Command " + ShowCommandHelper.CommandTypeSegment + @" -ShowCommandInfo )";
508-
}
509-
else
510-
{
511-
scriptBase += GetSerializedCommandScript();
512-
}
513-
}
514-
else
515-
{
516-
scriptBase += ";@(Get-Command " + ShowCommandHelper.CommandTypeSegment + ")";
517-
}
518-
519-
scriptBase += ShowCommandHelper.GetGetModuleSuffix();
520-
return scriptBase;
521-
}
522-
523492
/// <summary>
524493
/// Gets the command to be run in order to show help for a command.
525494
/// </summary>

src/System.Management.Automation/engine/Utils.cs

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1256,7 +1256,8 @@ internal static bool PathIsDevicePath(string path)
12561256
#if UNIX
12571257
return false;
12581258
#else
1259-
return path.StartsWith(@"\\.\") || path.StartsWith(@"\\?\");
1259+
// device paths can be network paths, we would need windows to parse it.
1260+
return path.StartsWith(@"\\.\") || path.StartsWith(@"\\?\") || path.StartsWith(@"\\;");
12601261
#endif
12611262
}
12621263

@@ -1523,6 +1524,23 @@ internal static string DisplayHumanReadableFileSize(long bytes)
15231524
_ => $"0 Bytes",
15241525
};
15251526
}
1527+
1528+
/// <summary>
1529+
/// Returns true if the current session is restricted (JEA or similar sessions)
1530+
/// </summary>
1531+
/// <param name="context">ExecutionContext.</param>
1532+
/// <returns>True if the session is restricted.</returns>
1533+
internal static bool IsSessionRestricted(ExecutionContext context)
1534+
{
1535+
CmdletInfo cmdletInfo = context.SessionState.InvokeCommand.GetCmdlet("Microsoft.PowerShell.Core\\Import-Module");
1536+
// if import-module is visible, then the session is not restricted,
1537+
// because the user can load arbitrary code.
1538+
if (cmdletInfo != null && cmdletInfo.Visibility == SessionStateEntryVisibility.Public)
1539+
{
1540+
return false;
1541+
}
1542+
return true;
1543+
}
15261544
}
15271545
}
15281546

src/System.Management.Automation/help/HelpCommands.cs

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -255,6 +255,17 @@ protected override void BeginProcessing()
255255
/// </summary>
256256
protected override void ProcessRecord()
257257
{
258+
#if !UNIX
259+
string fileSystemPath = SessionState.Path.GetUnresolvedProviderPathFromPSPath(this.Name);
260+
string normalizedName = FileSystemProvider.NormalizePath(fileSystemPath);
261+
// In a restricted session, do not allow help on network paths or device paths, because device paths can be used to bypass the restrictions.
262+
if (Utils.IsSessionRestricted(this.Context) && (FileSystemProvider.PathIsNetworkPath(normalizedName) || Utils.PathIsDevicePath(normalizedName))) {
263+
Exception e = new ArgumentException(HelpErrors.NoNetworkCommands, "Name");
264+
ErrorRecord errorRecord = new ErrorRecord(e, "CommandNameNotAllowed", ErrorCategory.InvalidArgument, null);
265+
this.ThrowTerminatingError(errorRecord);
266+
}
267+
#endif
268+
258269
HelpSystem helpSystem = this.Context.HelpSystem;
259270
try
260271
{
@@ -504,7 +515,7 @@ private void GetAndWriteParameterInfo(HelpInfo helpInfo)
504515
}
505516

506517
/// <summary>
507-
/// Validates input parameters.
518+
/// Validates input parameters.
508519
/// </summary>
509520
/// <param name="cat">Category specified by the user.</param>
510521
/// <exception cref="ArgumentException">

src/System.Management.Automation/namespaces/FileSystemProvider.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -105,7 +105,7 @@ public FileSystemProvider()
105105
/// <returns>
106106
/// The path with all / normalized to \
107107
/// </returns>
108-
private static string NormalizePath(string path)
108+
internal static string NormalizePath(string path)
109109
{
110110
return GetCorrectCasedPath(path.Replace(StringLiterals.AlternatePathSeparator, StringLiterals.DefaultPathSeparator));
111111
}

src/System.Management.Automation/resources/HelpErrors.resx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -187,4 +187,7 @@ To update these Help topics, start PowerShell by using the "Run as Administrator
187187
<data name="CircularDependencyInHelpForwarding" xml:space="preserve">
188188
<value>ForwardHelpTargetName cannot refer to the function itself.</value>
189189
</data>
190+
<data name="NoNetworkCommands" xml:space="preserve">
191+
<value>Cannot get help from a network location when in a restricted session.</value>
192+
</data>
190193
</root>

test/powershell/engine/Help/HelpSystem.OnlineHelp.Tests.ps1

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
# Copyright (c) Microsoft Corporation.
22
# Licensed under the MIT License.
33

4+
Import-Module HelpersCommon
5+
46
Describe 'Online help tests for PowerShell Cmdlets' -Tags "Feature" {
57

68
# 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" {
6163
{ Get-Help Get-Help -Online } | Should -Throw -ErrorId "InvalidOperation,Microsoft.PowerShell.Commands.GetHelpCommand"
6264
}
6365
}
66+
67+
Describe 'Get-Help should throw on network paths' -Tags "CI" {
68+
BeforeAll {
69+
$script:skipTest = -not $IsWindows
70+
}
71+
72+
It "Get-Help should throw not on <command>" -Skip:$skipTest -TestCases (Get-HelpNetworkTestCases -PositiveCases) {
73+
param(
74+
$Command,
75+
$ExpectedError
76+
)
77+
78+
{ Get-Help -Name $Command } | Should -Not -Throw
79+
}
80+
}

test/powershell/engine/Remoting/RemoteSession.Basic.Tests.ps1

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -106,7 +106,6 @@ Describe "JEA session Transcript script test" -Tag @("Feature", 'RequireAdminOnW
106106
Unregister-PSSessionConfiguration -Name JEA -Force -ErrorAction SilentlyContinue
107107
}
108108
}
109-
110109
}
111110

112111
Describe "JEA session Get-Help test" -Tag @("CI", 'RequireAdminOnWindows') {
@@ -155,6 +154,34 @@ Describe "JEA session Get-Help test" -Tag @("CI", 'RequireAdminOnWindows') {
155154
Remove-Item $RoleCapDirectory -Recurse -Force -ErrorAction SilentlyContinue
156155
}
157156
}
157+
158+
It "Get-Help should throw <ExpectedError> on <command>" -TestCases (Get-HelpNetworkTestCases) {
159+
param(
160+
$Command,
161+
$ExpectedError
162+
)
163+
164+
[string] $RoleCapDirectory = (New-Item -Path "$TestDrive\RoleCapability" -ItemType Directory -Force).FullName
165+
[string] $PSSessionConfigFile = "$RoleCapDirectory\TestConfig.pssc"
166+
$configurationName = 'RestrictedWithNoGetHelpProxy'
167+
try
168+
{
169+
New-PSSessionConfigurationFile -Path $PSSessionConfigFile `
170+
-SessionType Empty `
171+
-LanguageMode NoLanguage `
172+
-ModulesToImport 'Microsoft.PowerShell.Utility', 'Microsoft.PowerShell.Core' `
173+
-VisibleCmdlets 'Get-command', 'measure-object', 'select-object', 'enter-pssession', 'get-formatdata', 'out-default', 'out-file', 'exit-pssession', 'get-help'
174+
Register-PSSessionConfiguration -Name $configurationName -Path $PSSessionConfigFile -Force -ErrorAction SilentlyContinue
175+
$scriptBlock = [scriptblock]::Create("Get-Help -Name $Command")
176+
{Invoke-Command -ConfigurationName $configurationName -ComputerName localhost -ScriptBlock $scriptBlock -ErrorAction Stop} |
177+
Should -Throw -ErrorId $ExpectedError
178+
}
179+
finally
180+
{
181+
Unregister-PSSessionConfiguration -Name $configurationName -Force -ErrorAction SilentlyContinue
182+
Remove-Item $RoleCapDirectory -Recurse -Force -ErrorAction SilentlyContinue
183+
}
184+
}
158185
}
159186

160187
Describe "Remoting loopback tests" -Tags @('CI', 'RequireAdminOnWindows') {
@@ -358,6 +385,7 @@ Describe "Remoting loopback tests" -Tags @('CI', 'RequireAdminOnWindows') {
358385
$session = New-RemoteSession -ConfigurationName $endPoint
359386
try {
360387
$result = Invoke-Command -Session $session -ScriptBlock { $Host.Version }
388+
Write-Verbose "host version: $result" -Verbose
361389
$result | Should -Be $PSVersionTable.PSVersion
362390
}
363391
finally {

test/tools/Modules/HelpersCommon/HelpersCommon.psd1

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ FunctionsToExport = @(
4848
'Test-PSDefaultParameterValue'
4949
'Push-DefaultParameterValueStack'
5050
'Pop-DefaultParameterValueStack'
51+
'Get-HelpNetworkTestCases'
5152
)
5253

5354
CmdletsToExport= @()

test/tools/Modules/HelpersCommon/HelpersCommon.psm1

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -533,3 +533,87 @@ function Pop-DefaultParameterValueStack {
533533
return $false
534534
}
535535
}
536+
537+
function Get-HelpNetworkTestCases
538+
{
539+
param(
540+
[switch]
541+
$PositiveCases
542+
)
543+
# .NET doesn't consider these path rooted and we won't go to the network:
544+
# \\?
545+
# \\.
546+
# \??
547+
548+
# Command discovery does not follow symlinks to network locations for module qualified paths
549+
$networkBlockedError = "CommandNameNotAllowed,Microsoft.PowerShell.Commands.GetHelpCommand"
550+
$scriptBlockedError = "ScriptsNotAllowed"
551+
552+
$formats = @(
553+
'//{0}/share/{1}'
554+
'\\{0}\share\{1}'
555+
'//{0}\share/{1}'
556+
'Microsoft.PowerShell.Core\filesystem:://{0}/share/{1}'
557+
)
558+
559+
if (!$PositiveCases) {
560+
$formats += 'filesystem:://{0}/share/{1}'
561+
}
562+
563+
$moduleQualifiedCommand = 'test.dll\fakecommand'
564+
$lanManFormat = @(
565+
'//;LanmanRedirector/{0}/share/{1}'
566+
)
567+
568+
$hosts = @(
569+
'fakehost'
570+
'fakehost.pstest'
571+
)
572+
573+
$commands = @(
574+
'test.ps1'
575+
'test.dll'
576+
$moduleQualifiedCommand
577+
)
578+
579+
$variants = @()
580+
$cases = @()
581+
foreach($command in $commands) {
582+
$hostName = $hosts[0]
583+
$format = $formats[0]
584+
$cases += @{
585+
Command = $format -f $hostName, $command
586+
ExpectedError = $networkBlockedError
587+
}
588+
}
589+
590+
foreach($hostName in $hosts) {
591+
# chose the format with backslashes(\) to match the host with blackslashes
592+
$format = $formats[1]
593+
$command = $commands[0]
594+
$cases += @{
595+
Command = $format -f $hostName, $command
596+
ExpectedError = $networkBlockedError
597+
}
598+
}
599+
foreach($format in $formats) {
600+
$hostName = $hosts[0]
601+
$command = $commands[0]
602+
$cases += @{
603+
Command = $format -f $hostName, $command
604+
ExpectedError = $networkBlockedError
605+
}
606+
}
607+
608+
foreach($format in $lanManFormat) {
609+
$hostName = $hosts[0]
610+
$command = $moduleQualifiedCommand
611+
$cases += @{
612+
Command = $format -f $hostName, $command
613+
ExpectedError = $scriptBlockedError
614+
}
615+
}
616+
617+
return $cases | Sort-Object -Property ExpectedError, Command -Unique
618+
}
619+

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy