Powershell: In the context of remoting, Get-ChildItem can unexpectedly access the hidden system junctions

Created on 4 Aug 2020  路  14Comments  路  Source: PowerShell/PowerShell

On Windows, hidden system junctions such as "$HOME\My Documents" exist for compatibility with pre-Vista Windows versions - they simply redirect to the current well-known locations.

In a regular session, trying to get the items inside those junctions by design fails with an access-denied error, even in an elevated session.

Unexpectedly, however, in a _remoting_ session the items _can_ be accessed.

This can lead to undesired duplicate processing of items.

Steps to reproduce

Run on Windows, with elevation and remoting set up.

# OK
{ gci -ea stop -force "$HOME\My Documents" } | Should -Throw -ExceptionType System.UnauthorizedAccessException

# Unexpectedly does NOT throw
{ icm -cn . -ConfigurationName PowerShell.7 { $res = gci -ea stop -force "$HOME\My Documents"; $res.FullName | Out-Host } } | Should -Throw -ExceptionType System.UnauthorizedAccessException

Expected behavior

Both test should succeed.

Actual behavior

The 2nd test fails, because accessing the items inside the hidden junction unexpectedly succeeds in the context of remoting.

... # Out-Host output with item paths
InvalidResult:
Line |
   1 | { icm -cn . { $res = gci -ea stop -force "$HOME\My Documents"; $res.FullName | Out-Host } } | Should -Throw -ExceptionType System.UnauthorizedAccessException
     | Expected an exception, with type [System.UnauthorizedAccessException] to be thrown, but no exception was thrown.

Environment data

PowerShell Core 7.1.0-preview.5
Issue-Question Resolution-By Design WG-Remoting

Most helpful comment

Right, that is what I meant about this being a WinRM/SSH issue, since they create Network log on sessions. So this behavior is by design.

All 14 comments

Unexpectedly, however, in a remoting session the items can be accessed.

What does gci return in second case?

The usual (deserialized FileSystemInfo instances), because the code executing remotely seemingly has the required permissions to access these junctions, which locally running code doesn't (even as admin).

I'm _guessing_ that the problem stems from the user impersonation in the remotely executing code being more privileged than necessary.

This is probably due to a network logon having the SeBackupPrivilege enabled which allows you to bypass access checks when accessing items. An interactive logon has the privileges on the access token but they aren't enabled by default whereas a network logon enables all privileges. To see this in action just run whoami.exe /priv.

It sounds like the actual issue relates to https://github.com/PowerShell/PowerShell/issues/9126 where PowerShell follows a symlink/junction by default.

That's a great pointer, @jborean93, thanks.

Note that PowerShell Core _by default_ does _not_ follow symlinks or junctions: #9126 is about the very specific parameter combinations when it unexpectedly deviates from that default, which the code in the OP doesn't employ, however.

Ah yes sorry I see you are doing a gci on the junction itself in your example sorry I thought it was about how remoting would follow it not that it doesn't fail like it does when local. Still the SeBackupPrivilege would explain the difference in behaviour here.

/cc @PaulHigin Given @jborean93's explanation (thanks!), would this issue be considered by design?

@daxian-dbw

While the specific command in the OP is definitely an exotic case (_explicit_ targeting of a hidden for-legacy-code-only junction), I am concerned that the underlying problem (for which we now have a _technical_ explanation, thanks to @jborean93) may have other, more problematic ramifications.

In general:

  • You don't want code to behave differently just because it happens to be running via remoting (leaving the loss of type fidelity on the _caller_ side aside).

  • You don't want code to run with different - and especially not _more_ - privileges, just because it happens to be running via remoting.

This is not a PowerShell remoting issue, and as @jborean93 points out, a consequence of how the remote non-interactive log in session is created. You get the same behavior, without running any of the PowerShell remoting stack, by running PowerShell directly in an SSH session:

PS C:\> ssh -l username@domain target1
Microsoft Windows [Version 10.0.18363.959]
(c) 2019 Microsoft Corporation. All rights reserved.

domain\username@target1 c:\Users\username>
domain\username@target1 c:\Users\username>pwsh
PS C:\Users\username>
PS C:\Users\username> dir "$home/My Documents"

    Directory: C:\Users\username\My Documents

Mode                 LastWriteTime         Length Name
----                 -------------         ------ ----
...

I don't know if this is a privilege issue for non-interactive sessions, but if it is then it needs to be addressed in SSH and WinRM.

Thanks for clarifying, @PaulHigin - I didn't know at what layer this behavior is implemented.

Leaving SSH aside, does this mean that when PowerShell uses WinRM for its remoting it doesn't even have the _option_ to control this behavior?

Yes, AFAIK this restriction is enforced by Windows based on how the log in session is created. PowerShell, running within the session, has nothing to do with it.

Thanks, @PaulHigin. I'm closing this.

However, I think the behavior is problematic and, given that that it is part of the plumbing, has even wider ramifications than I feared.

I won't personally pursue this outside of PowerShell, but I encourage someone from the team to do so.

Will do. I'll contact the owners of WinRM and try to find out if this is expected.

This wouldn't be something with WinRM or even SSH but just how Windows deals with a Network logon. You can see it in action by using my PSAccessToken module (note this isn't released but just on GitHub).

# PSAccessToken requires this
# Install-Module PInvokeHelper -Scope CurrentUser

Import-Module .\PSAccessToken\PSAccessToken\PSAccessToken.psd1

Write-Output "Interactive token details"
Get-TokenPrivileges

$cred = Get-Credential
$logon = Invoke-LogonUser -Credential $cred -LogonType Network

try {
    Invoke-WithImpersonation -Token $logon.Token -ScriptBlock {
        Write-Output "Network token details"
        Get-TokenPrivileges
    }
} finally {
    $logon.Token.Dispose()
}

This results in

Interactive token details

Name                                                     Attributes
----                                                     ----------
SeAssignPrimaryTokenPrivilege                              Disabled
SeIncreaseQuotaPrivilege                                   Disabled
SeSecurityPrivilege                                        Disabled
SeTakeOwnershipPrivilege                                   Disabled
SeLoadDriverPrivilege                                      Disabled
SeSystemProfilePrivilege                                   Disabled
SeSystemtimePrivilege                                      Disabled
SeProfileSingleProcessPrivilege                            Disabled
SeIncreaseBasePriorityPrivilege                            Disabled
SeCreatePagefilePrivilege                                  Disabled
SeBackupPrivilege                                          Disabled
SeRestorePrivilege                                         Disabled
SeShutdownPrivilege                                        Disabled
SeDebugPrivilege                                            Enabled
SeSystemEnvironmentPrivilege                               Disabled
SeChangeNotifyPrivilege                   EnabledByDefault, Enabled
SeRemoteShutdownPrivilege                                  Disabled
SeUndockPrivilege                                          Disabled
SeManageVolumePrivilege                                    Disabled
SeImpersonatePrivilege                    EnabledByDefault, Enabled
SeCreateGlobalPrivilege                   EnabledByDefault, Enabled
SeIncreaseWorkingSetPrivilege                              Disabled
SeTimeZonePrivilege                                        Disabled
SeCreateSymbolicLinkPrivilege                              Disabled
SeDelegateSessionUserImpersonatePrivilege                  Disabled


Network token details

SeAssignPrimaryTokenPrivilege             EnabledByDefault, Enabled
SeIncreaseQuotaPrivilege                  EnabledByDefault, Enabled
SeSecurityPrivilege                       EnabledByDefault, Enabled
SeTakeOwnershipPrivilege                  EnabledByDefault, Enabled
SeLoadDriverPrivilege                     EnabledByDefault, Enabled
SeSystemProfilePrivilege                  EnabledByDefault, Enabled
SeSystemtimePrivilege                     EnabledByDefault, Enabled
SeProfileSingleProcessPrivilege           EnabledByDefault, Enabled
SeIncreaseBasePriorityPrivilege           EnabledByDefault, Enabled
SeCreatePagefilePrivilege                 EnabledByDefault, Enabled
SeBackupPrivilege                         EnabledByDefault, Enabled
SeRestorePrivilege                        EnabledByDefault, Enabled
SeShutdownPrivilege                       EnabledByDefault, Enabled
SeDebugPrivilege                          EnabledByDefault, Enabled
SeSystemEnvironmentPrivilege              EnabledByDefault, Enabled
SeChangeNotifyPrivilege                   EnabledByDefault, Enabled
SeRemoteShutdownPrivilege                 EnabledByDefault, Enabled
SeUndockPrivilege                         EnabledByDefault, Enabled
SeManageVolumePrivilege                   EnabledByDefault, Enabled
SeImpersonatePrivilege                    EnabledByDefault, Enabled
SeCreateGlobalPrivilege                   EnabledByDefault, Enabled
SeIncreaseWorkingSetPrivilege             EnabledByDefault, Enabled
SeTimeZonePrivilege                       EnabledByDefault, Enabled
SeCreateSymbolicLinkPrivilege             EnabledByDefault, Enabled
SeDelegateSessionUserImpersonatePrivilege EnabledByDefault, Enabled

This is fundamentally how Windows treats a network logon, it will create the access token with all the privileges assigned to that user and enable them all. WinRM and SSH are affected because it is creating a process based on the network logon token so they have those privileges enabled by default.

My guess as to why this happens is because network logon like services, like SMB, doesn't have a mechanism to adjust the token privileges from the client's perspective. This would cause havok on things like RPC commands over named pipes (over SMB) and even backup systems that rely on the SeBackupPrivilege/SeRestorePrivilege to read/write files on a network share without having explicit permission.

Right, that is what I meant about this being a WinRM/SSH issue, since they create Network log on sessions. So this behavior is by design.

Was this page helpful?
0 / 5 - 0 ratings