Clickjacking in Nestjs with Hmac Signatures
Clickjacking in Nestjs with Hmac Signatures — how this specific combination creates or exposes the vulnerability
Clickjacking is a client-side UI redress attack where an attacker tricks a user into interacting with a hidden or disguised element inside an invisible or misleading layer. In a NestJS application that uses Hmac Signatures for request authentication, introducing security headers incorrectly—or omitting frame-protection headers—can leave the app vulnerable even when the signature logic is sound.
Hmac Signatures typically protect the integrity and authenticity of requests by verifying a signature derived from selected headers, a timestamp, and a shared secret. This mechanism helps prevent tampering and replay, but it does not inherently prevent the response from being embedded in an attacker-controlled page via <iframe>, <object>, or <embed>. If a NestJS endpoint that validates Hmac Signatures returns responses without Content-Security-Policy frame-ancestors rules or X-Frame-Options, an attacker can host a page that frames the trusted endpoint. The user’s browser will load the framed content, and UI elements such as buttons or links can be positioned transparently over the attacker’s controls, causing unintended actions when the user interacts with what they believe is a different site.
The risk is compounded when the Hmac verification focuses solely on request headers and does not account for the possibility that a legitimate authenticated request can be coerced via a forged UI context. For example, an endpoint that processes fund transfers based on a verified Hmac Signature may still be invoked through a malicious frame if the response is not protected by frame-locking headers. Attackers can overlay invisible submit buttons on top of legitimate UI components, leveraging the user’s authenticated session to execute actions without explicit consent. Since the Hmac validation passes, the server treats the request as valid, making the combination of strong signature verification and absent frame protection a particularly dangerous scenario.
To determine whether a NestJS endpoint is exposed, security scans such as those performed by middleBrick evaluate the presence and correctness of frame-protection headers alongside the authentication mechanism. The tool checks whether Content-Security-Policy includes frame-ancestors or whether X-Frame-Options is set, and correlates this with the presence of Hmac Signature validation in request handling. This combined analysis reveals whether strong request integrity controls inadvertently create a false sense of security while the UI surface remains embeddable.
Hmac Signatures-Specific Remediation in Nestjs — concrete code fixes
Remediation requires both robust Hmac Signature verification and explicit framing controls. On the server side, NestJS middleware or guards should validate the Hmac Signature before processing the request, and responses must include headers that prevent framing. Below are concrete examples that demonstrate both aspects in a NestJS application.
Hmac Signature verification middleware
The following middleware validates an Hmac Signature present in a custom header (e.g., X-API-Signature) against a shared secret and a canonical string composed of selected headers, timestamp, and payload. It rejects requests with invalid signatures and includes security headers in the response.
import { Injectable, NestMiddleware } from '@nestjs/common';
import { Request, Response, NextFunction } from 'express';
import * as crypto from 'crypto';
@Injectable()
export class HmacValidationMiddleware implements NestMiddleware {
private readonly sharedSecret = process.env.HMAC_SHARED_SECRET || 'change-this-to-a-secure-random-string';
use(req: Request, res: Response, next: NextFunction) {
// Exclude verification for preflight to avoid complexity; rely on framework handling
if (req.method === 'OPTIONS') {
return next();
}
const receivedSignature = req.header('X-API-Signature');
const timestamp = req.header('X-Timestamp');
const nonce = req.header('X-Nonce');
if (!receivedSignature || !timestamp || !nonce) {
res.set({
'Content-Security-Policy': "frame-ancestors 'none'",
'X-Frame-Options': 'DENY',
});
return res.status(400).send('Missing required headers');
}
// Canonical string: method + path + timestamp + nonce + body (or sorted keys for JSON)
const canonical = `${req.method}\n${req.path}\n${timestamp}\n${nonce}\n${JSON.stringify(req.body)}`;
const expected = crypto.createHmac('sha256', this.sharedSecret).update(canonical).digest('hex');
// Constant-time comparison to avoid timing attacks
const isValid = crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(receivedSignature));
// Enforce frame protection on every response
res.set({
'Content-Security-Policy': "frame-ancestors 'none'",
'X-Frame-Options': 'DENY',
'X-Content-Type-Options': 'nosniff',
});
if (!isValid) {
return res.status(401).send('Invalid signature');
}
next();
}
}
Applying the middleware in a NestJS module
Register the middleware globally or on specific routes. The example applies it to all routes under /api, ensuring Hmac verification and frame protection are consistently enforced.
import { Module, MiddlewareConsumer, RequestMethod } from '@nestjs/common';
import { HmacValidationMiddleware } from './hmac-validation.middleware';
@Module({
controllers: [],
providers: [],
})
export class SecurityModule {
constructor(consumer: MiddlewareConsumer) {
consumer
.apply(HmacValidationMiddleware)
.forRoutes({ path: 'api', method: RequestMethod.ALL });
}
}
Frontend defensive practices
While server-side headers are primary, frontends should avoid making state-changing requests via GET and should include a custom, attacker-uncontrollable header (e.g., X-Requested-With) to complement Hmac verification. This reduces the risk of accidental leakage via accidental GET requests or misconfigured endpoints.
Testing the mitigation
Use curl to confirm headers are present and framing is blocked:
curl -I https://api.example.com/api/resource \
-H 'X-Timestamp: 1700000000' \
-H 'X-Nonce: unique-value-123' \
-H 'X-API-Signature: '
Expected response headers should include Content-Security-Policy: frame-ancestors 'none' and X-Frame-Options: DENY.