Docs: System.Uri AbsolutePath and LocalPath decode percent-encoded slashes and null bytes differently
- Dominant language
- C#
- Stars
- 18.3k
- Forks
- 5.6k
- PR merge metrics
- PR metrics pending
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
```csharp
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:
```csharp
// Middleware checks access using AbsolutePath
var uri = new Uri("https://host" + request.RawUrl);
if (!uri.AbsolutePath.StartsWith("/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*
Contributor guide
Assessment
This issue has not been assessed yet.