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

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.

New image of danger sign displayed

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.

.gitignore file contents shown decoded in Burp Suite proxy view

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

Duplicate hard had photo shown

But there is no data returned if you request /ImagePreview?AllowlistedFileName=danger.jpg or /ImagePreview?AllowlistedFileName=../../.gitignore

image

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) : "";