PowerShell/Win32-OpenSSH

sshd_config doesn't seem to respect "-NoLogo -NoProfile"

eabase opened this issue ยท 18 comments

I also tried creating both SymbolicLink and HardLink to pwsh.exe and even via Registry edit, but all to no avail. For some reason the OpenSSH sshd_config, never respects the -NoLogo -NoProfile when starting the Subsystem


"OpenSSH for Windows" version

7.7.2.2

Server OperatingSystems

  OS Name               : Microsoft Windows 10 Home (64-bit)
  OS Version            : 10.0.18363  [2020-09-09 12:38:59 AM]
  OS BuildLabEx         : 18362.1
  OS HAL                : 10.0.18362.752
  OS Kernel             : 10.0.18362.1110
  OS UBR                : 1110

Client OperatingSystem

I tried both:

  • Win10 Home
  • Win 8 Home +/- Cygwin

What is failing

The sshd_config subsystem item:
Subsystem pwsh c:/progra~1/powershell/7/pwsh.exe -NoLogo -NoProfile -sshs

Expected output

Login shell without Logo and without running my [powershell] profile(s).

Actual output

With the current setting above and registry items (from below) I still get the output:

Windows PowerShell
Copyright (C) Microsoft Corporation. All rights reserved.

Try the new cross-platform PowerShell https://aka.ms/pscore6

...and dumping me into:
PowerShell Version : 5.1.18362.1110


PS. I already tried things like:

#Subsystem powershell c:/progra~1/powershell/7/pwsh.exe -sshs -NoLogo
#Subsystem pwsh "c:/progra~1/powershell/7/pwsh.exe -NoLogo -NoProfile"
#Subsystem powershell c:/progra~1/powershell/7/pwsh.exe -sshs -NoLogo -NoProfile
#Subsystem pwsh c:/progra~1/powershell/7/pwsh.exe -sshs -NoLogo -NoProfile

I have also tried with (and without) setting:

Get-ItemProperty -Path "HKLM:\SOFTWARE\OpenSSH\" -Name DefaultShell
# DefaultShell : C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe

Get-ItemProperty -Path "HKLM:\SOFTWARE\OpenSSH" -Name DefaultShellCommandOption
# DefaultShellCommandOption : -NoProfile -NoLogo -c

PS2.

  • If I remove the DefaultShell registry item, then I only get CMD shell, and not the expected pwsh.
  • If I change the DefaultShell registry item to C:\Program Files\PowerShell\7\pwsh.exe, then pwsh is ran with both Logo and Profile (in ...\Documents\PowerShell\Microsoft.PowerShell_profile.ps1).

Where I used one of:

Set-ItemProperty -Path "HKLM:\SOFTWARE\OpenSSH\" -Name DefaultShell -Value "C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe"
# or this:
Set-ItemProperty -Path "HKLM:\SOFTWARE\OpenSSH" -Name DefaultShell -Value "C:\Program Files\PowerShell\7\pwsh.exe"

^^^ This is where I also tried to hardlink (or softlink) the pwsh.exe to C:\unix\pwsh.exe, which also did not work.

This issue is related to #784, as Win32-OpenSSH is not able to distinguish between spaces in a Path and spaced separating command options, leading to erroneous behavior when setting the registry items for default shell and command options. As one of the powershell developers said:

The Subsystem is only used as part of a PSRemoting session, through cmdlets like Enter-PSSession and Invoke-Command so from that perspective it is working as intended. There's no logo that will appear when you do an interactive PSRemoting session through Enter-PSSession and -NoProfile is being honoured correctly by it not loading the profile on a logon.

When you run ssh username@hostname it isn't using that Subsystem at all, but rather the Default* properties under HKLM:\SOFTWARE\OpenSSH\ to define what application to use for the shell and what args to run with it. If that isn't working then you need to go through the SSH repo and ask there as it isn't a problem with PowerShell.

@bagajjal
This issue is related to what appear to be a dysfunctional parsing of the registry values:

Set-ItemProperty -Path "HKLM:\SOFTWARE\OpenSSH" -Name DefaultShell -Value "C:\Program Files\PowerShell\7\pwsh.exe"
Set-ItemProperty -Path "HKLM:\SOFTWARE\OpenSSH" -Name DefaultShellCommandOption -Value "-NoLogo -NoProfile -c"

Where the DefaultShellCommandOption seem to be ignored, resulting in loading profiles and logo, when accessing by normal ssh user@server (or when using FTP via internal-sftp subsystem, overflowing the receive buffer.) So there are 3 places where you need to check correct parsing:

  1. The additional cmd-line options in:
    Subsystem pwsh c:/progra~1/powershell/7/pwsh.exe -NoLogo -NoProfile -sshs

  2. Currect handling of spaces when given as:
    Subsystem pwsh "C:\Program Files\PowerShell\7\pwsh.exe -NoLogo -NoProfile -sshs"

  3. Parsing the registry item DefaultShellCommandOption

Some further research, it seems like DefaultShellCommandOption is being ignored, I've found that an interactive logon just calls the value of DefaultShell but a non-interactive (ssh with a command) is always "{DefaultShell}" -c "{command}". I would have expected this to run with the value set by DefaultShellCommandOption.

The subsystem part is fine, that's doing exactly what it's meant to and is unrelated to opening a normal ssh connection using the ssh binary.

PowerShell/PowerShell#13753 (comment)

I think I've found the problem, the buffer that is used to store DefaultShellCommandOption is only 32 bytes long https://github.com/PowerShell/openssh-portable/blob/8ab565c53f3619d6a1f5ac229e212cad8a52852c/contrib/win32/win32compat/pwd.c#L62. When I tried -NoLogo -Command that is 16 characters long but when represented as a null terminated UTF-16 string it is 34 bytes in total (16 * 2 + 2). This causes the buffer to not be set to the value resulting in the default command option -c being used. If I was to increase that buffer to 34 it's just enough to fit that raw reg value. So ultimately when DefaultShellCommandOption is 16 characters or more then it will be ignored based on the current code. I'm no C programmer but it sounds like it should be using RegGetValueW in 2 calls

  1. To get the length of the string value
  2. To get the actual value once the buffer has been dynamically evaluated.

This could also be done with RegQueryValueExW but it sounds like RegGetValueW is better for this case as

  1. We can explicitly state what types that are allowed, ignoring incorrect ones like REG_DWORD
  2. It will ensure the value is null terminated, avoiding any buffer overflow errors whereas RegQueryValueExW requires a manual check

If the data has the REG_SZ, REG_MULTI_SZ or REG_EXPAND_SZ type, the string may not have been stored with the proper terminating null characters. Therefore, even if the function returns ERROR_SUCCESS, the application should ensure that the string is properly terminated before using it; otherwise, it may overwrite a buffer

Finally I don't believe DefaultShellCommandOption is meant to be used for an interactive SSH session so this would only affect ssh connections that specify the command like ssh username@hostname my_command. I'm not sure whether that's a design decision or just an accidental omission, the project maintainers would have to weigh in on that.

@jborean93

Finally I don't believe DefaultShellCommandOption is meant to be used for an interactive SSH session so this would only affect ssh connections that specify the command like ssh username@hostname my_command.

Yes, that is a series of unfortunate events, as most SFTP servers are using exactly that to do SCP access via SSH, not expecting any other garbage, while not being able to modify the FTP connection strings in such a way to do: ssh user@server "pwsh -NoLogo -NoProfile".

So is there a way or work-around for detecting if my *profile.ps1 script is being run interactively from a remote connection?

PS. Thank you! I really appreciate your investigation and looking into this issue. ๐Ÿ’ฏ

I think you can do something like create a batch file that contains pwsh -NoLogo -NoProfile then set the DefaultShell to that batch file.

/cc @PaulHigin Could you please weight @jborean93's investigations?

@jborean93
So are you saying it might work with:

Set-ItemProperty -Path "HKLM:\SOFTWARE\OpenSSH" -Name DefaultShellCommandOption -Value "-nol -nop -c"

Because -nol -nop -c is 12 characters long, and thus represents 12*2 + 2 =26 bytes in UTF-16, and should work?
I need to test this, hoping that the short-hand versions will work.

.. set the DefaultShell to that batch file.

Can I set that to any file, like myScript.ps1 or does it have to be a *.bat

So are you saying it might work with:

Yes but only for non-interactive ssh commands like ssh username@hostname my_command. If you are starting an interactive session then DefaultShellCommandOption isn't used at all.

If you want this to be applied to an interactive session then you need to create a bat file with the command you want to run and set DefaultShell to that bat file.

I just tried using "-nol -nop -c" with ....pwsh.exe and it then crashes with:

The argument '-nol -nop -c' is not recognized as the name of a script file. 
Check the spelling of the name, or if a path was included, verify that the 
path is correct and try again.

I don't understand what is trying to be accomplished here. The PowerShell -sshs switch should only be used to run PowerShell in remoting SSH server mode. It is specific only to PowerShell remoting and does not start a normal PowerShell interactive shell. Instead it runs in server mode that handles PSRP messages through stdIn/stdOut.

@PaulHigin I could be wrong here but I don't think -sshs is at play at all here. It seems like OP just wants PowerShell that is spawned outside of PSRemoting's subsystem to be run with -NoLogo and -NoProfile. Unfortunately there are 2 complications

  • An interactive logon doesn't use the DefaultShellCommandOption property so it's just running whatever the DefaultShell is set to
  • DefaultShellCommandOption is used for non-interactive logons but has a hard limit of 15 characters.

My suggestion was to create a batch file with the contents and set that btch file as the DefaultShell

@echo off
"C:\Program Files\PowerShell\7\pwsh" -NoProfile -NoLogo

In my testing this works perfectly for interactive logons and achieves what I think OP is trying to do. Anything else would require further changes in the OpenSSH code. The hard limit for DefaultShellCommandOption should probably be looked at as I can't think of any real reasons to cap it at 15 characters.

@PaulHigin

I don't understand what is trying to be accomplished here.

Hi Paul,
The issue here is that if you are using SCP (with for example WinSCP) to access the server, you will get the profile scripts to be loaded regardless what is you settings in either the DefaultShellCommandOption registy or the sshd_config files. This will spam your SFTP/SCP session to the extent it will fail, unless you disable your scripts completely. (There is currently no way to check if your profile scripts are running in an interactive session or not. AFAIK.) So I have filed 3 closely related issues for this problem:

@eabase - I tested with OpenSSH V8.1 and it works as expected.

My sshd_config has
Subsystem pwsh c:/progra~1/powershell/7-preview/pwsh.exe -NoLogo -NoProfile

image

Please note DefaultShellCommandOption is intended to specify the shell command argument.
Incase of powershell it's "-command" or "-c".

If you want to have the similar experience without a setting up subsystem in sshd_config then try the attached private sshd binary.

These changes will be part of next release openssh v8.5.

You need to populate the registry entries
"DefaultShell" with "c:/progra~1/powershell/7-preview/pwsh.exe"
"DefaultShellArguments" with "-NoLogo -NoProfile".
"DefaultShellCommandOption" with "-c"

Just got around to test this out and it looks like DefaultShellArguments only works if you start an interactive SSH session. If you were to do ssh user@hostname 'command to run' it will ignore the options configured and still start powershell.exe -c "command to run". Is there any chance to have it apply to both scenarios? It is pretty important for you to be able to do ssh username@hostname 'command to run' without loading in a user profile or even specifying -NonInteractive and right now that's not possible without resorting to some temp batch file used as a stub.

Is there a reason at all to split DefaultShellCommandOption and DefaultShellArguments into separate options. Why not just have DefaultShellCommandOption control the whole thing. Is it possible to document these options as well in some official place.

@bagajjal - Do we have any chances to get DefaultShellArguments also working for interactive SSH sessions?

In my opinion it is even more important to not load a user profile and not showing the copyright banner when executing a command via SSH than when working on an interactive SSH session.

Drllap commented

@PaulHigin I could be wrong here but I don't think -sshs is at play at all here. It seems like OP just wants PowerShell that is spawned outside of PSRemoting's subsystem to be run with -NoLogo and -NoProfile. Unfortunately there are 2 complications

* An interactive logon doesn't use the `DefaultShellCommandOption` property so it's just running whatever the `DefaultShell` is set to

* `DefaultShellCommandOption` is used for non-interactive logons but has a hard limit of 15 characters.

My suggestion was to create a batch file with the contents and set that btch file as the DefaultShell

@echo off
"C:\Program Files\PowerShell\7\pwsh" -NoProfile -NoLogo

In my testing this works perfectly for interactive logons and achieves what I think OP is trying to do. Anything else would require further changes in the OpenSSH code. The hard limit for DefaultShellCommandOption should probably be looked at as I can't think of any real reasons to cap it at 15 characters.

I added %* to forward potential arguments:

@echo off
powershell -NoProfile -NoLogo %*