GuardAPI Logo
GuardAPI

Fix Insecure Webhooks in Spring WebFlux

Webhooks are effectively unauthenticated entry points unless you implement cryptographic signing. In a reactive Spring WebFlux environment, failing to verify the HMAC signature of incoming payloads allows attackers to forge events, leading to unauthorized data modification or remote code execution. The goal is to intercept the raw request body, compute a keyed-hash (HMAC-SHA256), and perform a constant-time comparison against the provider's signature header.

The Vulnerable Pattern

@RestController
@RequestMapping("/api/hooks")
public class WebhookController {
@PostMapping("/process")
public Mono<Void> handleWebhook(@RequestBody String payload) {
    // VULNERABILITY: No signature verification.
    // Attacker can send arbitrary JSON to trigger internal logic.
    return service.executeInternalAction(payload);
}

}

The Secure Implementation

The secure implementation introduces three critical layers of defense. First, it extracts the 'X-Hub-Signature-256' header (common in GitHub/Stripe hooks). Second, it re-calculates the HMAC-SHA256 hash of the raw request body using a pre-shared secret. Third, it uses 'MessageDigest.isEqual()' for the comparison; this is vital because standard string equality is short-circuiting, which leaks timing information that an attacker can use to brute-force the signature byte-by-byte. In WebFlux, ensure the 'String' body is used to maintain the integrity of the payload as received, preventing any JSON-parsing discrepancies between the validator and the service.

@RestController
@RequestMapping("/api/hooks")
public class WebhookController {
@Value("${webhook.secret}")
private String secret;

@PostMapping("/process")
public Mono<ResponseEntity<Object>> handleWebhook(
        @RequestHeader("X-Hub-Signature-256") String signature,
        @RequestBody String payload) {

    return Mono.just(payload)
        .filter(body -> isValidSignature(body, signature))
        .flatMap(body -> service.executeInternalAction(body)
            .thenReturn(ResponseEntity.ok().build()))
        .switchIfEmpty(Mono.just(ResponseEntity.status(HttpStatus.UNAUTHORIZED).build()));
}

private boolean isValidSignature(String payload, String signatureHeader) {
    try {
        Mac mac = Mac.getInstance("HmacSHA256");
        SecretKeySpec secretKey = new SecretKeySpec(secret.getBytes(StandardCharsets.UTF_8), "HmacSHA256");
        mac.init(secretKey);
        byte[] hash = mac.doFinal(payload.getBytes(StandardCharsets.UTF_8));
        String expectedSignature = "sha256=" + Hex.encodeHexString(hash);
        
        // Use MessageDigest.isEqual for constant-time comparison to prevent timing attacks
        return MessageDigest.isEqual(
            expectedSignature.getBytes(StandardCharsets.UTF_8), 
            signatureHeader.getBytes(StandardCharsets.UTF_8)
        );
    } catch (Exception e) {
        return false;
    }
}

}

System Alert • ID: 8280
Target: Spring WebFlux API
Potential Vulnerability

Your Spring WebFlux API might be exposed to Insecure Webhooks

74% of Spring WebFlux apps fail this check. Hackers use automated scanners to find this specific flaw. Check your codebase before they do.

RUN FREE SECURITY DIAGNOSTIC
GuardLabs Engine: ONLINE

Free Tier • No Credit Card • Instant Report

Verified by Ghost Labs Security Team

This content is continuously validated by our automated security engine and reviewed by our research team. Ghost Labs analyzes over 500+ vulnerability patterns across 40+ frameworks to provide up-to-date remediation strategies.