HIGH bleichenbacher attacknestjsbearer tokens

Bleichenbacher Attack in Nestjs with Bearer Tokens

Bleichenbacher Attack in Nestjs with Bearer Tokens — how this specific combination creates or exposes the vulnerability

A Bleichenbacher attack is a chosen-ciphertext attack against asymmetric encryption padding schemes, most commonly PKCS#1 v1.5 in RSA. In a NestJS API that uses Bearer Tokens for authentication, this attack can manifest when an API endpoint performs decryption or signature verification using a private key or secret and reveals distinct timing differences or error messages depending on whether the padding is valid. An attacker who can send modified Bearer Tokens (e.g., JWTs or custom bearer payloads) crafted to target the padding verification routine can iteratively learn the plaintext or the key material by observing server responses and elapsed time.

In NestJS, this often occurs in scenarios where developers implement custom JWT verification or encrypt/decrypt tokens using Node.js crypto with RSA and PKCS#1 v1.5 padding. For example, consider an endpoint that decodes a Bearer Token and performs asymmetric decryption to extract user claims:

import { Controller, Get, Req } from '@nestjs/common';import * as crypto from 'crypto';@Controller()export class AuthController {  @Get('/user')  async getUser(@Req() req: any) {    const auth = req.headers['authorization'];    if (!auth?.startsWith('Bearer ')) {      throw new Error('Unauthorized');    }    const token = auth.split(' ')[1];    // Vulnerable: using crypto.privateDecrypt with PKCS#1 v1.5 padding    const decrypted = crypto.privateDecrypt(      { key: process.env.PRIVATE_KEY_PKCS1, padding: crypto.constants.RSA_PKCS1_PADDING },      Buffer.from(token, 'base64')    );    const payload = JSON.parse(decrypted.toString('utf8'));    return { userId: payload.sub };  }}

If the server returns different error responses such as BadPaddingError versus other errors, or if the decryption/verification time varies measurably, an attacker can exploit this as a side channel. By sending many Bearer Tokens where only the padding is altered, and measuring success or timing, the attacker gradually decrypts an intercepted token without needing the private key. This is a server-side issue enabled by the use of weak padding and non-constant-time verification, and it is especially dangerous when Bearer Tokens are treated as opaque but are decrypted server-side in a non-hardened way.

Even when using JWT libraries, a NestJS service that manually verifies RSA signatures with PKCS#1 v1.5 and does not enforce strict padding validation or constant-time comparison can be vulnerable. For instance:

import { Injectable } from '@nestjs/common';import * as crypto from 'crypto';import { createVerify } from 'crypto';@Injectable()export class JwtValidatorService {  verifyToken(token: string): boolean {    const [headerB64, payloadB64, signatureB64] = token.split('.');    const data = `${headerB64}.${payloadB64}`;    const verifier = createVerify('RSA-SHA256');    verifier.update(data);    // Vulnerable: uses legacy verify with PKCS#1 padding by default in some Node versions    return verifier.verify(process.env.PUBLIC_KEY_PEM, Buffer.from(signatureB64 + '==', 'base64'));  }}

Here, if the underlying verify implementation does not enforce strict, constant-time padding validation, an attacker can mount a Bleichenbacher-style adaptive attack by observing whether the server accepts or rejects manipulated signatures. The combination of Bearer Tokens (which may carry encrypted or signed claims) with server-side decryption/verification that leaks padding errors creates an exploitable condition in NestJS applications that do not enforce modern, safe padding (OAEP) or use constant-time verification patterns.

Bearer Tokens-Specific Remediation in Nestjs — concrete code fixes

Remediation focuses on avoiding legacy padding schemes and ensuring constant-time verification. For RSA encryption, prefer RSA-OAEP instead of PKCS#1 v1.5. For signatures, prefer RSASSA-PSS or use maintained libraries that handle constant-time comparison internally. Below are concrete, secure examples for NestJS.

Secure RSA-OAEP decryption

Replace PKCS#1 v1.5 with OAEP, which is not malleable and does not leak padding information:

import { Controller, Get, Req } from '@nestjs/common';import * as crypto from 'crypto';@Controller()export class AuthController {  @Get('/user')  async getUser(@Req() req: any) {    const auth = req.headers['authorization'];    if (!auth?.startsWith('Bearer ')) {      throw new Error('Unauthorized');    }    const token = auth.split(' ')[1];    try {      const decrypted = crypto.privateDecrypt(        {          key: process.env.PRIVATE_KEY_PEM,          oaepHash: 'sha256',          label: undefined,        },        Buffer.from(token, 'base64')      );      const payload = JSON.parse(decrypted.toString('utf8'));      return { userId: payload.sub };    } catch (err) {      // Use a generic error to avoid leaking padding or decryption details      throw new Error('Invalid token');    }  }}

Secure signature verification with PSS and constant-time handling

Use PSS padding for RSA signatures and ensure errors are handled uniformly:

import { Injectable } from '@nestjs/common';import * as crypto from 'crypto';@Injectable()export class JwtValidatorService {  verifyToken(token: string): boolean {    try {      const [headerB64, payloadB64, signatureB64] = token.split('.');      const data = `${headerB64}.${payloadB64}`;      const verifier = crypto.createVerify('RSA-SHA256');      verifier.update(data);      // Use PSS key with salt length -1 for maximum security      const publicKey = `-----BEGIN PUBLIC KEY-----\n${process.env.PUBLIC_KEY_PEM}\n-----END PUBLIC KEY-----`;      return verifier.verify(publicKey, Buffer.from(signatureB64 + '==', 'base64'), 32 /* pss salt length */);    } catch {      return false; // constant-time failure path    }  }}

Additionally, consider using a maintained JWT library (e.g., jsonwebtoken with modern algorithms like RS256 and RSASSA-PSS) and ensure that error handling does not distinguish between malformed tokens and invalid signatures. For Bearer Tokens that carry encrypted data, validate using authenticated encryption (e.g., RSA-OAEP or AES-GCM) so that decryption either fully succeeds or fails with a generic error, eliminating timing and padding side channels.

Finally, rotate keys and enforce short token lifetimes to reduce the impact of any successful attack. With these changes, the NestJS application mitigates the conditions required for a Bleichenbacher-style adaptive chosen-ciphertext attack on Bearer Tokens.

Frequently Asked Questions

Why are Bearer Tokens with RSA PKCS#1 v1.5 more vulnerable to Bleichenbacher attacks in NestJS?
PKCS#1 v1.5 padding is malleable and can produce distinguishable error messages (e.g., BadPaddingError) or timing differences when processed by crypto APIs. In NestJS, if endpoints decrypt or verify tokens using this padding and expose such errors or timing variations, an attacker can iteratively adapt token ciphertexts to learn plaintext or recover keying material, making Bearer Tokens a viable attack vector.
Does middleBrick detect server-side padding or side-channel risks in API endpoints that use Bearer Tokens?
middleBrick scans the unauthenticated attack surface and tests security controls such as authentication, input validation, and data exposure. While it does not inspect server-side crypto implementation details, it can identify endpoints that accept Bearer Tokens and surface related findings like missing rate limiting or input validation issues that could facilitate token manipulation. For deep crypto implementation checks, code review and specialized security testing are recommended.