crowdsecurity/crowdsec · error

invalid ticket in challenge response

Error message

invalid ticket in challenge response

What it means

Raised in ValidateChallengeResponse when the ticket/nonce material (the 'r' field) fails ticket validation — e.g. the ticket is expired, unknown to this runtime, or malformed. Signals a stale or replayed challenge session rather than a client-side computation failure.

Source

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

	clientNonce := vars.Get("n")
	clientPowSalt := vars.Get("p")
	clientPowMAC := vars.Get("m")
	clientDifficultyStr := vars.Get("d")
	clientPath := vars.Get("u")

	if encryptedFingerprint == "" || clientR == "" || clientTS == "" || clientSig == "" || clientNonce == "" || clientPowSalt == "" || clientPowMAC == "" || clientDifficultyStr == "" || clientPath == "" {
		return nil, FingerprintData{}, 0, errors.New("missing required fields in challenge response")
	}

	// Override the request path so the WAF sees the original URL the client
	request.URL.Path = clientPath

	// The difficulty the client claims it solved. It is untrusted until the PoW
	// MAC (which binds it) is verified in verifyChallenge below. Bound to the
	// valid PoW range so a malformed value can't reach the PoW/seal logic.
	clientDifficulty, err := strconv.Atoi(clientDifficultyStr)
	if err != nil || clientDifficulty < PowDifficultyDisabled || clientDifficulty > PowDifficultyImpossible {
		return nil, FingerprintData{}, 0, errors.New("invalid ticket in challenge response")
	}

	// Server-issued `r` is a 16-byte nonce in hex (generateChallengeNonce):
	// exactly 32 hex chars. Reject other shapes early so a K_epoch holder can't
	// bloat the spent-set with oversized keys, and to keep the key space canonical.
	if _, err := hex.DecodeString(clientR); err != nil || len(clientR) != 32 {
		return nil, FingerprintData{}, 0, errors.New("invalid ticket in challenge response")
	}

	// Verify freshness + PoW-salt/difficulty authenticity and recover the
	// per-epoch sign key (stateless). Key knowledge is proven by `sig` below.
	signKey, ok := c.verifyChallenge(clientR, clientTS, clientPowSalt, clientPowMAC, clientDifficulty)
	if !ok {
		return nil, FingerprintData{}, 0, errors.New("invalid ticket in challenge response")
	}

	// Impossible difficulty is a deliberate hard-block: never accept anything.
	// clientDifficulty is now MAC-authenticated, so this enforces the difficulty

View on GitHub (pinned to 909b515798)

Solutions

  1. Issue a fresh challenge to the client when the ticket is stale
  2. Verify clock skew between crowdsec nodes is minimal so freshly issued tickets aren't rejected as expired
Defensive patterns

Strategy: validation

When it happens

Trigger: Thrown at pkg/appsec/challenge/challenge.go:640 when the library encounters an invalid state.

Common situations: See trigger scenarios.


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