Skip to content

Update PSRP protocol to deprecate session key exchange between newer client and server #25774

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 4 commits into from
Aug 6, 2025

Conversation

daxian-dbw
Copy link
Member

@daxian-dbw daxian-dbw commented Jul 22, 2025

PR Summary

Today, a Session_Key is used to encrypt a SecureString before sending it on wire and decrypt it after receiving one. The PowerShell Remoting Protocol (PSRP) does the Session_Key exchange between client and server when a SecureString object needs to be transferred. It involves the following steps:

  1. The client side generates a public key pair and sends the public key to the server.
  2. The server generates a session key (symmetric)
  3. The server uses the public key to encrypt the session key and then send it to the client.
  4. Afterwards, both the client and server will use the session key to encrypt a SecureString object before sending it, and decrypt after receiving one.

However, the padding algorithm RSAEncryptionPadding.Pkcs1 used in the PSRP Session_Key exchange is NOT secure, and therefore, the PSRP needs to be used on top of a secure transport and the Session_Key doesn't add any extra security.

So, we decided to deprecate the Session_Key exchange in PSRP, and instead, require secure transportation layer for secure data transfer between PSRP clients and servers.

This PR increments the protocol version to v2.4 with the following changes:

  1. The following PSRP messages are deprecated when both client and server are v2.4+
    • PUBLIC_KEY
    • PUBLIC_KEY_REQUEST
    • ENCRYPTED_SESSION_KEY
  2. The encryption and decryption steps for SecureString are skipped when both client and server are v2.4+
  3. The change is backward compatible. For old client or server (v2.3 or prior), the key exchange will still be kicked off as needed.
  4. Updated the pipe_option for creating the named pipe (used for Enter-PSHostProcess) in PowerShell to reject remote client.

  • PowerShell remoting over WinRM uses HTTP by default. It uses Kerberos or NTLM session key to encrypt at the message level and is considered secure in trusted networks, but not in untrusted networks. Need to assess the implications to it.
  • PowerShell has built-in support for 3 non-network transports:
    • Standard I/O -- both client and server are local, so we should be good.
    • HyperV direct -- we've confirmed with the HyperV team that the man-in-the-middle attacks are not possible with HyperV sockets.
    • Named pipe -- it's possible for a remote client to connect to named pipe, which 1) may not be intended from the beginning of this feature 2) could be problematic after SecureString is no longer encrypted with a session key. So, it's better off to disable remote client (done by the pipe_option change above).

PR Checklist

@daxian-dbw daxian-dbw requested a review from TravisEz13 as a code owner July 22, 2025 22:38
@daxian-dbw daxian-dbw added the CL-Engine Indicates that a PR should be marked as an engine change in the Change Log label Jul 22, 2025
Comment on lines -79 to +83
internal static readonly Version ProtocolVersionWin7RC = new Version(2, 0);
internal static readonly Version ProtocolVersionWin7RTM = new Version(2, 1);
internal static readonly Version ProtocolVersionWin8RTM = new Version(2, 2);
internal static readonly Version ProtocolVersionWin10RTM = new Version(2, 3);
internal static readonly Version ProtocolVersion_2_0 = new(2, 0); // Window 7 RC
internal static readonly Version ProtocolVersion_2_1 = new(2, 1); // Window 7 RTM
internal static readonly Version ProtocolVersion_2_2 = new(2, 2); // Window 8 RTM
internal static readonly Version ProtocolVersion_2_3 = new(2, 3); // Window 10 RTM
internal static readonly Version ProtocolVersion_2_4 = new(2, 4); // PowerShell 7.6
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Renamed the variable names because:

  1. It's hard to map the original names to the true protocol versions when reading code
  2. The change to v2.4 doesn't map to a Windows RTM.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This makes perfect sense

Version clientProtocolVersion = session.Context.ClientCapability.ProtocolVersion;
if (clientProtocolVersion >= RemotingConstants.ProtocolVersion_2_4)
{
// For client v2.4+, we no longer encrypt secure strings, but rely on the underlying secure transport to do the right thing.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So does this mean that there is no check to see if a "secure" transport is used? What happens if someone is using a custom transport that isn't secure, uses something like WSMan without encryption (or weak NTLM encryption), etc?

Copy link
Member Author

@daxian-dbw daxian-dbw Jul 24, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for taking a look!

The recommendation from the internal crypto board is to require secure transport for network connections. Perhaps it's possible to check if the WSMan connection is secure, but I doubt if that's doable in general.

The plan is to update docs to add this requirement for network connections, but I'm not sure if this change would be considered a "regression" for users who are currently using PSRP over in-secure network transports. PowerShell remoting over WinRM uses HTTP by default. Even though it uses Kerberos or NTLM session key to encrypt at the message level, it's not considered as secure in untrusted networks. So, this protocol change may also have implications to it.

The Security WG will review and discuss about this on Monday. I will post the update afterwards.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yea the fact that custom transports can do whatever they want makes it hard to guarantee a "secure transport". It's a pity that this won't be updated to a newer standard so that even on insecure transports SecureString's will stay encrypted but I understand not wanting to be in the same situation in x years time.

While I doubt many people are using custom transports it is a possibility and this change would mean SecureStrings are essentially plaintext.

@@ -454,7 +454,7 @@ private static NamedPipeServerStream CreateNamedPipe(
SafePipeHandle pipeHandle = NamedPipeNative.CreateNamedPipe(
fullPipeName,
NamedPipeNative.PIPE_ACCESS_DUPLEX | NamedPipeNative.FILE_FLAG_FIRST_PIPE_INSTANCE | NamedPipeNative.FILE_FLAG_OVERLAPPED,
NamedPipeNative.PIPE_TYPE_MESSAGE | NamedPipeNative.PIPE_READMODE_MESSAGE,
NamedPipeNative.PIPE_TYPE_MESSAGE | NamedPipeNative.PIPE_READMODE_MESSAGE | NamedPipeNative.PIPE_REJECT_REMOTE_CLIENTS,
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This seems like it would be better off as a separate commit or at a minimum pointed out that this has changed here.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is actually related to this protocol change. PowerShell has built-in support for 3 non-network transports:

  • Standard I/O
  • Named pipe
  • HyperV direct

For HyperV direct, we've confirmed with the HyperV team that the man-in-the-middle attacks are not possible with HyperV sockets.

For standard I/O, both client and server are local, so we should be good.

For named pipe, it's possible for a remote client to connect to named pipe, which 1) may not be intended from the beginning of this feature 2) could be problematic after SecureString is no longer encrypted with a session key. So, it's better off to disable remote client for it.

  1. Updated the pipe_option for creating the named pipe (used for Enter-PSHostProcess) in PowerShell to reject remote client.

I called it out in the PR description above. I will add more about it in the description.

@SydneyhSmith
Copy link
Contributor

This PR was approved by the security-wg

@daxian-dbw daxian-dbw closed this Jul 28, 2025
@daxian-dbw daxian-dbw reopened this Jul 28, 2025
Copy link
Member

@TravisEz13 TravisEz13 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please add the codeql suppression of the issues that triggered this fix, noting that it is no longer involved in the current protocol.

@microsoft-github-policy-service microsoft-github-policy-service bot added the Waiting on Author The PR was reviewed and requires changes or comments from the author before being accept label Aug 6, 2025
@microsoft-github-policy-service microsoft-github-policy-service bot removed the Waiting on Author The PR was reviewed and requires changes or comments from the author before being accept label Aug 6, 2025
@TravisEz13 TravisEz13 self-assigned this Aug 6, 2025
@TravisEz13 TravisEz13 enabled auto-merge (squash) August 6, 2025 20:40
@daxian-dbw
Copy link
Member Author

/azp run

@TravisEz13 TravisEz13 merged commit f4a452e into PowerShell:master Aug 6, 2025
38 checks passed
Copy link

Azure Pipelines successfully started running 1 pipeline(s).

@daxian-dbw daxian-dbw deleted the remoting branch August 6, 2025 22:54
Copy link
Contributor

microsoft-github-policy-service bot commented Aug 6, 2025

📣 Hey @@daxian-dbw, how did we do? We would love to hear your feedback with the link below! 🗣️

🔗 https://aka.ms/PSRepoFeedback

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
CL-Engine Indicates that a PR should be marked as an engine change in the Change Log
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants