Derive URL Rewrite absolute-target mode from configured literal syntax
- Dominant language
- C#
- Stars
- 38.4k
- Forks
- 10.9k
- Avg merge
- 2d 10h
- Merged PRs (30d)
- 281
Description
## Summary
URL Rewrite currently determines whether a rewrite or redirect replacement is absolute after evaluating dynamic replacement content. Absolute-target selection should instead come from trusted configured syntax so runtime substitutions cannot change a path rewrite into a scheme-and-host rewrite, or turn a path redirect into a cross-host `Location` redirect (open redirect).
## What is wrong
* `RewriteRule` expands a .NET replacement and then checks the evaluated string for `Uri.SchemeDelimiter` before choosing between path-only handling and `Request.Scheme`/`Request.Host` mutation.
* Imported IIS and Apache rules similarly evaluate a segmented `Pattern` in `RewriteAction` before selecting the same behavior.
* The redirect surfaces have the same shape. `RedirectRule` (from `AddRedirect`) expands a .NET replacement and then scans the evaluated string for `Uri.SchemeDelimiter` to split out scheme/host, emitting an absolute cross-host `Location` header via `UriHelper.BuildAbsolute`.
* Imported IIS and Apache redirect rules do the same in `RedirectAction`: an evaluated pattern that already contains `Uri.SchemeDelimiter` bypasses the leading-slash guard and is written to `Location` verbatim.
* `UrlNormalizer.CollapseLeadingSlashes` (PR #66961 / PR #67928, issue #67812) already runs on all of these surfaces, but it only neutralizes the scheme-relative `//host` / `\host` form; it returns an absolute `scheme://host` unchanged, so the absolute `scheme://` discriminator remains attacker-completable on the redirect surfaces too.
* This breaks the invariant that authority-changing behavior is selected by trusted rule configuration. Runtime data can currently supply or complete the discriminator that selects absolute-target mode.
* The construction path already retains enough provenance to enforce the invariant: direct rules retain the raw replacement, and imported rules retain ordered literal and dynamic segments.
## Why it matters (defense in depth)
* Rewrite configuration is expected to define whether a rule changes only path and query state or intentionally changes request authority.
* Deriving that choice from configured syntax strengthens the boundary between trusted configuration and runtime substitution while preserving explicit absolute rewrite templates.
* The change also makes behavior align more closely with the public `AddRewrite` description, which describes path rewriting.
## Affected code
* `src/Middleware/Rewrite/src/RewriteRule.cs:17-25` - retains the raw direct replacement at construction time
* `src/Middleware/Rewrite/src/RewriteRule.cs:27-101` - currently selects absolute handling from the evaluated replacement
* `src/Middleware/Rewrite/src/Pattern.cs:6-24` - owns ordered imported replacement segments and runtime evaluation
* `src/Middleware/Rewrite/src/PatternSegments/LiteralSegment.cs:6-19` - owns trusted configured literal text
* `src/Middleware/Rewrite/src/UrlActions/RewriteAction.cs:16-43` - receives the parsed pattern and Apache escape/query options
* `src/Middleware/Rewrite/src/UrlActions/RewriteAction.cs:45-135` - currently selects absolute handling after evaluation and optional escaping
* `src/Middleware/Rewrite/src/RewriteOptionsExtensions.cs:46-50` - constructs direct rewrite rules; no signature change is required
* `src/Middleware/Rewrite/src/IISUrlRewrite/InputParser.cs:56-83` - preserves ordered IIS replacement segments
* `src/Middleware/Rewrite/src/IISUrlRewrite/InputParser.cs:85-172` - emits dynamic IIS segments
* `src/Middleware/Rewrite/src/IISUrlRewrite/InputParser.cs:204-225` - emits maximal contiguous IIS literal segments
* `src/Middleware/Rewrite/src/IISUrlRewrite/UrlRewriteFileParser.cs:188-251` - parses replacement text and constructs imported actions; no call-site change is required
* `src/Middleware/Rewrite/src/ApacheModRewrite/TestStringParser.cs:32-80` - preserves ordered Apache replacement segments
* `src/Middleware/Rewrite/src/ApacheModRewrite/TestStringParser.cs:91-140` - emits dynamic Apache segments
* `src/Middleware/Rewrite/src/ApacheModRewrite/TestStringParser.cs:148-168` - emits maximal contiguous Apache literal segments
* `src/Middleware/Rewrite/src/ApacheModRewrite/RuleBuilder.cs:173-223` - passes Apache escape and query flags; no signature change is required
* `src/Middleware/Rewrite/src/RedirectRule.cs:27-105` - selects absolute handling from the evaluated replacement (`IndexOf(Uri.SchemeDelimiter)` split, then `UriHelper.BuildAbsolute` -> `Response.Headers.Location`); `CollapseLeadingSlashes` applied at line 46 does not cover the absolute form
* `src/Middleware/Rewrite/src/UrlActions/RedirectAction.cs:29-81` - the `!pattern.Contains(Uri.SchemeDelimiter) && pattern[0] != '/'` guard (line 48) lets an evaluated `scheme://` pattern reach `Response.Headers.Location` untouched (lines 65/73/77); this single class serves both IIS (`UrlRewriteFileParser`) and Apache (`RuleBuilder`) redirect imports
## Recommended fix
* Add an internal helper that recognizes absolute intent only when configured text begins with a syntactically valid URI scheme followed by the literal `scheme://` delimiter. Use `Uri.CheckSchemeName` for the configured prefix. Do not parse the full destination or change `UriHelper.FromAbsolute`.
* In `RewriteRule`, compute an immutable absolute-mode flag from the raw replacement in the constructor and use that flag in `ApplyRule`. All .NET replacement substitutions begin with `$`, which cannot be part of a valid scheme prefix, so runtime substitution cannot create or complete the discriminator.
* Expose the configured text from `LiteralSegment` through an internal member. In `Pattern`, compute immutable absolute intent only when the first segment is a `LiteralSegment` containing the complete discriminator. The existing IIS and Apache parsers already emit maximal literal runs, so they need no changes.
* In `RewriteAction`, compute an immutable flag from `pattern.IsAbsolute`. Disable that flag when `escapeBackReferences` is enabled because Apache `[B]` escapes the complete evaluated output before path handling today. Keep evaluation, query flags, `UriHelper.FromAbsolute`, and request assignments otherwise unchanged.
* Apply the same configured-syntax classification to the redirect surfaces. Compute an immutable absolute-mode flag in the `RedirectRule` constructor from the raw `Replacement`, and in `RedirectAction` from `pattern.IsAbsolute`, reusing the same `RewritePatternHelper`. A redirect enters scheme/host splitting only when trusted configured syntax carries the complete literal `scheme://` discriminator; otherwise the evaluated result is treated as path-and-query and emitted as a relative `Location`. `UrlNormalizer.CollapseLeadingSlashes` stays in place for the scheme-relative form.
* Likely production changes are `src/Middleware/Rewrite/src/RewritePatternHelper.cs`, `src/Middleware/Rewrite/src/RewriteRule.cs`, `src/Middleware/Rewrite/src/Pattern.cs`, `src/Middleware/Rewrite/src/PatternSegments/LiteralSegment.cs`, `src/Middleware/Rewrite/src/UrlActions/RewriteAction.cs`, `src/Middleware/Rewrite/src/RedirectRule.cs`, and `src/Middleware/Rewrite/src/UrlActions/RedirectAction.cs`.
* Runtime valid-scheme validation is not sufficient because it still lets evaluated content select the mode. Removing all authority mutation is not selected because it would break established explicit absolute templates. Revalidating against inbound host policy is not selected because inbound and rewrite-destination policy are different concerns. A distinct authority-rewrite API is a clearer long-term design, but it requires public API and migration planning beyond this focused change.
* This is an intentional compatibility change for configurations that assemble any part of the scheme discriminator dynamically. Migrate those rules by placing the complete literal `scheme://` prefix at the start of trusted configuration. Dynamic host and path components after that prefix remain supported. Document possible query ordering and fragment-handling changes when a formerly absolute dynamic result moves to relative handling.
* No public API or API baseline change is required. The change is suitable for servicing as defense-in-depth hardening with release-note coverage. A future major version can consider making generic rewrites path-only and exposing authority changes through an explicit capability.
## Acceptance criteria
* [ ] A rewrite changes `Request.Scheme` or `Request.Host` only when trusted configured syntax begins with a complete literal valid `scheme://` discriminator.
* [ ] Runtime substitutions, IIS variables/maps/transforms, and Apache variables/backreferences cannot create or complete the discriminator that selects absolute mode.
* [ ] Explicit literal absolute templates remain supported, including dynamic host or path components after the configured discriminator.
* [ ] Apache `[B]` rewrites preserve current escaped-path behavior and do not enter absolute mode.
* [ ] Direct tests cover all documented .NET replacement forms, valid literal scheme forms, and query ordering after relative handling.
* [ ] IIS parser and middleware tests cover first-literal classification, leading dynamic segment types, literal absolute compatibility, and query append/replace behavior.
* [ ] Apache parser and middleware tests cover first-literal classification, leading variables/backreferences, literal absolute compatibility, `[B]`, `QSA`, and `QSD`.
* [ ] A redirect emits an absolute cross-host `Location` only when trusted configured syntax begins with a complete literal valid `scheme://` discriminator; runtime substitutions and IIS/Apache back-references cannot create or complete it.
* [ ] `RedirectRule` and `RedirectAction` tests cover literal absolute compatibility, leading dynamic back-references, and relative-`Location` handling when the discriminator is not configured.
* [ ] PR #66961 leading-slash normalization remains effective wherever that change is present or ported.
* [ ] `AddRewrite`, `AddRedirect`, and importer public signatures and API baselines remain unchanged.
* [ ] Compatibility and migration guidance is included in release notes.
Contributor guide
Research direction
Start with the affected RewriteRule.cs, Pattern.cs, LiteralSegment.cs, RewriteAction.cs, RedirectRule.cs, and RedirectAction.cs files, then trace the direct, IIS, and Apache parser entry points and their middleware tests. Done means absolute behavior is selected only by a complete literal configured scheme:// prefix, runtime substitutions cannot create it, existing literal absolute templates and Apache [B] behavior remain supported, and compatibility guidance is included.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- csharp
- Domain
- backend, security
- Issue type
- Bug
- Difficulty
- 5/5
- Estimated time
- Over a week
- Activity status
- Quiet
- Clarity
- Clearly specified
- Newbie friendliness
- 45/100