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.
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
Both test should succeed.
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.
PowerShell Core 7.1.0-preview.5
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.
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.