Path Traversal in Express with Hmac Signatures
Path Traversal in Express with Hmac Signatures — how this specific combination creates or exposes the vulnerability
Path Traversal in Express applications occurs when user-controlled input is used to construct file system paths without proper validation, allowing an attacker to access files outside the intended directory. When Hmac Signatures are used to verify the integrity of requests or configuration values, the presence of Path Traversal can undermine trust in the signed data and enable server-side request forgery or unauthorized file access.
Consider an Express route that reads a file based on a user-supplied parameter and uses an Hmac to validate a resource identifier:
const crypto = require('crypto');
const express = require('express');
const app = express();
const SHARED_SECRET = process.env.SECRET || 'weak-secret';
function verifySignature(token, payload) {
const hmac = crypto.createHmac('sha256', SHARED_SECRET);
hmac.update(payload);
return hmac.digest('hex');
}
app.get('/download', (req, res) => {
const { file, token } = req.query;
const expected = verifySignature(token, file);
if (req.query.signature !== expected) {
return res.status(403).send('Invalid signature');
}
// Vulnerable: file path from user input used directly
const fs = require('fs');
fs.readFile(`./uploads/${file}`, (err, data) => {
if (err) return res.status(404).send('Not found');
res.send(data);
});
});
In this example, although the Hmac Signature ensures that the file parameter has not been tampered with, it does not prevent Path Traversal within the resolved path. An attacker can supply file=../../../etc/passwd and a valid Hmac if the token is predictable or leaked. Because the signature is computed over the raw user input, a malicious file path that traverses directories can still be signed and accepted, leading to unauthorized file disclosure.
Additionally, if Hmac Signatures are used to sign configuration or endpoint references (e.g., URLs or API routes), Path Traversal in the application logic may allow an attacker to manipulate which resource is accessed. For instance, if the signature covers a combination of parameters that include a directory identifier, an attacker could attempt directory traversal sequences to reference sensitive system files or configuration files that should be inaccessible.
Another scenario involves log or inventory files generated by middleware that include user-controlled segments in paths. If those paths are later referenced using signed tokens, Path Traversal can redirect the reference to critical system files. The Hmac does not validate whether the path is safe; it only validates that the value has not been altered after signing. This separation of concerns means developers must implement path normalization and strict allowlists alongside signature verification.
Even when signatures are verified before path resolution, improper error handling or inconsistent validation order can expose timing differences that aid attackers. For example, an attacker might probe valid traversal patterns by observing differences in response times or error messages, even when the Hmac check fails, potentially leaking information about file system layout.
To mitigate these risks, Path Traversal defenses must be applied independently of Hmac signature checks. This includes canonicalizing paths, rejecting sequences like .., and confining file access to a strict base directory. Signature verification should confirm data integrity but not be relied upon to enforce access control or path safety.
Hmac Signatures-Specific Remediation in Express — concrete code fixes
Remediation focuses on ensuring that user input used in file system operations is constrained and that Hmac Signatures are applied only to safe, canonicalized values. The goal is to prevent Path Traversal regardless of signature validity.
First, normalize and validate the file path before using it in any system operation. Use a whitelist of allowed files or a strict prefix check to ensure the resolved path remains within the intended directory:
const crypto = require('crypto');
const express = require('express');
const path = require('path');
const fs = require('fs');
const app = express();
const SHARED_SECRET = process.env.SECRET || 'strong-secret';
function verifySignature(token, payload) {
const hmac = crypto.createHmac('sha256', SHARED_SECRET);
hmac.update(payload);
return hmac.digest('hex');
}
function safeFilePath(userFile) {
const resolved = path.resolve('./uploads', userFile);
const base = path.resolve('./uploads');
if (!resolved.startsWith(base)) {
return null;
}
return resolved;
}
app.get('/download', (req, res) => {
const { file, signature } = req.query;
const safeFile = safeFilePath(file);
if (!safeFile) {
return res.status(400).send('Invalid path');
}
const expected = verifySignature(signature, file);
if (signature !== expected) {
return res.status(403).send('Invalid signature');
}
fs.readFile(safeFile, (err, data) => {
if (err) return res.status(404).send('Not found');
res.send(data);
});
});
Second, avoid including user-controlled path components in the data covered by the Hmac if those components influence file system location. Instead, use the signature to authorize an action or a mapping identifier, then resolve the path server-side using a controlled mapping:
const crypto = require('crypto');
const express = require('express');
const path = require('path');
const app = express();
const SHARED_SECRET = process.env.SECRET;
const fileMap = {
'report': 'financials/report_q3.pdf',
'invoice': 'invoices/2025/inv_001.pdf',
};
function verifySignature(token, payload) {
const hmac = crypto.createHmac('sha256', SHARED_SECRET);
hmac.update(payload);
return hmac.digest('hex');
}
app.get('/document', (req, res) => {
const { docId, token } = req.query;
const expected = verifySignature(token, docId);
if (token !== expected) {
return res.status(403).send('Invalid signature');
}
const filePath = fileMap[docId];
if (!filePath) {
return res.status(400).send('Invalid document ID');
}
const safePath = path.resolve('./storage', filePath);
const base = path.resolve('./storage');
if (!safePath.startsWith(base)) {
return res.status(403).send('Path traversal detected');
}
res.sendFile(safePath);
});
Third, enforce strict input constraints and use constant-time comparison for signatures to reduce side-channel risks. Ensure that the value used in signature generation is canonical and does not include extraneous path segments. Combine these practices with logging and monitoring to detect probing behavior that may indicate Path Traversal attempts against signed endpoints.
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 |