I have data in an XML file, that will eventually be used as a Registry path, which MAY contain non printing characters (for example when copying the path from a web site into the XML). I want to validate the data and throw a specific error if non printing characters are found.
In Powershell, if I define a variable with non printing characters in single quotes and then test-Path
it tests as a valid path as the non printing character is handled as a literal.
Test-Path 'HKEY_LOCAL_MACHINE\SOFTWARE\Test\`[email protected]/GENUINE\@microsoft.com/GENUINE' -isValid
The same thing with double quotes will "expand" the non printing characters and return false, which is what I need.
Test-Path "HKEY_LOCAL_MACHINE\SOFTWARE\Test\`[email protected]/GENUINE\@microsoft.com/GENUINE" -isValid
I have found reference to [string]::Format(()
being used to expand the non printing characters, but
$invalidPath = 'HKEY_LOCAL_MACHINE\SOFTWARE\Test\`[email protected]/GENUINE\@microsoft.com/GENUINE'
[string]::Format("{0}",$invalidPath)
does not expand the non printing character as expected.
I have also seen reference to using Invoke-Expression
but that is NOT safe, and not an option.
Finally I found $ExecutionContext.InvokeCommand.ExpandString()
, which seems to work,
$ExecutionContext.InvokeCommand.ExpandString('HKEY_LOCAL_MACHINE\SOFTWARE\Test\`[email protected]/GENUINE\@microsoft.com/GENUINE')
returns a multiline string to the console, while
$ExecutionContext.InvokeCommand.ExpandString('Write-Host "Screwed"')
returns the actual string to the console, rather than actually executing the Write-Host
and only returning Screwed
to the console.
Finally,
$invalidPath = 'HKEY_LOCAL_MACHINE\SOFTWARE\Test\`[email protected]/GENUINE\@microsoft.com/GENUINE'
Test-Path ($ExecutionContext.InvokeCommand.ExpandString($invalidPath)) -isValid
returns false as expected. Which has me thinking this is the correct approach to pursue, but given all the gotchas elsewhere, I want to be 100% sure there is no way for this approach to be used as a security weak point. Am I on the right track, or are there gotchas my Google-Fu hasn't turned up yet?
Like Invoke-Expression
, $ExecutionContext.InvokeCommand.ExpandString()
is vulnerable to injection of unwanted commands, except that in the latter case such commands are only recognized if enclosed in $(...)
, the subexpression operator, as that is the only way to embed commands in expandable strings (which the method's argument is interpreted as).
For instance:
$ExecutionContext.InvokeCommand.ExpandString('a $(Write-Host -Fore Red Injected!) b')
A simple way to prevent this is to categorically treat all embedded $
chars. verbatim, by escaping them with `
:
'a $(Write-Host -Fore Red Injected!) b',
'There''s no place like $HOME',
'Too `$(Get-Date) clever by half' |
ForEach-Object {
$ExecutionContext.InvokeCommand.ExpandString(($_ -replace '(`*)\$', '$1$1`$$'))
}
Note: It is sufficient to escape verbatim $
in the input string. A Unicode escape-sequence representation of $
(or (
/ )
) (`u{24}
(or `u{28}
/ `u{29}
), supported in PowerShell (Core) v6+ only), is not a concern, because PowerShell treats the resulting characters verbatim.
Of course, you may choose to report an error if there's a risk of command (or variable-value) injection, which can be as simple as:
$path = 'There''s no place like $HOME'
if ($path -match '\$') { Throw 'Unsupported characters in path.' }
However, this also prevents legitimate use of a verbatim $
in paths.
Taking a step back:
You state that the paths may have been copy-pasted from a web site. Such a pasted string may indeed contain (perhaps hidden) control characters, but they would be contained verbatim rather than as PowerShell_escape sequences.
As such, it may be sufficient to test for / quietly remove control characters from the string's literal content (before calling Test-Path -IsValid
):
# Test for control characters.
if ($path -match '\p{C}') { throw 'Path contains control characters.' }
# Quietly remove them.
$sanitizedPath = $path -replace '\p{C}'