first commit
This commit is contained in:
parent
883dd76931
commit
2e22667181
17 changed files with 889 additions and 5 deletions
41
tools/rate-limit-probe/README.md
Normal file
41
tools/rate-limit-probe/README.md
Normal file
|
|
@ -0,0 +1,41 @@
|
|||
# rate-limit-probe
|
||||
|
||||
Mesure si l'endpoint d'authentification (ou tout endpoint sensible)
|
||||
de la preprod met en place un rate-limiting / verrouillage contre le
|
||||
bruteforce.
|
||||
|
||||
## Principe
|
||||
|
||||
Envoie `RATE_PROBE_COUNT` requêtes (20 par défaut) avec un mauvais
|
||||
mot de passe sur `LOGIN_PATH` et observe les codes de réponse :
|
||||
|
||||
- **429** → rate-limiting actif (bon). Le seuil (après combien de
|
||||
requêtes) se lit dans le journal.
|
||||
- **changement soudain de code** (ex : 401 → 403/423) → verrouillage
|
||||
(lockout) après N échecs.
|
||||
- **rien de tout ça** (toujours 401/400) → AUCUN rate-limit :
|
||||
vulnérabilité, le bruteforce n'est pas freiné.
|
||||
|
||||
## Lancement
|
||||
|
||||
```bash
|
||||
./probe.sh
|
||||
```
|
||||
|
||||
Paramètres (dans `targets.env`) :
|
||||
- `RATE_PROBE_COUNT` — nombre de requêtes (défaut 20).
|
||||
- `RATE_PROBE_DELAY` — délai entre requêtes en secondes (0 = rafale ;
|
||||
pour tester le rate-limit on veut justement une rafale, mais
|
||||
gardez un count modéré).
|
||||
|
||||
## Sortie
|
||||
|
||||
- stdout : verdict (rate-limit actif / lockout / absent).
|
||||
- `output/rate-limit.log` : `horodatage \t n/N \t code`.
|
||||
|
||||
## Actions typiques
|
||||
|
||||
- Aucun rate-limit → en ajouter un au reverse proxy (ex : `limit_req`
|
||||
nginx) ou côté application (bucket par IP+utilisateur).
|
||||
- Rate-limit trop agressif (429 dès la 2e requête) → risque de bloquer
|
||||
les légitimes ; ajuster le seuil.
|
||||
96
tools/rate-limit-probe/probe.sh
Executable file
96
tools/rate-limit-probe/probe.sh
Executable file
|
|
@ -0,0 +1,96 @@
|
|||
#!/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
|
||||
Loading…
Add table
Add a link
Reference in a new issue