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

9 files changed

Lines changed: 166 additions & 35 deletions

File tree

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)