HIGH bleichenbacher attacknestjsmutual tls

Bleichenbacher Attack in Nestjs with Mutual Tls

Bleichenbacher Attack in Nestjs with Mutual Tls — how this specific combination creates or exposes the vulnerability

A Bleichenbacher attack targets RSA encryption schemes that use PKCS#1 v1.5 padding and rely on error messages that distinguish between padding errors and other failures. In a NestJS application using Mutual TLS (mTLS), the transport is authenticated with client certificates, but the application layer may still perform RSA decryption of incoming data (for example, JWTs or encrypted payloads) using a vulnerable implementation. The presence of mTLS does not prevent a Bleichenbacher attack; it changes the attacker’s position. An attacker that presents a valid client certificate can establish a TLS session, but if the application decrypts data and reveals padding-error timing or response differences, the attack can proceed within the authenticated TLS channel.

In NestJS, this often occurs when JWT verification or custom RSA decryption uses a library that distinguishes padding errors. For example, if you use Node’s crypto with verify or a third-party JWT library that does not use constant-time checks, the server’s responses to malformed ciphertext can be observable even over mTLS. The attacker sends many ciphertexts and uses the server’s behavior (timing or error messages) to gradually decrypt data or recover the private key without needing to break RSA itself. mTLS ensures the client is known, but it does not hide these application-level decryption behaviors.

Consider a NestJS service that verifies a JWT signed with RS256 and expects the public key to validate the signature. If the JWT library leaks padding errors, an authenticated client (with a valid mTLS cert) can mount a Bleichenbacher attack against the signature verification path. This is a realistic scenario when endpoints accept tokens or encrypted blobs and perform RSA operations after mTLS authentication. The attack does not require breaking mTLS, but it exploits how the application handles decryption/padding checks after the TLS session is established.

Another specific configuration that increases risk is when NestJS applications act as TLS-terminating proxies or gateways and also perform decryption of upstream payloads. In such setups, mTLS secures the client-to-NestJS segment, but the NestJS service may still use vulnerable RSA decryption toward upstream services. An attacker who can inject ciphertext into this pipeline (for example, via a compromised token or manipulated encrypted field) can exploit the Bleichenbacher vulnerability despite mTLS being in place. Therefore, mTLS must be complemented by padding-safe cryptographic practices.

To detect this during a scan, middleBrick runs checks that include input validation and insecure consumption tests, looking for endpoints that process encrypted or signed data where error handling may leak information. If your API accepts JWTs or RSA-encrypted data and uses libraries susceptible to padding-oracle behavior, a scanner can identify the risk even when mTLS is used, because the vulnerability resides in application logic, not the transport layer.

Mutual Tls-Specific Remediation in Nestjs — concrete code fixes

Remediation focuses on using constant-time verification and avoiding padding-oracle-prone operations. For JWTs, prefer asymmetric algorithms with safer padding (e.g., RSASSA-PSS) or use libraries that implement constant-time checks. Do not rely on mTLS alone to prevent application-layer decryption leaks.

Example 1: RS256 JWT verification with a constant-time-safe library approach in NestJS.

import { Injectable } from '@nestjs/common';
import { createRemoteJWKSet, jwtDecode } from 'jose';

@Injectable()
export class AuthService {
  private readonly jwks = createRemoteJWKSet(new URL('https://auth.example.com/.well-known/jwks.json'));

  async validateToken(token: string): Promise {
    // jose verifies signatures using constant-time operations and does not leak padding errors
    const { payload } = await jwtDecode(token, { key: this.jwks });
    return payload;
  }
}

Example 2: Using Node’s crypto with strict error handling and constant-time comparison when custom verification is required.

import { createVerify } from 'crypto';

export function verifySignatureConstantTime(key: string, data: Buffer, signature: string): boolean {
  const verifier = createVerify('SHA256');
  verifier.update(data);
  verifier.end();
  // Use verifier.verify({ key, padding: crypto.constants.RSA_PKCS1_PADDING }, signature) with caution;
  // prefer higher-level libraries that abstract padding risks.
  // If you must use low-level verify, ensure you do not expose distinct error paths based on padding.
  try {
    return verifier.verify({ key, padding: crypto.constants.RSA_PKCS1_PADDING }, signature);
  } catch (err) {
    // Do not leak error details; return a generic failure to avoid timing or error-based oracles
    return false;
  }
}

Example 3: NestJS Guard that uses safe token validation and avoids branching on padding errors.

import { Injectable, CanActivate, ExecutionContext } from '@nestjs/common';
import { AuthService } from './auth.service';

@Injectable()
export class JwtGuard implements CanActivate {
  constructor(private readonly authService: AuthService) {}

  async canActivate(context: ExecutionContext): Promise {
    const request = context.switchToHttp().getRequest();
    const token = this.extractToken(request);
    if (!token) {
      return false;
    }
    try {
      const payload = await this.authService.validateToken(token);
      request.user = payload;
      return true;
    } catch {
      // Always fail the same way regardless of error type to avoid information leakage
      return false;
    }
  }

  private extractToken(request: any): string | null {
    const header = request.headers.authorization;
    if (header && header.startsWith('Bearer ')) {
      return header.substring(7);
    }
    return null;
  }
}

Additional recommendations: use libraries that implement RSASSA-PSS where supported, rotate keys regularly, and ensure that error messages are generic. Even with mTLS, avoid returning distinct HTTP status codes or response bodies for padding failures; always use a uniform failure response and constant-time code paths.

Frequently Asked Questions

Does mutual TLS prevent Bleichenbacher attacks?
No. Mutual TLS secures the transport and authenticates the client, but it does not prevent application-layer RSA decryption or signing operations that may leak padding errors. A Bleichenbacher attack can still be mounted if the application uses vulnerable padding checks.
How can I test my NestJS endpoints for Bleichenbacher risks?
Use a scanner that exercises input validation and insecure consumption checks while providing authenticated sessions. Such scans can detect whether your endpoints leak timing or error behavior during RSA operations, even when mTLS is in use.