Command Injection in Aspnet with Hmac Signatures
Command Injection in Aspnet with Hmac Signatures — how this specific combination creates or exposes the vulnerability
Command injection occurs when an attacker can cause an application to execute unintended operating system commands. In ASP.NET applications that use HMAC signatures to validate requests, a common misconfiguration is including user-controlled data inside the string that is signed without proper canonicalization, and then passing a field from the signed payload to a system process or shell. For example, if a signature is computed over a concatenation of user input fields and later the application uses one of those fields in Process.Start or System.Diagnostics.Process without validation, the signed value can be abused to inject shell metacharacters.
Consider an API that signs parameters such as action, resource, and target using HMAC-SHA256. If the server reuses the signed target value in a shell command to select a file or invoke a tool, and the signature does not bound the command template, an attacker who can influence target may append shell operators and commands. Because the signature validates only integrity (not semantics), the malicious additions may still produce a valid signature if the server does not enforce strict field separation and allowed values. This pattern is risky when the application runs with elevated privileges or exposes endpoints that trigger backend utilities.
In ASP.NET, a vulnerable implementation might compute the HMAC over raw query strings or form fields and then use one of those fields in Process invocations. For instance, reading a filename from a signed parameter and passing it directly to ffmpeg or tar allows an attacker to escape the expected argument boundaries using shell metacharacters like ;, &, or |. Even if the signature includes a timestamp or nonce, if the server does not validate the content and length of each signed field, the attack surface remains. This is compounded when the application deserializes JSON or form data into objects and then reconstructs a command line using string interpolation, because the HMAC may cover only a subset of the data used in the command.
An example attack chain: an endpoint accepts fileId and action, signs them, and later runs a system command such as convert {fileId}. If fileId is not strictly validated and the signature does not prevent extra parameters, an attacker can set fileId to ';cat /etc/passwd #. If the server reconstructs the command via string concatenation, the shell will execute the injected command. Because the signature appears valid, the request bypasses integrity checks. This illustrates why HMAC signatures must be paired with strict allowlists, canonical command templates, and avoidance of shell injection-prone APIs.
Hmac Signatures-Specific Remediation in Aspnet — concrete code fixes
To remediate command injection when using HMAC signatures in ASP.NET, separate signing from command construction, validate all inputs against strict allowlists, and avoid passing raw user data to shell commands. Use a parameterized command pattern or a library that does not invoke a shell, and ensure the HMAC covers the exact data used for command decisions.
Example: Safe HMAC validation and command execution in ASP.NET Core
Use System.Security.Cryptography.HMACSHA256 to validate a signature, then map validated inputs to a predefined command template. Never concatenate raw input into a shell command.
using System;
using System.Security.Cryptography;
using System.Text;
using System.Diagnostics;
public class CommandService
{
private static readonly byte[] Key = Convert.FromBase64String("YOUR_BASE64_KEY_HERE");
public bool TryExecute(string fileId, string signature, out string output)
{
output = string.Empty;
if (!IsValidSignature(fileId, signature))
{
return false;
}
// Strict allowlist: only safe file identifiers are permitted
var allowedFiles = new System.Collections.Generic.HashSet<string> { "report", "export", "summary" };
if (!allowedFiles.Contains(fileId))
{
return false;
}
// Use a process start that does not invoke a shell
var startInfo = new ProcessStartInfo
{
FileName = "/usr/bin/convert", // absolute path
Arguments = $"\"/data/in/{fileId}.pdf\" \" /data/out/{fileId}.png\"",
RedirectStandardOutput = true,
UseShellExecute = false,
CreateNoWindow = true
};
using (var process = Process.Start(startInfo))
{
output = process.StandardOutput.ReadToEnd();
process.WaitForExit();
}
return true;
}
private bool IsValidSignature(string fileId, string signature)
{
using (var hmac = new HMACSHA256(Key))
{
var computed = hmac.ComputeHash(Encoding.UTF8.GetBytes(fileId));
var expected = Convert.ToBase64String(computed);
// Use a time-constant comparison to avoid timing attacks
return CryptographicOperations.FixedTimeEquals(
Convert.FromBase64String(signature),
Encoding.UTF8.GetBytes(expected));
}
}
}
Key practices:
- Do not include command syntax (e.g.,
|,&,&&) in signed fields; keep the signature over canonical identifiers only. - Use allowlists for file names or action types; do not rely on blacklists.
- Prefer
ProcessStartInfowithUseShellExecute = falseand absolute paths for executables. - Avoid passing raw user input directly to shell utilities; if shell features are required, use a controlled wrapper with strict validation.
- Apply fixed-time comparisons for HMAC verification to prevent timing-based side channels.
Example: HMAC with JSON payload in ASP.NET Core controller
Sign a normalized JSON representation and validate before using any field in sensitive operations.
[ApiController]
[Route("api/[controller]")]
public class ExportController : ControllerBase
{
private static readonly byte[] Key = Convert.FromBase64String("YOUR_BASE64_KEY_HERE");
[HttpPost("export")]
public IActionResult Export([FromBody] ExportRequest request)
{
// Recompute signature over a canonical JSON (sorted keys, no extra whitespace)
var canonicalJson = System.Text.Json.JsonSerializer.Serialize(request, new JsonSerializerOptions { PropertyNamingPolicy = JsonNamingPolicy.CamelCase });
string expectedSig;
using (var hmac = new HMACSHA256(Key))
{
var hash = hmac.ComputeHash(Encoding.UTF8.GetBytes(canonicalJson));
expectedSig = Convert.ToBase64String(hash);
}
if (!TimingSafeEquals(expectedSig, request.Signature))
{
return Unauthorized();
}
// Safe handling: map to a controlled command template
if (!Enum.TryParse<ExportType>(request.Type, out var exportType))
{
return BadRequest("Invalid type");
}
// Use a non-shell executor or library appropriate to the operation
// ...
return Ok();
}
private bool TimingSafeEquals(string a, string b)
{
var aBytes = Encoding.UTF8.GetBytes(a);
var bBytes = Encoding.UTF8.GetBytes(b);
return CryptographicOperations.FixedTimeEquals(aBytes, bBytes);
}
}
public class ExportRequest
{
public string Type { get; set; }
public string Signature { get; set; }
}
Related CWEs: inputValidation
| CWE ID | Name | Severity |
|---|---|---|
| CWE-20 | Improper Input Validation | HIGH |
| CWE-22 | Path Traversal | HIGH |
| CWE-74 | Injection | CRITICAL |
| CWE-77 | Command Injection | CRITICAL |
| CWE-78 | OS Command Injection | CRITICAL |
| CWE-79 | Cross-site Scripting (XSS) | HIGH |
| CWE-89 | SQL Injection | CRITICAL |
| CWE-90 | LDAP Injection | HIGH |
| CWE-91 | XML Injection | HIGH |
| CWE-94 | Code Injection | CRITICAL |