security-tools/lib/common.sh
2026-09-24 17:55:11 +01:00

138 lines
No EOL
5.5 KiB
Shell

#!/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 -------------------------------------------------------
# http_probe METHOD URL [curl-args...]
# Émet une requête, renvoie le code HTTP sur stdout, journalise le corps
# dans $OUTPUT_DIR si VERBOSE=1. N'échoue jamais (set +e interne) : on
# veut le code, pas un plantage du pipeline.
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"
printf '000'
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
# Renvoie aussi le chemin du corps via une variable globale : pas
# idéal, mais évite des sous-shells coûteux sur les boucles de
# bruteforce. L'appelant doit appeler drop_body après lecture.
LAST_BODY="$body_file"
printf '%s' "$code"
}
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"
}