fix: WindowsFeaturesService binary-planting LPE via extension-less powershell launch#1419
Merged
Merged
Conversation
…wershell launch All three WindowsFeaturesService call sites (ListFeaturesAsync, EnableFeatureAsync, DisableFeatureAsync — enable/disable run elevated) launched PowerShell as the bare name "powershell". PowerShellRunner hardens the target via SystemPaths.ResolveSystemTool, but that helper probed only the exact name via File.Exists — File.Exists(...\WindowsPowerShell\v1.0\powershell) is false (the file is powershell.exe), so both probes missed and it returned the UNROOTED bare name. With UseShellExecute=false, Win32 CreateProcess then searches the calling process's own directory first, so an attacker-planted powershell.exe beside SysManager's portable .exe (often run from Downloads, sometimes elevated) would run with the app's privileges — the same binary-planting/LPE class already fixed for System32 tools (#3) and winget (#4). Fix: all three sites now pass "powershell.exe" (matching PowerShellRunner.RunScriptViaPwshAsync and every other system-tool caller). Fix-the-class: SystemPaths.ResolveSystemTool now also probes "<name>.exe" for an extension-less bare name, so a future caller can't silently reopen the hole. Source: fresh multi-agent ultra-audit (security-deep dimension), confirmed by 3 adversarial verifier lenses (isReal=true, high confidence) against real source. Regression tests: ResolveSystemTool_BareNameWithoutExeSuffix_ResolvesToRootedExistingPath + ResolveSystemTool_BareSystem32NameWithoutExe_ResolvesToRootedExistingPath — both red before the fix (bare name returned unrooted), green after.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Fresh ultra-audit finding (security-deep, P1), confirmed by 3 adversarial verifier lenses (isReal=true, high confidence) against real source.
All three
WindowsFeaturesServicecall sites —ListFeaturesAsync(:36),EnableFeatureAsync(:70),DisableFeatureAsync(:107); the enable/disable paths run elevated — launched PowerShell as the extension-less bare name"powershell".PowerShellRunner.RunProcessAsynchardens the target viaSystemPaths.ResolveSystemTool, but that helper resolved byFile.Existson the exact name only:File.Exists(@"…\System32\WindowsPowerShell\v1.0\powershell")is false (the file ispowershell.exe), so both probes missed and it returned the unrooted"powershell". WithUseShellExecute=false, Win32CreateProcessthen searches the calling process's own directory first — so an attacker-plantedpowershell.exenext to SysManager's single portable .exe (often run from a user-writable folder like Downloads, sometimes elevated) would execute with the app's privileges. This is the same binary-planting / LPE class already fixed for System32 tools and winget.Fix
"powershell.exe"— matchingPowerShellRunner.RunScriptViaPwshAsync(:163) and every other system-tool caller (reg.exe,schtasks.exe,mdsched.exe,netplwiz.exe), which all carry the.exesuffix.SystemPaths.ResolveSystemToolnow also probes"<name>.exe"when handed an extension-less bare name, so a future caller that forgets the suffix cannot silently reopen the same hole.Regression tests
ResolveSystemTool_BareNameWithoutExeSuffix_ResolvesToRootedExistingPath—"powershell"must resolve to a rooted, existingpowershell.exe. Red before (returned the unrooted bare name).ResolveSystemTool_BareSystem32NameWithoutExe_ResolvesToRootedExistingPath—"cmd"→ full path tocmd.exe. Red before.Checks
wingetlaunches are a separate finding (winget is an App Execution Alias, not a System32 tool — needs a winget-specific resolver); tracked for its own PR.