Skip to content

[SECURITY] Timing attack in validateWebhookSignature() — non-constant-time HMAC-SHA256 comparison (CWE-208) #479

Description

@ryotagubusiness-B1z

Responsible Disclosure — Security Vulnerability

Severity: High
CWE: CWE-208 (Observable Timing Discrepancy), CWE-330 (Insufficient Randomness)
Package: razorpay (npm) — affected ≤ 2.9.6
Disclosure deadline: 90 days from filing (2026-09-07)


Summary

validateWebhookSignature() and validatePaymentVerification() in lib/utils/razorpay-utils.js compare HMAC-SHA256 digests using JavaScript's === operator, which is not constant-time. String comparison short-circuits at the first mismatched character, allowing an attacker to recover the correct signature byte-by-byte by measuring server response times.

Vulnerable Code

// lib/utils/razorpay-utils.js
var expectedSignature = crypto.createHmac('sha256', secret).update(body).digest('hex');
return expectedSignature === signature;  // ← CWE-208

Impact

An attacker who can submit crafted webhook requests and observe response latency can reconstruct a valid HMAC-SHA256 signature without knowing the webhook secret, enabling injection of fake payment.captured events into merchant systems.

Secondary Issue — Fixed IV in AES-GCM (CWE-330)

const iv = Buffer.alloc(12);
keyBytes.copy(iv, 0, 0, 12);  // Fixed IV derived from key — reuse breaks AES-GCM confidentiality

Recommended Fix

const a = Buffer.from(expectedSignature, 'hex');
const b = Buffer.from(signature, 'hex');
if (a.length !== b.length) return false;
return crypto.timingSafeEqual(a, b);

No full proof-of-concept exploit is shared publicly. If private disclosure is preferred, please enable GitHub Private Vulnerability Reporting.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions