crowdsecurity/crowdsec · warning

failed to deobfuscate fingerprint: %w

Error message

failed to deobfuscate fingerprint: %w

What it means

After verifying the PoW, ValidateChallengeResponse de-obfuscates the client-submitted encrypted fingerprint using a key derived from the server session and the client's nonce r. If deobfuscateFingerprint fails (wrong key, tampered ciphertext, bad encoding), the error is wrapped as 'failed to deobfuscate fingerprint'. This indicates the client cannot be trusted or its payload is corrupt.

Source

Thrown at pkg/appsec/challenge/challenge.go:690

	// client derived s from the per-epoch key in the obfuscated dynamic module.
	s := deriveChallengeSecret(signKey, clientR)

	expectedSig := hmacSHA256Hex([]byte(s), []byte(clientR+clientTS+clientNonce+encryptedFingerprint))
	if !hmac.Equal([]byte(clientSig), []byte(expectedSig)) {
		return nil, FingerprintData{}, 0, errors.New("invalid HMAC in challenge response")
	}

	// Single-use: burn `r` (rejects replays). Done last so the spent-set only
	// grows on fully-valid submissions.
	if !c.spent.checkAndInsert(clientR, ticketAgeBackstop) {
		return nil, FingerprintData{}, 0, errors.New("challenge response already used")
	}

	obfKey := deriveFingerprintObfKey(s, clientR)

	fingerprint, err := deobfuscateFingerprint(obfKey, encryptedFingerprint)
	if err != nil {
		return nil, FingerprintData{}, 0, fmt.Errorf("failed to deobfuscate fingerprint: %w", err)
	}

	var fpData FingerprintData

	if err := json.Unmarshal([]byte(fingerprint), &fpData); err != nil {
		return nil, FingerprintData{}, 0, fmt.Errorf("%w: failed to unmarshal fingerprint data: %w", ErrChallengePayload, err)
	}

	// Debug diagnostic: a validated submission. Guarded so `k_epoch` (forgeable
	// signing material — DESIGN.md §2.1) is only formatted at debug.
	if c.log().Logger.IsLevelEnabled(log.DebugLevel) {
		c.log().WithFields(log.Fields{
			"r":       clientR,
			"epoch":   c.epochForTimestamp(clientTS),
			"k_epoch": fmt.Sprintf("%x", signKey),
			"fsid":    fpData.FSID,
			"is_bot":  fpData.FastBotDetection,
		}).Debug("validated submission")

View on GitHub (pinned to 909b515798)

Solutions

  1. Treat as an invalid challenge: the client must reload the challenge page and resubmit with a freshly generated fingerprint.
  2. If legitimate browsers fail en masse, check for a challenge JS / server version skew after an upgrade and hard-reload clients (purge CDN cache).
  3. Verify nothing mutates the 'f' form field between browser and server (WAF rules, re-encoding proxies).
  4. For replay testing, ensure the same (r, session) pair from the original challenge is used.
Defensive patterns

Strategy: validation

Try / catch

cookie, fp, diff, err := rt.ValidateChallengeResponse(req, body)
if err != nil && strings.Contains(err.Error(), "deobfuscate fingerprint") {
    // tampered/replayed payload: force a fresh challenge
    http.Redirect(w, req, challengePath, http.StatusFound)
    return
}

Prevention

When it happens

Trigger: Calling ValidateChallengeResponse with an 'f' field that was not produced by the served challenge JS — tampered value, replayed fingerprint paired with a different nonce r, or a client script that failed to obfuscate correctly.

Common situations: Replay attempts reusing an old fingerprint with a fresh challenge; headless bots that run a mangled/partial challenge script; a version mismatch where the served challenge JS and server code disagree on the obfuscation scheme; intermediaries corrupting the form field.

Related errors


AI-assisted analysis of crowdsecurity/crowdsec@909b515798 (2026-09-06). Data as JSON: /api/errors/95ace0deee29b0dc. Report an issue: GitHub.