From 7c3f01c9ebe75ffcfc364f238160b142d35effd3 Mon Sep 17 00:00:00 2001 From: Egutierrez Date: Sun, 12 Apr 2026 13:54:25 +0200 Subject: [PATCH] feat: add bash cybersecurity audit and hardening functions 12 funciones Bash del dominio cybersecurity: auditoria de red y servicios (analyze_dns, audit_http_headers, inspect_ssl_cert, list_active_connections, enumerate_subdomains, geolocate_ip), auditoria de sistema (audit_ssh_config, check_firewall, detect_suspicious_users), y utilidades crypto (encrypt_file, generate_password, verify_file_hash). Dominio nuevo en bash/functions/. --- bash/functions/cybersecurity/analyze_dns.md | 52 +++++ bash/functions/cybersecurity/analyze_dns.sh | 170 +++++++++++++++++ .../cybersecurity/audit_http_headers.md | 47 +++++ .../cybersecurity/audit_http_headers.sh | 154 +++++++++++++++ .../cybersecurity/audit_ssh_config.md | 44 +++++ .../cybersecurity/audit_ssh_config.sh | 177 ++++++++++++++++++ .../functions/cybersecurity/check_firewall.md | 38 ++++ .../functions/cybersecurity/check_firewall.sh | 154 +++++++++++++++ .../cybersecurity/detect_suspicious_users.md | 38 ++++ .../cybersecurity/detect_suspicious_users.sh | 162 ++++++++++++++++ bash/functions/cybersecurity/encrypt_file.md | 50 +++++ bash/functions/cybersecurity/encrypt_file.sh | 165 ++++++++++++++++ .../cybersecurity/enumerate_subdomains.md | 46 +++++ .../cybersecurity/enumerate_subdomains.sh | 134 +++++++++++++ .../cybersecurity/generate_password.md | 54 ++++++ .../cybersecurity/generate_password.sh | 143 ++++++++++++++ bash/functions/cybersecurity/geolocate_ip.md | 44 +++++ bash/functions/cybersecurity/geolocate_ip.sh | 150 +++++++++++++++ .../cybersecurity/inspect_ssl_cert.md | 47 +++++ .../cybersecurity/inspect_ssl_cert.sh | 169 +++++++++++++++++ .../cybersecurity/list_active_connections.md | 47 +++++ .../cybersecurity/list_active_connections.sh | 109 +++++++++++ .../cybersecurity/verify_file_hash.md | 51 +++++ .../cybersecurity/verify_file_hash.sh | 100 ++++++++++ 24 files changed, 2345 insertions(+) create mode 100644 bash/functions/cybersecurity/analyze_dns.md create mode 100644 bash/functions/cybersecurity/analyze_dns.sh create mode 100644 bash/functions/cybersecurity/audit_http_headers.md create mode 100644 bash/functions/cybersecurity/audit_http_headers.sh create mode 100644 bash/functions/cybersecurity/audit_ssh_config.md create mode 100644 bash/functions/cybersecurity/audit_ssh_config.sh create mode 100644 bash/functions/cybersecurity/check_firewall.md create mode 100644 bash/functions/cybersecurity/check_firewall.sh create mode 100644 bash/functions/cybersecurity/detect_suspicious_users.md create mode 100644 bash/functions/cybersecurity/detect_suspicious_users.sh create mode 100644 bash/functions/cybersecurity/encrypt_file.md create mode 100644 bash/functions/cybersecurity/encrypt_file.sh create mode 100644 bash/functions/cybersecurity/enumerate_subdomains.md create mode 100644 bash/functions/cybersecurity/enumerate_subdomains.sh create mode 100644 bash/functions/cybersecurity/generate_password.md create mode 100644 bash/functions/cybersecurity/generate_password.sh create mode 100644 bash/functions/cybersecurity/geolocate_ip.md create mode 100644 bash/functions/cybersecurity/geolocate_ip.sh create mode 100644 bash/functions/cybersecurity/inspect_ssl_cert.md create mode 100644 bash/functions/cybersecurity/inspect_ssl_cert.sh create mode 100644 bash/functions/cybersecurity/list_active_connections.md create mode 100644 bash/functions/cybersecurity/list_active_connections.sh create mode 100644 bash/functions/cybersecurity/verify_file_hash.md create mode 100644 bash/functions/cybersecurity/verify_file_hash.sh diff --git a/bash/functions/cybersecurity/analyze_dns.md b/bash/functions/cybersecurity/analyze_dns.md new file mode 100644 index 00000000..3f2e284b --- /dev/null +++ b/bash/functions/cybersecurity/analyze_dns.md @@ -0,0 +1,52 @@ +--- +name: analyze_dns +kind: function +lang: bash +domain: cybersecurity +version: "1.0.0" +purity: impure +signature: "analyze_dns(domain: string, mode: string) -> void" +description: "Análisis DNS completo de un dominio: registros A/AAAA/MX/NS/TXT/CNAME/SOA, consulta whois y verificación contra listas negras DNSBL (spamhaus, spamcop, sorbs, barracuda)." +tags: [bash, cybersecurity, dns, network, whois, dnsbl, reconnaissance] +uses_functions: [] +uses_types: [] +returns: [] +returns_optional: false +error_type: "error_go_core" +imports: [] +params: + - name: domain + desc: "dominio a analizar, ej: example.com" + - name: mode + desc: "modo de análisis: records (solo registros DNS), whois (solo whois), dnsbl (solo listas negras) o all (todo, por defecto)" +output: "imprime registros DNS, información whois y estado DNSBL a stdout con colores ANSI" +tested: false +tests: [] +test_file_path: "" +file_path: "bash/functions/cybersecurity/analyze_dns.sh" +source_repo: "https://gitea-dgg044oo04woo4ggcsws4gk0.organic-machine.com/egutierrez/DevLauncher.git" +source_license: "MIT" +source_file: "scripts/linux/ciberseguridad/redes/analisis_dns.sh" +--- + +## Ejemplo + +```bash +source bash/functions/cybersecurity/analyze_dns.sh + +# Análisis completo +analyze_dns example.com + +# Solo registros DNS +analyze_dns example.com records + +# Solo whois +analyze_dns example.com whois + +# Solo DNSBL +analyze_dns example.com dnsbl +``` + +## Notas + +Requiere `dig` (paquete dnsutils). `whois` es opcional — si no está instalado y el modo es `all`, se omite el paso whois con aviso. Las listas negras DNSBL se consultan via DNS inverso (técnica estándar sin HTTP). El modo `dnsbl` resuelve primero la IP del dominio y luego construye la consulta invertida para cada blacklist. diff --git a/bash/functions/cybersecurity/analyze_dns.sh b/bash/functions/cybersecurity/analyze_dns.sh new file mode 100644 index 00000000..5b5046f7 --- /dev/null +++ b/bash/functions/cybersecurity/analyze_dns.sh @@ -0,0 +1,170 @@ +#!/usr/bin/env bash +# analyze_dns +# ----------- +# Análisis DNS completo de un dominio: registros A/AAAA/MX/NS/TXT/CNAME/SOA, +# consulta whois y verificación contra listas negras DNSBL. +# +# USO (directo): +# analyze_dns example.com [records|whois|dnsbl|all] +# +# Depende de: dig, whois (opcional), curl (para DNSBL) + +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 ────────────────────────────────────────────────────────── + +_dns_is_valid_domain() { + local domain="$1" + [[ -n "$domain" && "$domain" =~ ^[a-zA-Z0-9._-]+\.[a-zA-Z]{2,}$ ]] +} + +_dns_build_dnsbl_query() { + local ip="$1" + local bl="$2" + local reversed + reversed="$(echo "$ip" | awk -F. '{print $4"."$3"."$2"."$1}')" + echo "${reversed}.${bl}" +} + +# ─── Funciones de efecto ────────────────────────────────────────────────────── + +_dns_query_record() { + local domain="$1" + local type="$2" + local result + result="$(dig +short "$type" "$domain" 2>/dev/null || true)" + if [[ -z "$result" ]]; then + echo " (sin registros)" + else + echo "$result" | while IFS= read -r line; do + echo " * $line" + done + fi +} + +_dns_show_all_records() { + local domain="$1" + echo "" + for type in A AAAA MX NS TXT CNAME SOA; do + echo -e "${CYAN}── ${type} ──────────────────${NC}" + _dns_query_record "$domain" "$type" + echo "" + done +} + +_dns_show_whois() { + local domain="$1" + echo "" + info "Consultando whois de ${domain}..." + echo -e "${PURPLE}════════════════════════════════════════════════════════════${NC}" + whois "$domain" 2>/dev/null \ + | grep -iE "(registrar|registrant|creation|expiry|expire|updated|name server|status)" \ + | head -20 \ + | while IFS= read -r line; do echo -e " ${DIM_GRAY}${line}${NC}"; done + echo -e "${PURPLE}════════════════════════════════════════════════════════════${NC}" +} + +_dns_check_dnsbl() { + local domain="$1" + local ip + ip="$(dig +short A "$domain" 2>/dev/null | head -1 || true)" + + if [[ -z "$ip" ]]; then + warning "No se pudo resolver la IP de $domain para comprobar DNSBL" + return + fi + + info "IP a comprobar: $ip" + echo "" + + local blacklists=( + "zen.spamhaus.org" + "bl.spamcop.net" + "dnsbl.sorbs.net" + "b.barracudacentral.org" + ) + + local found=0 + for bl in "${blacklists[@]}"; do + local query + query="$(_dns_build_dnsbl_query "$ip" "$bl")" + local result + result="$(dig +short A "$query" 2>/dev/null || true)" + if [[ -n "$result" ]]; then + echo -e " ${RED}LISTADO${NC} ${bl} ($result)" + found=$((found + 1)) + else + echo -e " ${GREEN}limpio${NC} ${bl}" + fi + done + + echo "" + if [[ $found -eq 0 ]]; then + success "La IP no aparece en ninguna lista negra comprobada" + else + warning "La IP aparece en ${found} lista(s) negra(s)" + fi +} + +# ─── Punto de entrada ───────────────────────────────────────────────────────── + +analyze_dns() { + local domain="$1" + local mode="${2:-all}" + + if [[ -z "$domain" ]]; then + error "analyze_dns: se requiere un dominio como primer argumento" >&2 + return 1 + fi + + if ! _dns_is_valid_domain "$domain"; then + error "analyze_dns: dominio no válido: '$domain'" >&2 + return 1 + fi + + if ! command -v dig &>/dev/null; then + error "analyze_dns: 'dig' no está instalado (sudo apt install dnsutils)" >&2 + return 1 + fi + + info "Analizando: ${domain}" + + case "$mode" in + records) + _dns_show_all_records "$domain" + ;; + whois) + if ! command -v whois &>/dev/null; then + error "analyze_dns: 'whois' no está instalado (sudo apt install whois)" >&2 + return 1 + fi + _dns_show_whois "$domain" + ;; + dnsbl) + _dns_check_dnsbl "$domain" + ;; + all) + _dns_show_all_records "$domain" + if command -v whois &>/dev/null; then + _dns_show_whois "$domain" + else + warning "whois no disponible, omitiendo" + fi + echo "" + _dns_check_dnsbl "$domain" + ;; + *) + error "analyze_dns: modo no válido '$mode'. Use: records|whois|dnsbl|all" >&2 + return 1 + ;; + esac +} + +# Ejecutar si se llama directamente +if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then + analyze_dns "$@" +fi diff --git a/bash/functions/cybersecurity/audit_http_headers.md b/bash/functions/cybersecurity/audit_http_headers.md new file mode 100644 index 00000000..2c9d0189 --- /dev/null +++ b/bash/functions/cybersecurity/audit_http_headers.md @@ -0,0 +1,47 @@ +--- +name: audit_http_headers +kind: function +lang: bash +domain: cybersecurity +version: "1.0.0" +purity: impure +signature: "audit_http_headers(url: string) -> void" +description: "Audita las cabeceras HTTP de seguridad de una URL: verifica la presencia de HSTS (con validación de max-age mínimo de 6 meses), Content-Security-Policy, X-Frame-Options, X-Content-Type-Options, Referrer-Policy, Permissions-Policy y cabeceras CORS. También detecta cabeceras que exponen información del servidor." +tags: [bash, cybersecurity, web, http, headers, security, hsts, csp, hardening] +uses_functions: [] +uses_types: [] +returns: [] +returns_optional: false +error_type: "error_go_core" +imports: [] +params: + - name: url + desc: "URL del sitio web a auditar; si no tiene esquema se añade https:// automáticamente" +output: "imprime el estado de cada cabecera de seguridad (ok/falta/advertencia), el valor de las presentes y cabeceras que exponen información del servidor" +tested: false +tests: [] +test_file_path: "" +file_path: "bash/functions/cybersecurity/audit_http_headers.sh" +source_repo: "https://gitea-dgg044oo04woo4ggcsws4gk0.organic-machine.com/egutierrez/DevLauncher.git" +source_license: "MIT" +source_file: "scripts/linux/ciberseguridad/web/cabeceras_http.sh" +--- + +## Ejemplo + +```bash +source bash/functions/cybersecurity/audit_http_headers.sh + +# Con URL completa +audit_http_headers https://example.com + +# Sin esquema (añade https:// automáticamente) +audit_http_headers example.com + +# Seguir redirecciones +audit_http_headers http://example.com +``` + +## Notas + +Usa `curl -sI --location` para seguir redirecciones y obtener solo cabeceras. El check de HSTS valida que `max-age` sea >= 15.768.000 segundos (6 meses), valor mínimo recomendado por OWASP. Las cabeceras Server, X-Powered-By, X-AspNet-Version y X-Generator se marcan como advertencia por revelar información del stack tecnológico. Timeout de 15 segundos para evitar cuelgues. diff --git a/bash/functions/cybersecurity/audit_http_headers.sh b/bash/functions/cybersecurity/audit_http_headers.sh new file mode 100644 index 00000000..591087a9 --- /dev/null +++ b/bash/functions/cybersecurity/audit_http_headers.sh @@ -0,0 +1,154 @@ +#!/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 diff --git a/bash/functions/cybersecurity/audit_ssh_config.md b/bash/functions/cybersecurity/audit_ssh_config.md new file mode 100644 index 00000000..94b51286 --- /dev/null +++ b/bash/functions/cybersecurity/audit_ssh_config.md @@ -0,0 +1,44 @@ +--- +name: audit_ssh_config +kind: function +lang: bash +domain: cybersecurity +version: "1.0.0" +purity: impure +signature: "audit_ssh_config(config_path: string) -> void" +description: "Audita la configuración de sshd_config evaluando parámetros de seguridad críticos (PermitRootLogin, PasswordAuthentication, Port, MaxAuthTries, X11Forwarding, AllowUsers). También revisa intentos de login fallidos en los logs y lista las claves autorizadas del usuario actual." +tags: [bash, cybersecurity, ssh, audit, security, hardening, linux] +uses_functions: [] +uses_types: [] +returns: [] +returns_optional: false +error_type: "error_go_core" +imports: [] +params: + - name: config_path + desc: "ruta al archivo sshd_config a auditar (por defecto: /etc/ssh/sshd_config)" +output: "imprime checks con nivel ok/warn/bad para cada parámetro, últimos 10 intentos de login fallidos y lista de claves autorizadas" +tested: false +tests: [] +test_file_path: "" +file_path: "bash/functions/cybersecurity/audit_ssh_config.sh" +source_repo: "https://gitea-dgg044oo04woo4ggcsws4gk0.organic-machine.com/egutierrez/DevLauncher.git" +source_license: "MIT" +source_file: "scripts/linux/ciberseguridad/sistema/auditar_ssh.sh" +--- + +## Ejemplo + +```bash +source bash/functions/cybersecurity/audit_ssh_config.sh + +# Auditar la configuración por defecto +audit_ssh_config + +# Auditar un archivo alternativo +audit_ssh_config /etc/ssh/sshd_config.d/custom.conf +``` + +## Notas + +Los logs de intentos fallidos se buscan primero en `journalctl` (systemd) y si no está disponible en `/var/log/auth.log`. Leer `/etc/ssh/sshd_config` puede requerir permisos de root en algunos sistemas. Los criterios de evaluación siguen las recomendaciones de CIS Benchmark para SSH: PermitRootLogin=no, PasswordAuthentication=no, MaxAuthTries<=3. diff --git a/bash/functions/cybersecurity/audit_ssh_config.sh b/bash/functions/cybersecurity/audit_ssh_config.sh new file mode 100644 index 00000000..a1020080 --- /dev/null +++ b/bash/functions/cybersecurity/audit_ssh_config.sh @@ -0,0 +1,177 @@ +#!/usr/bin/env bash +# audit_ssh_config +# ---------------- +# Audita la configuración de sshd_config evaluando parámetros de seguridad, +# revisa intentos de login fallidos y lista las claves autorizadas del usuario. +# +# USO (directo): +# audit_ssh_config [/ruta/a/sshd_config] +# +# Depende de: grep, ssh (opcional para validación), journalctl o /var/log/auth.log + +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 ────────────────────────────────────────────────────────── + +_ssh_get_value() { + local config="$1" + local key="$2" + grep -iE "^[[:space:]]*${key}[[:space:]]" "$config" 2>/dev/null \ + | tail -1 | awk '{print $2}' | xargs +} + +_ssh_eval_permit_root() { + local val="${1:-yes}" + case "${val,,}" in + no|prohibit-password) echo "ok" ;; + without-password) echo "warn" ;; + *) echo "bad" ;; + esac +} + +_ssh_eval_password_auth() { + local val="${1:-yes}" + [[ "${val,,}" == "no" ]] && echo "ok" || echo "bad" +} + +_ssh_eval_max_auth_tries() { + local val="${1:-6}" + [[ "$val" -le 3 ]] && echo "ok" || echo "warn" +} + +_ssh_eval_x11_forwarding() { + local val="${1:-no}" + [[ "${val,,}" == "no" ]] && echo "ok" || echo "warn" +} + +# ─── Funciones de presentación ──────────────────────────────────────────────── + +_ssh_print_check() { + local level="$1" + local label="$2" + local value="$3" + local note="$4" + + case "$level" in + ok) echo -e " ${GREEN}[ok]${NC} ${label}: ${value}" ;; + warn) echo -e " ${YELLOW}[!] ${NC} ${label}: ${value} -- ${note}" ;; + bad) echo -e " ${RED}[x] ${NC} ${label}: ${value} -- ${note}" ;; + esac +} + +_ssh_show_config_checks() { + local config="$1" + + echo -e "${PURPLE}════════ Configuración sshd_config ════════════${NC}" + echo "" + + local permit_root + permit_root="$(_ssh_get_value "$config" "PermitRootLogin")" + permit_root="${permit_root:-yes (por defecto)}" + _ssh_print_check "$(_ssh_eval_permit_root "$permit_root")" \ + "PermitRootLogin" "$permit_root" "debería ser 'no' o 'prohibit-password'" + + local pass_auth + pass_auth="$(_ssh_get_value "$config" "PasswordAuthentication")" + pass_auth="${pass_auth:-yes (por defecto)}" + _ssh_print_check "$(_ssh_eval_password_auth "$pass_auth")" \ + "PasswordAuthentication" "$pass_auth" "debería ser 'no' (usar claves)" + + local port + port="$(_ssh_get_value "$config" "Port")" + port="${port:-22 (por defecto)}" + if [[ "$port" == "22"* ]]; then + _ssh_print_check "warn" "Port" "$port" "considera cambiar el puerto 22" + else + _ssh_print_check "ok" "Port" "$port" "" + fi + + local max_tries + max_tries="$(_ssh_get_value "$config" "MaxAuthTries")" + max_tries="${max_tries:-6 (por defecto)}" + _ssh_print_check "$(_ssh_eval_max_auth_tries "${max_tries%% *}")" \ + "MaxAuthTries" "$max_tries" "recomendado <= 3" + + local x11 + x11="$(_ssh_get_value "$config" "X11Forwarding")" + x11="${x11:-no (por defecto)}" + _ssh_print_check "$(_ssh_eval_x11_forwarding "$x11")" \ + "X11Forwarding" "$x11" "deshabilitar si no se usa" + + local allow_users allow_groups + allow_users="$(_ssh_get_value "$config" "AllowUsers")" + allow_groups="$(_ssh_get_value "$config" "AllowGroups")" + if [[ -z "$allow_users" && -z "$allow_groups" ]]; then + _ssh_print_check "warn" "AllowUsers/AllowGroups" "(no definidos)" "considera restringir acceso por usuario o grupo" + else + [[ -n "$allow_users" ]] && _ssh_print_check "ok" "AllowUsers" "$allow_users" "" + [[ -n "$allow_groups" ]] && _ssh_print_check "ok" "AllowGroups" "$allow_groups" "" + fi + + echo "" +} + +_ssh_show_failed_logins() { + echo -e "${PURPLE}════════ Últimos intentos de login fallidos ════${NC}" + echo "" + + if command -v journalctl &>/dev/null; then + journalctl -u ssh -u sshd --no-pager -q 2>/dev/null \ + | grep -i "failed\|invalid\|error" | tail -10 \ + | while IFS= read -r line; do echo -e " ${DIM_GRAY}${line}${NC}"; done || true + elif [[ -f /var/log/auth.log ]]; then + grep -i "failed\|invalid" /var/log/auth.log 2>/dev/null | tail -10 \ + | while IFS= read -r line; do echo -e " ${DIM_GRAY}${line}${NC}"; done || true + else + info "No se encontró fuente de logs de autenticación" + fi + + echo "" +} + +_ssh_show_authorized_keys() { + echo -e "${PURPLE}════════ Claves autorizadas (~/.ssh) ═══════════${NC}" + echo "" + + local auth_keys="$HOME/.ssh/authorized_keys" + if [[ -f "$auth_keys" ]]; then + local count + count="$(wc -l < "$auth_keys")" + info "${count} clave(s) en authorized_keys:" + while IFS= read -r line; do + [[ -z "$line" || "$line" == "#"* ]] && continue + local key_type key_comment + key_type="$(echo "$line" | awk '{print $1}')" + key_comment="$(echo "$line" | awk '{print $NF}')" + echo -e " ${GREEN}*${NC} ${key_type} -- ${key_comment}" + done < "$auth_keys" + else + info "No existe $auth_keys" + fi + + echo "" +} + +# ─── Punto de entrada ───────────────────────────────────────────────────────── + +audit_ssh_config() { + local config_path="${1:-/etc/ssh/sshd_config}" + + if [[ ! -f "$config_path" ]]; then + warning "audit_ssh_config: no se encontró $config_path -- ¿está instalado sshd?" + else + _ssh_show_config_checks "$config_path" + fi + + _ssh_show_failed_logins + _ssh_show_authorized_keys +} + +# Ejecutar si se llama directamente +if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then + audit_ssh_config "$@" +fi diff --git a/bash/functions/cybersecurity/check_firewall.md b/bash/functions/cybersecurity/check_firewall.md new file mode 100644 index 00000000..9d625e7f --- /dev/null +++ b/bash/functions/cybersecurity/check_firewall.md @@ -0,0 +1,38 @@ +--- +name: check_firewall +kind: function +lang: bash +domain: cybersecurity +version: "1.0.0" +purity: impure +signature: "check_firewall() -> void" +description: "Detecta el firewall activo del sistema (ufw, firewalld o iptables) y muestra su estado, reglas activas y puertos en escucha para cruzar con las reglas. Si no se detecta ningún firewall, emite una advertencia de exposición." +tags: [bash, cybersecurity, firewall, ufw, iptables, network, hardening, linux] +uses_functions: [] +uses_types: [] +returns: [] +returns_optional: false +error_type: "error_go_core" +imports: [] +params: [] +output: "imprime el firewall detectado, su estado (activo/inactivo), reglas vigentes y lista de puertos en escucha" +tested: false +tests: [] +test_file_path: "" +file_path: "bash/functions/cybersecurity/check_firewall.sh" +source_repo: "https://gitea-dgg044oo04woo4ggcsws4gk0.organic-machine.com/egutierrez/DevLauncher.git" +source_license: "MIT" +source_file: "scripts/linux/ciberseguridad/sistema/firewall_status.sh" +--- + +## Ejemplo + +```bash +source bash/functions/cybersecurity/check_firewall.sh + +check_firewall +``` + +## Notas + +La detección sigue el orden: ufw > firewalld > iptables > none. Para ufw muestra `ufw status verbose`; para firewalld muestra zona por defecto, servicios y puertos permitidos; para iptables muestra las cadenas INPUT/OUTPUT/FORWARD. Leer reglas de iptables requiere privilegios de root. El cruce de puertos en escucha (via `ss -tlnp`) ayuda a identificar servicios sin regla de firewall correspondiente. diff --git a/bash/functions/cybersecurity/check_firewall.sh b/bash/functions/cybersecurity/check_firewall.sh new file mode 100644 index 00000000..a72dcd29 --- /dev/null +++ b/bash/functions/cybersecurity/check_firewall.sh @@ -0,0 +1,154 @@ +#!/usr/bin/env bash +# check_firewall +# -------------- +# Detecta el firewall activo del sistema (ufw, firewalld o iptables) y muestra +# su estado y reglas. También lista los puertos en escucha para cruzar con reglas. +# +# USO (directo): +# check_firewall +# +# Depende de: ufw, firewall-cmd o iptables (el que esté disponible), ss + +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 ────────────────────────────────────────────────────────── + +_fw_detect() { + if command -v ufw &>/dev/null && ufw status 2>/dev/null | grep -q "Status:"; then + echo "ufw" + elif command -v firewall-cmd &>/dev/null && firewall-cmd --state 2>/dev/null | grep -q "running"; then + echo "firewalld" + elif command -v iptables &>/dev/null; then + echo "iptables" + else + echo "none" + fi +} + +_fw_ufw_is_active() { + ufw status 2>/dev/null | grep -q "Status: active" +} + +_fw_firewalld_is_running() { + firewall-cmd --state 2>/dev/null | grep -q "running" +} + +_fw_iptables_has_rules() { + local count + count="$(iptables -L INPUT --line-numbers 2>/dev/null | grep -c "^[0-9]" || echo 0)" + [[ "$count" -gt 0 ]] +} + +# ─── Funciones de efecto ────────────────────────────────────────────────────── + +_fw_show_ufw() { + echo -e "${PURPLE}════════ UFW ════════════════════════════════════${NC}" + echo "" + + if _fw_ufw_is_active; then + success "UFW está activo" + else + echo -e " ${RED}[x]${NC} UFW está INACTIVO" + fi + + echo "" + info "Reglas activas:" + ufw status verbose 2>/dev/null | while IFS= read -r line; do + echo -e " ${DIM_GRAY}${line}${NC}" + done + + echo "" +} + +_fw_show_firewalld() { + echo -e "${PURPLE}════════ FirewallD ══════════════════════════════${NC}" + echo "" + + if _fw_firewalld_is_running; then + success "firewalld está activo" + else + echo -e " ${RED}[x]${NC} firewalld está INACTIVO" + fi + + echo "" + local zone + zone="$(firewall-cmd --get-default-zone 2>/dev/null || echo "desconocida")" + info "Zona por defecto: ${zone}" + echo "" + + info "Servicios permitidos en zona ${zone}:" + firewall-cmd --zone="$zone" --list-services 2>/dev/null \ + | tr ' ' '\n' | while IFS= read -r svc; do + echo -e " ${GREEN}*${NC} ${svc}" + done + + echo "" + info "Puertos permitidos:" + firewall-cmd --zone="$zone" --list-ports 2>/dev/null \ + | tr ' ' '\n' | while IFS= read -r port; do + [[ -n "$port" ]] && echo -e " ${YELLOW}*${NC} ${port}" + done || true + + echo "" +} + +_fw_show_iptables() { + echo -e "${PURPLE}════════ iptables ═══════════════════════════════${NC}" + echo "" + + for chain in INPUT OUTPUT FORWARD; do + echo -e "${CYAN}── ${chain} ──${NC}" + iptables -L "$chain" --line-numbers -n 2>/dev/null \ + | while IFS= read -r line; do echo -e " ${DIM_GRAY}${line}${NC}"; done + echo "" + done + + if ! _fw_iptables_has_rules; then + echo -e " ${YELLOW}[!]${NC} No hay reglas INPUT definidas -- el sistema puede estar sin filtrar tráfico" + fi +} + +_fw_show_none() { + echo "" + echo -e " ${RED}[x]${NC} No se detectó ningún firewall activo (ufw, firewalld, iptables)" + echo -e " ${YELLOW}[!]${NC} El sistema puede estar completamente expuesto" + echo "" + info "Para instalar y activar ufw: sudo apt install ufw && sudo ufw enable" +} + +_fw_show_listening_crosscheck() { + echo "" + echo -e "${PURPLE}════════ Puertos en escucha (para cruzar con reglas) ════${NC}" + echo "" + ss -tlnp 2>/dev/null | tail -n +2 | while IFS= read -r line; do + echo -e " ${DIM_GRAY}${line}${NC}" + done +} + +# ─── Punto de entrada ───────────────────────────────────────────────────────── + +check_firewall() { + local fw + fw="$(_fw_detect)" + + info "Firewall detectado: ${fw}" + echo "" + + case "$fw" in + ufw) _fw_show_ufw ;; + firewalld) _fw_show_firewalld ;; + iptables) _fw_show_iptables ;; + none) _fw_show_none ;; + esac + + _fw_show_listening_crosscheck +} + +# Ejecutar si se llama directamente +if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then + check_firewall "$@" +fi diff --git a/bash/functions/cybersecurity/detect_suspicious_users.md b/bash/functions/cybersecurity/detect_suspicious_users.md new file mode 100644 index 00000000..177f91b5 --- /dev/null +++ b/bash/functions/cybersecurity/detect_suspicious_users.md @@ -0,0 +1,38 @@ +--- +name: detect_suspicious_users +kind: function +lang: bash +domain: cybersecurity +version: "1.0.0" +purity: impure +signature: "detect_suspicious_users() -> void" +description: "Revisa el sistema en busca de indicadores de compromiso en cuentas de usuario: UIDs 0 extras (además de root), usuarios con shell de login válida, homes en rutas inusuales, miembros de grupos privilegiados (sudo, docker, wheel, adm, etc.) y sesiones activas." +tags: [bash, cybersecurity, users, audit, linux, privilege-escalation, hardening] +uses_functions: [] +uses_types: [] +returns: [] +returns_optional: false +error_type: "error_go_core" +imports: [] +params: [] +output: "imprime secciones con UIDs 0, usuarios con shell, homes inusuales, grupos privilegiados, últimos logins y sesiones activas" +tested: false +tests: [] +test_file_path: "" +file_path: "bash/functions/cybersecurity/detect_suspicious_users.sh" +source_repo: "https://gitea-dgg044oo04woo4ggcsws4gk0.organic-machine.com/egutierrez/DevLauncher.git" +source_license: "MIT" +source_file: "scripts/linux/ciberseguridad/sistema/usuarios_sospechosos.sh" +--- + +## Ejemplo + +```bash +source bash/functions/cybersecurity/detect_suspicious_users.sh + +detect_suspicious_users +``` + +## Notas + +Los usuarios de sistema (UID <= 999) se excluyen del check de shell válida para evitar falsos positivos. Los grupos privilegiados monitorizados son: sudo, wheel, docker, adm, lxd, libvirt, kvm, disk, shadow. Homes inusuales son aquellos fuera de /home, /root, /var, /srv, /nonexistent y /tmp. `lastlog` puede no estar disponible en todas las distribuciones. diff --git a/bash/functions/cybersecurity/detect_suspicious_users.sh b/bash/functions/cybersecurity/detect_suspicious_users.sh new file mode 100644 index 00000000..d3e1d757 --- /dev/null +++ b/bash/functions/cybersecurity/detect_suspicious_users.sh @@ -0,0 +1,162 @@ +#!/usr/bin/env bash +# detect_suspicious_users +# ----------------------- +# Revisa el sistema en busca de usuarios potencialmente sospechosos: +# UIDs 0 extras, shells válidas, homes en rutas inusuales, grupos privilegiados +# y sesiones activas. +# +# USO (directo): +# detect_suspicious_users +# +# Depende de: /etc/passwd, getent, w, lastlog + +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 + +# ─── Constantes ─────────────────────────────────────────────────────────────── + +_SUSPICIOUS_VALID_SHELLS=("/bin/bash" "/bin/sh" "/bin/zsh" "/bin/fish" "/usr/bin/bash" "/usr/bin/zsh" "/usr/bin/fish") +_SUSPICIOUS_PRIVILEGED_GROUPS=("sudo" "wheel" "docker" "adm" "lxd" "libvirt" "kvm" "disk" "shadow") +_SUSPICIOUS_SYSTEM_USERS_MAX_UID=999 + +# ─── Funciones puras ────────────────────────────────────────────────────────── + +_sus_is_valid_shell() { + local shell="$1" + for s in "${_SUSPICIOUS_VALID_SHELLS[@]}"; do + [[ "$shell" == "$s" ]] && return 0 + done + return 1 +} + +_sus_is_system_user() { + local uid="$1" + [[ "$uid" -le $_SUSPICIOUS_SYSTEM_USERS_MAX_UID ]] +} + +_sus_is_unusual_home() { + local home="$1" + [[ ! "$home" =~ ^(/home|/root|/var|/srv|/nonexistent|/tmp) ]] +} + +# ─── Funciones de efecto ────────────────────────────────────────────────────── + +_sus_show_uid0_users() { + echo -e "${PURPLE}════════ Usuarios con UID 0 (root) ═════════════${NC}" + echo "" + + local found=0 + while IFS=: read -r username _ uid _; do + if [[ "$uid" -eq 0 ]]; then + if [[ "$username" == "root" ]]; then + echo -e " ${GREEN}[ok]${NC} root (esperado)" + else + echo -e " ${RED}[x]${NC} ${username} tiene UID 0 -- SOSPECHOSO" + fi + found=$((found + 1)) + fi + done < /etc/passwd + + if [[ $found -eq 1 ]]; then + echo "" + success "Solo root tiene UID 0" + fi + + echo "" +} + +_sus_show_users_with_shell() { + echo -e "${PURPLE}════════ Usuarios con shell de login válida ═════${NC}" + echo "" + echo -e " ${GRAY}(excluye usuarios de sistema con UID <= ${_SUSPICIOUS_SYSTEM_USERS_MAX_UID})${NC}" + echo "" + + local found=0 + while IFS=: read -r username _ uid _ _ home shell; do + if ! _sus_is_system_user "$uid" && _sus_is_valid_shell "$shell"; then + echo -e " ${CYAN}*${NC} ${username} (UID ${uid}) -- shell: ${shell} -- home: ${home}" + found=$((found + 1)) + fi + done < /etc/passwd + + [[ $found -eq 0 ]] && info "No se encontraron usuarios normales con shell válida" + echo "" +} + +_sus_show_unusual_homes() { + echo -e "${PURPLE}════════ Usuarios con home inusual ══════════════${NC}" + echo "" + + local found=0 + while IFS=: read -r username _ uid _ _ home shell; do + if _sus_is_valid_shell "$shell" && _sus_is_unusual_home "$home"; then + echo -e " ${YELLOW}[!]${NC} ${username} -- home: ${home}" + found=$((found + 1)) + fi + done < /etc/passwd + + [[ $found -eq 0 ]] && success "No se detectaron homes en rutas inusuales" + echo "" +} + +_sus_show_privileged_groups() { + echo -e "${PURPLE}════════ Grupos privilegiados y sus miembros ════${NC}" + echo "" + + for group in "${_SUSPICIOUS_PRIVILEGED_GROUPS[@]}"; do + local members + members="$(getent group "$group" 2>/dev/null | cut -d: -f4 || true)" + if [[ -n "$members" ]]; then + echo -e " ${YELLOW}*${NC} ${group}: ${members}" + fi + done + + echo "" +} + +_sus_show_active_sessions() { + echo -e "${PURPLE}════════ Sesiones activas ═══════════════════════${NC}" + echo "" + + w 2>/dev/null | while IFS= read -r line; do + echo -e " ${DIM_GRAY}${line}${NC}" + done + + echo "" +} + +_sus_show_last_logins() { + echo -e "${PURPLE}════════ Últimos logins por usuario ═════════════${NC}" + echo "" + + if command -v lastlog &>/dev/null; then + lastlog 2>/dev/null | awk 'NR==1 || $NF != "logged" { + if (NR==1 || $2 != "**Never") printf " %-16s %-10s %s\n", $1, $2, $NF + }' | grep -v "^$" | while IFS= read -r line; do + echo -e " ${DIM_GRAY}${line}${NC}" + done + else + warning "lastlog no disponible" + fi + + echo "" +} + +# ─── Punto de entrada ───────────────────────────────────────────────────────── + +detect_suspicious_users() { + _sus_show_uid0_users + _sus_show_users_with_shell + _sus_show_unusual_homes + _sus_show_privileged_groups + _sus_show_last_logins + _sus_show_active_sessions +} + +# Ejecutar si se llama directamente +if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then + detect_suspicious_users "$@" +fi diff --git a/bash/functions/cybersecurity/encrypt_file.md b/bash/functions/cybersecurity/encrypt_file.md new file mode 100644 index 00000000..9fe0e8e7 --- /dev/null +++ b/bash/functions/cybersecurity/encrypt_file.md @@ -0,0 +1,50 @@ +--- +name: encrypt_file +kind: function +lang: bash +domain: cybersecurity +version: "1.0.0" +purity: impure +signature: "encrypt_file(mode: string, file: string) -> void" +description: "Cifra o descifra un archivo usando AES-256-CBC con PBKDF2 (310.000 iteraciones) via openssl. La contraseña se lee de la variable de entorno ENCRYPT_PASSWORD o se solicita interactivamente. El archivo cifrado se guarda con extensión .enc; al descifrar se recupera el nombre original." +tags: [bash, cybersecurity, encryption, aes256, openssl, crypto, pbkdf2] +uses_functions: [] +uses_types: [] +returns: [] +returns_optional: false +error_type: "error_go_core" +imports: [] +params: + - name: mode + desc: "operación a realizar: encrypt (cifrar) o decrypt (descifrar)" + - name: file + desc: "ruta al archivo a cifrar o descifrar" +output: "genera el archivo cifrado (input.enc) o descifrado (input sin .enc, o input.dec) e imprime progreso a stdout" +tested: false +tests: [] +test_file_path: "" +file_path: "bash/functions/cybersecurity/encrypt_file.sh" +source_repo: "https://gitea-dgg044oo04woo4ggcsws4gk0.organic-machine.com/egutierrez/DevLauncher.git" +source_license: "MIT" +source_file: "scripts/linux/ciberseguridad/utilidades/cifrar_archivo.sh" +--- + +## Ejemplo + +```bash +source bash/functions/cybersecurity/encrypt_file.sh + +# Cifrar (solicita contraseña interactivamente) +encrypt_file encrypt documento.pdf + +# Descifrar +encrypt_file decrypt documento.pdf.enc + +# Con contraseña via variable de entorno (no interactivo) +ENCRYPT_PASSWORD="mi-secreto-seguro" encrypt_file encrypt datos.tar.gz +ENCRYPT_PASSWORD="mi-secreto-seguro" encrypt_file decrypt datos.tar.gz.enc +``` + +## Notas + +Usa `openssl enc -aes-256-cbc -pbkdf2 -iter 310000` — compatible con OpenSSL 1.1.1+. Las 310.000 iteraciones de PBKDF2 siguen las recomendaciones NIST para derivación de claves en 2024. La contraseña se limpia de memoria al terminar. Si el archivo de salida ya existe, la función falla silenciosamente (no sobrescribe por seguridad cuando se usa con ENCRYPT_PASSWORD). diff --git a/bash/functions/cybersecurity/encrypt_file.sh b/bash/functions/cybersecurity/encrypt_file.sh new file mode 100644 index 00000000..77e9be1b --- /dev/null +++ b/bash/functions/cybersecurity/encrypt_file.sh @@ -0,0 +1,165 @@ +#!/usr/bin/env bash +# encrypt_file +# ------------ +# Cifra o descifra un archivo usando AES-256-CBC con PBKDF2 (310.000 iteraciones). +# La contraseña se lee de la variable de entorno ENCRYPT_PASSWORD o se solicita +# interactivamente por stdin. +# +# USO (directo): +# encrypt_file encrypt +# encrypt_file decrypt +# ENCRYPT_PASSWORD=secreto encrypt_file encrypt archivo.txt +# +# Depende de: openssl + +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 ────────────────────────────────────────────────────────── + +_enc_output_path_encrypt() { + local input="$1" + echo "${input}.enc" +} + +_enc_output_path_decrypt() { + local input="$1" + if [[ "$input" == *.enc ]]; then + echo "${input%.enc}" + else + echo "${input}.dec" + fi +} + +_enc_human_size() { + local file="$1" + du -sh "$file" 2>/dev/null | cut -f1 +} + +# ─── Funciones de efecto ──────────────────────────────────────────name────────── + +_enc_ask_password() { + local pass + read -rsp "Contraseña: " pass + echo "" >&2 + echo "$pass" +} + +_enc_ask_password_confirm() { + local pass1 pass2 + read -rsp "Contraseña: " pass1 + echo "" >&2 + read -rsp "Confirmar contraseña: " pass2 + echo "" >&2 + + if [[ "$pass1" != "$pass2" ]]; then + error "Las contraseñas no coinciden" >&2 + return 1 + fi + + if [[ ${#pass1} -lt 8 ]]; then + warning "La contraseña es muy corta (mínimo 8 caracteres recomendado)" + fi + + echo "$pass1" +} + +_enc_do_encrypt() { + local input="$1" + local output="$2" + local password="$3" + + openssl enc -aes-256-cbc -pbkdf2 -iter 310000 \ + -in "$input" -out "$output" \ + -pass "pass:${password}" 2>/dev/null +} + +_enc_do_decrypt() { + local input="$1" + local output="$2" + local password="$3" + + openssl enc -d -aes-256-cbc -pbkdf2 -iter 310000 \ + -in "$input" -out "$output" \ + -pass "pass:${password}" 2>/dev/null +} + +# ─── Punto de entrada ───────────────────────────────────────────────────────── + +encrypt_file() { + local mode="$1" + local file="$2" + + if [[ -z "$mode" || -z "$file" ]]; then + error "encrypt_file: uso: encrypt_file " >&2 + return 1 + fi + + if ! command -v openssl &>/dev/null; then + error "encrypt_file: 'openssl' no está instalado (sudo apt install openssl)" >&2 + return 1 + fi + + if [[ ! -f "$file" ]]; then + error "encrypt_file: archivo no encontrado: $file" >&2 + return 1 + fi + + local password + if [[ -n "${ENCRYPT_PASSWORD:-}" ]]; then + password="$ENCRYPT_PASSWORD" + else + case "$mode" in + encrypt) password="$(_enc_ask_password_confirm)" || return 1 ;; + decrypt) password="$(_enc_ask_password)" ;; + *) + error "encrypt_file: modo no válido '$mode'. Use: encrypt|decrypt" >&2 + return 1 + ;; + esac + fi + + case "$mode" in + encrypt) + local output + output="$(_enc_output_path_encrypt "$file")" + info "Archivo: ${file} ($(_enc_human_size "$file"))" + info "Salida: ${output}" + info "Cifrando con AES-256-CBC + PBKDF2..." + + if _enc_do_encrypt "$file" "$output" "$password"; then + success "Archivo cifrado: ${output} ($(_enc_human_size "$output"))" + else + error "encrypt_file: el cifrado falló" >&2 + rm -f "$output" + return 1 + fi + ;; + decrypt) + local output + output="$(_enc_output_path_decrypt "$file")" + info "Archivo: ${file} ($(_enc_human_size "$file"))" + info "Salida: ${output}" + info "Descifrando..." + + if _enc_do_decrypt "$file" "$output" "$password"; then + success "Archivo descifrado: ${output} ($(_enc_human_size "$output"))" + else + error "encrypt_file: el descifrado falló -- ¿contraseña incorrecta?" >&2 + rm -f "$output" + return 1 + fi + ;; + esac + + # Limpiar contraseña de memoria + password="" +} + +# Ejecutar si se llama directamente +if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then + encrypt_file "$@" +fi diff --git a/bash/functions/cybersecurity/enumerate_subdomains.md b/bash/functions/cybersecurity/enumerate_subdomains.md new file mode 100644 index 00000000..53b10d12 --- /dev/null +++ b/bash/functions/cybersecurity/enumerate_subdomains.md @@ -0,0 +1,46 @@ +--- +name: enumerate_subdomains +kind: function +lang: bash +domain: cybersecurity +version: "1.0.0" +purity: impure +signature: "enumerate_subdomains(domain: string, output_file: string) -> void" +description: "Enumera subdominios de un dominio objetivo usando un diccionario integrado de ~100 subdominios comunes (www, mail, api, dev, admin, vpn, etc.). Detecta tanto registros A (IP directa) como CNAME. Muestra progreso cada 20 subdominios y opcionalmente guarda los resultados en un archivo." +tags: [bash, cybersecurity, dns, subdomain, enumeration, reconnaissance, osint] +uses_functions: [] +uses_types: [] +returns: [] +returns_optional: false +error_type: "error_go_core" +imports: [] +params: + - name: domain + desc: "dominio objetivo a enumerar, ej: example.com" + - name: output_file + desc: "ruta al archivo donde guardar los resultados (opcional; si se omite, solo imprime a stdout)" +output: "imprime subdominios encontrados con su IP o CNAME, progreso cada 20 entradas y resumen final con total encontrados" +tested: false +tests: [] +test_file_path: "" +file_path: "bash/functions/cybersecurity/enumerate_subdomains.sh" +source_repo: "https://gitea-dgg044oo04woo4ggcsws4gk0.organic-machine.com/egutierrez/DevLauncher.git" +source_license: "MIT" +source_file: "scripts/linux/ciberseguridad/web/subdominios.sh" +--- + +## Ejemplo + +```bash +source bash/functions/cybersecurity/enumerate_subdomains.sh + +# Solo imprimir resultados +enumerate_subdomains example.com + +# Guardar resultados en archivo +enumerate_subdomains example.com /tmp/subdominios.txt +``` + +## Notas + +Requiere `dig` (paquete dnsutils). El diccionario integrado cubre subdominios comunes en entornos corporativos y de desarrollo: www, api, dev, admin, vpn, git, jenkins, staging, prod, db, mail, smtp, ns1/ns2, grafana, kibana, docker, k8s, auth, sso, etc. (~100 entradas). La enumeración es puramente pasiva via DNS — no realiza ningún tipo de conexión al servidor web. Los subdominios con CNAME sin resolución A se marcan en amarillo. diff --git a/bash/functions/cybersecurity/enumerate_subdomains.sh b/bash/functions/cybersecurity/enumerate_subdomains.sh new file mode 100644 index 00000000..d2eac98d --- /dev/null +++ b/bash/functions/cybersecurity/enumerate_subdomains.sh @@ -0,0 +1,134 @@ +#!/usr/bin/env bash +# enumerate_subdomains +# -------------------- +# Enumera subdominios de un dominio objetivo usando un diccionario integrado de +# ~100 subdominios comunes. Detecta tanto registros A (IP directa) como CNAME. +# Opcionalmente guarda el resultado en un archivo. +# +# USO (directo): +# enumerate_subdomains [archivo_salida] +# enumerate_subdomains example.com +# enumerate_subdomains example.com /tmp/resultado.txt +# +# Depende de: dig (dnsutils) + +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 + +# ─── Diccionario integrado ───────────────────────────────────────────────────── + +_SUBDOMAIN_WORDLIST=( + www mail ftp api dev admin vpn ssh git gitlab github jenkins ci cd + staging prod test demo beta alpha app web portal intranet extranet + remote desktop files cdn static assets media img images upload + db database mysql postgres redis mongo smtp pop imap webmail mx + ns1 ns2 dns autodiscover autoconfig crm erp shop store payment + backup old legacy v1 v2 v3 internal corp office support helpdesk + wiki docs doc status monitor grafana kibana elastic search + registry docker k8s kubernetes auth sso login oauth api2 mobile +) + +# ─── Funciones puras ────────────────────────────────────────────────────────── + +_sub_is_valid_domain() { + [[ -n "$1" && "$1" =~ ^[a-zA-Z0-9._-]+\.[a-zA-Z]{2,}$ ]] +} + +_sub_build_fqdn() { + local sub="$1" + local domain="$2" + echo "${sub}.${domain}" +} + +# ─── Funciones de efecto ────────────────────────────────────────────────────── + +_sub_resolve_a() { + local fqdn="$1" + dig +short A "$fqdn" 2>/dev/null | grep -E '^[0-9]+\.' | head -1 || true +} + +_sub_resolve_cname() { + local fqdn="$1" + dig +short CNAME "$fqdn" 2>/dev/null | head -1 || true +} + +# ─── Punto de entrada ───────────────────────────────────────────────────────── + +enumerate_subdomains() { + local domain="$1" + local output_file="${2:-}" + + if [[ -z "$domain" ]]; then + error "enumerate_subdomains: se requiere un dominio como argumento" >&2 + return 1 + fi + + if ! _sub_is_valid_domain "$domain"; then + error "enumerate_subdomains: dominio no válido: '$domain'" >&2 + return 1 + fi + + if ! command -v dig &>/dev/null; then + error "enumerate_subdomains: 'dig' no está instalado (sudo apt install dnsutils)" >&2 + return 1 + fi + + local total="${#_SUBDOMAIN_WORDLIST[@]}" + local found=0 + local checked=0 + + info "Probando ${total} subdominios en ${domain}..." + echo "" + + if [[ -n "$output_file" ]]; then + echo "# Subdominios encontrados en ${domain} -- $(date)" > "$output_file" + fi + + for sub in "${_SUBDOMAIN_WORDLIST[@]}"; do + local fqdn + fqdn="$(_sub_build_fqdn "$sub" "$domain")" + checked=$((checked + 1)) + + local ip + ip="$(_sub_resolve_a "$fqdn")" + + if [[ -n "$ip" ]]; then + echo -e " ${GREEN}[ok]${NC} ${fqdn} -> ${CYAN}${ip}${NC}" + [[ -n "$output_file" ]] && echo "${fqdn} -> ${ip}" >> "$output_file" + found=$((found + 1)) + else + local cname + cname="$(_sub_resolve_cname "$fqdn")" + if [[ -n "$cname" ]]; then + echo -e " ${YELLOW}[cn]${NC} ${fqdn} -> CNAME: ${cname}" + [[ -n "$output_file" ]] && echo "${fqdn} -> CNAME: ${cname}" >> "$output_file" + found=$((found + 1)) + fi + fi + + # Progreso cada 20 subdominios + if (( checked % 20 == 0 )); then + echo -e " ${DIM_GRAY}[${checked}/${total} probados, ${found} encontrados]${NC}" + fi + done + + echo "" + echo -e "${PURPLE}════════════════════════════════════════════════════════════${NC}" + + if [[ $found -eq 0 ]]; then + info "No se encontraron subdominios en el diccionario" + else + success "Total encontrados: ${found} de ${total} probados" + [[ -n "$output_file" ]] && info "Resultado guardado en: ${output_file}" + fi + + echo -e "${PURPLE}════════════════════════════════════════════════════════════${NC}" +} + +# Ejecutar si se llama directamente +if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then + enumerate_subdomains "$@" +fi diff --git a/bash/functions/cybersecurity/generate_password.md b/bash/functions/cybersecurity/generate_password.md new file mode 100644 index 00000000..3bd6bec0 --- /dev/null +++ b/bash/functions/cybersecurity/generate_password.md @@ -0,0 +1,54 @@ +--- +name: generate_password +kind: function +lang: bash +domain: cybersecurity +version: "1.0.0" +purity: impure +signature: "generate_password(mode: string, length: int, count: int) -> void" +description: "Genera contraseñas seguras en cuatro modos: full (alfanumérico + símbolos, excluye caracteres ambiguos), alpha (solo alfanumérico), passphrase (palabras aleatorias unidas con guión) y pin (numérico). Calcula y muestra la entropía en bits para cada modo." +tags: [bash, cybersecurity, password, generator, entropy, security, urandom] +uses_functions: [] +uses_types: [] +returns: [] +returns_optional: false +error_type: "error_go_core" +imports: [] +params: + - name: mode + desc: "modo de generación: full (alfanumérico+símbolos, por defecto), alpha (solo letras y números), passphrase (palabras), pin (numérico)" + - name: length + desc: "longitud en caracteres para full/alpha/pin, o número de palabras para passphrase (por defecto: 16)" + - name: count + desc: "número de contraseñas a generar (por defecto: 1)" +output: "imprime las contraseñas generadas a stdout (una por línea) con información de entropía" +tested: false +tests: [] +test_file_path: "" +file_path: "bash/functions/cybersecurity/generate_password.sh" +source_repo: "https://gitea-dgg044oo04woo4ggcsws4gk0.organic-machine.com/egutierrez/DevLauncher.git" +source_license: "MIT" +source_file: "scripts/linux/ciberseguridad/utilidades/generar_password.sh" +--- + +## Ejemplo + +```bash +source bash/functions/cybersecurity/generate_password.sh + +# Contraseña completa de 20 caracteres +generate_password full 20 + +# 5 contraseñas alfanuméricas de 16 caracteres +generate_password alpha 16 5 + +# Passphrase de 6 palabras +generate_password passphrase 6 + +# PIN de 8 dígitos +generate_password pin 8 +``` + +## Notas + +Usa `/dev/urandom` como fuente de aleatoriedad criptográficamente segura. El modo `full` excluye caracteres ambiguos (0, O, l, I, 1) para mejorar legibilidad. El modo `passphrase` requiere un diccionario del sistema (`/usr/share/dict/words` o similar). La entropía se calcula como log2(charset^length) en bits. Las contraseñas nunca se escriben a disco. diff --git a/bash/functions/cybersecurity/generate_password.sh b/bash/functions/cybersecurity/generate_password.sh new file mode 100644 index 00000000..88f31114 --- /dev/null +++ b/bash/functions/cybersecurity/generate_password.sh @@ -0,0 +1,143 @@ +#!/usr/bin/env bash +# generate_password +# ----------------- +# Genera contraseñas seguras en varios modos: completo (alfanumérico + símbolos), +# solo alfanumérico, passphrase de palabras o PIN numérico. +# Calcula la entropía en bits para cada contraseña generada. +# +# USO (directo): +# generate_password [full|alpha|passphrase|pin] [longitud] [cantidad] +# +# Depende de: /dev/urandom, python3 (para entropía), shuf (para passphrases) + +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 + +# ─── Constantes ─────────────────────────────────────────────────────────────── + +_GENPW_DEFAULT_LENGTH=16 +_GENPW_DEFAULT_COUNT=1 +_GENPW_WORDLIST_PATHS=("/usr/share/dict/words" "/usr/dict/words" "/usr/share/dict/american-english") + +# ─── Funciones puras ────────────────────────────────────────────────────────── + +_genpw_find_wordlist() { + for path in "${_GENPW_WORDLIST_PATHS[@]}"; do + [[ -f "$path" ]] && echo "$path" && return + done + echo "" +} + +_genpw_calc_entropy() { + local charset_size="$1" + local length="$2" + python3 -c "import math; print(f'{math.log2(${charset_size}**${length}):.1f}')" 2>/dev/null || echo "?" +} + +# ─── Funciones de generación ────────────────────────────────────────────────── + +_genpw_gen_full() { + local length="$1" + # Alfanumérico + símbolos (excluye ambiguos: 0OlI1) + tr -dc 'A-HJ-NP-Za-km-z2-9!@#$%^&*()_+-=[]{}|;:,.<>?' \ + < /dev/urandom | head -c "$length" + echo +} + +_genpw_gen_alpha() { + local length="$1" + tr -dc 'A-HJ-NP-Za-km-z2-9' \ + < /dev/urandom | head -c "$length" + echo +} + +_genpw_gen_passphrase() { + local words="$1" + local wordlist + wordlist="$(_genpw_find_wordlist)" + + if [[ -z "$wordlist" ]]; then + error "generate_password: no se encontró diccionario (sudo apt install wamerican)" >&2 + return 1 + fi + + local phrase="" + for ((i=0; i&2 + return 1 + fi + + local charset_size entropy + + case "$mode" in + full) + charset_size=78 + entropy="$(_genpw_calc_entropy $charset_size "$length")" + info "Contraseñas alfanuméricas + símbolos (longitud: ${length}, entropía: ~${entropy} bits)" + echo "" + for ((i=1; i<=count; i++)); do + _genpw_gen_full "$length" + done + ;; + alpha) + charset_size=56 + entropy="$(_genpw_calc_entropy $charset_size "$length")" + info "Contraseñas alfanuméricas (longitud: ${length}, entropía: ~${entropy} bits)" + echo "" + for ((i=1; i<=count; i++)); do + _genpw_gen_alpha "$length" + done + ;; + passphrase) + info "Passphrases (${length} palabras)" + echo "" + for ((i=1; i<=count; i++)); do + _genpw_gen_passphrase "$length" || return 1 + done + ;; + pin) + charset_size=10 + entropy="$(_genpw_calc_entropy $charset_size "$length")" + info "PINs numéricos (longitud: ${length}, entropía: ~${entropy} bits)" + echo "" + for ((i=1; i<=count; i++)); do + _genpw_gen_pin "$length" + done + ;; + *) + error "generate_password: modo no válido '$mode'. Use: full|alpha|passphrase|pin" >&2 + return 1 + ;; + esac +} + +# Ejecutar si se llama directamente +if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then + generate_password "$@" +fi diff --git a/bash/functions/cybersecurity/geolocate_ip.md b/bash/functions/cybersecurity/geolocate_ip.md new file mode 100644 index 00000000..7ceb3f44 --- /dev/null +++ b/bash/functions/cybersecurity/geolocate_ip.md @@ -0,0 +1,44 @@ +--- +name: geolocate_ip +kind: function +lang: bash +domain: cybersecurity +version: "1.0.0" +purity: impure +signature: "geolocate_ip(target: string) -> void" +description: "Geolocaliza una dirección IP o dominio usando la API pública de ip-api.com. Muestra país, región, ciudad, coordenadas, ISP, ASN y detecta VPN, Proxy o infraestructura de hosting." +tags: [bash, cybersecurity, network, geoip, ip, osint, reconnaissance] +uses_functions: [] +uses_types: [] +returns: [] +returns_optional: false +error_type: "error_go_core" +imports: [] +params: + - name: target + desc: "dirección IP (IPv4) o nombre de dominio a geolocalizar" +output: "imprime información de geolocalización a stdout: país, ciudad, ISP, ASN, coordenadas y flags de VPN/Proxy/Hosting" +tested: false +tests: [] +test_file_path: "" +file_path: "bash/functions/cybersecurity/geolocate_ip.sh" +source_repo: "https://gitea-dgg044oo04woo4ggcsws4gk0.organic-machine.com/egutierrez/DevLauncher.git" +source_license: "MIT" +source_file: "scripts/linux/ciberseguridad/redes/geoip.sh" +--- + +## Ejemplo + +```bash +source bash/functions/cybersecurity/geolocate_ip.sh + +# Geolocalizar una IP +geolocate_ip 8.8.8.8 + +# Geolocalizar un dominio (resuelve a IP primero) +geolocate_ip example.com +``` + +## Notas + +Requiere `curl`. Si se pasa un dominio en lugar de una IP, se resuelve a IP usando `dig` antes de consultar la API. La API de ip-api.com es gratuita para uso no comercial con límite de 45 req/min. Los campos `proxy=true` y `hosting=true` indican posible uso de VPN, proxy Tor o datacenter. diff --git a/bash/functions/cybersecurity/geolocate_ip.sh b/bash/functions/cybersecurity/geolocate_ip.sh new file mode 100644 index 00000000..fd4eaea2 --- /dev/null +++ b/bash/functions/cybersecurity/geolocate_ip.sh @@ -0,0 +1,150 @@ +#!/usr/bin/env bash +# geolocate_ip +# ------------ +# Geolocaliza una IP o dominio usando la API pública de ip-api.com. +# Muestra país, ciudad, ISP, ASN y detecta VPN/Proxy/Hosting. +# +# USO (directo): +# geolocate_ip +# +# Depende de: curl, dig (para resolver dominios) + +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 + +# ─── Constantes ─────────────────────────────────────────────────────────────── + +_GEOIP_API="http://ip-api.com/json" +_GEOIP_FIELDS="status,message,country,countryCode,regionName,city,zip,lat,lon,isp,org,as,proxy,hosting,query" + +# ─── Funciones puras ────────────────────────────────────────────────────────── + +_geo_is_ip() { + [[ "$1" =~ ^[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}$ ]] +} + +_geo_build_api_url() { + local target="$1" + echo "${_GEOIP_API}/${target}?fields=${_GEOIP_FIELDS}" +} + +_geo_extract_field() { + local json="$1" + local key="$2" + echo "$json" | grep -o "\"${key}\":[^,}]*" | cut -d: -f2- | tr -d '"' | xargs +} + +# ─── Funciones de efecto ────────────────────────────────────────────────────── + +_geo_resolve_domain() { + local domain="$1" + dig +short A "$domain" 2>/dev/null | head -1 || true +} + +_geo_fetch() { + local target="$1" + local url + url="$(_geo_build_api_url "$target")" + curl -s --max-time 10 "$url" 2>/dev/null +} + +_geo_display_result() { + local json="$1" + + local status + status="$(_geo_extract_field "$json" "status")" + if [[ "$status" != "success" ]]; then + local msg + msg="$(_geo_extract_field "$json" "message")" + error "La API devolvió error: ${msg:-respuesta inesperada}" >&2 + return 1 + fi + + local ip_queried country country_code region city zip lat lon isp org asn proxy hosting + ip_queried="$(_geo_extract_field "$json" "query")" + country="$(_geo_extract_field "$json" "country")" + country_code="$(_geo_extract_field "$json" "countryCode")" + region="$(_geo_extract_field "$json" "regionName")" + city="$(_geo_extract_field "$json" "city")" + zip="$(_geo_extract_field "$json" "zip")" + lat="$(_geo_extract_field "$json" "lat")" + lon="$(_geo_extract_field "$json" "lon")" + isp="$(_geo_extract_field "$json" "isp")" + org="$(_geo_extract_field "$json" "org")" + asn="$(_geo_extract_field "$json" "as")" + proxy="$(_geo_extract_field "$json" "proxy")" + hosting="$(_geo_extract_field "$json" "hosting")" + + echo "" + echo -e "${PURPLE}════════════════════════════════════════════════════════════${NC}" + echo -e " ${CYAN}IP consultada:${NC} ${ip_queried}" + echo -e " ${CYAN}País:${NC} ${country} (${country_code})" + echo -e " ${CYAN}Región:${NC} ${region}" + echo -e " ${CYAN}Ciudad:${NC} ${city} ${zip}" + echo -e " ${CYAN}Coordenadas:${NC} ${lat}, ${lon}" + echo -e " ${CYAN}ISP:${NC} ${isp}" + echo -e " ${CYAN}Organización:${NC} ${org}" + echo -e " ${CYAN}ASN:${NC} ${asn}" + echo "" + + if [[ "$proxy" == "true" ]]; then + echo -e " ${RED}[!] VPN / Proxy detectado${NC}" + fi + if [[ "$hosting" == "true" ]]; then + echo -e " ${YELLOW}[i] Hosting / datacenter detectado${NC}" + fi + if [[ "$proxy" != "true" && "$hosting" != "true" ]]; then + echo -e " ${GREEN}[ok] Sin indicios de VPN, Proxy o Tor${NC}" + fi + + echo -e "${PURPLE}════════════════════════════════════════════════════════════${NC}" +} + +# ─── Punto de entrada ───────────────────────────────────────────────────────── + +geolocate_ip() { + local target="$1" + + if [[ -z "$target" ]]; then + error "geolocate_ip: se requiere una IP o dominio como argumento" >&2 + return 1 + fi + + if ! command -v curl &>/dev/null; then + error "geolocate_ip: 'curl' no está instalado (sudo apt install curl)" >&2 + return 1 + fi + + local query_target="$target" + + if ! _geo_is_ip "$target"; then + info "Resolviendo dominio a IP..." + local resolved + resolved="$(_geo_resolve_domain "$target")" + if [[ -z "$resolved" ]]; then + error "geolocate_ip: no se pudo resolver '$target'" >&2 + return 1 + fi + info "Resuelto: ${target} -> ${resolved}" + query_target="$resolved" + fi + + info "Consultando geolocalización de ${query_target}..." + local json + json="$(_geo_fetch "$query_target")" + + if [[ -z "$json" ]]; then + error "geolocate_ip: no se obtuvo respuesta de la API" >&2 + return 1 + fi + + _geo_display_result "$json" +} + +# Ejecutar si se llama directamente +if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then + geolocate_ip "$@" +fi diff --git a/bash/functions/cybersecurity/inspect_ssl_cert.md b/bash/functions/cybersecurity/inspect_ssl_cert.md new file mode 100644 index 00000000..800011ff --- /dev/null +++ b/bash/functions/cybersecurity/inspect_ssl_cert.md @@ -0,0 +1,47 @@ +--- +name: inspect_ssl_cert +kind: function +lang: bash +domain: cybersecurity +version: "1.0.0" +purity: impure +signature: "inspect_ssl_cert(host: string) -> void" +description: "Inspecciona el certificado SSL/TLS de un host: muestra sujeto, emisor, fechas de validez, días hasta expiración, SANs (Subject Alternative Names), cadena de confianza completa y detecta soporte de versiones inseguras TLS 1.0/1.1." +tags: [bash, cybersecurity, ssl, tls, certificate, web, openssl, security] +uses_functions: [] +uses_types: [] +returns: [] +returns_optional: false +error_type: "error_go_core" +imports: [] +params: + - name: host + desc: "host a inspeccionar, acepta formato host o host:puerto (por defecto puerto 443), ej: example.com o example.com:8443" +output: "imprime detalles del certificado SSL/TLS, días hasta expiración con nivel de alerta, SANs, cadena de confianza y resultado de checks de versiones TLS" +tested: false +tests: [] +test_file_path: "" +file_path: "bash/functions/cybersecurity/inspect_ssl_cert.sh" +source_repo: "https://gitea-dgg044oo04woo4ggcsws4gk0.organic-machine.com/egutierrez/DevLauncher.git" +source_license: "MIT" +source_file: "scripts/linux/ciberseguridad/web/ssl_cert_info.sh" +--- + +## Ejemplo + +```bash +source bash/functions/cybersecurity/inspect_ssl_cert.sh + +# Puerto 443 por defecto +inspect_ssl_cert example.com + +# Puerto personalizado +inspect_ssl_cert example.com:8443 + +# API interna +inspect_ssl_cert api.internal.example.com:4443 +``` + +## Notas + +Requiere `openssl` y `timeout`. Usa `openssl s_client` con SNI (`-servername`) para soportar virtual hosting. La alerta de expiración se activa a 30 días o menos. La detección de TLS 1.0/1.1 usa flags `-tls1` y `-tls1_1` de openssl s_client — si el servidor acepta la conexión y negocia un cipher, el protocolo inseguro está habilitado. Cada conexión tiene timeout de 10 segundos para evitar cuelgues en hosts sin respuesta. diff --git a/bash/functions/cybersecurity/inspect_ssl_cert.sh b/bash/functions/cybersecurity/inspect_ssl_cert.sh new file mode 100644 index 00000000..810a82ee --- /dev/null +++ b/bash/functions/cybersecurity/inspect_ssl_cert.sh @@ -0,0 +1,169 @@ +#!/usr/bin/env bash +# inspect_ssl_cert +# ---------------- +# Inspecciona el certificado SSL/TLS de un host: sujeto, emisor, fechas de validez, +# SANs, cadena de confianza y versiones de TLS aceptadas (detecta TLS 1.0/1.1 inseguros). +# +# USO (directo): +# inspect_ssl_cert +# inspect_ssl_cert example.com +# inspect_ssl_cert example.com:8443 +# +# Depende de: openssl, timeout + +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 + +# ─── Constantes ─────────────────────────────────────────────────────────────── + +_SSL_WARN_DAYS=30 + +# ─── Funciones puras ────────────────────────────────────────────────────────── + +_ssl_parse_host_port() { + local input="$1" + if [[ "$input" =~ ^(.+):([0-9]+)$ ]]; then + echo "${BASH_REMATCH[1]} ${BASH_REMATCH[2]}" + else + echo "${input} 443" + fi +} + +_ssl_days_until_expiry() { + local expiry_str="$1" + local expiry_epoch + expiry_epoch="$(date -d "$expiry_str" +%s 2>/dev/null || echo 0)" + local now_epoch + now_epoch="$(date +%s)" + echo $(( (expiry_epoch - now_epoch) / 86400 )) +} + +# ─── Funciones de efecto ────────────────────────────────────────────────────── + +_ssl_fetch_subject() { + local host="$1" + local port="$2" + echo | timeout 10 openssl s_client -connect "${host}:${port}" -servername "$host" 2>/dev/null \ + | openssl x509 -noout -subject -issuer 2>/dev/null +} + +_ssl_fetch_dates() { + local host="$1" + local port="$2" + echo | timeout 10 openssl s_client -connect "${host}:${port}" -servername "$host" 2>/dev/null \ + | openssl x509 -noout -dates 2>/dev/null +} + +_ssl_fetch_san() { + local host="$1" + local port="$2" + echo | timeout 10 openssl s_client -connect "${host}:${port}" -servername "$host" 2>/dev/null \ + | openssl x509 -noout -ext subjectAltName 2>/dev/null \ + | grep -oE 'DNS:[^,]+' | sed 's/DNS://g' | tr '\n' ' ' +} + +_ssl_fetch_chain() { + local host="$1" + local port="$2" + echo | timeout 10 openssl s_client -connect "${host}:${port}" -servername "$host" \ + -showcerts 2>/dev/null \ + | grep -E "^(subject|issuer)=" | sed 's/^/ /' +} + +_ssl_check_tls_version() { + local host="$1" + local port="$2" + local proto="$3" + local label="$4" + if echo | timeout 5 openssl s_client -connect "${host}:${port}" \ + -servername "$host" "${proto}" 2>/dev/null | grep -q "Cipher"; then + echo -e " ${RED}[x]${NC} ${label} -- soportado (inseguro)" + else + echo -e " ${GREEN}[ok]${NC} ${label} no soportado" + fi +} + +# ─── Punto de entrada ───────────────────────────────────────────────────────── + +inspect_ssl_cert() { + local input="$1" + + if [[ -z "$input" ]]; then + error "inspect_ssl_cert: se requiere un host como argumento (ej: example.com o example.com:8443)" >&2 + return 1 + fi + + if ! command -v openssl &>/dev/null; then + error "inspect_ssl_cert: 'openssl' no está instalado (sudo apt install openssl)" >&2 + return 1 + fi + + local host port + read -r host port <<< "$(_ssl_parse_host_port "$input")" + + info "Conectando a ${host}:${port}..." + echo "" + + local subj_issuer + subj_issuer="$(_ssl_fetch_subject "$host" "$port")" + if [[ -z "$subj_issuer" ]]; then + error "inspect_ssl_cert: no se pudo obtener el certificado. ¿El host está disponible?" >&2 + return 1 + fi + + local subject issuer + subject="$(echo "$subj_issuer" | grep ^subject | cut -d= -f2- | xargs)" + issuer="$(echo "$subj_issuer" | grep ^issuer | cut -d= -f2- | xargs)" + + local dates + dates="$(_ssl_fetch_dates "$host" "$port")" + local not_before not_after + not_before="$(echo "$dates" | grep notBefore | cut -d= -f2)" + not_after="$(echo "$dates" | grep notAfter | cut -d= -f2)" + + local days_left + days_left="$(_ssl_days_until_expiry "$not_after")" + + local sans + sans="$(_ssl_fetch_san "$host" "$port")" + + echo -e "${PURPLE}════════ Certificado SSL/TLS ════════════════════${NC}" + echo -e " ${CYAN}Sujeto:${NC} ${subject}" + echo -e " ${CYAN}Emisor:${NC} ${issuer}" + echo -e " ${CYAN}Válido desde:${NC} ${not_before}" + echo -e " ${CYAN}Válido hasta:${NC} ${not_after}" + echo "" + + if [[ $days_left -le 0 ]]; then + echo -e " ${RED}[x] CERTIFICADO EXPIRADO${NC}" + elif [[ $days_left -le $_SSL_WARN_DAYS ]]; then + echo -e " ${YELLOW}[!] Expira en ${days_left} días -- renovar pronto${NC}" + else + echo -e " ${GREEN}[ok] Válido -- expira en ${days_left} días${NC}" + fi + + echo "" + echo -e " ${CYAN}SANs:${NC}" + echo "$sans" | tr ' ' '\n' | grep -v '^$' | while IFS= read -r san; do + echo -e " ${GREEN}*${NC} ${san}" + done + + echo "" + echo -e "${PURPLE}════════ Cadena de confianza ════════════════════${NC}" + _ssl_fetch_chain "$host" "$port" + + echo "" + echo -e "${PURPLE}════════ Versiones TLS aceptadas ════════════════${NC}" + _ssl_check_tls_version "$host" "$port" "-tls1" "TLS 1.0" + _ssl_check_tls_version "$host" "$port" "-tls1_1" "TLS 1.1" + echo -e " ${GREEN}[ok]${NC} TLS 1.2 / 1.3 (estándar)" + echo -e "${PURPLE}═════════════════════════════════════════════════${NC}" +} + +# Ejecutar si se llama directamente +if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then + inspect_ssl_cert "$@" +fi diff --git a/bash/functions/cybersecurity/list_active_connections.md b/bash/functions/cybersecurity/list_active_connections.md new file mode 100644 index 00000000..24420984 --- /dev/null +++ b/bash/functions/cybersecurity/list_active_connections.md @@ -0,0 +1,47 @@ +--- +name: list_active_connections +kind: function +lang: bash +domain: cybersecurity +version: "1.0.0" +purity: impure +signature: "list_active_connections(mode: string) -> void" +description: "Muestra conexiones de red activas del sistema usando ss: puertos en escucha, conexiones establecidas y detección de conexiones hacia IPs externas (excluye RFC1918, loopback y link-local)." +tags: [bash, cybersecurity, network, connections, monitoring, ss, ports] +uses_functions: [] +uses_types: [] +returns: [] +returns_optional: false +error_type: "error_go_core" +imports: [] +params: + - name: mode + desc: "modo de visualización: listening (puertos en escucha), established (conexiones activas), external (solo IPs externas) o all (todo, por defecto)" +output: "imprime tabla de conexiones de red a stdout con colores ANSI; las conexiones a IPs externas se resaltan en amarillo" +tested: false +tests: [] +test_file_path: "" +file_path: "bash/functions/cybersecurity/list_active_connections.sh" +source_repo: "https://gitea-dgg044oo04woo4ggcsws4gk0.organic-machine.com/egutierrez/DevLauncher.git" +source_license: "MIT" +source_file: "scripts/linux/ciberseguridad/redes/conexiones_activas.sh" +--- + +## Ejemplo + +```bash +source bash/functions/cybersecurity/list_active_connections.sh + +# Todas las conexiones +list_active_connections + +# Solo puertos en escucha +list_active_connections listening + +# Solo conexiones hacia internet +list_active_connections external +``` + +## Notas + +Requiere `ss` del paquete iproute2 (disponible por defecto en la mayoría de distribuciones modernas). La detección de IPs externas excluye: 127.x, ::1, 0.0.0.0, rangos RFC1918 (10.x, 172.16-31.x, 192.168.x) y link-local (fe80:). Usa `ss -tnp` para mostrar el proceso asociado a cada conexión (puede requerir sudo para ver procesos de otros usuarios). diff --git a/bash/functions/cybersecurity/list_active_connections.sh b/bash/functions/cybersecurity/list_active_connections.sh new file mode 100644 index 00000000..247630d0 --- /dev/null +++ b/bash/functions/cybersecurity/list_active_connections.sh @@ -0,0 +1,109 @@ +#!/usr/bin/env bash +# list_active_connections +# ----------------------- +# Muestra conexiones de red activas del sistema: puertos en escucha, +# conexiones establecidas y detección de IPs externas (no RFC1918). +# +# USO (directo): +# list_active_connections [listening|established|external|all] +# +# Depende de: ss (iproute2) + +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 ────────────────────────────────────────────────────────── + +_conn_is_external_ip() { + local ip="$1" + # Devuelve 0 (verdadero) si no es loopback, link-local ni RFC1918 + [[ ! "$ip" =~ ^(127\.|::1|0\.0\.0\.0|10\.|172\.(1[6-9]|2[0-9]|3[01])\.|192\.168\.|fe80:) ]] +} + +# ─── Funciones de efecto ────────────────────────────────────────────────────── + +_conn_show_listening() { + info "Puertos en escucha con proceso asociado..." + echo "" + echo -e "${PURPLE}════════════════════════════════════════════════════════════${NC}" + ss -tlnp 2>/dev/null \ + | awk 'NR==1 {printf "%-6s %-25s %-25s %s\n", "Proto", "Local", "Peer", "Proceso"} NR>1 {printf "%-6s %-25s %-25s %s\n", $1, $4, $5, $7}' \ + | while IFS= read -r line; do echo -e " ${DIM_GRAY}${line}${NC}"; done + echo -e "${PURPLE}════════════════════════════════════════════════════════════${NC}" +} + +_conn_show_established() { + info "Conexiones establecidas..." + echo "" + echo -e "${PURPLE}════════════════════════════════════════════════════════════${NC}" + ss -tnp state established 2>/dev/null \ + | awk 'NR==1 {printf "%-6s %-25s %-25s %s\n", "Proto", "Local", "Peer", "Proceso"} NR>1 {printf "%-6s %-25s %-25s %s\n", $1, $4, $5, $6}' \ + | while IFS= read -r line; do echo -e " ${DIM_GRAY}${line}${NC}"; done + echo -e "${PURPLE}════════════════════════════════════════════════════════════${NC}" +} + +_conn_show_external() { + info "Conexiones hacia IPs externas..." + echo "" + echo -e "${PURPLE}════════════════════════════════════════════════════════════${NC}" + + local found=0 + while IFS= read -r line; do + local peer + peer="$(echo "$line" | awk '{print $5}' | cut -d: -f1)" + if _conn_is_external_ip "$peer"; then + echo -e " ${YELLOW}*${NC} $line" + found=$((found + 1)) + fi + done < <(ss -tnp state established 2>/dev/null | tail -n +2) + + echo -e "${PURPLE}════════════════════════════════════════════════════════════${NC}" + echo "" + if [[ $found -eq 0 ]]; then + success "No se detectaron conexiones hacia IPs externas" + else + info "Total conexiones externas: $found" + fi +} + +# ─── Punto de entrada ───────────────────────────────────────────────────────── + +list_active_connections() { + local mode="${1:-all}" + + if ! command -v ss &>/dev/null; then + error "list_active_connections: 'ss' no está disponible (sudo apt install iproute2)" >&2 + return 1 + fi + + case "$mode" in + listening) + _conn_show_listening + ;; + established) + _conn_show_established + ;; + external) + _conn_show_external + ;; + all) + _conn_show_listening + echo "" + _conn_show_established + echo "" + _conn_show_external + ;; + *) + error "list_active_connections: modo no válido '$mode'. Use: listening|established|external|all" >&2 + return 1 + ;; + esac +} + +# Ejecutar si se llama directamente +if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then + list_active_connections "$@" +fi diff --git a/bash/functions/cybersecurity/verify_file_hash.md b/bash/functions/cybersecurity/verify_file_hash.md new file mode 100644 index 00000000..3c893813 --- /dev/null +++ b/bash/functions/cybersecurity/verify_file_hash.md @@ -0,0 +1,51 @@ +--- +name: verify_file_hash +kind: function +lang: bash +domain: cybersecurity +version: "1.0.0" +purity: impure +signature: "verify_file_hash(file: string, algorithm: string, expected_hash: string) -> void" +description: "Calcula el hash criptográfico de un archivo con el algoritmo especificado (md5, sha1, sha256, sha512) y opcionalmente lo compara con un hash esperado para verificar integridad. Retorna exit code 1 si los hashes no coinciden." +tags: [bash, cybersecurity, hash, integrity, checksum, md5, sha256, sha512] +uses_functions: [] +uses_types: [] +returns: [] +returns_optional: false +error_type: "error_go_core" +imports: [] +params: + - name: file + desc: "ruta al archivo del que calcular el hash" + - name: algorithm + desc: "algoritmo de hash a usar: md5, sha1, sha256 (recomendado) o sha512" + - name: expected_hash + desc: "hash esperado en hexadecimal para verificar integridad (opcional; si se omite, solo calcula e imprime)" +output: "imprime el hash calculado; si se proporcionó expected_hash, imprime COINCIDE o NO COINCIDE y retorna exit code 1 si no coinciden" +tested: false +tests: [] +test_file_path: "" +file_path: "bash/functions/cybersecurity/verify_file_hash.sh" +source_repo: "https://gitea-dgg044oo04woo4ggcsws4gk0.organic-machine.com/egutierrez/DevLauncher.git" +source_license: "MIT" +source_file: "scripts/linux/ciberseguridad/utilidades/verificar_hash.sh" +--- + +## Ejemplo + +```bash +source bash/functions/cybersecurity/verify_file_hash.sh + +# Solo calcular el hash +verify_file_hash archivo.iso sha256 + +# Verificar contra un hash conocido +verify_file_hash archivo.iso sha256 "a1b2c3d4e5f6..." + +# MD5 (solo para compatibilidad, no recomendado para seguridad) +verify_file_hash documento.pdf md5 +``` + +## Notas + +La comparación de hashes es case-insensitive (normaliza a minúsculas). SHA256 es el algoritmo recomendado para verificación de integridad. MD5 y SHA1 están deprecados para uso en seguridad pero se incluyen para compatibilidad con sumas publicadas en sistemas legacy. Retorna exit code 1 cuando los hashes no coinciden, lo que permite usar la función en scripts con `set -e`. diff --git a/bash/functions/cybersecurity/verify_file_hash.sh b/bash/functions/cybersecurity/verify_file_hash.sh new file mode 100644 index 00000000..2f10f812 --- /dev/null +++ b/bash/functions/cybersecurity/verify_file_hash.sh @@ -0,0 +1,100 @@ +#!/usr/bin/env bash +# verify_file_hash +# ---------------- +# Calcula el hash de un archivo con el algoritmo indicado (md5, sha1, sha256, sha512) +# y opcionalmente lo compara con un hash esperado. +# +# USO (directo): +# verify_file_hash [hash_esperado] +# +# Depende de: md5sum, sha1sum, sha256sum, sha512sum + +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 ────────────────────────────────────────────────────────── + +_hash_select_cmd() { + local algo="$1" + case "$algo" in + md5) echo "md5sum" ;; + sha1) echo "sha1sum" ;; + sha256) echo "sha256sum" ;; + sha512) echo "sha512sum" ;; + *) echo "" ;; + esac +} + +_hash_hashes_match() { + local a="${1,,}" + local b="${2,,}" + [[ "$a" == "$b" ]] +} + +# ─── Funciones de efecto ────────────────────────────────────────────────────── + +_hash_compute() { + local cmd="$1" + local file="$2" + "$cmd" "$file" 2>/dev/null | awk '{print $1}' +} + +# ─── Punto de entrada ───────────────────────────────────────────────────────── + +verify_file_hash() { + local file="$1" + local algorithm="$2" + local expected_hash="${3:-}" + + if [[ -z "$file" || -z "$algorithm" ]]; then + error "verify_file_hash: uso: verify_file_hash [hash_esperado]" >&2 + return 1 + fi + + if [[ ! -f "$file" ]]; then + error "verify_file_hash: archivo no encontrado: $file" >&2 + return 1 + fi + + local cmd + cmd="$(_hash_select_cmd "$algorithm")" + + if [[ -z "$cmd" ]]; then + error "verify_file_hash: algoritmo no válido '$algorithm'. Use: md5|sha1|sha256|sha512" >&2 + return 1 + fi + + if ! command -v "$cmd" &>/dev/null; then + error "verify_file_hash: '$cmd' no está disponible" >&2 + return 1 + fi + + info "Calculando ${algorithm^^} de: $(basename "$file")" + local hash + hash="$(_hash_compute "$cmd" "$file")" + + echo "" + echo -e " ${CYAN}Archivo:${NC} ${file}" + echo -e " ${CYAN}${algorithm^^}:${NC} ${hash}" + echo "" + + if [[ -n "$expected_hash" ]]; then + if _hash_hashes_match "$hash" "$expected_hash"; then + echo -e " ${GREEN}[COINCIDE]${NC} La integridad del archivo es correcta" + else + echo -e " ${RED}[NO COINCIDE]${NC} El archivo puede estar corrupto o modificado" + echo "" + echo -e " ${CYAN}Calculado:${NC} ${hash}" + echo -e " ${CYAN}Esperado: ${NC} ${expected_hash}" + return 1 + fi + fi +} + +# Ejecutar si se llama directamente +if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then + verify_file_hash "$@" +fi