Symlink Attack in Aspnet with Hmac Signatures
Symlink Attack in Aspnet with Hmac Signatures — how this specific combination creates or exposes the vulnerability
A Symlink Attack in an ASP.NET context occurs when an attacker tricks the application into reading or writing files outside the intended directory by leveraging symbolic links. When HMAC signatures are used to protect the integrity of request data or file paths, a common design mistake is to sign only the file name or a subset of the path while trusting the rest of the path supplied by the client. If the server resolves the path after verifying the HMAC, an attacker can create a symbolic link that points to a sensitive location (such as /etc/passwd or an application configuration file) and supply the benign name that matches the signature. Because the signature was computed over the attacker-controlled logical name, verification passes, and the server follows the link to the privileged resource.
Consider an endpoint that accepts a file identifier, looks up a physical path on disk, verifies an HMAC included in headers or query parameters, and then opens the file. If the identifier is not canonicalized and validated against directory traversal or symlink resolution, an attacker can first create a symlink in a location the application writes to (for example, a temporary upload folder) that points to a sensitive file. When the application later resolves the path, it follows the symlink. The HMAC may cover the identifier and a timestamp, but if the mapping from identifier to path is insecure, the signature does not protect the actual resolved location.
Real-world patterns that increase risk include using relative paths, failing to restrict parent directory sequences (..), and resolving paths on the server after signature verification rather than before. In ASP.NET, this can intersect with features such as static file serving or user-content handling when IWebHostEnvironment paths are combined with unchecked input. The vulnerability is not in HMAC itself, which correctly ensures data integrity, but in the order and scope of operations: signing too little, resolving too late, and permitting the runtime to follow links to sensitive locations.
Hmac Signatures-Specific Remediation in Aspnet — concrete code fixes
To mitigate symlink risks while using HMAC signatures in ASP.NET, ensure that path resolution and canonicalization occur before signature verification and that the resolved path is constrained to an allowed base directory. Do not sign only the client-supplied name; include enough context (such as a user ID or a scoped identifier) and validate the final path on the server.
Use Path.GetFullPath combined with Path.IsPathRooted and directory normalization to reject paths that escape the target folder. Compare the normalized path prefix against an absolute allowed base directory, and avoid using user input directly in file system operations after signature validation.
The following example demonstrates a safer approach in ASP.NET Core. It computes the HMAC over a combination of a stable file key, a user identifier, and a timestamp, and it resolves the file path only after strict validation:
using System.Security.Cryptography;
using System.Text;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Http;
public class FileService
{
private readonly string _baseDirectory;
private readonly byte[] _secretKey;
public FileService(IWebHostEnvironment env)
{
_baseDirectory = Path.GetFullPath(env.ContentRootPath + "/safe-files/");
_secretKey = Encoding.UTF8.GetBytes(Environment.GetEnvironmentVariable("HMAC_SECRET") ?? string.Empty);
}
public string ComputeHmac(string fileKey, string userId, long timestamp)
{
using var hmac = new HMACSHA256(_secretKey);
var payload = $"{fileKey}:{userId}:{timestamp}";
var hash = hmac.ComputeHash(Encoding.UTF8.GetBytes(payload));
return Convert.ToBase64String(hash);
}
public bool TryResolvePath(string fileKey, string userId, long timestamp, string receivedHmac, out string safePath)
{
safePath = string.Empty;
if (!long.TryParse(timestamp.ToString(), out var ts) || ts < DateTimeOffset.UtcNow.ToUnixTimeSeconds() - 300)
{
return false;
}
var expected = ComputeHmac(fileKey, userId, timestamp);
if (!CryptographicOperations.FixedTimeEquals(Convert.FromBase64String(receivedHmac), Encoding.UTF8.GetBytes(expected)))
{
return false;
}
var candidate = Path.GetFullPath(Path.Combine(_baseDirectory, fileKey));
if (!candidate.StartsWith(_baseDirectory, StringComparison.OrdinalIgnoreCase))
{
return false;
}
safePath = candidate;
return File.Exists(candidate);
}
public ActionResult Download(string fileKey, string userId, long timestamp, string hmac)
{
if (TryResolvePath(fileKey, userId, timestamp, hmac, out var path))
{
var stream = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read);
return File(stream, "application/octet-stream", Path.GetFileName(path));
}
return Unauthorized();
}
}
In this pattern, the base directory is fixed, the file key is appended after canonicalization, and the resolved path is verified to start with the allowed prefix before any file access. HMAC covers a structured payload that includes a timestamp to limit replay, and the comparison uses a fixed-time operation to avoid timing attacks. This approach reduces the window for symlink-based attacks by resolving and validating paths before the runtime follows any links.
Frequently Asked Questions
Does HMAC alone prevent symlink attacks in ASP.NET?
What additional practices reduce symlink risks in ASP.NET file handling?
.. or root elements, resolve paths with Path.GetFullPath, compare the final path prefix against the allowed base, and avoid using raw user input in filesystem operations. Consider storing files outside the web root and serving them via a controlled endpoint.