CVE-2026-42897 is a cross-site scripting bug in Outlook Web Access. Microsoft published it on May 15. CISA added it to KEV the same week with a remediation deadline of May 29. Microsoft's Exchange On-premises Mitigation Tool installs a Content-Security-Policy header on OWA by inserting one IIS rewrite rule named EOMT OWA CSP - outbound. Microsoft's Exchange Health Checker is the diagnostic tool administrators run to confirm their server's configuration. The Health Checker's Get-URLRewriteRule.ps1 reads inbound rewrite rules at three places. It never reads outbound rules. An admin who applies the mitigation correctly and runs Health Checker to verify it will receive a report that does not mention the CSP rule. Both tools ship from microsoft/CSS-Exchange.
CVE-2026-42897: EOMT Deploys to Outbound Rules. Health Checker Reads Inbound.
patterns
cve
proof of concept
The XSS is real. The PoC is not about the XSS.
CVE-2026-42897 lands in the May 15 OWA security update. The CVSS vector is AV:N/AC:L/PR:N/UI:R/S:U/C:H/I:H/A:N: network attack, no privileges, one click. The mechanism is the textbook OWA-renders-attacker-controlled-HTML shape. An email is composed with a payload that survives OWA's sanitization, the recipient opens it, the script executes in the browser tab that holds an authenticated Exchange session. The Blackpoint Adversary Pursuit Group post on infosec.exchange names the kill chain: session token theft, credential harvesting, lateral pivot from the Exchange box. CISA puts it on the Known Exploited Vulnerabilities list with a May 29 deadline. None of that is novel. Every OWA XSS since 2020 reads like this.
The public PoC for CVE-2026-42897 was published on GitHub on May 15 by an author who goes by atiilla. The repository is named for the CVE. It contains one PowerShell script and a README. Neither file demonstrates the XSS. Neither file builds an exploit email. Neither file touches Exchange's HTML sanitization, OWA's response rendering, or the script-injection sink. The PoC demonstrates a different bug.
The bug it demonstrates is in Microsoft's own diagnostic tooling. Specifically, in the function the Exchange Health Checker uses to enumerate IIS URL Rewrite rules. The PoC's claim, in one sentence: Microsoft's mitigation for CVE-2026-42897 deploys an IIS rewrite rule into a section of the IIS configuration that Microsoft's verification tool does not read.
The README rates that bug at CVSS 5.3 and credits the original discovery to whoever filed microsoft/CSS-Exchange issue #2539. The author's choice to publish under the headline CVE rather than open a new one is editorial. It is also accurate in the way the author intends. The bug is meaningful only if you know there is a mitigation worth verifying. The XSS gives the mitigation its weight. The mitigation gives the audit tool its purpose. The audit tool gives the admin a reason to believe the server is safe. The chain collapses at the audit tool.
Get-URLRewriteRule.ps1 reads .rewrite.rules in three places.
The Health Checker enumerates IIS URL Rewrite configuration by reading three sources, in this order: the application's web.config, the per-location entries in applicationHost.config, and the global system.webServer block in applicationHost.config. Each source is parsed independently. Each read targets the same XPath suffix.
From Diagnostics/HealthChecker/Analyzer/Get-URLRewriteRule.ps1 at HEAD on microsoft/CSS-Exchange:
# Path 1: web.config (line 47)
$rules = $content.configuration.'system.webServer'.rewrite.rules
# Path 2: applicationHost.config per-location entry (line 62)
$rules = $location.'system.webServer'.rewrite.rules
# Path 3: applicationHost.config global system.webServer (line 79)
$rules = $ApplicationHostConfig.configuration.'system.webServer'.rewrite.rulesThree reads of .rewrite.rules. Zero reads of .rewrite.outboundRules. The variable that receives each read is named $rules, which is what $rules is in IIS: the inbound collection. The outbound collection has a different XML name (outboundRules) and a different schema. The Health Checker function never touches it.
The downstream consumer in Invoke-AnalyzerIISInformation.ps1 iterates $currentRewriteRules.rule, the singular subtree under whichever collection was read:
$displayRewriteRules = ($currentRewriteRules.rule |
Where-Object { $_.enabled -ne "false" }).name |
Where-Object { $_ -notcontains $excludeRules }Filtering is by enabled -ne "false". If a rule is present in the inbound collection, it is reported. If a rule is present in the outbound collection, the loop never sees it. The outbound collection isn't in $urlRewriteRules to begin with, because nothing upstream put it there.
The three read paths exist because IIS stores rewrite configuration in three places, and the Health Checker correctly walks all three. The bug is not that the function missed a path. The function missed a sibling node at every path.
EOMT deploys to the section Health Checker doesn't read.
The Exchange On-premises Mitigation Tool is Microsoft's emergency stopgap when a critical bug needs to be neutralized faster than admins can stage a full Cumulative Update. EOMT modules deploy as PowerShell scripts under Security/src/EOMT/Mitigations/. They run with admin rights on the Exchange box, edit IIS configuration in place, and leave the server with a narrowed attack surface.
For CVE-2026-42897, EOMT installs a Content-Security-Policy header on every OWA response. The CSP forbids inline script execution by default, which is the class of attack the XSS enables. The CSP cannot be added by modifying the response in OWA's code path, because the response is rendered by the same vulnerable stack the mitigation is trying to protect. So EOMT installs it at the IIS layer, where rewrite rules can transform the response after Exchange has handed it off.
IIS rewrite rules transform incoming requests through <rules> and outgoing responses through <outboundRules>. A CSP response header is, by definition, outgoing. There is no other place an IIS rewrite rule can install a response header. EOMT's mitigation block looks like this:
<rewrite>
<outboundRules>
<rule name="EOMT OWA CSP - outbound" enabled="true">
<match serverVariable="RESPONSE_Content-Security-Policy" pattern=".*" />
<action type="Rewrite"
value="default-src 'self';
script-src 'self' 'unsafe-inline' 'unsafe-eval';
style-src 'self' 'unsafe-inline';
img-src 'self' data:;
font-src 'self';
frame-ancestors 'none';" />
</rule>
</outboundRules>
</rewrite>The rule is named EOMT OWA CSP - outbound. The string "outbound" is in the rule's display name because the rule lives in <outboundRules> because there is nowhere else it could live. Every part of the EOMT design forces this placement. The mitigation cannot be inbound. The header is on the response.
Get-URLRewriteRule.ps1, three lines deep, has already decided it is not going to look there.
The report for a mitigated server is identical to the report for an unmitigated one.
Run Health Checker on an Exchange server before EOMT. The IIS section of the report enumerates rewrite rules. The EOMT OWA CSP - outbound rule is absent because it has not been deployed. The admin reads "not detected" and knows the mitigation is missing.
Run Health Checker on the same Exchange server after EOMT. The IIS section enumerates rewrite rules. The EOMT OWA CSP - outbound rule is absent because Health Checker did not look at the section where the rule was deployed. The admin reads "not detected" and is told the mitigation is missing.
Two distinct server states. One report. The admin cannot distinguish the case where they have work to do from the case where the work is already done. The wire format of "I looked and didn't find it" is the same as the wire format of "I didn't look in the place it would be." Both render as the same dashboard tile. Both leave the administrator one of two equally bad options: redeploy the mitigation, possibly stacking duplicate rules, or inspect the IIS configuration by hand and bypass the audit tool entirely.
This is the Idle Indistinguishable From Broken pattern in its purest form, instantiated not in a polling cron but in an audit report. The pattern's mechanism essay names the return-type design choice that fuses success-without-effect into could-not-determine: when the wire format has no field for "I did not enumerate this collection," every possible read of the report collapses both states into one. The cron version of this pattern is a green checkmark that means "nothing to do" and "I have no idea." The Health Checker version is a missing rule entry that means "the mitigation is absent" and "I did not look where the mitigation lives." (See Seventeen Green Checkmarks for the canonical exhibit; this CVE is the same pattern at a different layer.)
Downstream automation inherits the conflation. Compliance dashboards that parse Health Checker JSON to attest mitigation status will report EOMT OWA CSP - outbound: NOT DETECTED for every server in the estate, regardless of whether EOMT ran. SOC playbooks that fire on the absence of the rule will fire on every server, mitigated and unmitigated alike. The KEV deadline pressure compounds the failure. Admins on the May 29 clock will run Health Checker, see "not detected," and assume the mitigation never took. The remediation evidence Microsoft asks for cannot be gathered from the tool Microsoft ships for gathering it.
Both tools ship from the same Microsoft repository.
microsoft/CSS-Exchange is the repository. It hosts the Health Checker. It hosts EOMT. Both modules live under the same top-level directory tree. Both modules are maintained by the same Microsoft engineering organization. Both modules are signed and released through the same CSS-Exchange release cadence.
The EOMT module for CVE-2026-42897 was written by an engineer who understood that an IIS response header had to be installed via outboundRules. The Health Checker function for URL rewrite enumeration was written by an engineer who understood that customer-visible rewrite rules typically live under <rules>. These two understandings are individually correct. They were never reconciled against each other inside one team's design review.
There is also a Security Metric Theater layer to this. Health Checker's IIS section is presented to administrators as a comprehensive enumeration of URL Rewrite configuration. The denominator the report claims to cover is "URL Rewrite rules on this server." The numerator the function actually walks is "inbound URL Rewrite rules at three configuration sources on this server." The visible coverage looks total. The omitted set is exactly the set that contains the security control. An admin reading the report has no way to know the denominator was cropped before the count began.
This is the structural failure. There is no architectural decision document that says "the audit tool will not enumerate outbound rules" and there is none that says "the mitigation will live where the audit tool does not look." Both omissions emerged from teams optimizing locally. The audit team optimized for the common case: inbound rules are what admins write. The mitigation team optimized for the only case: a CSP response header can only live in outbound rules. Microsoft is the institution that owns the reconciliation. The reconciliation did not happen.
The patch is three lines per path.
The remediation, when it ships, will read .rewrite.outboundRules at each of the three paths and iterate .rule from both collections in the display layer. The PoC includes the patched logic verbatim. The diff per path is on the order of three lines:
- $rules = $content.configuration.'system.webServer'.rewrite.rules
+ $inbound = $content.configuration.'system.webServer'.rewrite.rules
+ $outbound = $content.configuration.'system.webServer'.rewrite.outboundRules
+ $rules = @{ inbound = $inbound; outbound = $outbound }Three lines, three times, plus a display-layer update that iterates both halves of the hashtable. The fix is mechanical. The bug it fixes was in the code for as long as the function has existed, because the function was never written to enumerate outbound rules. The mitigation that exposes the gap is new. The gap is not.
The May 29 KEV deadline is for the XSS, not for the audit tool. Health Checker has been blind to outbound rewrite rules across every previous EOMT release that used the same primitive, and across every customer-authored outbound rule the audit tool has ever been pointed at. CVE-2026-42897 is the first time the consequence has KEV pressure and a public PoC at the same time. It is not the first time the consequence has existed.
The verifier cannot tell a server that needs the mitigation from a server that already has it.
The patch closes this CVE. The audit tool that admins run to confirm the patch never reads where the patch lives.