Skip to content

Collapse scheme-relative leading slashes in Rewrite middleware redirect/rewrite targets#66961

Open
rokonec wants to merge 1 commit into
dotnet:mainfrom
rokonec:rokonec/rewrite-collapse-scheme-relative-redirect
Open

Collapse scheme-relative leading slashes in Rewrite middleware redirect/rewrite targets#66961
rokonec wants to merge 1 commit into
dotnet:mainfrom
rokonec:rokonec/rewrite-collapse-scheme-relative-redirect

Conversation

@rokonec
Copy link
Copy Markdown
Member

@rokonec rokonec commented Jun 1, 2026

Summary

AddRedirect, IIS URL Rewrite, and Apache mod_rewrite rules let a back-reference (or a literal rule replacement) produce a redirect/rewrite target that starts with //, ///, or /\ (and longer runs). When PathBase is empty, the resulting Location header is scheme-relative and resolves off-origin — i.e. an open redirect.

Example today:

app.UseRewriter(new RewriteOptions()
    .AddRedirect("legacy/(.*)", "/$1", statusCode: 302));

Fix

Introduce a small internal static class UrlNormalizer with one method:

public static string CollapseLeadingSlashes(string url);

It mirrors the rejection predicate in SharedUrlHelper.IsLocalUrl but coerces instead of returning a bool: any leading run of / and \ is collapsed to a single /. The fast-path is zero-allocation and reference-returning when the input does not begin with // or /\.

The helper is invoked at the three sinks that emit Location or mutate Request.Path:

Sink Surface
RedirectRule.ApplyRule AddRedirect(...) rules
UrlActions.RedirectAction.ApplyAction IIS / Apache redirect rule imports
UrlActions.RewriteAction.ApplyAction Defense in depth for Request.Path

Also removes a stale // TODO check for false positives comment in RedirectAction that was tracking exactly this gap.

Tests

Four new [Theory] methods covering all three surfaces, including back-reference-synthesized cases:

  • CheckRedirect_CollapsesSchemeRelativeTargetAddRedirect (7 rows)
  • Invoke_RedirectCollapsesSchemeRelativeBackReference — IIS URL Rewrite (3 rows)
  • Invoke_RedirectCollapsesSchemeRelativeBackReference — Apache mod_rewrite, [R=302,L] (3 rows)
  • Invoke_RewriteCollapsesSchemeRelativeBackReference — Apache mod_rewrite, no [R] flag, asserts Request.Path (3 rows)

Each surface is covered for //, ///, //////, and /\ shapes, both as literal replacements and as $1 / {R:1} captures.

Compatibility

  • No public API surface change. PublicAPI.Shipped.txt and PublicAPI.Unshipped.txt are byte-identical.
  • All 42 pre-existing Headers.Location assertions in the Rewrite test suite still pass — full suite is 473 / 473 green.
  • Behavior change is intentional and security-relevant: targets that were resolving as scheme-relative authorities now resolve as local paths. Prior behavior was an open redirect, not a documented contract, so no opt-out is added on main.

Validation

dotnet build src/Middleware/Rewrite/src/Microsoft.AspNetCore.Rewrite.csproj -c Debug
  → 0 errors, 0 warnings

dotnet test src/Middleware/Rewrite/test/Microsoft.AspNetCore.Rewrite.Tests.csproj -c Debug
  → total: 473, succeeded: 473, failed: 0, skipped: 0

Fixes: #66486

@github-actions github-actions Bot added the area-middleware Includes: URL rewrite, redirect, response cache/compression, session, and other general middlewares label Jun 1, 2026
…ct/rewrite targets

`AddRedirect`, IIS URL Rewrite, and Apache mod_rewrite rules let an attacker-controlled or
misconfigured back-reference produce a target starting with `//`, `///`, or `/\` (and longer
runs). When `PathBase` is empty, the resulting `Location` header is scheme-relative and
resolves off-origin — an open redirect.

This change adds a small internal helper `UrlNormalizer.CollapseLeadingSlashes` that mirrors
the rejection predicate in `SharedUrlHelper.IsLocalUrl` but coerces instead of returning a
boolean: any leading run of `/` and `\` is collapsed to a single `/`. The helper is applied
at the three sinks that emit `Location` or mutate `Request.Path`:

* `RedirectRule.ApplyRule` (`AddRedirect` surface)
* `UrlActions.RedirectAction.ApplyAction` (IIS / Apache redirect import)
* `UrlActions.RewriteAction.ApplyAction` (defense in depth for `Request.Path`)

Adds regression theories on all three surfaces covering `//`, `///`, `//////`, `/\`
shapes, both as literal replacements and as back-reference-synthesized captures.

No public API surface change; `PublicAPI.Shipped.txt` / `PublicAPI.Unshipped.txt` unchanged.
@rokonec rokonec force-pushed the rokonec/rewrite-collapse-scheme-relative-redirect branch from fa44f13 to f9ce7b0 Compare June 1, 2026 20:59
@rokonec rokonec self-assigned this Jun 1, 2026
@rokonec rokonec marked this pull request as ready for review June 1, 2026 21:02
@rokonec rokonec requested a review from BrennanConroy as a code owner June 1, 2026 21:02
Copilot AI review requested due to automatic review settings June 1, 2026 21:02
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR fixes an open-redirect vulnerability (#66486) in the Rewrite middleware where scheme-relative URLs (//host, /\host) produced by AddRedirect, IIS URL Rewrite, or Apache mod_rewrite rules could be emitted as Location headers and resolved off-origin by browsers. It introduces a small UrlNormalizer.CollapseLeadingSlashes helper that mirrors the rejection logic in SharedUrlHelper.IsLocalUrl and applies it at the three sinks that emit Location headers or mutate Request.Path.

Changes:

  • Add internal static class UrlNormalizer with a zero-allocation fast-path CollapseLeadingSlashes method.
  • Invoke the helper in RedirectRule.ApplyRule, RedirectAction.ApplyAction, and RewriteAction.ApplyAction (defense-in-depth); remove the stale // TODO check for false positives comment.
  • Add [Theory] coverage for //, ///, //////, and /\ shapes across AddRedirect, IIS URL Rewrite, and Apache mod_rewrite (both redirect and rewrite flows).

Reviewed changes

Copilot reviewed 7 out of 7 changed files in this pull request and generated no comments.

Show a summary per file
File Description
src/Middleware/Rewrite/src/UrlNormalizer.cs New helper that collapses a leading run of //\ to a single /.
src/Middleware/Rewrite/src/RedirectRule.cs Normalize replacement before host/path parsing in AddRedirect.
src/Middleware/Rewrite/src/UrlActions/RedirectAction.cs Normalize pattern in IIS/Apache redirect imports; remove stale TODO.
src/Middleware/Rewrite/src/UrlActions/RewriteAction.cs Defense-in-depth normalization before assigning to Request.Path.
src/Middleware/Rewrite/test/MiddlewareTests.cs Theory covering AddRedirect scheme-relative collapse.
src/Middleware/Rewrite/test/IISUrlRewrite/MiddleWareTests.cs Theory covering IIS URL Rewrite back-reference collapse.
src/Middleware/Rewrite/test/ApacheModRewrite/ModRewriteMiddlewareTest.cs Theories covering Apache mod_rewrite redirect and rewrite collapse.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area-middleware Includes: URL rewrite, redirect, response cache/compression, session, and other general middlewares

Projects

None yet

Development

Successfully merging this pull request may close these issues.

URL Rewrite middleware does not detect scheme-relative URLs (//host) in redirect targets

2 participants