Les cinc lliçons anteriors han afegit mesures de seguretat d'una en una: xarxa controlada, SSH amb claus, tallafocs, detecció d'intrusions, secrets xifrats i TLS. Cadascuna resolia un problema concret. Aquesta lliçó fa una cosa diferent: les converteix en una postura de seguretat, que és un conjunt coherent de decisions justificades per un model d'amenaces explícit, amb constància escrita de què està protegit, què s'ha decidit assumir i què queda fora de l'abast d'un administrador.
La diferència no és retòrica. Sense model d'amenaces, l'enfortiment degenera en cargo cult: s'apliquen receptes trobades en guies perquè «sonen segures», es trenquen coses sense entendre per què, i s'acaba amb un sistema fràgil i una falsa sensació de control. Amb model d'amenaces, cada mesura té un perquè i cada omissió és una decisió conscient.
A més tanques aquí el tercer incident pendent —els serveis que continuen fent servir la libssl vulnerable carregada a memòria— i saldes el deute que vas deixar a 05-01: PAM, el subsistema que decideix de debò com s'autentica algú en aquest sistema.
Advertiment previ. Dos dels apartats d'aquesta lliçó —PAM i els muntatges segurs— et poden deixar sense poder iniciar sessió al teu propi servidor si t'equivoques. En tots dos casos s'indica el procediment segur. No te'l saltis: el laboratori existeix precisament perquè aprenguis això on equivocar-se no costa res.
Contingut
- Els cinc principis que ordenen tot l'anterior
- El model d'amenaces de Tramontana
- Reducció de la superfície d'atac
- Enfortiment de comptes i política de contrasenyes
- PAM: com s'autentica realment aquest sistema
- Control d'accés obligatori amb AppArmor
- Enfortiment del nucli amb sysctl
- Muntatges segurs
- Gestió de vulnerabilitats i tancament de l'incident d'openssl
- Enfortiment de systemd revisitat
- Còpies com a últim control davant del ransomware
- Llista de comprovació d'enfortiment i manteniment de la postura
Els cinc principis que ordenen tot l'anterior
Tot el que has fet al mòdul respon, sense que ho haguem dit, a cinc principis. Anomenar-los permet aplicar-los a situacions noves en lloc de repetir receptes:
| Principi | Què exigeix | On l'has aplicat ja |
|---|---|---|
| Mínim privilegi | Cada procés i cada persona amb els permisos justos, ni un més | svc-tramontana sense shell, sudoers amb ordres concretes, CapabilityBoundingSet= buit |
| Superfície d'atac mínima | El que no existeix no es pot atacar | Servidor sense escriptori, port 8080 tancat a l'exterior, política de llista blanca a ufw |
| Defensa en profunditat | Diverses capes independents; que una falli no ha de bastar | Tallafocs + SSH enfortit + AIDE + auditd + secrets xifrats |
| Fallada segura | Quan alguna cosa es trenca, ha de quedar tancada, no oberta | policy drop per defecte a nftables, set -euo pipefail als scripts |
| Segur per defecte | L'estat inicial ha de ser el restrictiu | umask 027, permisos 600 als secrets, PasswordAuthentication no |
El més difícil d'aplicar a la pràctica és el segon, perquè exigeix treure coses, i treure coses fa por. El més oblidat és el quart: molta configuració «segura» falla obrint-se, i això és pitjor que no tenir-la, perquè ningú no se n'assabentarà.
El model d'amenaces de Tramontana
Un model d'amenaces respon a tres preguntes. Mitja pàgina és suficient, i és la mitja pàgina que fa que tota la resta tingui sentit.
Què protegim? Dues coses, en aquest ordre:
- Les dades personals dels hostes (noms, dates d'estada, imports a
reserves.csvi a la base de dades). El seu compromís és un dany a tercers, té conseqüències legals sota el RGPD i és irreversible: un cop filtrades, no es poden «recuperar». - La disponibilitat del servei de reserves. La seva interrupció té un cost econòmic directe i acotat en el temps.
De qui? Quatre actors realistes, ordenats per probabilitat:
| Amenaça | Probabilitat | Capacitat | Controls que li oposem |
|---|---|---|---|
| Escaneig automatitzat d'Internet | Constant | Baixa: explota el conegut i sense pedaç | Tallafocs, fail2ban, actualitzacions de seguretat, SSH sense contrasenya |
| Credencial filtrada o reutilitzada | Mitjana | Alta: entra com a usuari legítim | Claus en comptes de contrasenyes, secrets xifrats, rotació, auditd |
| Error propi de configuració | Alta | Variable, de vegades total | --dry-run, còpies abans d'editar, netplan try, mount -a, AIDE |
| Abús d'accés legítim | Baixa | Alta dins del seu àmbit | Mínim privilegi a sudoers, ACL, auditoria d'accessos |
Fixa't en la tercera fila. L'actor més probable ets tu, i aquella constatació explica per què mitja dotzena de convencions del curs —copiar abans d'editar, verificar amb diff -u, provar en sec, netplan try amb reversió automàtica, mount -a abans de reiniciar— són controls de seguretat de ple dret, i no manies d'estil.
Què assumim fora d'abast? Dir-ho és tan important com l'anterior, perquè delimita què no s'està protegint:
- Atacant amb recursos estatals o cadena de subministrament compromesa. Està fora de l'abast de qualsevol mesura que un administrador pugui prendre en un servidor d'una petita empresa.
- Accés físic a la màquina. El xifratge LUKS mitiga el robatori del disc, però un atacant amb accés físic prolongat i la màquina encesa té el sistema.
- Vulnerabilitat a la mateixa aplicació (injecció SQL, lògica de negoci). És responsabilitat del desenvolupament, no de l'administració, i l'enfortiment del sistema només limita el dany posterior.
- Compromís del proveïdor del núvol o de l'hipervisor.
I ara el criteri de fons: amb aquest model, invertir en un NIDS aporta poc (ja ho vas raonar a 06-04) i invertir en detecció de canvis i en control de l'autenticació aporta molt. Això és prendre decisions, no seguir una llista.
Reducció de la superfície d'atac
El que no existeix no es pot atacar, no necessita pedaços i no apareix en cap CVE. És el principi amb millor relació entre esforç i resultat.
Què escolta
$ sudo ss -tulpn
Netid State Local Address:Port Peer Address:Port Process
udp UNCONN 127.0.0.54:53 0.0.0.0:* users:(("systemd-resolve",pid=712,fd=17))
udp UNCONN 127.0.0.53%lo:53 0.0.0.0:* users:(("systemd-resolve",pid=712,fd=13))
tcp LISTEN 127.0.0.53%lo:53 0.0.0.0:* users:(("systemd-resolve",pid=712,fd=14))
tcp LISTEN 0.0.0.0:22 0.0.0.0:* users:(("sshd",pid=894,fd=3))
tcp LISTEN 0.0.0.0:8080 0.0.0.0:* users:(("tramontana",pid=4102,fd=6))
tcp LISTEN 10.0.2.15:5432 0.0.0.0:* users:(("postgres",pid=1105,fd=5))Llegeix cada línia preguntant «ha de ser aquí, i en aquella adreça?»:
systemd-resolvea127.0.0.53i127.0.0.54: local, correcte.sshda0.0.0.0:22: necessari, i ja enfortit.tramontanaa0.0.0.0:8080: escolta a totes les interfícies. El tallafocs el bloqueja des de fora, però això és defensa en una sola capa. El correcte és que l'aplicació escolti només a127.0.0.1, perquè quan arribi el servidor intermediari invers de 08-01 serà qui li parlarà, i així el tallafocs passa a ser la segona línia en lloc de l'única.postgresa10.0.2.15:5432: ja restringit a la interfície interna. Correcte.
El primer arreglament, doncs:
$ sudo cp -p /etc/tramontana/app.conf /etc/tramontana/app.conf.bak-$(date +%F)
$ sudo chattr -i /etc/tramontana/app.conf
$ echo 'escolta=127.0.0.1' | sudo tee -a /etc/tramontana/app.conf
$ sudo chattr +i /etc/tramontana/app.conf
$ sudo systemctl restart tramontana.service
$ sudo ss -tulpn | grep 8080
tcp LISTEN 127.0.0.1:8080 0.0.0.0:* users:(("tramontana",pid=4318,fd=6))Això és defensa en profunditat aplicada: ara calen dues fallades —una regla de tallafocs mal posada i una escolta massa àmplia— per exposar el 8080. Abans en bastava una.
Què està habilitat
$ systemctl list-unit-files --state=enabled --type=service --no-pager
UNIT FILE STATE PRESET
apparmor.service enabled enabled
auditd.service enabled enabled
cron.service enabled enabled
fail2ban.service enabled enabled
ModemManager.service enabled enabled
multipathd.service enabled enabled
postgresql.service enabled enabled
ssh.service enabled enabled
tramontana.service enabled enabled
unattended-upgrades.service enabled enabledModemManager gestiona mòdems i banda ampla mòbil. En una màquina virtual de servidor no té cap sentit. I aquí convé entendre la diferència entre les dues maneres d'apagar una cosa, perquè no són equivalents:
| Operació | Què fa | Es pot reactivar sola |
|---|---|---|
disable |
Esborra els enllaços de [Install]; no arrenca a l'inici |
Sí: si una altra unitat el declara a Wants= |
mask |
Enllaça la unitat a /dev/null; és inarrencable |
No: ni a mà ni per dependència |
$ sudo systemctl disable --now ModemManager.service
$ sudo systemctl mask ModemManager.service
Created symlink /etc/systemd/system/ModemManager.service → /dev/null.mask per al que no ha d'arrencar mai; disable per al que podries voler arrencar a mà algun dia. I el pas següent, quan n'estiguis segur, és desinstal·lar el paquet: un binari que no és al disc no té vulnerabilitats.
Sobre l'escriptori, per tancar l'apartat: un entorn gràfic afegeix centenars de paquets, un servidor X o Wayland, un gestor de sessions i un navegador — cadascun amb la seva superfície i el seu calendari de pedaços. Que srv-tramontana no en tingui no és austeritat, és la reducció de superfície més gran que es pot fer en un servidor, i per això l'instal·lador d'Ubuntu Server no l'ofereix per defecte.
Enfortiment de comptes i política de contrasenyes
Tres auditories que convé tenir guardades en un script, perquè detecten errors greus i són d'una línia cadascuna:
# 1. Comptes amb UID 0: nomes n hi ha d haver un, root
$ awk -F: '($3 == 0) {print $1}' /etc/passwd
root
# 2. Comptes sense contrasenya (segon camp buit a shadow): greu
$ sudo awk -F: '($2 == "") {print $1 " NO TE CONTRASENYA"}' /etc/shadow
# 3. Comptes de sistema (UID < 1000) amb shell d inici de sessio valida
$ awk -F: '($3 < 1000 && $7 !~ /(nologin|false|sync)$/) {print $1, $3, $7}' /etc/passwd
root 0 /bin/bash
sync 4 /bin/syncEls tres resultats són correctes: un únic UID 0, cap compte sense contrasenya, i els dos únics comptes de sistema amb shell són root —necessari per a la recuperació— i sync, que és un cas històric inofensiu. Que svc-tramontana no aparegui a la tercera llista és la confirmació que el -s /usr/sbin/nologin de 05-01 va fer la seva feina.
La política per defecte viu a /etc/login.defs, i afecta els comptes que es creïn a partir d'ara:
# /etc/login.defs (valors modificats)
PASS_MAX_DAYS 365 # caducitat maxima
PASS_MIN_DAYS 1 # evita que es canvii diverses vegades seguides per tornar a l anterior
PASS_WARN_AGE 14 # dies d avis abans de caducar
UMASK 027 # coherent amb la convencio del curs
SHA_CRYPT_MIN_ROUNDS 5000
ENCRYPT_METHOD YESCRYPTPASS_MIN_DAYS 1 és menys obvi que els altres: sense ell, algú a qui s'exigeix canviar la contrasenya la pot canviar cinc vegades seguides per esgotar l'historial i tornar a l'original. És un truc vell i continua funcionant on no es posa.
I per als comptes ja existents, chage:
$ sudo chage -M 365 -m 1 -W 14 luis
$ sudo chage -l luis
Last password change : ago 04, 2026
Password expires : ago 04, 2027
Password inactive : never
Account expires : never
Minimum number of days between password change : 1
Maximum number of days between password change : 365
Number of days of warning before password expires : 14
# El compte del becari te data de fi de practiques: caduca automaticament
$ sudo chage -E 2026-12-31 becari
$ sudo chage -l becari | grep 'Account expires'
Account expires : dic 31, 2026chage -E mereix atenció: és la manera correcta de gestionar un compte temporal. Caduca sol en la data prevista, sense dependre que algú es recordi de donar-lo de baixa — i les baixes de personal oblidades són una de les vies de compromís més comunes que existeixen.
PAM: com s'autentica realment aquest sistema
A 05-01 vam ajornar PAM. És el moment, perquè és la peça que decideix de debò qui entra en aquest sistema i amb quins requisits.
PAM (Pluggable Authentication Modules) és una capa d'indirecció: els programes que necessiten autenticar —login, sshd, sudo, su, passwd— no implementen la lògica, sinó que pregunten a PAM. Això permet canviar la política (exigir contrasenyes fortes, bloquejar després de fallades, afegir un segon factor) sense modificar ni recompilar cap programa. És la raó per la qual existeix.
L'estructura
Cada servei té el seu fitxer a /etc/pam.d/, i hi ha fitxers comuns que els altres inclouen:
$ ls /etc/pam.d/ | head -12
common-account
common-auth
common-password
common-session
cron
login
passwd
sshd
su
sudoCada línia té tres parts: tipus, control i mòdul.
Els quatre tipus, que són quatre preguntes diferents i responen en moments diferents:
| Tipus | Pregunta que respon |
|---|---|
auth |
És qui diu que és? (contrasenya, clau, segon factor) |
account |
Se li permet entrar ara? (compte caducat, horari, origen) |
password |
És acceptable la contrasenya nova que vol posar? |
session |
Què cal preparar i netejar al voltant de la sessió (límits, registre, home) |
Confondre auth amb account és el malentès més habitual: una contrasenya correcta en un compte caducat passa auth i falla account.
Els controls determinen què passa segons el resultat del mòdul:
| Control | Si el mòdul falla | Si té èxit |
|---|---|---|
required |
Falla el conjunt, però es continuen executant els altres | Continua |
requisite |
Falla i s'atura allà mateix | Continua |
sufficient |
S'ignora | Èxit immediat, si res required anterior no ha fallat |
optional |
S'ignora (llevat que sigui l'únic) | Continua |
required davant de requisite té una raó de ser que no és evident: required continua executant la pila per no revelar en quin pas va fallar. Si un atacant pogués distingir «usuari inexistent» de «contrasenya incorrecta» pel temps de resposta o pel missatge, tindria un oracle per enumerar usuaris.
La sintaxi moderna, més precisa, fa servir claudàtors: [success=ok default=die] permet especificar l'acció per a cada codi de retorn. És el que veuràs als fitxers d'Ubuntu.
El procediment per no deixar-se fora
Abans de tocar ni una sola línia. Un error de sintaxi a common-auth pot impedir tot inici de sessió, inclosa la consola local, i aleshores l'única sortida és el mode de recuperació:
- Deixa una sessió de root oberta en un altre terminal, i no la tanquis fins a haver acabat:
$ sudo -i # (no tancar aquesta sessio) - Copia el fitxer abans d'editar-lo, segons la convenció del curs:
$ sudo cp -p /etc/pam.d/common-password /etc/pam.d/common-password.bak-$(date +%F) - Prova en un tercer terminal, mai en el que tens la sessió de root.
- Tingues a mà la consola de la VM i sàpigues com entrar en mode de recuperació (es veu a fons a 07-01).
És exactament la mateixa disciplina de netplan try i de la segona sessió SSH: no tanquis mai la porta per la qual estàs entrant.
pam_pwquality: exigir contrasenyes decents
# /etc/security/pwquality.conf
minlen = 12 # longitud minima
minclass = 3 # almenys 3 de: minuscules, majuscules, digits, simbols
maxrepeat = 3 # no mes de 3 caracters iguals consecutius
dictcheck = 1 # rebutja paraules de diccionari
usercheck = 1 # rebutja contrasenyes que continguin el nom d usuari
enforce_for_root = 1 # tambe a root: sense aixo, root queda exempt
retry = 3$ passwd luis
New password: tramontana2026
Bad password: it is based on a dictionary word.
New password: Kj8-mQ2vX9pL
passwd: password updated successfullyDos comentaris de fons. El primer: minlen = 12 amb minclass = 3 és més raonable que les polítiques de «8 caràcters amb majúscula, número i símbol» que produeixen Passw0rd! — la longitud aporta molt més que la complexitat forçada, i així ho recullen les guies actuals (NIST SP 800-63B). El segon: enforce_for_root = 1 és fàcil d'oblidar, i sense ell el compte més important del sistema és l'únic exempt de la política.
pam_faillock: bloquejar després d'intents fallits
Complementa fail2ban, que actua sobre la IP; faillock actua sobre el compte, així que cobreix també els intents des de la consola o des d'IP diferents.
# /etc/security/faillock.conf
deny = 5 # bloqueja despres de 5 fallades
fail_interval = 900 # que passin dins de 15 minuts
unlock_time = 600 # desbloqueig automatic als 10 minuts
even_deny_root # tambe a root
root_unlock_time = 60 # pero root es desbloqueja en 1 minut: evita el bloqueig total
audit # registra l intent
silent # no revela a l atacant que el compte esta bloquejat# /etc/pam.d/common-auth
auth required pam_faillock.so preauth
auth [success=1 default=ignore] pam_unix.so nullok
auth [default=die] pam_faillock.so authfail
auth sufficient pam_faillock.so authsucc
auth requisite pam_deny.so
auth required pam_permit.soL'ordre importa i no és intuïtiu: preauth comprova si el compte ja està bloquejat abans de demanar credencials, authfail compta la fallada, i authsucc neteja el comptador després d'un accés correcte. Que falti authsucc produeix un bloqueig lent però inevitable, perquè el comptador no es reinicia mai.
I la combinació d'even_deny_root amb root_unlock_time = 60 és deliberada: protegir root sense crear la possibilitat d'un bloqueig total del sistema.
$ faillock --user luis
luis:
When Type Source Valid
2026-08-18 14:22:11 RHOST 203.0.113.44 V
2026-08-18 14:22:14 RHOST 203.0.113.44 V
$ sudo faillock --user luis --reset # desbloquejar a mapam_limits: límits de recursos
# /etc/security/limits.conf
# domini tipus element valor
* hard nproc 4096 # frena una bomba de processos
* hard core 0 # no generar abocaments: poden contenir secrets
svc-tramontana soft nofile 8192
svc-tramontana hard nofile 16384Dues d'aquestes línies són mesures de seguretat més que de rendiment. nproc limita el dany d'un bucle que crea processos sense control —accidental o no—. I core 0 evita els abocaments de memòria, que contenen tot el que el procés tenia a la RAM en aquell moment: inclosa la contrasenya que tanta feina t'ha costat xifrar a 06-05.
Compte amb l'abast: pam_limits s'aplica a sessions que passen per PAM. Per a un servei de systemd, els límits es posen a la unitat (LimitNOFILE, LimitNPROC), com vas veure a 05-07.
Segon factor, breument
libpam-google-authenticator afegeix un codi TOTP a l'autenticació SSH. És eficaç contra el robatori de credencials, i en un servidor amb accés per clau el benefici marginal és menor que en un amb contrasenyes. Si s'implanta, hi ha dues coses que no es poden oblidar: guardar els codis de recuperació fora del servidor, i decidir què passa si el dispositiu TOTP es perd. Sense això, és una manera elegant de quedar-se fora.
Tancar la troballa: l'excepció de PasswordAuthentication
A 06-04 els registres van mostrar que luis va entrar amb password, tot i que a 06-02 ho vas deshabilitar. Hora de localitzar-ho, començant per la font autoritzada:
$ sudo sshd -T | grep -i -E 'passwordauth|kbdinteractive'
passwordauthentication no
kbdinteractiveauthentication noLa configuració global és correcta. Però sshd -T sense arguments mostra la configuració global; els blocs Match són condicionals i no hi apareixen. Cal preguntar pel cas concret:
$ sudo grep -rn -A3 'Match' /etc/ssh/sshd_config /etc/ssh/sshd_config.d/
/etc/ssh/sshd_config.d/60-tramontana.conf:12:Match User luis
/etc/ssh/sshd_config.d/60-tramontana.conf-13- PasswordAuthentication yes
$ sudo sshd -T -C user=luis,host=portatil-luis,addr=10.0.2.44 | grep -i passwordauth
passwordauthentication yesAquí està. Un bloc Match User luis amb l'excepció, posada «temporalment» mentre el Luis generava la seva clau, i mai retirada. És l'actor més probable del model d'amenaces —l'error propi— en la seva forma més característica.
$ sudo cp -p /etc/ssh/sshd_config.d/60-tramontana.conf{,.bak-$(date +%F)}
$ sudo sed -i '/^Match User luis$/,+1d' /etc/ssh/sshd_config.d/60-tramontana.conf
$ sudo sshd -t && echo "sintaxi correcta"
sintaxi correcta
$ sudo sshd -T -C user=luis,host=portatil-luis,addr=10.0.2.44 | grep -i passwordauth
passwordauthentication no
$ sudo systemctl reload sshFixa't en sshd -T -C: l'opció -C avalua la configuració per a un context concret d'usuari, amfitrió i adreça, resolent els blocs Match. És l'única manera fiable de saber quina configuració s'aplica a algú, i mereix un lloc a la teva memòria: la meitat dels problemes desconcertants d'SSH són un Match oblidat.
Abans de recarregar, la disciplina de sempre: sshd -t valida, la segona sessió està oberta, i es fa servir reload en lloc de restart.
Control d'accés obligatori amb AppArmor
Els permisos que coneixes des de 02-07 són DAC (Discretionary Access Control): discrecionals perquè el propietari d'un recurs decideix qui hi accedeix. Tenen un límit estructural: un procés pot fer tot el que el seu usuari pot fer. Si l'aplicació de Tramontana es veu compromesa, l'atacant pot llegir qualsevol cosa llegible per svc-tramontana i escriure a qualsevol lloc on aquell usuari escrigui.
MAC (Mandatory Access Control) afegeix una capa que el propietari no pot relaxar: una política, definida per l'administrador i aplicada pel nucli, que diu què pot fer aquest programa, amb independència de qui l'executi.
| DAC (permisos Unix) | MAC (AppArmor / SELinux) | |
|---|---|---|
| Qui decideix | El propietari del fitxer | L'administrador, a la política |
| Unitat de control | Usuari i grup | Programa (perfil) |
| Ho pot relaxar el procés | Sí, dins del que és del seu usuari | No |
| Què conté | Un compromís del procés | Un compromís del procés i del seu usuari |
$ sudo aa-status
apparmor module is loaded.
32 profiles are loaded.
28 profiles are in enforce mode.
/usr/bin/man
/usr/sbin/sshd
...
2 profiles are in complain mode.
2 processes have profiles defined.Els dos modes són la clau del mètode de treball:
complain(mode de queixa): no bloqueja, només registra el que hauria bloquejat. És el mode d'aprenentatge.enforce(mode d'aplicació): bloqueja. És el mode de producció.
I el procediment professional és sempre el mateix: escriure el perfil, posar-lo en complain, exercitar l'aplicació de debò, recollir les denegacions i ampliar el perfil, i només aleshores passar a enforce. Començar en enforce significa trencar el servei i descobrir els accessos que falten de la pitjor manera.
Un perfil per a l'aplicació de Tramontana
# /etc/apparmor.d/opt.tramontana.app.tramontana
abi <abi/4.0>,
include <tunables/global>
/opt/tramontana/releases/*/tramontana {
include <abstractions/base>
include <abstractions/nameservice>
include <abstractions/openssl>
# El binari propi: llegir i executar
/opt/tramontana/releases/*/tramontana mr,
/opt/tramontana/releases/*/** r,
/opt/tramontana/app/** r,
# Configuracio: nomes lectura. Encara que el proces es vegi compromes,
# no pot modificar la seva propia configuracio.
/etc/tramontana/app.conf r,
# Credencial lliurada per systemd (tmpfs privat del servei)
/run/credentials/tramontana.service/* r,
# Registres propis: crear i afegir, mai sobreescriure ni esborrar
/var/log/tramontana/ r,
/var/log/tramontana/*.log rw,
# Dades pujades pels hostes: lectura i escriptura
/opt/tramontana/shared/uploads/ r,
/opt/tramontana/shared/uploads/** rwk,
# Xarxa: nomes TCP sobre IPv4/IPv6. No unix cru, no paquets crus.
network inet stream,
network inet6 stream,
# Res mes no esta permes: AppArmor denega per defecte.
# En particular, queden DENEGATS de forma implicita:
# /etc/shadow, /home/**, /srv/tramontana/backups/**,
# /root/**, l execucio de /bin/sh i la carrega de moduls.
}Aquell últim comentari és el que fa valuós el perfil. Un atacant que executi codi en el context de l'aplicació no pot llançar una shell, no pot llegir les còpies, no pot tocar els directoris personals i no pot modificar la seva pròpia configuració — coses que el DAC sí que li permetria fer en part, i que són precisament els primers passos de qualsevol postexplotació.
# 1. Carregar en mode aprenentatge
$ sudo apparmor_parser -r /etc/apparmor.d/opt.tramontana.app.tramontana
$ sudo aa-complain /opt/tramontana/releases/*/tramontana
# 2. Exercitar l aplicacio de debo: peticions, pujada de fitxer, desplegament
$ curl -s -o /dev/null -w '%{http_code}\n' http://127.0.0.1:8080/cases
200
$ ~/scripts/revisio_salut.sh; echo "estat: $?"
estat: 0
# 3. Recollir les denegacions registrades
$ sudo journalctl -k --since "10 min ago" | grep 'apparmor="ALLOWED"'
kernel: audit: type=1400 apparmor="ALLOWED" operation="open"
profile="/opt/tramontana/releases/*/tramontana" name="/opt/tramontana/shared/plantilles/factura.html"
pid=4318 comm="tramontana" requested_mask="r" denied_mask="r"En mode complain la marca és ALLOWED amb un denied_mask: significa «això ho hauria bloquejat». Aquí ha aparegut un accés legítim que el perfil no contemplava —les plantilles—, així que s'afegeix i es repeteix el cicle:
# 4. Ampliar de forma assistida si es prefereix, i passar a enforce
$ sudo aa-logprof
$ sudo aa-enforce /opt/tramontana/releases/*/tramontana
Setting /opt/tramontana/releases/*/tramontana to enforce mode.
$ sudo systemctl restart tramontana.service
$ ~/scripts/revisio_salut.sh; echo "estat: $?"
estat: 0A partir d'aquí, una denegació real apareix com a apparmor="DENIED", i és el primer que cal mirar quan el servei comença a fallar després d'un desplegament que canvia rutes:
SELinux, per a qui vagi a RHEL:
| AppArmor | SELinux | |
|---|---|---|
| Distribucions | Ubuntu, Debian, SUSE | RHEL, Fedora, CentOS Stream |
| Identifica objectes per | Ruta del fitxer | Etiqueta a l'inode |
| Corba d'aprenentatge | Suau; perfils llegibles | Pronunciada; més potent |
| Diagnòstic | journalctl + aa-logprof |
ausearch + audit2allow, restorecon |
| Mode permissiu | Per perfil (complain) |
Global o per domini |
La diferència per ruta davant d'etiqueta té una conseqüència pràctica: a SELinux, moure un fitxer pot canviar-ne el context i trencar l'accés (d'aquí restorecon); a AppArmor, un enllaç simbòlic o un bind mount pot saltar-se una regla basada en ruta. Cada model té el seu punt feble.
Enfortiment del nucli amb sysctl
A 06-03 vas aplicar els sysctl de xarxa. Aquests són els del mateix nucli, i són només de seguretat: els de rendiment es veuen a 07-03.
# /etc/sysctl.d/60-enfortiment.conf
# Nomes root pot llegir la memoria intermedia del nucli. Evita filtrar adreces
# de memoria i detalls del maquinari utils per construir un exploit.
kernel.dmesg_restrict = 1
# Oculta els punters del nucli a /proc i als registres.
kernel.kptr_restrict = 2
# ASLR complet: aleatoritza l espai d adreces, inclos el munt.
# 2 es el valor per defecte a Ubuntu; es declara perque sigui explicit.
kernel.randomize_va_space = 2
# Impedeix crear enllacos durs a fitxers dels quals no ets propietari i
# seguir enllacos simbolics aliens en directoris amb sticky bit.
# Tanca tota una familia d atacs de cursa a /tmp.
fs.protected_hardlinks = 1
fs.protected_symlinks = 1
# La mateixa proteccio per a FIFO i fitxers regulars en directoris
# escribibles per tothom.
fs.protected_fifos = 2
fs.protected_regular = 2
# Nomes root pot fer servir BPF. Redueix molt la superficie del subsistema.
kernel.unprivileged_bpf_disabled = 1
# Un proces nomes pot depurar els seus descendents directes.
# Impedeix que un proces compromes llegeixi la memoria d un altre del mateix
# usuari (i amb ella els secrets que aquell altre tingui carregats).
kernel.yama.ptrace_scope = 1
# No permetre a usuaris sense privilegis crear espais de noms d usuari.
# ATENCIO: trenca els contenidors sense privilegis. Comentat perque a
# 07-05 el necessitaras; descomentar nomes en servidors sense contenidors.
#kernel.unprivileged_userns_clone = 0$ sudo sysctl --system 2>&1 | grep -A9 '60-enfortiment'
* Applying /etc/sysctl.d/60-enfortiment.conf ...
kernel.dmesg_restrict = 1
kernel.kptr_restrict = 2
kernel.randomize_va_space = 2
fs.protected_hardlinks = 1
fs.protected_symlinks = 1
fs.protected_fifos = 2
fs.protected_regular = 2
kernel.unprivileged_bpf_disabled = 1
kernel.yama.ptrace_scope = 1
$ sysctl kernel.yama.ptrace_scope
kernel.yama.ptrace_scope = 1Dos avisos que has d'anotar, perquè són les dues maneres que això et mossegui més endavant:
kernel.yama.ptrace_scope = 1pot trencar depuradors.straceogdbsobre un procés que ja està en marxa i no és fill teu deixarà de funcionar sensesudo. A 07-02 faràs servir exactament aquelles eines; recorda que aquest paràmetre és l'explicació si et donaOperation not permitted.kernel.unprivileged_userns_clone = 0trenca contenidors sense privilegis, i a 07-05 muntaràs Docker. Per això queda comentat amb la raó escrita al costat. Un paràmetre d'enfortiment amb una nota explicant per què no està activat és documentació de qualitat; esborrar-lo i oblidar el motiu no ho és.
I el criteri general: cada línia d'aquest fitxer porta el seu comentari. Un sysctl.d amb quinze valors sense explicació és impossible de mantenir, perquè ningú —tu inclòs, d'aquí a sis mesos— no sabrà si se'n pot treure algun.
Muntatges segurs
Tres opcions de muntatge que limiten el dany als directoris on qualsevol pot escriure:
| Opció | Efecte |
|---|---|
noexec |
No s'hi pot executar res des d'allà |
nosuid |
S'ignoren els bits SUID/SGID |
nodev |
S'ignoren els fitxers de dispositiu |
El cas d'ús és directe: un atacant que aconsegueix escriure un fitxer sol necessitar executar-lo, i /tmp és el lloc on gairebé sempre pot escriure. Amb noexec, aquell pas falla.
I aquí és on la lliçó insisteix en una cosa que les guies d'enfortiment solen ometre: noexec a /tmp trenca coses reals. Diversos instal·ladors extreuen a /tmp i executen des d'allà; alguns gestors de paquets de llenguatges també. Cal comprovar-ho abans:
# 1. Comprovar l impacte en calent, sense tocar fstab
$ sudo mount -o remount,noexec,nosuid,nodev /tmp
# 2. Exercitar el que es podria trencar
$ sudo apt install --reinstall -y tree >/dev/null && echo "apt: correcte"
apt: correcte
$ ~/scripts/desplegar.sh --dry-run 3.2.1 && echo "desplegament: correcte"
desplegament: correcte
$ sudo needrestart -r l >/dev/null && echo "needrestart: correcte"
needrestart: correcte
# 3. I comprovar que la restriccio realment actua
$ printf '#!/bin/sh\necho hola\n' > /tmp/prova.sh && chmod +x /tmp/prova.sh
$ /tmp/prova.sh
bash: /tmp/prova.sh: Permission deniedAquell últim bloc és la verificació positiva: no n'hi ha prou que res no s'hagi trencat, cal comprovar que la mesura fa alguna cosa. Amb les dues comprovacions fetes, es fa persistent:
# /etc/fstab
UUID=3f8a... /tmp ext4 defaults,noatime,noexec,nosuid,nodev 0 2
/tmp /var/tmp none bind,noexec,nosuid,nodev 0 0
tmpfs /dev/shm tmpfs defaults,noexec,nosuid,nodev 0 0$ sudo systemctl daemon-reload
$ sudo mount -a && echo "fstab correcte"
fstab correcte
$ findmnt -o TARGET,OPTIONS /tmp /var/tmp /dev/shm
TARGET OPTIONS
/tmp rw,noexec,nosuid,nodev,relatime
/var/tmp rw,noexec,nosuid,nodev,relatime
/dev/shm rw,noexec,nosuid,nodevmount -a abans de reiniciar continua sent obligatori, pel que ja saps des de 05-04: un fstab mal escrit impedeix arrencar el servidor.
Gestió de vulnerabilitats i tancament de l'incident d'openssl
Llegir un CVE sense enganyar-se
Un CVE és l'identificador únic d'una vulnerabilitat concreta. El seu CVSS és una puntuació de 0 a 10 acompanyada d'un vector que descriu com s'explota:
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H → 9.8 CRITICA
│ │ │ │ │ │ └─ Confidencialitat/Integritat/Disponibilitat: alta
│ │ │ │ │ └─ Abast: sense canvi
│ │ │ │ └─ Interaccio de l'usuari: cap
│ │ │ └─ Privilegis requerits: cap
│ │ └─ Complexitat de l'atac: baixa
│ └─ Vector d'atac: xarxaI el punt que cal interioritzar: la puntuació base no és el risc real per a tu. Un 9.8 en un component que no tens instal·lat, o que no està exposat, o que ja està mitigat per una altra capa, és menys urgent que un 6.5 al servei que dona la cara a Internet. El vector és més informatiu que el número: AV:N (explotable per xarxa) sense privilegis ni interacció de l'usuari és el que obliga a actuar avui.
$ apt list --upgradable
$ pro security-status
1543 packages installed:
1489 packages from Ubuntu Main/Restricted repository
54 packages from Ubuntu Universe/Multiverse repository
Ubuntu Pro is not attached. Ubuntu Pro would provide security updates for
54 packages until 2034.
$ sudo apt install debsecan && debsecan --suite noble --format summary | head -5El tancament de l'incident
El 18 d'agost a les 06:12, unattended-upgrades va actualitzar openssl i libssl3t64. El paquet està actualitzat al disc, però els processos que van arrencar abans continuen amb la biblioteca antiga carregada a memòria. És una vulnerabilitat amb paperassa: l'informe diu que està apedaçada i el servei continua exposat.
$ grep -E 'openssl|libssl' /var/log/apt/history.log | tail -2
Start-Date: 2026-08-18 06:12:03
Upgrade: libssl3t64:amd64 (3.0.13-0ubuntu3.4, 3.0.13-0ubuntu3.5), openssl:amd64 (3.0.13-0ubuntu3.4, 3.0.13-0ubuntu3.5)
# La prova directa: processos amb la biblioteca ANTIGA (marcada DEL: esborrada
# del disc pero encara mapejada a memoria)
$ sudo lsof 2>/dev/null | grep -E 'DEL.*libssl'
postgres 1105 postgres DEL REG 253,0 /usr/lib/x86_64-linux-gnu/libssl.so.3
sshd 894 root DEL REG 253,0 /usr/lib/x86_64-linux-gnu/libssl.so.3
$ sudo apt install needrestart
$ sudo needrestart -r l
Scanning processes...
Scanning candidates...
Scanning linux images...
Services to be restarted:
systemctl restart [email protected]
systemctl restart ssh.service
Service restarts being deferred:
(none)
No containers need to be restarted.
No user sessions are running outdated binaries.
No VM guests are running outdated hypervisor (qemu) binaries on this host.Dos serveis. L'ordre importa, i per dues raons diferents:
# 1. La base de dades primer, i amb l aplicacio preparada per reconnectar.
# Reiniciar postgresql talla les connexions obertes de tramontana.service.
$ sudo systemctl restart [email protected]
$ sudo systemctl restart tramontana.service
$ ~/scripts/revisio_salut.sh; echo "estat: $?"
estat: 0
# 2. SSH al final, amb RELOAD i no restart, i amb la segona sessio oberta.
# reload no talla les sessions existents; restart tampoc no hauria de
# fer-ho, pero reload es l operacio correcta i no hi ha rao per arriscar-se.
$ sudo sshd -t && sudo systemctl reload sshUn matís important sobre ssh: reload recarrega la configuració, però no substitueix la biblioteca carregada al procés mestre. Per a això cal reiniciar el dimoni — que no talla les sessions ja establertes, perquè cadascuna viu en un procés fill propi. Amb la segona sessió oberta com a xarxa de seguretat:
$ sudo systemctl restart ssh
$ sudo systemctl is-active ssh
active
# Verificar des de la segona sessio que es pot obrir una tercera ABANS de tancar resI la verificació de tancament:
$ sudo lsof 2>/dev/null | grep -c -E 'DEL.*libssl'
0
$ sudo needrestart -r l
No services need to be restarted.
$ sudo ss -tulpn | grep -E ':22|:5432|:8080'
tcp LISTEN 0.0.0.0:22 users:(("sshd",pid=8841,fd=3))
tcp LISTEN 10.0.2.15:5432 users:(("postgres",pid=8902,fd=5))
tcp LISTEN 127.0.0.1:8080 users:(("tramontana",pid=8977,fd=6))Aquell 0 és el tancament. El segon incident queda tancat, i amb ell els tres.
Perquè no torni a passar, needrestart es configura en mode llista i s'integra a la revisió:
# /etc/needrestart/needrestart.conf
$nrconf{restart} = 'l'; # nomes llistar, mai reiniciar sol
$nrconf{kernelhints} = 1; # avisar si el nucli en us no es l instal·lat# Afegir a revisio_salut.sh: pendents de reinici despres d actualitzar
$ sudo needrestart -r l -p >/dev/null; echo "codi needrestart: $?"
codi needrestart: 0La decisió de no reiniciar automàticament és deliberada i coherent amb l'unattended-upgrades de 05-03: un reinici no supervisat de la base de dades a les 6 del matí és una interrupció de servei que la Marta ha de conèixer, no un efecte secundari.
Enfortiment de systemd revisitat
A 05-05 vas enfortir tramontana.service. Ara es mesura i es millora:
$ systemd-analyze security tramontana.service | tail -12
→ Overall exposure level for tramontana.service: 3.4 MEDIUM 🙂3.4 MEDIUM per a un servei de xarxa no està malament, i hi ha marge. Les directives que falten, cadascuna amb la seva raó:
# /etc/systemd/system/tramontana.service.d/enfortiment.conf
[Service]
# El servei no necessita ajustar el nucli ni carregar moduls.
ProtectKernelTunables=yes
ProtectKernelModules=yes
ProtectKernelLogs=yes
# No ha de manipular la jerarquia de cgroups.
ProtectControlGroups=yes
# No ha de crear fitxers SUID/SGID: tanca una via classica de persistencia.
RestrictSUIDSGID=yes
# Impedeix canviar la "personalitat" del proces per emular una altra arquitectura.
LockPersonality=yes
# Nomes crides al sistema propies d un servei normal. La resta es rebutja
# amb EPERM al nucli, abans d arribar al codi del proces.
SystemCallFilter=@system-service
SystemCallFilter=~@privileged @resources @obsolete
SystemCallArchitectures=native
# Nomes les families de sockets que necessita.
RestrictAddressFamilies=AF_INET AF_INET6 AF_UNIX
RestrictNamespaces=yes
RestrictRealtime=yes
# Sense acces a dispositius ni a /home
PrivateDevices=yes
ProtectHome=yes
ProtectProc=invisible
ProcSubset=pid
# ATENCIO: MemoryDenyWriteExecute trenca els temps d execucio amb JIT
# (Java, Node.js, .NET, alguns Python). Nomes si l aplicacio es nativa.
MemoryDenyWriteExecute=yesSystemCallFilter mereix explicació perquè és la directiva de més valor: instal·la un filtre seccomp al nucli que rebutja les crides al sistema fora del conjunt permès. Un exploit que aconsegueixi execució de codi dins del procés es troba que mount, ptrace, init_module o setuid fallen amb EPERM — no perquè faltin permisos d'usuari, sinó perquè el nucli no les accepta d'aquest procés. És contenció real, no configuració cosmètica.
I l'avís de MemoryDenyWriteExecute és el tipus de detall que separa una guia útil d'una llista copiada: impedeix que una pàgina de memòria sigui escrivible i executable alhora, cosa que bloqueja moltes tècniques d'explotació, i trenca qualsevol motor amb compilació en temps d'execució. Si l'aplicació de Tramontana fos Java o Node, aquesta línia la deixaria sense arrencar.
$ sudo systemctl daemon-reload && sudo systemctl restart tramontana.service
# Verificar que el servei CONTINUA FUNCIONANT: enfortir i trencar no es enfortir
$ ~/scripts/revisio_salut.sh; echo "estat: $?"
estat: 0
$ curl -s -o /dev/null -w '%{http_code}\n' http://127.0.0.1:8080/cases
200
$ systemd-analyze security tramontana.service | tail -3
→ Overall exposure level for tramontana.service: 1.6 OK 🙂De 3.4 MEDIUM a 1.6 OK, amb el servei funcionant i verificat. L'ordre —aplicar, verificar, mesurar— és el mateix que a la rotació de credencials i al diagnòstic de rendiment. Aplicar sense verificar és com es descobreix a les 3 de la matinada que l'enfortiment va tombar el servei.
Còpies com a últim control davant del ransomware
Quan tot l'anterior falla, queden les còpies. I davant del ransomware hi ha un detall que sol passar desapercebut: una còpia muntada permanentment és abastable pel ransomware, perquè el procés que xifra els fitxers del sistema xifra també els del volum muntat. Una còpia xifrada per l'atacant no és una còpia.
Les tres propietats que fan que una còpia resisteixi:
| Propietat | Com s'aconsegueix | Estat a Tramontana |
|---|---|---|
| Fora de línia o immutable | Mitjà desconnectat, o emmagatzematge amb retenció forçada | Pendent per a la destinació externa del 3-2-1 |
| Credencial separada | La contrasenya del dipòsit no és accessible des del servidor compromès | Parcial: és a pass, però el servidor la pot llegir |
| Restauració provada | Prova periòdica documentada | Fet: els tres escenaris de 05-08 |
La millora concreta que se'n dedueix: restic admet el mode append-only a la destinació remota, en què el client pot escriure còpies noves però no esborrar les existents. Amb això, un atacant amb accés al servidor no pot destruir l'històric. És la mesura que cal demanar al proveïdor de l'emmagatzematge extern, i queda anotada a la llista de comprovació.
Llista de comprovació d'enfortiment i manteniment de la postura
Aquesta és la taula que s'entrega, alineada amb l'estructura dels CIS Benchmarks i amb el que has fet realment al curs. Les tres columnes són deliberades: sense la d'evidència, una llista de comprovació és una declaració d'intencions.
| # | Control | Estat | Evidència |
|---|---|---|---|
| 1 | Sistema de fitxers: noexec,nosuid,nodev a /tmp, /var/tmp, /dev/shm |
Fet | findmnt -o TARGET,OPTIONS |
| 2 | Xifratge en repòs del volum de còpies | Fet | cryptsetup luksDump, /etc/crypttab |
| 3 | Dipòsits signats; només actualitzacions oficials | Fet | /etc/apt/sources.list.d/*.sources, /etc/apt/keyrings/ |
| 4 | Actualitzacions de seguretat automàtiques | Fet | unattended-upgrades, només -security |
| 5 | Serveis pendents de reinici després d'actualitzar | Fet | needrestart -r l sense pendents; incident tancat |
| 6 | Superfície d'escolta mínima | Fet | ss -tulpn: 22, 5432 intern, 8080 a localhost |
| 7 | Serveis innecessaris deshabilitats i emmascarats | Fet | ModemManager emmascarat i purgat |
| 8 | Sense entorn gràfic | Fet | Instal·lació d'Ubuntu Server |
| 9 | Tallafocs amb política de denegació per defecte | Fet | ufw status verbose; conjunt de regles nftables documentat |
| 10 | sysctl de xarxa enfortits |
Fet | /etc/sysctl.d/ (06-03) |
| 11 | sysctl de nucli enfortits |
Fet | /etc/sysctl.d/60-enfortiment.conf, comentat |
| 12 | SSH: sense root, sense contrasenya, només claus | Fet | sshd -T -C user=...; excepció de Match eliminada |
| 13 | Bloqueig per intents fallits (IP i compte) | Fet | fail2ban-client status sshd, faillock --user |
| 14 | Política de contrasenyes i caducitat | Fet | pwquality.conf, login.defs, chage -l |
| 15 | Un únic UID 0; sense comptes sense contrasenya | Fet | Les tres ordres d'auditoria d'una línia |
| 16 | Mínim privilegi a sudo |
Fet | /etc/sudoers.d/tramontana amb Cmnd_Alias |
| 17 | MAC: perfil d'AppArmor en enforce |
Fet | aa-status, perfil de tramontana |
| 18 | Servei amb enfortiment de systemd | Fet | systemd-analyze security: 1.6 OK |
| 19 | Secrets fora dels fitxers de configuració | Fet | LoadCredentialEncrypted, pass |
| 20 | TLS: material emès, verificat i amb renovació provada | Fet | certbot renew --dry-run, revisar_certificat.sh |
| 21 | Registre persistent amb rotació | Fet | /var/log/journal, logrotate.d/tramontana |
| 22 | Integritat de fitxers vigilada, base de dades fora | Fet | tramontana-integritat.timer, suma al portàtil |
| 23 | Auditoria d'accessos a configuració i identitats | Fet | auditctl -l, claus tramontana_conf i privilegis |
| 24 | Còpies 3-2-1, xifrades, amb restauració provada | Fet | restic snapshots, comprovar_copia.sh, manual d'operació |
| 25 | Xifratge en trànsit en vigor | Pendent (08-01) | Certificat a punt; falta el servidor intermediari invers. El 8080 no està exposat |
| 26 | Còpia externa en mode append-only |
Pendent | Requereix decisió de proveïdor; mitiga el ransomware |
| 27 | authorized_keys2 sense explicar |
Obert | Procediment de resposta a incidents de 06-04 en curs |
| 28 | Registres centralitzats fora del servidor | Assumit | Cost actual; primera inversió quan hi hagi pressupost |
| 29 | NIDS | Assumit | Aporta poc amb un sol servidor (raonat a 06-04) |
| 30 | Antivirus | Assumit | Servidor sense fitxers de tercers; consumeix memòria acotada |
| 31 | Segon factor a SSH | Assumit | Accés ja per clau; es revisa si creix l'equip |
| 32 | Seguretat de l'aplicació (injecció, lògica) | Fora d'abast | Responsabilitat de desenvolupament |
| 33 | Seguretat física i de l'hipervisor | Fora d'abast | Responsabilitat del proveïdor |
| 34 | Auditoria formal de seguretat | Fora d'abast | Requereix un tercer independent |
Les files 25 a 27 són les importants de debò, perquè són les que un informe honest no amaga. I les files 32 a 34 delimiten el que un administrador no pot resoldre, que és informació que la direcció necessita tenir.
Mantenir la postura
Un sistema enfortit es degrada sol: cada canvi, cada paquet nou, cada regla «temporal» l'allunya de l'estat que vas documentar. Quatre pràctiques el sostenen:
- Revisió periòdica amb mètriques comparables. Lynis mensual, amb el seu índex d'enfortiment com a sèrie temporal al costat de la línia base de rendiment de 05-07:
Del 68 de 06-04 al 82 actual. El rellevant no és el número: és que si baixa, hi ha una explicació per buscar.$ sudo lynis audit system --quiet | grep 'Hardening index' Hardening index : 82 [################ ] - Gestió del canvi. Tota modificació de configuració amb la seva còpia prèvia, el seu
diff -u, la seva verificació i la seva anotació. És la convenció del curs, i és el control contra l'actor més probable del model d'amenaces. - Documentació viva al manual d'operació, fora del servidor: el model d'amenaces, aquesta llista de comprovació amb dates, l'inventari de secrets amb la seva última rotació, i el procediment de resposta a incidents.
- Revisió del mateix model d'amenaces quan canviï la realitat: un servidor més, un servei nou, una dada nova que es comença a tractar. Un model d'amenaces de fa dos anys descriu un sistema que ja no existeix.
Advertiment de compliment
srv-tramontana tracta dades personals d'hostes. El RGPD exigeix mesures tècniques i organitzatives apropiades al risc (art. 32), i diverses de les decisions d'aquesta lliçó —la retenció de registres, l'abast de l'auditoria sobre l'activitat de persones treballadores, el termini de desplegament del xifratge en trànsit de la fila 25— tenen implicacions legals que no correspon decidir a un administrador. En un entorn real:
- El responsable de seguretat ha de revisar el model d'amenaces i la llista de comprovació, i validar els controls assumits.
- El responsable de protecció de dades ha de validar el tractament, els terminis de retenció i l'avaluació del risc residual.
- I aquesta llista de comprovació no substitueix una auditoria formal. És una autoavaluació honesta, feta per qui administra el sistema, i per definició té el punt cec de qui l'ha construït.
Errors Comuns i Consells
- Enfortir sense model d'amenaces. És la causa de la meitat dels sistemes fràgils: mesures copiades que trenquen coses i no protegeixen contra res que fos probable. Mitja pàgina de model canvia totes les decisions que vénen després.
- Tocar PAM sense una sessió de root oberta. Un error de sintaxi a
common-authpot impedir tot inici de sessió, inclosa la consola. Sessió de root en un altre terminal, còpia prèvia, i prova en un tercer. - Oblidar
pam_faillock.so authsucc. Sense aquella línia el comptador de fallades no es reinicia mai, i el bloqueig arriba dies després sense causa aparent. - Oblidar
enforce_for_root = 1apwquality.conf: el compte més important queda exempt de la política de contrasenyes. - Posar un perfil d'AppArmor directament en
enforce. Trenca el servei i t'obliga a descobrir els accessos que falten sota pressió. Semprecomplainprimer, exercitar de debò, i desprésenforce. - Aplicar
noexeca/tmpsense comprovar-ho. Hi ha instal·ladors que extreuen i executen allà. Prova ambmount -o remounti exercitaapti els teus scripts abans de tocarfstab. - Editar
fstabsensemount -a. Unfstabtrencat impedeix arrencar el servidor. És la xarxa de seguretat de 05-04 i continua sent obligatòria. - Confondre «paquet actualitzat» amb «vulnerabilitat corregida». Mentre el procés tingui la biblioteca vella a memòria, continua exposat.
needrestart -r ldesprés de cada actualització, ilsof | grep DELcom a confirmació. - Enfortir i no verificar.
systemd-analyze securitymillora la puntuació encara que hagis deixat el servei sense arrencar. Aplicar, verificar ambrevisio_salut.sh, i després mesurar. MemoryDenyWriteExecuteen una aplicació amb JIT. Java, Node i .NET no arrenquen. Llegeix què fa cada directiva abans de copiar-la.- Una llista de comprovació sense columna d'evidència. «Tallafocs configurat» no diu res;
ufw status verbosesí. Sense evidència, la llista de comprovació és una declaració d'intencions. - Consell de mètode. Converteix les auditories d'aquesta lliçó en
auditar_enfortiment.shfent servirlib/comuns.sh, amb un temporitzador mensual i el principi de silenci si tot va bé. El que depèn de la teva memòria no és un control.
Exercicis
Exercici 1
Després d'aplicar el drop-in d'enfortiment, tramontana.service deixa d'arrencar:
$ sudo systemctl status tramontana.service --no-pager | head -6
● tramontana.service - Tramontana Reserves
Active: failed (Result: exit-code) since Tue 2026-08-18 16:04:11 CEST
Process: 9214 ExecStart=/opt/tramontana/app/tramontana ... (code=killed, signal=SYS)Descriu el procediment de diagnòstic, identifica la causa més probable a partir de la informació del missatge, i explica com acotaries quina directiva concreta el provoca sense treure-les totes de cop.
Exercici 2
Escriu el perfil d'AppArmor per a l'embolcall /home/operador/bin/desplegar-segur, que s'executa via sudo i crida desplegar.sh. Raona què ha de permetre i —més important— què ha de denegar, i explica quin atac concret conté aquell perfil que ni el DAC ni la regla de sudoers no contenen.
Exercici 3
La Marta et demana un informe d'una pàgina per a la direcció: «estem segurs?». Redacta'l recolzant-te en el model d'amenaces i en la llista de comprovació, sense tecnicismes innecessaris, dient amb honestedat què està protegit, què no i quines decisions necessiten pressupost o autorització.
Solucions
Solució 1
La pista és al mateix missatge: code=killed, signal=SYS. SIGSYS és el senyal que envia el nucli quan un filtre seccomp rebutja una crida al sistema. Això apunta directament a SystemCallFilter, i no a les altres directives (que produirien típicament EACCES, EPERM o una fallada de ruta).
Procediment, del més informatiu al més costós:
# 1. Confirmar la hipotesi i veure QUINA crida es va rebutjar
$ sudo journalctl -u tramontana.service -n 30 --no-pager | grep -iE 'seccomp|SYS|syscall'
audit: type=1326 audit(1755530651.882:88): auid=4294967295 uid=997 pid=9214
comm="tramontana" exe="/opt/tramontana/releases/3.2.1/tramontana"
syscall=318 compat=0 ip=0x7f2a1c4b3e2a code=0x80000000
# 2. Traduir el numero de crida al seu nom
$ ausyscall 318
getrandomgetrandom és la crida per obtenir aleatorietat del nucli — l'aplicació la fa servir per a TLS i per a identificadors de sessió. És a @system-service, però la vaig excloure en afegir SystemCallFilter=~@privileged @resources @obsolete, i getrandom pertany al conjunt @resources en algunes versions de systemd. La segona línia, que semblava una millora, és la que trenca el servei.
Com acotar sense treure-ho tot, que és la part de mètode de l'exercici. La clau és que els drop-ins es poden apilar i anul·lar per separat:
# a) Un drop-in temporal que nomes neutralitza la directiva sospitosa.
# La linia buida REINICIA la llista acumulada; despres es posa nomes el minim.
$ sudo mkdir -p /etc/systemd/system/tramontana.service.d
$ sudo tee /etc/systemd/system/tramontana.service.d/99-diagnostic.conf >/dev/null <<'EOF'
[Service]
SystemCallFilter=
SystemCallFilter=@system-service
EOF
$ sudo systemctl daemon-reload && sudo systemctl restart tramontana.service
$ ~/scripts/revisio_salut.sh; echo "estat: $?"
estat: 0Arrenca. Amb això queda confirmat que el problema era l'exclusió, no el conjunt base. Ara s'afina en lloc de renunciar al filtre:
# b) Recuperar la restriccio util, tornant nomes el necessari
$ sudo tee /etc/systemd/system/tramontana.service.d/99-diagnostic.conf >/dev/null <<'EOF'
[Service]
SystemCallFilter=
SystemCallFilter=@system-service
SystemCallFilter=~@privileged @obsolete
SystemCallFilter=getrandom
EOF
$ sudo systemctl daemon-reload && sudo systemctl restart tramontana.service
$ ~/scripts/revisio_salut.sh && systemd-analyze security tramontana.service | tail -1
→ Overall exposure level for tramontana.service: 1.7 OK 🙂Es manté ~@privileged @obsolete —que és on hi ha gairebé tot el valor defensiu—, s'abandona @resources, i s'afegeix getrandom explícitament. Exposició 1.7 en lloc d'1.6, amb el servei funcionant: un enfortiment pitjor sobre el paper i infinitament millor a la pràctica, perquè el d'1.6 no arrencava.
Mètode general aplicable a qualsevol fallada d'enfortiment:
- Llegir el senyal o el codi d'error:
SIGSYS→ seccomp;EPERMen un fitxer →ProtectSystem/ReadWritePaths;EACCESambapparmor="DENIED"→ AppArmor; fallada en mapejar memòria →MemoryDenyWriteExecute. - Buscar l'evidència concreta al journal (
syscall=,denied_mask=, la ruta). - Neutralitzar una directiva amb un drop-in de prefix numèric més gran, mai editant l'original.
- Verificar que arrenca, afinar cap al mínim necessari, i tornar a mesurar.
- Consolidar el drop-in amb un nom definitiu i deixar escrit el motiu de l'excepció — igual que l'
unprivileged_userns_clonecomentat delsysctl.
Solució 2
# /etc/apparmor.d/home.operador.bin.desplegar-segur
abi <abi/4.0>,
include <tunables/global>
/home/operador/bin/desplegar-segur {
include <abstractions/base>
include <abstractions/bash>
# L embolcall i l script que invoca
/home/operador/bin/desplegar-segur r,
/home/operador/scripts/desplegar.sh rix,
/home/operador/scripts/lib/comuns.sh r,
/usr/bin/bash rix,
# Utilitats concretes que l script necessita, ENUMERADES
/usr/bin/{tar,curl,ln,sha256sum,systemctl,flock,mktemp,rm,mv,date,logger} rix,
# Origen del paquet de release: nomes lectura
/srv/tramontana/backups/enviaments/*.tar.gz r,
/srv/tramontana/backups/enviaments/*.sha256 r,
# Desti del desplegament: escriptura acotada a releases i a l enllac
/opt/tramontana/releases/ rw,
/opt/tramontana/releases/** rw,
/opt/tramontana/app rw,
/opt/tramontana/HISTORIAL rw,
# Registre i bloqueig
/var/log/tramontana/desplegament.log rw,
/var/lock/tramontana-desplegament.lock rwk,
/tmp/ r,
/tmp/** rw,
# Consultar l estat del servei despres del desplegament
/run/systemd/private rw,
# DENEGAT de forma explicita, perque hi consti i quedi registrat:
deny /etc/shadow rwklx,
deny /etc/tramontana/secrets/** rwklx,
deny /srv/tramontana/backups/restic/** rwklx,
deny /home/operador/.ssh/** rwklx,
deny /home/operador/.password-store/** rwklx,
deny /root/** rwklx,
deny /usr/bin/{nc,ncat,socat,ssh,scp,python3,perl} x,
}Què permet: llegir el paquet de release, escriure a releases/, canviar l'enllaç app, escriure el seu registre i el seu bloqueig, i parlar amb systemd per reiniciar el servei. Res més.
Què denega, i quin atac conté cada denegació:
| Denegació | Atac que conté |
|---|---|
/etc/tramontana/secrets/** |
El procés corre com a root via sudo, així que el DAC li permetria llegir la credencial xifrada. AppArmor no |
/srv/tramontana/backups/restic/** |
Un desplegament no té cap raó per tocar les còpies. Conté l'esborrat de còpies per ransomware |
/home/operador/.password-store/** |
Impedeix que l'embolcall accedeixi a la font de veritat dels secrets |
/home/operador/.ssh/** |
Bloqueja afegir una clau autoritzada — el mecanisme de persistència que AIDE va detectar a 06-04 |
x sobre nc, socat, python3, perl |
Bloqueja la shell inversa, que és el primer pas després d'aconseguir execució amb privilegis |
I ara el fons de l'exercici: què conté aquest perfil que el DAC i sudoers no contenen.
La regla de sudoers permet a operador executar desplegar-segur com a root. A partir d'aquell moment, el DAC no ofereix cap protecció: root pot llegir /etc/shadow, pot llegir la credencial de la base de dades, pot esborrar les còpies, es pot afegir una clau SSH i pot llançar nc per obrir una shell cap a l'exterior. L'embolcall es va escriure precisament per evitar donar root complet, però la contenció depèn per complet que el contingut de l'script sigui correcte i continuï sent-ho.
Aquí hi ha el forat. Si desplegar.sh té una fallada —una variable sense cometes que permeti injectar una ordre, un tar que extregui un fitxer amb ../ a la seva ruta— l'atacant executa codi com a root a través d'una via autoritzada. Ni sudo ni els permisos Unix no l'aturen: l'execució és legítima des del seu punt de vista.
El perfil d'AppArmor és la capa que sí que l'atura, perquè el control no depèn de l'usuari sinó del programa: sigui quin sigui el codi que s'acabi executant dins d'aquest perfil, no podrà llegir els secrets, no podrà tocar les còpies, no podrà escriure a authorized_keys i no podrà llançar un intèrpret per sortir-ne. El dany queda acotat al desplegament, que és exactament el que es pretenia quan es va escriure l'embolcall i el que fins ara era només una intenció.
Això és defensa en profunditat en la seva forma més concreta: el sudoers limita qui i quina ordre; AppArmor limita què pot fer aquella ordre, fins i tot quan l'ordre es comporta malament.
Solució 3
Informe de seguretat —
srv-tramontana/ Tramontana Reserves Per a: Direcció · De: Operacions de sistemes · 18 d'agost de 2026En una frase. El servidor està raonablement protegit davant de les amenaces que de debò l'afecten, amb tres qüestions pendents que detallo al final, una de les quals requereix una decisió de direcció.
Què protegim, per ordre d'importància. Primer, les dades personals dels nostres hostes: noms, dates d'estada i imports. La seva filtració seria irreversible, danyaria tercers i té conseqüències legals. Segon, la disponibilitat del servei de reserves, el cost del qual és econòmic i acotat en el temps.
De qui ens protegim, i què hem fet.
- Atacs automatitzats des d'Internet, que són constants. El servidor només accepta connexions per dues portes: la d'administració —que ja no admet contrasenyes, només claus criptogràfiques— i la del lloc web. Tota la resta està tancada per defecte, i qui insisteix a provar contrasenyes queda bloquejat automàticament. En l'últim mes s'han bloquejat 47 intents des d'una mateixa procedència.
- Robatori o filtració d'una contrasenya. Les credencials del sistema ja no estan escrites en cap fitxer llegible: estan xifrades i només el programa que les necessita les pot fer servir. Tenim a més un procediment escrit per canviar-les sense interrompre el servei.
- Els nostres propis errors, que estadísticament són el risc més probable. Tota modificació es fa amb còpia prèvia, es comprova abans d'aplicar-se i queda registrada. Un sistema independent vigila cada dia que cap fitxer crític no hagi canviat sense explicació.
- Ús indegut d'accessos legítims. Cada persona té només els permisos que la seva feina requereix, i els accessos a la configuració queden registrats.
Què fem si alguna cosa falla. Tenim còpies de seguretat xifrades, amb la restauració provada —no només configurada—, capaces de recuperar el servei en 8 hores perdent com a màxim 4 hores de reserves, terminis que es van acordar amb Operacions. I un procediment escrit de resposta a incidents, guardat fora del servidor.
Les tres qüestions pendents.
- El trànsit web encara no viatja xifrat. El certificat està emès i verificat, però falta desplegar el component que el presentarà, previst a la fase següent. Mentrestant el servei no és accessible des d'Internet, així que l'exposició es limita a la nostra xarxa interna. No requereix decisió: està planificat.
- Una troballa sense explicar. La nostra vigilància va detectar un fitxer de configuració d'accés que ningú no va crear. Està en investigació amb el procediment previst. N'informaré del resultat; si es confirmés accés a dades personals, hi ha obligació legal de notificar-ho en 72 hores.
- Còpia externa protegida contra esborrat. Avui, qui tingués control del servidor podria destruir les còpies. Existeix una modalitat d'emmagatzematge que no permet esborrar el que ja està guardat, i és la millor defensa disponible davant d'un atac de segrest de dades. Requereix decisió de direcció: implica contractar emmagatzematge extern.
Què no està a les nostres mans. Tres coses queden fora del que l'administració de sistemes pot resoldre, i convé que hi constin: la seguretat interna de la mateixa aplicació, que correspon a desenvolupament; la seguretat física i del proveïdor d'infraestructura; i un atacant amb recursos molt superiors als d'una empresa de la nostra mida.
I un advertiment necessari. Aquest informe és una autoavaluació feta per qui administra el sistema, i per tant té el punt cec de qui l'ha construït. No substitueix una auditoria independent. Recomano, a més, que el tractament de dades d'hostes i els terminis de conservació de registres els revisi el responsable de protecció de dades.
En resum: la feina està feta, mesurada i documentada. L'única decisió que necessito de la direcció és la del punt 3.
Conclusió
srv-tramontana té ara una postura de seguretat, i això és qualitativament diferent de tenir mesures de seguretat. Hi ha un model d'amenaces escrit que diu què protegim, de qui i què assumim fora d'abast, i cada decisió d'aquesta lliçó s'hi justifica. La superfície d'atac s'ha reduït: l'aplicació escolta només en local, el que no es fa servir està emmascarat i purgat. L'autenticació passa per PAM amb política de contrasenyes, bloqueig per compte i límits de recursos —i vas localitzar el Match User luis que portava setmanes contradient la teva pròpia configuració—. AppArmor conté l'aplicació en enforce, amb un perfil que impedeix llançar una shell o llegir les còpies encara que el procés es vegi compromès. Els sysctl d'enfortiment estan aplicats i comentats, inclosa la línia que vas deixar desactivada amb la raó escrita al costat. /tmp està sense permís d'execució, comprovat abans de fer-ho persistent. I systemd-analyze security ha passat de 3.4 a 1.6 amb el servei verificat i funcionant, que és l'única millora que compta.
Els tres incidents estan tancats: els 47 intents des de 203.0.113.44 amb fail2ban, la db_password en clar amb systemd-creds i pass, i avui la libssl vulnerable que continuava carregada a memòria — perquè un paquet actualitzat no és una vulnerabilitat corregida fins que els processos ho saben. Queda una llista de comprovació de 34 files amb la seva columna d'evidència, tres qüestions obertes dites sense adorns i un informe per a la direcció que no promet el que no pot complir. L'índex de Lynis ha pujat de 68 a 82, i l'important d'aquell número no és el seu valor sinó que ara és una sèrie temporal que algú vigila.
I amb això es tanca el Mòdul 6: vas configurar la xarxa de manera persistent amb la reversió automàtica de netplan try; vas enfortir SSH amb claus ed25519, sense root i sense contrasenyes, i vas aprendre a llegir sshd -T -C per saber quina configuració s'aplica realment a algú; vas aixecar un tallafocs de llista blanca sobre nftables i vas saber l'ordre en què s'aplica per no quedar-te fora; vas muntar detecció d'intrusions amb AIDE i auditd, amb la base de dades protegida fora del servidor, i un procediment de resposta a incidents amb la seva regla incòmoda; vas treure els secrets dels fitxers de configuració i vas preparar el material criptogràfic de TLS amb renovació provada i vigilància de la caducitat; i avui has reunit tot això en una postura coherent i documentada.
Fins aquí has operat un servidor, i l'has operat a mà. Saps fer-lo servir, administrar-lo i defensar-lo, però hi ha dues coses que encara no saps fer: mirar-hi dins i multiplicar-lo. Al Mòdul 7: Temes Avançats fas totes dues. Veuràs el procés d'arrencada complet —de la UEFI a GRUB, a l'initramfs i a systemd— i aprendràs a recuperar un sistema que no arrenca, que és l'habilitat que separa qui reinstal·la de qui arregla. Aprendràs diagnòstic avançat amb strace, perf i eBPF per respondre a «què està fent exactament aquest procés?» quan els comptadors no basten (i descobriràs que el ptrace_scope que acabes de posar és part de l'enunciat del problema). Ajustaràs el nucli amb sysctl, ara sí per rendiment. I després comença la multiplicació: virtualització amb KVM i libvirt, contenidors amb Docker i per què són processos aïllats i no màquines petites, automatització amb Ansible —que convertirà tota la feina manual dels mòduls 5 i 6 en codi versionat i reproduïble, i les 8 hores d'RTO en bastant menys—, i per fi alta disponibilitat i balanceig de càrrega, on srv-tramontana deixa de ser un únic punt de fallada. Actualitza la instantània de la teva VM, guarda el manual d'operació fora de la màquina, i ens veiem al Mòdul 7.
Curs de Linux: De Principiant a Administrador de Sistemes
Mòdul 1: Introducció a Linux
- Què és Linux?
- Història de Linux
- Distribucions de Linux
- Instal·lant Linux
- Primer Contacte amb el Sistema
- Estructura del Sistema de Fitxers de Linux
Mòdul 2: Comandes Bàsiques de Linux
- Introducció a la Línia de Comandes
- Obtenir Ajuda i Documentació del Sistema
- Navegant pel Sistema de Fitxers
- Operacions amb Fitxers i Directoris
- Visualització i Edició de Fitxers
- Enllaços Durs i Simbòlics
- Permisos i Propietat dels Fitxers
Mòdul 3: Habilitats Avançades en la Línia de Comandes
- L'Entorn del Shell: Variables, Àlies i Historial
- Ús de Comodins i Expressions Regulars
- Cerca de Fitxers i Contingut: find, locate i grep
- Canonades i Redirecció
- Processament de Text: cut, sort, uniq, sed i awk
- Gestió de Processos
- Programació de Tasques amb Cron
- Comandes de Xarxa
Mòdul 4: Scripting en Shell
- Introducció al Scripting en Shell
- Variables i Tipus de Dades
- Entrada, Sortida i Arguments d'un Script
- Estructures de Control
- Funcions i Biblioteques
- Depuració i Gestió d'Errors
- Scripts de Producció: Bones Pràctiques
Mòdul 5: Administració del Sistema
- Gestió d'Usuaris i Grups
- sudo i Permisos Especials
- Gestió de Paquets
- Gestió de Discs
- systemd i la Gestió de Serveis
- Registres del Sistema: journald i syslog
- Monitoratge del Sistema i Optimització del Rendiment
- Còpies de Seguretat i Restauració
Mòdul 6: Xarxes i Seguretat
- Configuració de Xarxes
- SSH i Accés Remot
- Tallafocs i Seguretat Perimetral
- Sistemes de Detecció d'Intrusions
- Gestió de Secrets i Certificats TLS
- Assegurant Sistemes Linux
Mòdul 7: Temes Avançats
- El Procés d'Arrencada i la Recuperació del Sistema
- Diagnòstic Avançat: strace, perf i eBPF
- Optimització del Nucli de Linux
- Virtualització amb Linux
- Contenidors de Linux i Docker
- Automatització amb Ansible
- Alta Disponibilitat i Balanceig de Càrrega
