| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [Original HTTPS Page] |
Sorry, something went wrong.
|
Azure Pipelines: There may be pipelines that require an authorized user to comment /azp run to run. |
Sorry, something went wrong.
| string promptFn = StringUtil.Format(RemotingErrorIdStrings.EnterVMSessionPrompt, | ||
| @"function global:prompt { """, | ||
| targetName, | ||
| targetNameRedacted, |
There was a problem hiding this comment.
Instead of redacting why don't we use [System.Management.Automation.Language.CodeGeneration]::EscapeSingleQuotedStringContent(...) to escape the target name so it can be safely used in single quotes. We just need to ensure the template we use has single quotes for the targetName and not double quotes but this means we don't need to scan for a dangerous token and keep it always in sync with new tokens or expressions added in the future. It also means the actual value will be shown literally in the prompt and not REDACTED.
Sorry, something went wrong.
There was a problem hiding this comment.
I did in the issue this is linked to try using that method, as it had been suggested as an suggested fix.
However in local testing of this with a Client Hyper-V Server2019 VM on W11 26H2 using the code provided it still can execute code in the prompt in the remote VM Session when that's likely not wanted, or if it is wanted, then it's certainly should not be exposed via this mechanism.
I had also tried making use of the AST as a way to determine if what is passed as the targetName had potential to be executable at all, but then rolled that back to this new internal method & only because I didn't know of anything already existing or spend the time to scan & try find it if it did exist.
The choice of using REDACTED here vs not passing anything at all, is that whilst it may seem that It also means the actual value will be shown literally in the prompt I was not convinced this to be the case from the reported string, as well as my own sample test string.
That and well picking REDACTED as it's a well enough known common string that for any documentation updates of this PR is taken would make searches easy & standout for those seeing it.
The below from another local build I'd ran
string escapedTargetName = CodeGeneration.EscapeSingleQuotedStringContent(targetName);
WriteVerbose($"Setting prompt for pushed runspace to: {escapedTargetName}");
//string targetNameRedacted = RedactIfPowerShellCode(targetName);
string promptFn = StringUtil.Format(RemotingErrorIdStrings.EnterVMSessionPrompt,
@"function global:prompt { """,
escapedTargetName,
@"PS $($executionContext.SessionState.Path.CurrentLocation)> "" }");where you can see the suggestion did not work

I'm sure that there's likely a more sensible approach than this that I missed & would be happy to take that one instead.
This was a relatively quick and dirty approach that I found to resolve the raised issue.
Sorry, something went wrong.
There was a problem hiding this comment.
That's because you kept the quotes as double quotes and didn't turn them into single quotes. The string template for EnterVMSessionPrompt is
{0}[{1}]: {2}
The only different one is the .ru.resx entry but that just has spaces so it's not a problem
{0} [ {1} ]: {2}
The code right now uses double quote @"function global:prompt { """ where the last " ends the string and "" before that turns into 1 " due to escaping. So in pwsh land this looks like
$targetName = 'dangerous$(whoami)'
$escapedTargetName = [System.Management.Automation.Language.CodeGeneration]::EscapeSingleQuotedStringContent(
$targetName)
# still the same as $ is safe within a single quoted string
'{0}[{1}]: {2}' -f @(
'function global:prompt {"',
$escapedTargetName,
'PS $($executionContext.SessionState.Path.CurrentLocation)> " }'
)This produces the following string which is still dangerous due to the double quotes. You can see why keeping the double quotes here isn't ideal.
function global:prompt {"[dangerous$(whoami)]: PS $($executionContext.SessionState.Path.CurrentLocation)> " }
Instead to make it safe you need to use single quotes:
string escapedTargetName = System.Management.Automation.Language.CodeGeneration.EscapeSingleQuotedStringContent(targetName);
string promptFn = StringUtil.Format(RemotingErrorIdStrings.EnterVMSessionPrompt,
@"function global:prompt { '",
escapedTargetName,
@"PS ' + $executionContext.SessionState.Path.CurrentLocation + '> ' }");This keeps the dyanamic value as escaped while also supporting the expression for getting the current location in the prompt. In this example I just temporarily changed the code to use this for SSH targets because I don't have a Hyper-V VM setup to test on but you can see that the target name had an expression and was escaped when using single quotes.
If it was a value with single quotes EscapeSingleQuotedStringContent also handles that for us automatically
If you wanted to make it simpler you can get rid of the template entirely but that's not necessary, just an optimisation as the resource template isn't doing much here. IMO it's simpler and easier to understand but that's just my personal opinion.
string promptFn = StringUtil.Format(
"function global:prompt {{ '[{0}]: ' + $executionContext.SessionState.Path.CurrentLocation + '> ' }}",
escapedTargetName);
Sorry, something went wrong.
There was a problem hiding this comment.
That's because you kept the quotes as double quotes and didn't turn them into single quotes.
No it's because I intentionally hadn't looked to change the rest of the makeup of how promptFn is built, even if simplifying how it does so makes sense. It can be done inside or outside of this PR but for now I think it's something that we can return to.
Mainly because I don't really think that if it is properly escaped we should pass the content through to the remote session in this case.
I feel that we only ever display a targetName that actually makes sense & should prevent passing any nonsense string that could be passed through to the end session at all. We should output a Verbose message of sorts along the lines of ya tried to do this, but we stopped it and didn't pass to the end session your unsanitized code which I am thinking should be done like this prior to creating the promptFn string
WriteVerbose($" Attempted to set {targetName} as the prompt for the pushed runspace.");
WriteVerbose($" Actually set: {targetNameRedacted}");The only thing I'm not too sure on is whether this accounts for existing behaviour for ssh connections & I'm guessing without testing may use '@' - if so then this'll need a minor rework
bool startsWithOrContainsDangerousToken =
input.StartsWith('$') ||
input.StartsWith("@{") ||
input.StartsWith('(') ||
input.StartsWith('"') ||
input.StartsWith('@') ||
input.StartsWith('\'') ||
input.Contains('$');
Sorry, something went wrong.
There was a problem hiding this comment.
Keeping it as a double quoted string just opens it up for future abuse. It also makes the UX for the end user poor when they have a VM name with something like @ as now their prompt won't actually show that anymore.
If we are touching this code to fix the security fix we may as well use the mechanisms in PowerShell to escape this properly than add another manual escape method that would need to be considered if the parser ever changes in the future.
Sorry, something went wrong.
There was a problem hiding this comment.
Keeping it as a double quoted string just opens it up for future abuse.
Yeah from review & chat here, I Agree, and is something that I'll push a change to tackle.
It also makes the UX for the end user poor when they have a VM name with something like @ as now their prompt won't actually show that anymore.
This was unintentional & will also push a change for this.
I still think we shouldn't just pass through to the Remote Runspace's prompt anything that most people would think isn't a realistically suitable targetName, escaped or otherwise. We should code around this providing a way that on discovery, mitigates & crucially provides a way to log this mitigation.
A possible future enhancement would be to make available a mechanism for passing a predefined prompt over to the remote runspace, but that is out of scope of what is needed here & much more work.
I've marked this as draftPR whilst I make changes based on your comments & build up the additional tests I think are needed for this & expect further pushes to happen in next 24hours.
Sorry, something went wrong.
| Back | FazBrowse Home | New Git URL |
PR Summary
This PR adds a redaction code path to negate potential remote prompt injections based on issue
It does so by a new internal static string method RedactIfPowerShellCode which checks if the targetName being passed may start with or contain a token that would be needed to passthrough unwanted code to the remote session's prompt & instead passes a string of REDACTED for the targetName to the prompt, which stops the passing through of any executable code from to the remote sessions prompt.
Confirmed locally that the code proposed fixes the following statements
with the below PowerShell
PR Context
Fixes #28255
Notes
Tests
I did go looking to see if there was a simple and elegant programmatic way to capture or return the prompt definition from a remote session. However the prompt used seems to be replace the one that is included in the remote sessions initial runspace's InitialSessionState. I also could not return this new prompt via Invoke-Command, likely as it's only set when connected to that remote runspace.
Because of this I've marked this PR as can only be tested interactively.
Breaking Changes & Documentation
Whilst this is a breaking change & will require updates to existing documentation, I feel that this update is required to guard against what most admins likely would not have expected to be feasible & would be unexpected behaviour by not implementing this change & allowing this to continue via this current code path.
This change has no impact to admins setting their own prompt once inside a remote session.
Screenshots
Below shows this having been run in a local build

& the prompt being different between InitialSessionState & the available prompt function
PR Checklist