147 lines
No EOL
5.8 KiB
Bash
147 lines
No EOL
5.8 KiB
Bash
#!/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/<nom>/, 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/<outil>/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"
|
|
} |