Repository navigation
Docs: System.Uri AbsolutePath and LocalPath decode percent-encoded slashes and null bytes differently #126402
Description
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/..%2FprivateWith the URL /api/public/..%2Fprivate:
AbsolutePath=/api/public/..%2Fprivate→ check passesLocalPath=/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:
- Normalize the decoded path (resolve
..segments after decoding), or - 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
dotnet-policy-service commentedon Apr 1, 2026
Tagging subscribers to this area: @dotnet/area-system-io
See info in area-owners.md if you want to be subscribed.
dotnet-policy-service commentedon Apr 1, 2026
Tagging subscribers to this area: @karelz, @dotnet/ncl
See info in area-owners.md if you want to be subscribed.
MihaZupan commentedon Apr 7, 2026
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.
[-]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[/+]