Skip to content

Docs: System.Uri AbsolutePath and LocalPath decode percent-encoded slashes and null bytes differently #126402

Open

Description

@travis-burmaster

Summary

System.Uri.AbsolutePath and System.Uri.LocalPath decode percent-encoded characters differently. This discrepancy can be exploited to bypass path-based access control checks in applications that use AbsolutePath for authorization but LocalPath (or the decoded form) for file system or downstream routing operations.

Environment

  • .NET version: 9.0.312 (also reproduced on .NET 8)
  • OS: Linux x64

Reproduction

using System;

var cases = new[]
{
    "https://host/safe/path/..%2Fsecret",
    "https://host/safe/path/%2F..%2Fsecret",
    "https://host/safe%2Fadmin",
    "https://host/safe/path%00secret",
    "https://host/safe/%2e%2e%2f/secret",
};

foreach (var url in cases)
{
    var uri = new Uri(url);
    Console.WriteLine($"URL:          {url}");
    Console.WriteLine($"AbsolutePath: {uri.AbsolutePath}");
    Console.WriteLine($"LocalPath:    {uri.LocalPath}");
    Console.WriteLine($"Differ:       {uri.AbsolutePath != uri.LocalPath}");
    Console.WriteLine();
}

Output:

URL:          https://host/safe/path/..%2Fsecret
AbsolutePath: /safe/path/..%2Fsecret
LocalPath:    /safe/path/../secret
Differ:       True

URL:          https://host/safe/path/%2F..%2Fsecret
AbsolutePath: /safe/path/%2F..%2Fsecret
LocalPath:    /safe/path//../secret
Differ:       True

URL:          https://host/safe%2Fadmin
AbsolutePath: /safe%2Fadmin
LocalPath:    /safe/admin
Differ:       True

URL:          https://host/safe/path%00secret
AbsolutePath: /safe/path%00secret
LocalPath:    /safe/pathsecret
Differ:       True

URL:          https://host/safe/%2e%2e%2f/secret
AbsolutePath: /safe/..%2f/secret
LocalPath:    /safe/..//secret
Differ:       True

Impact

Applications that implement path-based access control using AbsolutePath are vulnerable to bypass when the actual path resolution uses LocalPath or a decoded variant. A concrete attack scenario:

// Middleware checks access using AbsolutePath
var uri = new Uri("https://host" + request.RawUrl);
if (!uri.AbsolutePath.StartsWith("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/api/public"))
    return Forbidden();

// But routes or serves files using LocalPath
ServeFile(uri.LocalPath); // attacker reaches /api/private via /api/public/..%2Fprivate

With the URL /api/public/..%2Fprivate:

  • AbsolutePath = /api/public/..%2Fprivate → check passes
  • LocalPath = /api/public/../private = /api/private → traversal succeeds

Root Cause

AbsolutePath returns the path with percent-encoded characters preserved (RFC 3986 compliant). LocalPath performs additional decoding including %2F → / and strips null bytes, but does not normalize the resulting path segments. This creates a semantic gap between the two properties.

Suggested Fix / Documentation

At minimum, the documentation for Uri.AbsolutePath and Uri.LocalPath should include an explicit security warning that the two properties can represent different effective paths, and that using either for security-sensitive path checks without normalization is unsafe.

Ideally, LocalPath should either:

  1. Normalize the decoded path (resolve .. segments after decoding), or
  2. Throw or warn when the decoded path traverses above the root

Discovered via manual audit of System.Uri in .NET 9.0.312.
Reported by Travis Burmaster — travis@burmaster.com

Activity

  1. dotnet-policy-service commented on Apr 1, 2026

    @dotnet-policy-service
    Contributor

    Tagging subscribers to this area: @dotnet/area-system-io
    See info in area-owners.md if you want to be subscribed.

  2. dotnet-policy-service commented on Apr 1, 2026

    @dotnet-policy-service
    Contributor

    Tagging subscribers to this area: @karelz, @dotnet/ncl
    See info in area-owners.md if you want to be subscribed.

  3. MihaZupan commented on Apr 7, 2026

    @MihaZupan
    Member

    I'm not convinced that's a bug, just different properties meant for different things giving you back different results.
    If you are making decisions based on one and then using the other, that's a mistake of the caller IMO.

    Calling it out in docs seems fine though.

  4. added
    documentationDocumentation bug or enhancement, does not impact product or test code
    and removed
    untriagedNew issue has not been triaged by the area owner
    on Apr 7, 2026
  5. added this to the Future milestone on Apr 7, 2026
  6. changed the title [-]Bug: System.Uri AbsolutePath and LocalPath decode percent-encoded slashes and null bytes differently, enabling path traversal bypass[/-] [+]Docs: System.Uri AbsolutePath and LocalPath decode percent-encoded slashes and null bytes differently[/+] on May 14, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    area-System.NetdocumentationDocumentation bug or enhancement, does not impact product or test code

    Type

    No type

    Projects

    No projects

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions