Refuse Defender exclusions for script interpreters, pin ps2exe - #100
Merged
Conversation
A Defender process exclusion does not exempt one application, it exempts every payload that process will ever execute. Excluding powershell.exe stops Defender inspecting all PowerShell on the host permanently, which is why it is a catalogued attacker technique (MITRE T1562.001). The custom path prompt has warned on OS directories since v1.98. The custom process prompt had no validation at all: it read a name and excluded it. The shipped defaults were always clean (vmms/vmwp/vmsp/vmcompute, all Hyper-V), so this was a latent hole in what an operator could configure rather than a bad default. Test-InterpreterProcessName now refuses the known interpreters and LOLBAS hosts, matching on the leaf name so a full path, a quoted value, or a bare name without the extension all reach the same verdict. The refusal is fail closed with no override prompt, and points at a path exclusion scoped to the application's own folder as the correct alternative. Also pins ps2exe to 1.0.18. That module compiles the binary that ships to users, so an unpinned install let the released artifact change without a commit, the same exposure the SHA-pinning policy closes for actions. Run-Tests section 208 covers the guard, including normalization and the must-not-over-block cases. The first draft of the no-override assertion was killed by mutation testing: its regex stopped matching once the branch grew past 800 characters, and an empty match satisfies -notmatch vacuously, so it went green on the exact code it forbids. It now proves the branch was located before trusting the result, and also asserts the refusal never reaches Add-MpPreference. Structural tests 5438 -> 5474.
Codecov Report✅ All modified and coverable lines are covered by tests. 📢 Thoughts on this report? Let us know! |
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.
What
Two independent hardening changes surfaced while triaging the VirusTotal detections on v1.122.3.
1. Refuse Defender exclusions for script interpreters
A Defender process exclusion does not exempt one application — it exempts every payload that
process will ever execute. Excluding
powershell.exestops Defender inspecting all PowerShell onthe host, permanently. It is a catalogued attacker technique (MITRE T1562.001) and it is what
THOR's
SUSP_PS1_SCRIPT_Defender_Exlusions_Interpreterrule looks for.The custom path prompt has warned on OS directories since v1.98. The custom process prompt
had no validation at all — it read a name and excluded it.
The shipped defaults were never affected: the built-in list is
vmms/vmwp/vmsp/vmcompute, allHyper-V, all Microsoft-recommended. This was a latent hole in what an operator could configure,
not a bad default.
Test-InterpreterProcessNamerefuses the known interpreters and LOLBAS hosts, matching on theleaf name so
powershell,POWERSHELL.EXE,"powershell.exe"andC:\Windows\System32\WindowsPowerShell\v1.0\powershell.exeall reach the same verdict. Failclosed — no override prompt — and it points at a path exclusion scoped to the application's own
folder as the correct alternative.
2. Pin ps2exe to 1.0.18
Install-Module ps2exe -Forcewas unpinned. That module compiles the binary that ships to users,so the released artifact could change without a commit — the same supply-chain exposure the
SHA-pinning policy already closes for actions, including transitive ones.
Tests
Section 208, 36 assertions: the guard exists and is consulted, the refusal is fail-closed and
never reaches
Add-MpPreference, the built-in list contains no interpreter, every normalizationform is refused, and the must-not-over-block cases (
vmms.exe,sqlservr.exe,notpowershell.exe, empty, null) still pass.Mutation-verified. Ten mutations: dropped
.exenormalization, dropped path stripping,dropped quote trimming, removed an entry from the list, always-true, substring-instead-of-exact,
deleted the guard, bypassed the guard, injected an override prompt, injected a silent exclusion.
All caught.
Two mutations initially escaped and both were run down:
.ToLowerInvariant()changed nothing — PowerShell's-containsis alreadycase-insensitive (
-ccontainsis not). Equivalent mutant, not an escaped bug; switching thecomparison to
-ccontainsdoes flip the assertion.REFUSED:[\s\S]{0,800}?else\s*\{and asserted the result contained noConfirm-UserAction.Injecting a real override prompt pushed the branch past 800 characters, so the regex stopped
matching — and
'' -notmatch 'Confirm-UserAction'is vacuously true. It went green on the exactcode it exists to forbid. It now proves the branch was located before trusting the result, and
additionally asserts the refusal never reaches
Add-MpPreference.Structural tests 5438 → 5474. Full gate: 42 passed / 0 failed / 0 warnings, suite 5474/5474.
Not included
No version bump, so this does not cut a release on merge.