#!/usr/bin/env bash # common.sh — bibliothèque partagée des outils de pentest preprod. # # Fournit : chargement de la cible (targets.env), vérification # d'autorisation (scope), client HTTP (curl) avec journalisation, # sortie colorée, gestion du délai entre requêtes. # # Toutes les fonctions supposent que TARGET_BASE_URL est défini. # Charger avec : source "$(dirname "$0")/../lib/common.sh" # depuis un outil sous tools//, ou adapter COMMON_SH ci-dessous. set -uo pipefail # --- Couleurs (désactivées si pas de TTY) ------------------------------- if [[ -t 1 ]]; then C_RED=$'\033[31m'; C_GREEN=$'\033[32m'; C_YELLOW=$'\033[33m' C_CYAN=$'\033[36m'; C_DIM=$'\033[2m'; C_RESET=$'\033[0m' else C_RED=""; C_GREEN=""; C_YELLOW=""; C_CYAN=""; C_DIM=""; C_RESET="" fi info() { printf '%s[INFO]%s %s\n' "$C_CYAN" "$C_RESET" "$*"; } ok() { printf '%s[OK]%s %s\n' "$C_GREEN" "$C_RESET" "$*"; } warn() { printf '%s[WARN]%s %s\n' "$C_YELLOW" "$C_RESET" "$*"; } err() { printf '%s[ERR]%s %s\n' "$C_RED" "$C_RESET" "$*" >&2; } die() { err "$*"; exit 1; } # --- Chargement de la cible -------------------------------------------- # Résout le répertoire racine de la suite (parent du dossier tools/). # Usage depuis tools//script.sh : COMMON_SH pointe sur ../../lib suite_root() { # $BASH_SOURCE[0] = ce fichier (lib/common.sh) → racine = son parent local this; this="${BASH_SOURCE[0]}" local dir; dir="$(cd "$(dirname "$this")" && pwd)" printf '%s' "$(dirname "$dir")" } # Charge targets.env. Refuse de tourner sans fichier de cible. # Argument optionnel : chemin vers un targets.env alternatif. load_target() { local env_file="${1:-$(suite_root)/targets.env}" [[ -f "$env_file" ]] \ || die "Aucun fichier de cible trouvé ($env_file). Copiez targets.example.env → targets.env et renseignez vos valeurs." # shellcheck disable=SC1090 source "$env_file" [[ -n "${TARGET_BASE_URL:-}" ]] \ || die "targets.env: TARGET_BASE_URL est requis." [[ -n "${TARGET_SCOPE_ACK:-}" ]] \ || die "targets.env: TARGET_SCOPE_ACK doit valoir 'yes' (confirmation que la cible vous appartient et que le test est autorisé)." [[ "$TARGET_SCOPE_ACK" == "yes" ]] \ || die "targets.env: TARGET_SCOPE_ACK != 'yes'. Outil bloqué tant que l'autorisation n'est pas confirmée." # Hôtes autorisés. Par défaut : l'hôte de TARGET_BASE_URL. Si # TARGET_ALLOWED_HOSTS est défini (liste séparée par espaces), il # étend le périmètre — utile quand la preprod a plusieurs hôtes # (ex : Yavsc.Org / Yavsc.Api / Yavsc.Blogs). Voir docs/scope.md. local base_host; base_host="$(printf '%s' "$TARGET_BASE_URL" \ | sed -E 's#^[a-zA-Z]+://##; s#/.*$##; s#:.*$##')" # Construction + déduplication (l'hôte de base peut aussi figurer # dans TARGET_ALLOWED_HOSTS). local -A _seen=() AUTHORIZED_HOSTS=() for h in "$base_host" ${TARGET_ALLOWED_HOSTS:-}; do [[ -n "$h" && -z "${_seen[$h]:-}" ]] || continue _seen[$h]=1 AUTHORIZED_HOSTS+=( "$h" ) done export TARGET_BASE_URL TARGET_ALLOWED_HOSTS AUTHORIZED_HOSTS } # Vérifie qu'une URL donnée appartient bien au périmètre autorisé. # Empêche un outil de dériver vers un tiers par maladresse. assert_in_scope() { local url="$1" local host; host="$(printf '%s' "$url" \ | sed -E 's#^[a-zA-Z]+://##; s#/.*$##; s#:.*$##')" local h for h in "${AUTHORIZED_HOSTS[@]}"; do [[ "$host" == "$h" || "$host" == *".$h" ]] && return 0 done die "Hors périmètre : $host (autorisé : ${AUTHORIZED_HOSTS[*]}). Abandon." } # --- Client HTTP ------------------------------------------------------- # Globales positionnées par http_probe. Initialisées ici pour ne pas # déclencher set -u au premier appel. LAST_CODE="" LAST_BODY="" # http_probe METHOD URL [curl-args...] # Émet une requête et positionne les globales LAST_CODE (code HTTP, ou # "000" si curl a échoué) et LAST_BODY (chemin d'un fichier temporaire # contenant le corps de la réponse, ou "" si échec). # # IMPORTANT : ne PAS appeler dans une substitution de commande # (code=$(http_probe ...)) : la substitution s'exécute dans un # sous-shell et les globales ne remonteraient pas. Appeler # directement, puis lire $LAST_CODE / $LAST_BODY, et appeler # drop_body une fois le corps traité. http_probe() { local method="$1"; shift local url="$1"; shift assert_in_scope "$url" local code body_file body_file="$(mktemp)" code="$(set +e; curl -s -o "$body_file" -w '%{http_code}' \ -X "$method" \ --connect-timeout "${CURL_TIMEOUT:-10}" \ --max-time "${CURL_MAX_TIME:-30}" \ "$@" "$url")" local rc=$? if [[ $rc -ne 0 ]]; then warn "curl a échoué (rc=$rc) sur $method $url" rm -f "$body_file" LAST_CODE="000" LAST_BODY="" return 0 fi if [[ "${VERBOSE:-0}" == "1" ]]; then printf '%s%s %s%s → %s%s\n' "$C_DIM" "$method" "$url" "$C_RESET" "$C_CYAN" "$code" [[ -s "$body_file" ]] && head -c 200 "$body_file" && printf '\n---\n' fi LAST_CODE="$code" LAST_BODY="$body_file" } drop_body() { [[ -n "${LAST_BODY:-}" ]] && rm -f "$LAST_BODY"; LAST_BODY=""; } # Renvoie les URL de base (une par hôte autorisé) en conservant le # schéma de TARGET_BASE_URL. Les scanners itèrent dessus pour couvrir # tout le périmètre multi-hôtes. each_scope_url() { local scheme; scheme="$(printf '%s' "$TARGET_BASE_URL" \ | sed -E 's#://.*$##')" [[ -z "$scheme" ]] && scheme="https" local h for h in "${AUTHORIZED_HOSTS[@]}"; do printf '%s://%s\n' "$scheme" "$h" done } # Délai entre requêtes (anti-DoS involontaire). Pacing obligatoire. pace() { local d="${REQUEST_DELAY:-0}" [[ "$d" != "0" ]] && sleep "$d" }