Format String in Express with Hmac Signatures
Format String in Express with Hmac Signatures — how this specific combination creates or exposes the vulnerability
A format string vulnerability in an Express application using Hmac Signatures typically arises when user-controlled input is passed directly into a formatting function such as util.format or console.log while also being involved in Hmac signature generation or logging. For example, if an endpoint accepts a user-supplied string that is interpolated into a log message using a format string, an attacker can supply format specifiers like %s, %x, or %n. These specifiers may cause the logging function to read from the stack or heap, potentially leaking parts of the Hmac key or signature if those values are present in memory at the time of formatting.
Consider an Express route that builds a logging message using user input and also uses Hmac Signatures to sign a payload:
const express = require('express');
const crypto = require('crypto');
const app = express();
app.use(express.json());
const secret = 'super-secret-key';
app.post('/webhook', (req, res) => {
const { data, userSupplied } = req.body;
const hmac = crypto.createHmac('sha256', secret);
hmac.update(JSON.stringify(data));
const signature = hmac.digest('hex');
// Risky: userSupplied used directly in a format string via console.log
console.log(`Received: ${userSupplied}, signature: ${signature}`);
res.json({ received: data, signature });
});
app.listen(3000);
If the attacker sends userSupplied as %%s %%x %%x, the logging call may behave unexpectedly depending on the implementation of console.log and the underlying C bindings. In vulnerable environments, this can lead to reading memory contents (information disclosure) or even writing values via %n (arbitrary memory write), potentially exposing the Hmac secret or signature. The combination of format string issues and Hmac Signatures is particularly dangerous because the signature is sensitive: leaking it can enable replay attacks or aid in forging requests if the secret is also exposed.
Moreover, if the application uses formatted strings to construct responses or error messages that include the Hmac signature, an attacker may use format specifiers to manipulate the output or cause crashes (denial of service). For instance, supplying a crafted payload that leads to excessive memory reads can destabilize the process. Although Hmac Signatures themselves are not directly parsed as format strings, the surrounding code that logs or processes user input must be carefully audited to avoid these interactions.
Hmac Signatures-Specific Remediation in Express — concrete code fixes
To mitigate format string vulnerabilities when working with Hmac Signatures in Express, ensure that user-controlled data is never used directly in formatting functions. Instead, use strict concatenation or structured logging that does not interpret format specifiers. Below are concrete, safe patterns.
1. Avoid formatting functions for user input
Do not pass user input to console.log with interpolation that could be interpreted as format specifiers. Use concatenation or JSON-safe stringification.
const express = require('express');
const crypto = require('crypto');
const app = express();
app.use(express.json());
const secret = 'super-secret-key';
app.post('/webhook', (req, res) => {
const { data, userSupplied } = req.body;
const hmac = crypto.createHmac('sha256', secret);
hmac.update(JSON.stringify(data));
const signature = hmac.digest('hex');
// Safe: no format specifiers in console output
console.log('Received user input:', userSupplied);
console.log('Computed signature:', signature);
res.json({ received: data, signature });
});
app.listen(3000);
2. Use a structured logger with explicit message templates
If you need structured logging, use an object-based approach rather than string formatting that could be interpolated.
const pino = require('pino')();
const express = require('express');
const crypto = require('crypto');
const app = express();
app.use(express.json());
const secret = 'super-secret-key';
app.post('/webhook', (req, res) => {
const { data, userSupplied } = req.body;
const hmac = crypto.createHmac('sha256', secret);
hmac.update(JSON.stringify(data));
const signature = hmac.digest('hex');
pino.info({ event: 'webhook_received', userSupplied, signature }, 'webhook processed');
res.json({ received: data, signature });
});
app.listen(3000);
3. Validate and sanitize user input before any use
Reject or sanitize input that contains percent signs or other suspicious characters if they are not expected. For Hmac workflows, the signature should be treated as an opaque value and never concatenated into format strings.
const express = require('express');
const crypto = require('crypto');
const app = express();
app.use(express.json());
const secret = 'super-secret-key';
function containsFormatSpecifier(str) {
return /[%#0\- +0-9]*[diouxXeEfFgGaAcCsSpn]/.test(str);
}
app.post('/webhook', (req, res) => {
const { data, userSupplied } = req.body;
if (containsFormatSpecifier(String(userSupplied))) {
return res.status(400).json({ error: 'Invalid input: format specifiers not allowed' });
}
const hmac = crypto.createHmac('sha256', secret);
hmac.update(JSON.stringify(data));
const signature = hmac.digest('hex');
console.log('Received user input:', userSupplied);
console.log('Computed signature:', signature);
res.json({ received: data, signature });
});
app.listen(3000);
By following these practices, you ensure that Hmac Signatures and related sensitive values are not exposed through format string issues, reducing the risk of information disclosure or memory corruption.