#!/usr/bin/env bash # What an unknown kid costs the issuer. # # Spring Security builds its JWKSource with rateLimited(false), overriding Nimbus's own # default of a 30-second minimum interval between forced refreshes. Nothing then stands # between a token bearing an unrecognised kid and an HTTP request to the issuer. set -eu . "$(dirname "$0")/lib.sh" PROFILES="${1:-unknown}" N="${2:-25}" head1 "resource server profiles: $PROFILES" echo "Baseline: $N requests with a VALID token, whose kid is in the cached JWK Set." curl -s -X POST "$STUB/admin/reset-counter" >/dev/null GOOD=$(stub_token "sub=alice&aud=reports-api") curl -s -o /dev/null -H "Authorization: Bearer $GOOD" "$RS/api/me" # warm curl -s -X POST "$STUB/admin/reset-counter" >/dev/null for i in $(seq 1 "$N"); do curl -s -o /dev/null -H "Authorization: Bearer $GOOD" "$RS/api/me" done echo " requests to the resource server : $N" echo " fetches of /jwks.json : $(stub_fetches)" echo echo "Now $N requests carrying a token whose kid has never existed." echo "Each one is refused - but look at what it costs the issuer first." curl -s -X POST "$STUB/admin/reset-counter" >/dev/null for i in $(seq 1 "$N"); do BAD=$(curl -s -X POST "$STUB/token/unknown-kid") curl -s -o /dev/null -H "Authorization: Bearer $BAD" "$RS/api/me" done FETCHES=$(stub_fetches) echo " requests to the resource server : $N" echo " fetches of /jwks.json : $FETCHES" echo echo "Every rejected request became a request to the authorization server. An attacker who" echo "can reach an unauthenticated endpoint of your resource server can point that ratio at" echo "your identity provider, from one connection, using tokens that are never valid." echo echo "And it does not need an authenticated endpoint. /api/public/ping is permitAll()." curl -s -X POST "$STUB/admin/reset-counter" >/dev/null for i in $(seq 1 "$N"); do BAD=$(curl -s -X POST "$STUB/token/unknown-kid") curl -s -o /dev/null -H "Authorization: Bearer $BAD" "$RS/api/public/ping" done echo " requests to /api/public/ping : $N" echo " fetches of /jwks.json : $(stub_fetches)" echo echo "A permitAll() endpoint still evaluates a bearer token if one is present, because" echo "BearerTokenAuthenticationFilter runs before any authorization rule. Presenting a" echo "broken token to a public endpoint gets a 401 from the public endpoint - and a" echo "JWKS fetch on the way." call "GET /api/public/ping with an unknown kid" "$RS/api/public/ping" "$(curl -s -X POST "$STUB/token/unknown-kid")" echo echo "The status returned to the caller is unremarkable:" BAD=$(curl -s -X POST "$STUB/token/unknown-kid") call "GET /api/me with an unknown kid" "$RS/api/me" "$BAD"