#!/usr/bin/env bash # fuzz.sh — énumère les endpoints d'une API et cartographie les codes # de réponse, pour repérer les routes ouvertes, les fuites d'info dans # les messages d'erreur, et les variations de comportement. # # USAGE # ./fuzz.sh [targets.env] [endpoints.txt] # # Lit une liste de chemins (un par ligne) depuis le 2e argument, sinon # FUZZ_PATHS, sinon ./endpoints.txt à côté du script. Pour chaque # chemin, émet GET et OPTIONS (pour détecter les verbes autorisés via # l'en-tête Allow). # # Journalise dans $OUTPUT_DIR/api-fuzz.log. COMMON_SH="$(cd "$(dirname "$0")/../.." && pwd)/lib/common.sh" # shellcheck disable=SC1091 source "$COMMON_SH" load_target "${1:-}" LIST="${2:-${FUZZ_PATHS:-$(dirname "$0")/endpoints.txt}}" [[ -f "$LIST" ]] \ || die "Liste d'endpoints introuvable : $LIST (créez endpoints.txt ou réglez FUZZ_PATHS)" mkdir -p "${OUTPUT_DIR:-./output}" LOG="${OUTPUT_DIR:-./output}/api-fuzz.log" : > "$LOG" info "Fuzz de $(wc -l < "$LIST") endpoints sur ${#AUTHORIZED_HOSTS[@]} hôte(s) du périmètre" info "Liste : $LIST" info "Journal : $LOG" # Endpoints **intentionnellement publics** : un 200 dessus est normal, # pas une anomalie. Ex : la discovery OIDC d'IdentityServer, que les # clients (PostIt) interrogent pour découvrir les endpoints d'auth. EXPECTED_PUBLIC=( "/.well-known/openid-configuration" "/.well-known/jwks" ) is_expected_public() { local p="$1" local e for e in "${EXPECTED_PUBLIC[@]}"; do [[ "$e" == "$p" ]] && return 0; done return 1 } while IFS= read -r base; do info "→ $base" while IFS= read -r p || [[ -n "$p" ]]; do [[ -z "$p" || "$p" == \#* ]] && continue url="${base}${p}" http_probe GET "$url" code="$LAST_CODE" size=0 [[ -n "$LAST_BODY" && -s "$LAST_BODY" ]] && size=$(wc -c < "$LAST_BODY") printf '%s\tGET\t%s\t%s\t%8d\t%s\n' "$(date -Is)" "$base" "$code" "$size" "$p" >> "$LOG" # Verbes autorisés : la réponse à OPTIONS peut révéler POST/DELETE # même sur une route GET-only côté serveur. allow="$(set +e; curl -s -D - -o /dev/null -X OPTIONS \ --connect-timeout "${CURL_TIMEOUT:-10}" --max-time "${CURL_MAX_TIME:-30}" \ "$url" | tr -d '\r' | awk -F': ' 'tolower($1)=="allow"{print $2}')" [[ -n "$allow" ]] && printf '%s\tOPT\t%s\tAllow: %s\t%s\n' "$(date -Is)" "$base" "$allow" "$p" >> "$LOG" case "$code" in 200) if is_expected_public "$p"; then ok "200 $base$p — public attendu (OIDC discovery)" else warn "200 $base$p — accessible" fi ;; 401|403) ;; 404) ;; 500) err "500 $base$p — erreur serveur (fuite d'info possible)" ;; *) warn "$code $base$p" ;; esac drop_body pace done < "$LIST" done < <(each_scope_url) info "Terminé. Voir $LOG."