Script en Bash per a Ubuntu 24.04 (Noble) que fa un auditoria de vulnerabilitats i configuracions de seguretat sense canviar res del sistema. Detecta:

  • Paquets amb actualitzacions de seguretat pendents
  • CVE que afecten paquets instal·lats (via debsecan, opcional)
  • Kernel i necessitat de reinici
  • Estat de Livepatch/Ubuntu Pro
  • Ports oberts i serveis escoltant
  • Estat de firewall (UFW) i AppArmor
  • Configuració SSH (riscos típics)
  • Fitxers/directoris world-writable, binaris SUID/SGID
  • Usuari(s) amb UID 0, sudoers sense contrasenya
  • Actualitzacions automàtiques (unattended-upgrades)
  • Paràmetres del sistema (core dumps, etc.)

🔒 Només detecció. No aplica canvis.
🧪 Opcionalment pot instal·lar eines lleugeres (debsecan, apparmor-utils, jq, mokutil, …) amb el flag --install-tools.
📝 Genera un informe Markdown + arxius adjunts amb detalls.


Ús ràpid

  1. Guarda el contingut a ubuntu24_vuln_audit.sh
  2. Dona execució i executa:

Shell

chmod +x ubuntu24_vuln_audit.sh
sudo ./ubuntu24_vuln_audit.sh --install-tools

Flags disponibles:

  • --install-tools → instal·la eines recomanades per ampliar la detecció.
  • --output <fitxer.md> → especifica ruta de l’informe.
  • --network-scan → inclou escaneig local bàsic de ports (sense nmap).
  • --quick → omet comprovacions pesades (llistes llargues).
  • --no-apt-update → no fa apt update (per entorns restringits).

Script: ubuntu24_vuln_audit.sh

#!/usr/bin/env bash
set -euo pipefail

# ------------------------------------------------------------
# Auditoria de vulnerabilitats i seguretat per a Ubuntu 24.04
# Ús:
#   sudo ./ubuntu24_vuln_audit.sh [--install-tools] [--output informe.md] [--network-scan] [--quick] [--no-apt-update]
# ------------------------------------------------------------

# Colors
if command -v tput >/dev/null 2>&1; then
  GREEN="$(tput setaf 2)"; YELLOW="$(tput setaf 3)"; RED="$(tput setaf 1)"; BLUE="$(tput setaf 4)"; BOLD="$(tput bold)"; RESET="$(tput sgr0)"
else
  GREEN=""; YELLOW=""; RED=""; BLUE=""; BOLD=""; RESET=""
fi

# Flags i paràmetres
INSTALL_TOOLS=0
OUTPUT=""
NETWORK_SCAN=0
QUICK=0
APT_UPDATE=1

while [[ $# -gt 0 ]]; do
  case "$1" in
    --install-tools) INSTALL_TOOLS=1; shift ;;
    --output) OUTPUT="$2"; shift 2 ;;
    --network-scan) NETWORK_SCAN=1; shift ;;
    --quick) QUICK=1; shift ;;
    --no-apt-update) APT_UPDATE=0; shift ;;
    *) echo "Paràmetre desconegut: $1"; exit 1 ;;
  esac
done

# Requereix root
if [[ $EUID -ne 0 ]]; then
  echo -e "${RED}[ERROR]${RESET} Cal executar com a root (sudo)."
  exit 1
fi

# Fitxers de sortida
HOST="$(hostname -s)"
STAMP="$(date +%F_%H%M%S)"
REPORT="${OUTPUT:-./audit_${HOST}_${STAMP}.md}"
OUTDIR="./audit_${HOST}_${STAMP}_files"
mkdir -p "$OUTDIR"

log()   { echo -e "$1" | tee -a "$REPORT" >/dev/null; }
say()   { echo -e "$1" | tee -a "$REPORT"; }
hr()    { printf '\n---\n\n' | tee -a "$REPORT"; }
sec()   { say "## $1"; }
sub()   { say "### $1"; }
ok()    { say "${GREEN}✔${RESET} $1"; }
warn()  { say "${YELLOW}⚠${RESET} $1"; }
err()   { say "${RED}✖${RESET} $1"; }
info()  { say "${BLUE}ℹ${RESET} $1"; }

# Detectar OS
if [[ -r /etc/os-release ]]; then
  . /etc/os-release
  ID="${ID:-}"; VERSION_ID="${VERSION_ID:-}"
else
  err "No es pot determinar el sistema. Falta /etc/os-release."
  exit 1
fi

if [[ "$ID" != "ubuntu" ]]; then
  warn "Sistema no Ubuntu (ID=$ID). L'script pot no ser exacte."
fi
if [[ "${VERSION_ID:-}" != "24.04" ]]; then
  warn "Versió no 24.04 (VERSION_ID=${VERSION_ID:-desconeguda}). Continuo, però algunes comprovacions poden variar."
fi

# Capçalera informe
cat > "$REPORT" <<EOF
# Auditoria de Vulnerabilitats i Seguretat — Ubuntu $VERSION_ID ($NAME)

**Host:** $HOST  
**Data:** $(date -Is)  
**Usuari:** $(whoami)

> **Nota:** Auditoria només de lectura. No s'apliquen canvis. Algunes seccions guarden detalls a \`$OUTDIR\`.

EOF

hr
sec "1) Informació del sistema"
{
  echo '```text'
  echo "OS: $PRETTY_NAME"
  command -v hostnamectl >/dev/null && hostnamectl
  echo
  echo "Kernel: $(uname -r) ($(uname -m))"
  echo "Uptime: $(uptime -p 2>/dev/null || true)"
  echo "CPU(s): $(nproc) | Memòria: $(free -h | awk '/Mem:/ {print $2\" total\"}')"
  echo
  echo "Particions (df -hT):"
  df -hT
  echo '```'
} | tee -a "$REPORT" >/dev/null

# Opcional: apt update per tenir informació d'actualitzacions
if [[ $APT_UPDATE -eq 1 ]]; then
  sub "Actualitzant índex de paquets (apt update)"
  if apt-get update -qq; then
    ok "Índex de paquets actualitzat."
  else
    warn "No s'ha pogut actualitzar l'índex de paquets. Continuo amb dades locals."
  fi
else
  info "S'ha omès 'apt update' per indicació (--no-apt-update)."
fi

hr
sec "2) Actualitzacions i vulnerabilitats de paquets"

# Paquets actualitzables
sub "Paquets actualitzables"
apt list --upgradable 2>/dev/null | tee "$OUTDIR/upgradable.txt" >/dev/null || true
UPG_COUNT=$(grep -c 'upgradable from' "$OUTDIR/upgradable.txt" || true)
if [[ ${UPG_COUNT:-0} -gt 0 ]]; then
  warn "$UPG_COUNT paquets amb actualitzacions disponibles."
  say ""
  say "<details><summary>Llista (clic per veure)</summary>"
  say ""
  say '```text'
  sed -n '1,200p' "$OUTDIR/upgradable.txt" | sed 's/^/  /'
  [[ $QUICK -eq 1 ]] && echo "... (mode ràpid: truncat)" || sed -n '201,9999p' "$OUTDIR/upgradable.txt" | sed 's/^/  /' || true
  say '```'
  say "</details>"
else
  ok "No s'han trobat paquets actualitzables."
fi

# ubuntu-security-status (si disponible)
if command -v ubuntu-security-status >/dev/null 2>&1; then
  sub "ubuntu-security-status"
  ubuntu-security-status 2>&1 | tee "$OUTDIR/ubuntu-security-status.txt" >/dev/null || true
  say ""
  say "<details><summary>Sortida</summary>"
  say ""
  say '```text'
  cat "$OUTDIR/ubuntu-security-status.txt"
  say '```'
  say "</details>"
else
  info "`ubuntu-security-status` no està disponible (normalment a update-notifier-common)."
fi

# debsecan per CVEs (opcional)
if [[ $INSTALL_TOOLS -eq 1 ]]; then
  sub "Instal·lant eines addicionals (debsecan, apparmor-utils, jq, mokutil)"
  apt-get install -y -qq debsecan apparmor-utils jq mokutil >/dev/null || true
  ok "Eines instal·lades (si no hi eren)."
fi

if command -v debsecan >/dev/null 2>&1; then
  sub "CVE que afecten paquets instal·lats (debsecan)"
  SUITE="${VERSION_CODENAME:-noble}"
  # Llista de CVEs amb detall per a paquets instal·lats
  debsecan --suite "$SUITE" --format detail --only-fixed 2>/dev/null | tee "$OUTDIR/debsecan_fixed.txt" >/dev/null || true
  FIXED_COUNT=$(grep -c '^CVE-' "$OUTDIR/debsecan_fixed.txt" || true)

  debsecan --suite "$SUITE" --format detail 2>/dev/null | tee "$OUTDIR/debsecan_all.txt" >/dev/null || true
  ALL_COUNT=$(grep -c '^CVE-' "$OUTDIR/debsecan_all.txt" || true)

  if [[ ${ALL_COUNT:-0} -gt 0 ]]; then
    warn "CVE detectades: $ALL_COUNT total | amb correcció disponible: $FIXED_COUNT"
    say ""
    say "<details><summary>Resum CVE (debsecan)</summary>"
    say ""
    say '```text'
    head -n $([[ $QUICK -eq 1 ]] && echo 100 || echo 500) "$OUTDIR/debsecan_all.txt"
    [[ $QUICK -eq 1 ]] && echo "... (mode ràpid: truncat)"
    say '```'
    say "</details>"
  else
    ok "No s'han detectat CVE amb debsecan."
  fi
else
  info "Per llistat de CVE, instal·la debsecan (afegeix --install-tools)."
fi

hr
sec "3) Kernel i Livepatch"

# Kernel
CUR_KERNEL="$(uname -r)"
say "**Kernel actual:** \`$CUR_KERNEL\`"
if apt-cache policy linux-image-generic >/dev/null 2>&1; then
  CANDIDATE="$(apt-cache policy linux-image-generic | awk '/Candidate:/ {print $2}')"
  say "**Kernel meta-package candidate:** \`$CANDIDATE\`"
fi

if [[ -f /var/run/reboot-required ]]; then
  warn "El sistema indica que cal reiniciar (fitxer /var/run/reboot-required present)."
else
  ok "No hi ha senyal de reinici pendent."
fi

# Livepatch / Ubuntu Pro
if command -v pro >/dev/null 2>&1; then
  sub "Ubuntu Pro / Livepatch (pro status)"
  pro status || true | tee "$OUTDIR/pro_status.txt" >/dev/null
  say ""
  say "<details><summary>Sortida</summary>"
  say ""
  say '```text'
  cat "$OUTDIR/pro_status.txt"
  say '```'
  say "</details>"
elif command -v canonical-livepatch >/dev/null 2>&1; then
  sub "Canonical Livepatch"
  canonical-livepatch status || true | tee "$OUTDIR/livepatch_status.txt" >/dev/null
  say ""
  say "<details><summary>Sortida</summary>"
  say ""
  say '```text'
  cat "$OUTDIR/livepatch_status.txt"
  say '```'
  say "</details>"
else
  info "Livepatch/Ubuntu Pro no detectat. (Opcional però recomanable per parches de kernel en calent)"
fi

hr
sec "4) Superfície d'atac (xarxa i serveis)"

sub "Serveis escoltant (ss -tulpen)"
ss -tulpen 2>/dev/null | tee "$OUTDIR/ss_listen.txt" >/dev/null || true
say ""
say "<details><summary>Sortida</summary>"
say ""
say '```text'
cat "$OUTDIR/ss_listen.txt"
say '```'
say "</details>"

if [[ $NETWORK_SCAN -eq 1 ]]; then
  sub "Escaneig local bàsic de ports (127.0.0.1, sense nmap)"
  # Prova ràpida per ports comuns
  PORTS=(22 25 53 80 110 143 443 465 587 993 995 3306 5432 6379 8080 8443)
  echo '```text' | tee -a "$REPORT"
  for p in "${PORTS[@]}"; do
    if timeout 0.5 bash -c "</dev/tcp/127.0.0.1/$p" 2>/dev/null; then
      echo "Port $p obert" | tee -a "$REPORT"
    fi
  done
  echo '```' | tee -a "$REPORT"
fi

hr
sec "5) Estat de Firewall i AppArmor"

# UFW
sub "UFW"
if command -v ufw >/dev/null 2>&1; then
  ufw status verbose 2>&1 | tee "$OUTDIR/ufw_status.txt" >/dev/null || true
  if grep -qE '^Status: active' "$OUTDIR/ufw_status.txt"; then
    ok "UFW actiu."
  else
    warn "UFW no actiu."
  fi
  say ""
  say "<details><summary>Regles UFW</summary>"
  say ""
  say '```text'
  cat "$OUTDIR/ufw_status.txt"
  say '```'
  say "</details>"
else
  info "UFW no instal·lat. Comprovo nftables (si disponible)."
  if command -v nft >/dev/null 2>&1; then
    nft list ruleset 2>/dev/null | tee "$OUTDIR/nft_ruleset.txt" >/dev/null || true
    say "<details><summary>nftables ruleset</summary>"
    say ""
    say '```text'
    cat "$OUTDIR/nft_ruleset.txt"
    say '```'
    say "</details>"
  fi
fi

# AppArmor
sub "AppArmor"
if command -v apparmor_status >/dev/null 2>&1; then
  apparmor_status 2>&1 | tee "$OUTDIR/apparmor_status.txt" >/dev/null || true
  if grep -q "profiles are in enforce mode" "$OUTDIR/apparmor_status.txt"; then
    ok "AppArmor amb perfils en mode enforce."
  else
    warn "AppArmor sense perfils en mode enforce o inactiu."
  fi
  say ""
  say "<details><summary>Detall AppArmor</summary>"
  say ""
  say '```text'
  cat "$OUTDIR/apparmor_status.txt"
  say '```'
  say "</details>"
else
  info "Comanda apparmor_status no disponible (paquet apparmor-utils)."
fi

hr
sec "6) Configuració SSH i comptes"

# SSH efectiu (sshd -T)
sub "Configuració SSH"
SSH_WARN=0
if command -v sshd >/dev/null 2>&1; then
  sshd -T 1>"$OUTDIR/sshd_effective.txt" 2>/dev/null || true
  if [[ -s "$OUTDIR/sshd_effective.txt" ]]; then
    say "<details><summary>Configuració efectiva (sshd -T)</summary>"
    say ""
    say '```text'
    cat "$OUTDIR/sshd_effective.txt"
    say '```'
    say "</details>"

    # Regles de risc comú
    grep -qi '^permitrootlogin yes' "$OUTDIR/sshd_effective.txt" && { warn "SSH permet root login"; SSH_WARN=1; }
    grep -qi '^passwordauthentication yes' "$OUTDIR/sshd_effective.txt" && { warn "SSH permet autenticació per contrasenya"; SSH_WARN=1; }
    grep -qi '^challengeresponseauthentication yes' "$OUTDIR/sshd_effective.txt" && { warn "ChallengeResponseAuthentication habilitat"; SSH_WARN=1; }
    grep -qi '^x11forwarding yes' "$OUTDIR/sshd_effective.txt" && { warn "X11Forwarding habilitat"; SSH_WARN=1; }
    if [[ $SSH_WARN -eq 0 ]]; then ok "Configuració SSH sense riscos típics (root/password/X11)."; fi
  else
    info "No s'ha pogut obtenir la configuració efectiva amb sshd -T."
  fi
else
  info "sshd no disponible."
fi

# Comptes UID 0
sub "Comptes amb UID 0"
awk -F: '$3 == 0 {print $1}' /etc/passwd | tee "$OUTDIR/uid0.txt" >/dev/null
UID0_COUNT=$(wc -l < "$OUTDIR/uid0.txt")
if [[ $UID0_COUNT -gt 1 ]]; then
  warn "Més d'un compte amb UID 0: $(tr '\n' ' ' < "$OUTDIR/uid0.txt")"
else
  ok "Només root amb UID 0."
fi

# Sudoers NOPASSWD
sub "Regles sudo NOPASSWD"
( grep -R --line-number -E '^[^#].*NOPASSWD' /etc/sudoers /etc/sudoers.d 2>/dev/null || true ) | tee "$OUTDIR/sudo_nopasswd.txt" >/dev/null
if [[ -s "$OUTDIR/sudo_nopasswd.txt" ]]; then
  warn "S'han trobat entrades NOPASSWD a sudoers (revisar fitxer adjunt)."
else
  ok "No s'han trobat NOPASSWD a sudoers."
fi

hr
sec "7) Fitxers sensibles i permisos"

# /etc/passwd i /etc/shadow
sub "Permisos /etc/passwd i /etc/shadow"
PASSWD_PERM="$(stat -c '%a %U:%G' /etc/passwd)"
SHADOW_PERM="$(stat -c '%a %U:%G' /etc/shadow)"
say "- /etc/passwd: \`$PASSWD_PERM\` (esperat ~644 root:root)"
say "- /etc/shadow: \`$SHADOW_PERM\` (esperat ~640 root:shadow o 600 root:root)"
[[ "${PASSWD_PERM%% *}" != "644" ]] && warn "/etc/passwd amb permisos no estàndard."
[[ "${SHADOW_PERM%% *}" != "640" && "${SHADOW_PERM%% *}" != "600" ]] && warn "/etc/shadow amb permisos no estàndard."

# World-writable
sub "Directoris i fitxers world-writable (fora de /proc)"
find / -xdev -type d -perm -0002 -not -path "/proc/*" 2>/dev/null | tee "$OUTDIR/world_writable_dirs.txt" >/dev/null || true
find / -xdev -type f -perm -0002 -not -path "/proc/*" 2>/dev/null | tee "$OUTDIR/world_writable_files.txt" >/dev/null || true

WWD_COUNT=$(wc -l < "$OUTDIR/world_writable_dirs.txt" || echo 0)
WWF_COUNT=$(wc -l < "$OUTDIR/world_writable_files.txt" || echo 0)
if [[ $WWD_COUNT -gt 0 || $WWF_COUNT -gt 0 ]]; then
  warn "World-writable: $WWD_COUNT directoris | $WWF_COUNT fitxers (veure adjunts)."
else
  ok "No s'han detectat world-writable fora de /proc."
fi

# SUID/SGID
sub "Binaris SUID/SGID"
find / -xdev \( -perm -4000 -o -perm -2000 \) -type f 2>/dev/null | tee "$OUTDIR/suid_sgid.txt" >/dev/null || true
SUID_COUNT=$(wc -l < "$OUTDIR/suid_sgid.txt" || echo 0)
if [[ $SUID_COUNT -gt 0 ]]; then
  warn "$SUID_COUNT binaris SUID/SGID (revisar; alguns són normals)."
  if [[ $QUICK -eq 1 ]]; then
    say ""
    say "<details><summary>Mostra (primeres 50 línies)</summary>"
    say ""
    say '```text'
    head -n 50 "$OUTDIR/suid_sgid.txt"
    say '```'
    say "</details>"
  else
    say ""
    say "<details><summary>Llista completa</summary>"
    say ""
    say '```text'
    cat "$OUTDIR/suid_sgid.txt"
    say '```'
    say "</details>"
  fi
else
  ok "No s'han trobat SUID/SGID (poc habitual)."
fi

hr
sec "8) Paràmetres del sistema"

sub "Core dumps i suid_dumpable"
SYS_CORE="$(sysctl -n kernel.core_pattern 2>/dev/null || echo '?')"
SYS_SUID="$(sysctl -n fs.suid_dumpable 2>/dev/null || echo '?')"
say "- kernel.core_pattern: \`$SYS_CORE\`"
say "- fs.suid_dumpable: \`$SYS_SUID\` (0 recomanat)"
[[ "$SYS_SUID" != "0" ]] && warn "fs.suid_dumpable no és 0."

# Secure Boot
sub "Secure Boot"
if command -v mokutil >/dev/null 2>&1; then
  SB_STATE="$(mokutil --sb-state 2>/dev/null || true)"
  say '```text'
  echo "$SB_STATE"
  say '```'
  echo "$SB_STATE" | grep -qi 'enabled' && ok "Secure Boot habilitat." || warn "Secure Boot deshabilitat."
else
  info "mokutil no disponible (afegeix --install-tools per comprovar Secure Boot)."
fi

hr
sec "9) Resum i recomanacions"

say "- **Actualitzables:** $UPG_COUNT paquets."
if [[ -n "${ALL_COUNT:-}" ]]; then
  say "- **CVE totals (debsecan):** ${ALL_COUNT:-0} | **Amb fix disponible:** ${FIXED_COUNT:-0}"
fi
[[ -f /var/run/reboot-required ]] && say "- **Reinici pendent:** Sí" || say "- **Reinici pendent:** No"

say ""
say "### Recomanacions generals"
say "1. Aplicar **actualitzacions de seguretat** immediates: \`sudo apt-get upgrade\` (o \`unattended-upgrades\`)."
say "2. Si exposeu serveis, habilitar i configurar **UFW** i assegurar **AppArmor**."
say "3. **SSH**: deshabilitar root login i autenticació per contrasenya si és possible (usar claus)."
say "4. Revisar **SUID/SGID** i **world-writable** innecessaris."
say "5. Considerar **Ubuntu Pro/Livepatch** per a parches de kernel en calent."
say "6. Revisar **NOPASSWD** a sudoers i perfils d'usuari amb privilegis."

hr
ok "Informe generat: $REPORT"
info "Detalls i adjunts a: $OUTDIR"
``

Què obtindràs

  • Un informe Markdown amb seccions clares per a decisió ràpida.
  • Arxius adjunts amb detall complet (llistes de ports, CVE, SUID/SGID, etc.).
  • Cap canvi al sistema (excepte si passes --install-tools, que només instal·la utilitats de lectura).