crowdsecurity/crowdsec · error

missing required fields in challenge response

Error message

missing required fields in challenge response

What it means

Generic guard in ValidateChallengeResponse: at least one of the required form fields (f, r, ts, sig, n, p, m, d, u) is missing from the parsed submission body. Deliberately non-specific so the response leaks nothing about which fields exist.

Source

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

// return a generic error so the caller doesn't leak which stage failed.
func (c *ChallengeRuntime) ValidateChallengeResponse(request *http.Request, body []byte) (*cookie.AppsecCookie, FingerprintData, int, error) {
	vars, err := url.ParseQuery(string(body))
	if err != nil {
		return nil, FingerprintData{}, 0, fmt.Errorf("%w: %w", ErrChallengePayload, err)
	}

	encryptedFingerprint := vars.Get("f")
	clientR := vars.Get("r")
	clientTS := vars.Get("ts")
	clientSig := vars.Get("sig")
	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")

View on GitHub (pinned to 909b515798)

Solutions

  1. Ensure the browser actually ran the full challenge script that assembles all fields (f, r, ts, sig, n, p, m, d, u)
  2. Check no proxy or bouncer strips form fields from the POST to /crowdsec-internal/challenge/submit
Defensive patterns

Strategy: validation

When it happens

Trigger: Thrown at pkg/appsec/challenge/challenge.go:629 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/773e14b07306b153. Report an issue: GitHub.