first commit
This commit is contained in:
parent
883dd76931
commit
2e22667181
17 changed files with 889 additions and 5 deletions
130
tools/brute-login/brute-login.sh
Executable file
130
tools/brute-login/brute-login.sh
Executable file
|
|
@ -0,0 +1,130 @@
|
|||
#!/usr/bin/env bash
|
||||
# brute-login.sh — teste la robustesse de l'authentification de la
|
||||
# preprod par énumération de mots de passe sur un utilisateur connu.
|
||||
#
|
||||
# USAGE
|
||||
# ./brute-login.sh [targets.env]
|
||||
#
|
||||
# Comportement :
|
||||
# - lit la wordlist LOGIN_WORDLIST (un mot de passe par ligne) ;
|
||||
# - pour chaque entrée, émet POST LOGIN_PATH avec {{USER}}/{{PASS}} ;
|
||||
# - considère réussite si le code HTTP == LOGIN_SUCCESS_CODE ;
|
||||
# - s'arrête à la première réussite (ou à LOGIN_MAX_ATTEMPTS) ;
|
||||
# - journalise les tentatives dans $OUTPUT_DIR/brute-login.log.
|
||||
#
|
||||
# Le délai REQUEST_DELAY espace les requêtes (voir docs/scope.md).
|
||||
# Voir targets.example.env pour tous les paramètres.
|
||||
|
||||
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}/brute-login.log"
|
||||
: > "$LOG"
|
||||
|
||||
[[ -f "${LOGIN_WORDLIST}" ]] \
|
||||
|| die "Wordlist introuvable : ${LOGIN_WORDLIST} (réglez LOGIN_WORDLIST dans targets.env)"
|
||||
|
||||
# URL absolue de login (LOGIN_PATH peut être relatif ou absolu dans le périmètre).
|
||||
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 "Cible : $URL"
|
||||
info "Utilisateur : ${LOGIN_USERNAME}"
|
||||
info "Wordlist : ${LOGIN_WORDLIST}"
|
||||
info "Succès si code == ${LOGIN_SUCCESS_CODE}"
|
||||
info "Délai : ${REQUEST_DELAY}s entre requêtes"
|
||||
info "Journal : $LOG"
|
||||
|
||||
# Construit le corps de la requête pour un couple (user, pass).
|
||||
# Deux formats selon LOGIN_CONTENT_TYPE : "json" ou "form".
|
||||
build_body() {
|
||||
local user="$1" pass="$2"
|
||||
local ct="${LOGIN_CONTENT_TYPE:-json}"
|
||||
case "$ct" in
|
||||
form)
|
||||
if [[ -n "${LOGIN_BODY_FORM:-}" ]]; then
|
||||
printf '%s' "${LOGIN_BODY_FORM//\{\{USER\}\}/$user}" \
|
||||
| sed "s/{{PASS}}/$(printf '%s' "$pass" | sed 's/[&/\]/\\&/g')/"
|
||||
else
|
||||
# form-urlencoded : grant_type=password + user + pass.
|
||||
# Encodage minimal des caractères spéciaux pour URL.
|
||||
local u p
|
||||
u="$(printf '%s' "$user" | sed 's/[&+/@=%]/\\&/g')"
|
||||
p="$(printf '%s' "$pass" | sed 's/[&+/@=%]/\\&/g')"
|
||||
printf 'grant_type=password&%s=%s&%s=%s' \
|
||||
"$LOGIN_USER_FIELD" "$u" "$LOGIN_PASS_FIELD" "$p"
|
||||
fi
|
||||
;;
|
||||
json|*)
|
||||
if [[ -n "${LOGIN_BODY_JSON:-}" ]]; then
|
||||
printf '%s' "${LOGIN_BODY_JSON//\{\{USER\}\}/$user}" \
|
||||
| sed "s/{{PASS}}/$(printf '%s' "$pass" | sed 's/[&/\]/\\&/g')/"
|
||||
else
|
||||
# Construction JSON simple, échappement basique.
|
||||
local u p
|
||||
u="$(printf '%s' "$user" | sed 's/[\\"]/\\&/g')"
|
||||
p="$(printf '%s' "$pass" | sed 's/[\\"]/\\&/g')"
|
||||
printf '{"%s":"%s","%s":"%s"}' \
|
||||
"$LOGIN_USER_FIELD" "$u" "$LOGIN_PASS_FIELD" "$p"
|
||||
fi
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
# En-tête Content-Type selon le format.
|
||||
content_type_header() {
|
||||
case "${LOGIN_CONTENT_TYPE:-json}" in
|
||||
form) printf 'application/x-www-form-urlencoded' ;;
|
||||
*) printf 'application/json' ;;
|
||||
esac
|
||||
}
|
||||
|
||||
count=0
|
||||
found=0
|
||||
while IFS= read -r pass || [[ -n "$pass" ]]; do
|
||||
# ignore lignes vides et commentaires
|
||||
[[ -z "$pass" || "$pass" == \#* ]] && continue
|
||||
count=$((count + 1))
|
||||
if [[ "${LOGIN_MAX_ATTEMPTS}" != "0" && "$count" -gt "${LOGIN_MAX_ATTEMPTS}" ]]; then
|
||||
info "Limite LOGIN_MAX_ATTEMPTS=${LOGIN_MAX_ATTEMPTS} atteinte, arrêt."
|
||||
break
|
||||
fi
|
||||
|
||||
body="$(build_body "$LOGIN_USERNAME" "$pass")"
|
||||
http_probe POST "$URL" \
|
||||
-H "Content-Type: $(content_type_header)" \
|
||||
--data "$body"
|
||||
code="$LAST_CODE"
|
||||
printf '%s\t%s\t%s\n' "$(date -Is)" "$code" "$pass" >> "$LOG"
|
||||
drop_body
|
||||
|
||||
if [[ "$code" == "${LOGIN_SUCCESS_CODE}" ]]; then
|
||||
ok "Connexion réussie avec : $pass (code $code après $count tentatives)"
|
||||
found=1
|
||||
break
|
||||
fi
|
||||
|
||||
# Anti-lockout : si on voit un 429 (trop de requêtes) ou un 403
|
||||
# persistant, on prévient plutôt que de marteler.
|
||||
if [[ "$code" == "429" ]]; then
|
||||
warn "Code 429 (rate limit) reçu — la cible limite les tentatives. Pause + longue."
|
||||
sleep 5
|
||||
fi
|
||||
|
||||
pace
|
||||
done < "${LOGIN_WORDLIST}"
|
||||
|
||||
if [[ "$found" -eq 0 ]]; then
|
||||
warn "Aucun mot de passe trouvé après $count tentatives. Voir $LOG."
|
||||
exit 1
|
||||
fi
|
||||
Loading…
Add table
Add a link
Reference in a new issue