Bleichenbacher Attack in Nestjs
How Bleichenbacher Attack Manifests in Nestjs
The Bleichenbacher attack (CVE-1998-0101) is a padding oracle exploit against RSA PKCS#1 v1.5 encryption. In a Nestjs application it appears when an endpoint decrypts RSA‑PKCS#1 v1.5 ciphertexts and returns distinct error messages (or timing differences) based on whether the padding is valid. This enables an attacker to iteratively craft ciphertexts and recover the plaintext without the private key.
Typical vulnerable code paths in Nestjs include:
- Custom controllers that use Node’s
cryptomodule directly for decryption, e.g. a@Post('decrypt')route that callscrypto.privateDecryptwith the defaultRSA_PKCS1_V15_PADDING. - Authentication flows that reuse RSA decryption for token verification (e.g., decrypting a JWT‑like payload) and expose validation errors via HTTP 400 responses.
- Middleware or guards that decrypt request bodies using RSA and throw
BadRequestExceptionwith the underlying OpenSSL error message when padding fails.
Because Nestjs encourages modular, reusable services, the vulnerable decryption logic often lives in a shared CryptoService injected into multiple controllers. If any of those services return different HTTP status codes or error strings for bad padding versus other decryption failures, an attacker can query the endpoint repeatedly and gradually decrypt arbitrary data.
Nestjs-Specific Detection
Detecting a Bleichenbacher oracle in a Nestjs API requires active probing of the decryption endpoint. middleBrick’s unauthenticated black‑box scan includes an Encryption check that sends a series of specially crafted RSA ciphertexts to the target URL and observes the responses.
When you run a scan from the CLI, the tool looks for:
- Different HTTP status codes (e.g., 400 vs 500) returned for ciphertexts with valid versus invalid PKCS#1 v1.5 padding.
- Variations in response time that correlate with padding validation steps.
- Error messages that leak OpenSSL specifics such as
error:0407006A:rsa routines:RSA_padding_check_PKCS1_type_2:block type is not 02.
Example CLI usage:
# Install the middleBrick CLI (if not already) npm i -g middlebrick # Scan a Nestjs API that exposes a decryption route middlebrick scan https://api.example.com/decryptThe scan completes in 5–15 seconds and returns a risk score (A–F) with a finding such as:
{ "id": "ENC-BLEICHENBACHER", "name": "Bleichenbacher padding oracle (RSA PKCS#1 v1.5)", "severity": "high", "description": "The /decrypt endpoint distinguishes valid from invalid PKCS#1 v1.5 padding, enabling plaintext recovery.", "remediation": "Use RSA‑OAEP padding or avoid RSA encryption for confidential data." }In the Dashboard you can view the finding over time, set alerts, and integrate the result into CI/CD via the GitHub Action (
uses: middlebrick/action@v1) to fail a build if the score drops below your threshold.
Nestjs-Specific Remediation
The most reliable fix is to replace RSA PKCS#1 v1.5 encryption with RSA‑OAEP, which is not vulnerable to Bleichenbacher, or to avoid RSA encryption altogether for confidentiality and use authenticated symmetric encryption (e.g., AES‑GCM). In Nestjs you can achieve this by adjusting the crypto calls or by leveraging the @nestjs/jwt module for signing only.
Example of a vulnerable service:
import { Injectable, BadRequestException } from '@nestjs/common'; import { privateDecrypt, constants } from 'crypto'; @Injectable() export class CryptoService { constructor(private readonly key: Buffer) {} decrypt(ciphertext: Buffer): string { try { // Vulnerable: uses PKCS#1 v1.5 padding const decrypted = privateDecrypt( { key: this.key, padding: constants.RSA_PKCS1_PADDING, // ← vulnerable }, ciphertext, ); return decrypted.toString('utf8'); } catch (err) { // Leaks padding error details throw new BadRequestException(`Decryption failed: ${err.message}`); } } }Remediated service using OAEP:
import { Injectable, BadRequestException } from '@nestjs/common'; import { privateDecrypt, constants } from 'crypto'; @Injectable() export class CryptoService { constructor(private readonly key: Buffer) {} decrypt(ciphertext: Buffer): string { try { // Fixed: use RSA‑OAEP padding const decrypted = privateDecrypt( { key: this.key, padding: constants.RSA_PKCS1_OAEP_PADDING, // ← safe }, ciphertext, ); return decrypted.toString('utf8'); } catch { // Generic error – no padding oracle leaked throw new BadRequestException('Decryption failed'); } } }If you must retain PKCS#1 v1.5 for compatibility, ensure that all error paths return the same HTTP status code and a generic message, and consider adding a constant‑time comparison to avoid timing leaks. However, migrating to OAEP is the recommended approach and aligns with OWASP API Top 10 2023 A2: Cryptographic Failures.
After applying the fix, rerun middleBrick (
middlebrick scan https://api.example.com/decrypt) to confirm the Encryption check now passes and the risk score improves.