security-tools/lib/common.sh

147 lines
5.8 KiB
Bash
Raw Permalink Normal View History

2026-09-24 17:50:43 +01:00
#!/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"
}