#!/usr/bin/env bash # probe.sh — mesure la mise en place d'un rate-limiting / lockout sur # l'endpoint d'authentification (ou tout endpoint sensible) de la # preprod. # # USAGE # ./probe.sh [targets.env] # # Envoie RATE_PROBE_COUNT requêtes rapides (identiques, échec volontaire) # sur LOGIN_PATH et observe l'évolution des codes HTTP : # - 429 trop tôt → rate-limit agressif (pe-être trop pour les # légitimes) ; # - jamais de 429, toujours 401/400 → AUCUN rate-limit : vulnérabilité # (bruteforce non freiné) ; # - verrouillage après N échecs (code change, ex 403/423) → lockout. # # Journalise dans $OUTPUT_DIR/rate-limit.log. COMMON_SH="$(cd "$(dirname "$0")/../.." && pwd)/lib/common.sh" # shellcheck disable=SC1091 source "$COMMON_SH" load_target "${1:-}" mkdir -p "${OUTPUT_DIR:-./output}" LOG="${OUTPUT_DIR:-./output}/rate-limit.log" : > "$LOG" RATE_PROBE_COUNT="${RATE_PROBE_COUNT:-20}" RATE_PROBE_DELAY="${RATE_PROBE_DELAY:-0}" # 0 = rafale, par défaut # URL de login (même logique que brute-login). login_url() { case "${LOGIN_PATH:-}" in http*) printf '%s' "$LOGIN_PATH" ;; /*) printf '%s%s' "$TARGET_BASE_URL" "${LOGIN_PATH}" ;; *) printf '%s/%s' "$TARGET_BASE_URL" "${LOGIN_PATH}" ;; esac } URL="$(login_url)" assert_in_scope "$URL" info "Rate-limit probe : $RATE_PROBE_COUNT requêtes sur $URL" info "Délai entre requêtes : ${RATE_PROBE_DELAY}s (0 = rafale)" info "Journal : $LOG" # Corps d'échec volontaire (mauvais mot de passe). On ne cherche pas # à réussir ici, juste à saturer pour observer le rate-limit. build_bad_body() { case "${LOGIN_CONTENT_TYPE:-json}" in form) local u; u="$(printf '%s' "${LOGIN_USERNAME:-nobody}" | sed 's/[&+/@=%]/\\&/g')" printf 'grant_type=password&%s=%s&%s=definitely-wrong-pass-%%42' \ "${LOGIN_USER_FIELD:-username}" "$u" "${LOGIN_PASS_FIELD:-password}" ;; *) printf '{"%s":"%s","%s":"definitely-wrong-pass"}' \ "${LOGIN_USER_FIELD:-username}" "${LOGIN_USERNAME:-nobody}" \ "${LOGIN_PASS_FIELD:-password}" ;; esac } ct() { case "${LOGIN_CONTENT_TYPE:-json}" in form) printf 'application/x-www-form-urlencoded';; *) printf 'application/json';; esac; } body="$(build_bad_body)" seen_429=0 seen_lock=0 first_change="" prev_code="" for i in $(seq 1 "$RATE_PROBE_COUNT"); do http_probe POST "$URL" -H "Content-Type: $(ct)" --data "$body" code="$LAST_CODE" printf '%s\t%3d/%d\t%s\n' "$(date -Is)" "$i" "$RATE_PROBE_COUNT" "$code" >> "$LOG" drop_body [[ "$code" == "429" ]] && seen_429=$((seen_429 + 1)) # 423 Locked, ou un changement soudain de code après des 401 → lockout possible. if [[ "$code" == "423" || ( -n "$prev_code" && "$code" != "$prev_code" && "$code" != "429" ) ]]; then seen_lock=$((seen_lock + 1)) [[ -z "$first_change" ]] && first_change="req $i : $prev_code → $code" fi prev_code="$code" [[ "${VERBOSE:-0}" == "1" ]] && printf ' %3d %s\n' "$i" "$code" [[ "$RATE_PROBE_DELAY" != "0" ]] && sleep "$RATE_PROBE_DELAY" done info "Résultats : $RATE_PROBE_COUNT requêtes, $seen_429 code(s) 429, $seen_lock changement(s) de code." if [[ -n "$first_change" ]]; then warn "Changement observé : $first_change (lockout possible)" fi if [[ "$seen_429" -eq 0 && -z "$first_change" ]]; then err "AUCUN rate-limit détecté (pas de 429, pas de verrouillage). Endpoint vulnérable au bruteforce." else ok "Rate-limiting actif ($seen_429 réponses 429). Voir $LOG pour le seuil." fi