Directory Traversal - alexwaibel/vulnerable-aspnetcore-webapp GitHub Wiki
Introduction
Directory traversal (aka path traversal) vulnerabilities occur where improperly validated user input is used in the construction of a filesystem path. This can enable an attacker to access arbitrary files on the host.
Useful Resources
- PortSwigger write-up
- Security considerations when serving static files in .NET
Write-up
Once you've started the application, navigate to the /ImagePreview endpoint. Here you'll see an image preview.
Good example
The image shown is not itself vulnerable to path traversal attacks. It serves the image using a hard-coded path to a file in the web root
<img src="~/images/hardhat.jpg" class="img-fluid" alt="Protective equipment" />
Which has been enabled using the UseStaticFiles method in Program.cs.
app.UseStaticFiles();
Bad example
However, this page expose a parameter UnvalidatedFileName which can be used to specify another file name. Try /ImagePreview?UnvalidatedFileName=danger.jpg for example and you should see another image from the web root.
This alone is fine, the web root is intended to be public, but this file name is blindly combined with a known file path using Path.Combine, which is a common source of directory traversal attacks. You can add a relative path to the file name and access any file on the host.
return FetchImageDataURLFromPath(Path.Combine([System.IO.Directory.GetCurrentDirectory(), "wwwroot", "images", fileName]));
Try /ImagePreview?UnvalidatedFileName=../../.gitignore for example and you should see the contents of this project's .gitignore.
This example also highlights that properties using model binding on GET requests become a vector for user input and must be validated.
Mitigation
If possible, it is best to avoid passing user-supplied strings to filesystem APIs. First, consider if you can rewrite your feature in a safer way. If passing user-supplied strings to a filesystem API can't be avoided, it's recommended to both:
- Validate the user input before it is used
- Ideally comparing against an allowlist
- Otherwise, verifying input doesn't contain any unexpected characters
- Be careful of clever encodings (such as double URL encoding escape sequences), which can often trivially bypass such validation
- In .NET Path.GetInvalidFileNameChars will attempt to identify invalid path characters, but as the remark section states the set of dangerous characters can very widely by file system.
- Combine the validated user input string with the filename input using a filesystem API, and then verify the result path matches your expectation.
Validate against allow-list
The application exposes a parameter AllowlistedFileName which mitigates these path traversals by comparing the input to an allow-list and only permitting known good values. For instance you may request /ImagePreview?AllowlistedFileName=hardhat.jpg
But there is no data returned if you request /ImagePreview?AllowlistedFileName=danger.jpg or /ImagePreview?AllowlistedFileName=../../.gitignore
Path and extension validation
The application also exposes a FileName parameter on this route, which uses simple filesystem APIs to try to remove path characters and validate the extension and path are as expected. This catches and prevents many traversal strings, but it may still be possible for a cleverly encoded payload to slip through.
var sanitizedFileName = Path.GetFileName(fileName);
var expectedPath = Path.Combine([System.IO.Directory.GetCurrentDirectory(), "wwwroot", "images"]);
var expectedExtension = ".jpg";
var fullPath = Path.Combine(expectedPath, sanitizedFileName);
return Path.GetDirectoryName(fullPath) == expectedPath && Path.GetExtension(fullPath) == expectedExtension ? FetchImageDataURLFromPath(fullPath) : "";