#!/usr/bin/env bash # audit_http_headers # ------------------ # Audita las cabeceras HTTP de seguridad de una URL: HSTS, CSP, X-Frame-Options, # X-Content-Type-Options, Referrer-Policy, Permissions-Policy y otras. # También muestra cabeceras que exponen información del servidor. # # USO (directo): # audit_http_headers # # Depende de: curl SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" source "$SCRIPT_DIR/../shell/bash_colors.sh" source "$SCRIPT_DIR/../shell/bash_log.sh" bash_colors bash_log_init # ─── Funciones puras ────────────────────────────────────────────────────────── _hdr_normalize_url() { local url="$1" if [[ ! "$url" =~ ^https?:// ]]; then echo "https://${url}" else echo "$url" fi } _hdr_header_present() { local headers="$1" local name="$2" echo "$headers" | grep -qi "^${name}:" } _hdr_extract_value() { local headers="$1" local name="$2" echo "$headers" | grep -i "^${name}:" | cut -d: -f2- | xargs } _hdr_hsts_is_strong() { local value="$1" local max_age max_age="$(echo "$value" | grep -oE 'max-age=[0-9]+' | cut -d= -f2 || echo 0)" [[ "$max_age" -ge 15768000 ]] # 6 meses en segundos } # ─── Funciones de efecto ────────────────────────────────────────────────────── _hdr_fetch() { local url="$1" curl -sI --max-time 15 --location "$url" 2>/dev/null | tr -d '\r' } _hdr_check_header() { local headers="$1" local name="$2" local description="$3" if _hdr_header_present "$headers" "$name"; then local value value="$(_hdr_extract_value "$headers" "$name")" echo -e " ${GREEN}[ok]${NC} ${name}" echo -e " ${GRAY}${value}${NC}" else echo -e " ${RED}[x] ${NC} ${name} -- ${description}" fi } _hdr_check_hsts() { local headers="$1" local name="Strict-Transport-Security" if _hdr_header_present "$headers" "$name"; then local value value="$(_hdr_extract_value "$headers" "$name")" if _hdr_hsts_is_strong "$value"; then echo -e " ${GREEN}[ok]${NC} ${name}" else echo -e " ${YELLOW}[!] ${NC} ${name} -- max-age demasiado corto (<6 meses)" fi echo -e " ${GRAY}${value}${NC}" else echo -e " ${RED}[x] ${NC} ${name} -- HSTS no configurado" fi } _hdr_show_server_info() { local headers="$1" echo "" echo -e "${CYAN}── Información del servidor ──────────────────────${NC}" for h in Server X-Powered-By X-AspNet-Version X-Generator; do if _hdr_header_present "$headers" "$h"; then local val val="$(_hdr_extract_value "$headers" "$h")" echo -e " ${YELLOW}[!]${NC} ${h}: ${val} (información expuesta)" fi done local status status="$(echo "$headers" | head -1)" echo -e " ${CYAN}Status:${NC} ${status}" } # ─── Punto de entrada ───────────────────────────────────────────────────────── audit_http_headers() { local raw_url="$1" if [[ -z "$raw_url" ]]; then error "audit_http_headers: se requiere una URL como argumento" >&2 return 1 fi if ! command -v curl &>/dev/null; then error "audit_http_headers: 'curl' no está instalado (sudo apt install curl)" >&2 return 1 fi local url url="$(_hdr_normalize_url "$raw_url")" info "Consultando cabeceras de: ${url}" local headers headers="$(_hdr_fetch "$url")" if [[ -z "$headers" ]]; then error "audit_http_headers: no se pudieron obtener las cabeceras. ¿El sitio está disponible?" >&2 return 1 fi echo "" echo -e "${PURPLE}════════ Cabeceras de Seguridad ════════════════${NC}" echo "" _hdr_check_hsts "$headers" _hdr_check_header "$headers" "Content-Security-Policy" "Previene XSS e inyección de contenido" _hdr_check_header "$headers" "X-Frame-Options" "Previene clickjacking" _hdr_check_header "$headers" "X-Content-Type-Options" "Previene MIME sniffing" _hdr_check_header "$headers" "Referrer-Policy" "Controla información del referrer" _hdr_check_header "$headers" "Permissions-Policy" "Controla acceso a APIs del navegador" _hdr_check_header "$headers" "Cross-Origin-Opener-Policy" "Aísla el contexto de navegación" _hdr_check_header "$headers" "Cross-Origin-Resource-Policy" "Controla compartición de recursos" _hdr_show_server_info "$headers" echo "" } # Ejecutar si se llama directamente if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then audit_http_headers "$@" fi