+39 3662317539/+34 620899163
info@simonecosci.com

sshd_config: le righe che contano davvero su un VPS esposto

Sviluppo Siti Web a Tenerife

sshd_config: le righe che contano davvero su un VPS esposto

Un VPS appena creato ha la porta 22 aperta su Internet e i primi tentativi di accesso arrivano nel giro di minuti. Non è un attacco mirato: sono scanner che provano root con password da dizionario su tutto l’IPv4. Difendersi non richiede molto, ma le guide che girano in rete sono lunghe elenchi di direttive copiate da altre guide, spesso già attive di default o addirittura controproducenti.

Questo è l’elenco corto: le righe che cambiano qualcosa davvero, con i valori di default presi dal manuale di OpenSSH, così sai anche cosa non serve scrivere.

Prima di tutto: dove finisce quello che scrivi

Su Debian e Ubuntu il file /etc/ssh/sshd_config comincia con questa riga:

Include /etc/ssh/sshd_config.d/*.conf

E il manuale di sshd_config dice una cosa che va letta due volte:

Unless noted otherwise, for each keyword, the first obtained value will be used.

Il primo valore trovato vince, non l’ultimo. Siccome l’Include sta all’inizio del file, tutto quello che c’è in /etc/ssh/sshd_config.d/ ha la precedenza su quello che scrivi sotto. La documentazione Debian lo mette nero su bianco: “/etc/ssh/sshd_config.d/*.conf files are included at the start of the configuration file, so options set there will override those in /etc/ssh/sshd_config”.

È il motivo per cui, su un VPS di un provider cloud, metti PermitRootLogin no, riavvii, e root entra ancora: l’immagine di partenza ha lasciato un file in sshd_config.d/ che dice il contrario. Guarda sempre lì dentro prima di dare la colpa a qualcos’altro.

Da qui la regola operativa: o metti le tue modifiche in un file tuo dentro sshd_config.d/ (con un nome che ordini prima degli altri, visto che i glob sono espansi in ordine lessicale), oppure sai esattamente cosa contiene quella directory.

Le righe che contano

Solo chiavi, e sul serio

PubkeyAuthentication yes
PasswordAuthentication no
KbdInteractiveAuthentication no

La prima è già il default, ma scriverla non costa niente ed è documentazione per chi legge il file fra un anno.

La terza è quella che quasi tutti dimenticano. Con PAM attivo — e su Debian UsePAM yes è lo standard di pacchetto — l’autenticazione keyboard-interactive può chiedere una password esattamente come farebbe PasswordAuthentication. Il manuale è esplicito:

Because PAM keyboard-interactive authentication usually serves an equivalent role to password authentication, you should disable either PasswordAuthentication or KbdInteractiveAuthentication.

Il default upstream di KbdInteractiveAuthentication è yes. Debian lo porta a no nel proprio sshd_config, ma è una scelta di pacchetto, non una garanzia: su altre distribuzioni, o se qualcuno ha rimaneggiato il file, resta aperta. Disabilitarle entrambe esplicitamente è l’unico modo per non doverci pensare.

Se vuoi essere categorico, c’è anche:

AuthenticationMethods publickey

Questa dice che per entrare bisogna completare quel metodo, punto. Il default è any, cioè va bene un metodo qualsiasi fra quelli abilitati; se la sovrascrivi, l’accesso richiede il completamento di ogni metodo in almeno una delle liste indicate. Utile anche per imporre due fattori: publickey,keyboard-interactive significa chiave e poi secondo fattore, non l’uno o l’altro.

Niente root

PermitRootLogin no

Il default è prohibit-password: root può entrare, ma solo con chiave. Non è terribile, ma no è meglio per un motivo pratico più che crittografico: con un utente normale più sudo hai un nome nei log e nel file sudoers, e puoi togliere l’accesso a una persona sola senza cambiare niente per le altre. Con root condiviso non hai nessuna delle due cose.

Ovviamente: crea l’utente, copia la chiave, verifica che entri da un secondo terminale, e solo dopo chiudi root.

L’elenco di chi può entrare

AllowUsers simone deploy

oppure, se preferisci ragionare a gruppi:

AllowGroups ssh-users

È la direttiva che rende più difficile la vita a chi prova a caso, perché sposta il problema da “indovina la password” a “indovina anche il nome utente”. Senza AllowUsers tutti gli utenti del sistema possono tentare l’accesso, compresi quelli di servizio creati da un pacchetto che non ti aspettavi.

Due dettagli del manuale che vale la pena conoscere. Il primo: l’ordine di valutazione è DenyUsers, poi AllowUsers (e analogamente DenyGroups, poi AllowGroups). Il secondo: un pattern può avere la forma USER@HOST, e in quel caso utente e host vengono controllati separatamente. Quindi

AllowUsers deploy@203.0.113.10 simone

limita deploy a un solo indirizzo di provenienza, lasciando simone libero di collegarsi da dove capita. È un’alternativa leggera al mettere tutto dietro una VPN, quando l’IP di partenza è fisso.

Preferisci sempre AllowUsers a DenyUsers: una lista di permessi è chiusa per costruzione, una lista di divieti è aperta a tutto quello che non hai pensato di scrivere.

Quanti tentativi per connessione

MaxAuthTries 3

Il default è 6. Il manuale aggiunge un dettaglio che rende questa direttiva più interessante di quanto sembri: “Once the number of failures reaches half this value, additional failures are logged.” Con MaxAuthTries 3, i fallimenti finiscono nei log a partire dal secondo. È un modo per rendere più parlanti i log senza alzare il livello di debug.

Attenzione a un effetto collaterale che coglie tutti almeno una volta: ogni chiave offerta dal tuo agent conta come un tentativo. Il client SSH, per default, propone tutte le identità che ssh-agent conosce. Se hai sei chiavi caricate e quella giusta è l’ultima, con MaxAuthTries 3 il server chiude la connessione prima di arrivarci, e il messaggio che vedi (Too many authentication failures) non ti dice affatto qual è il problema.

La soluzione sta dal lato client, nel tuo ~/.ssh/config:

Host vps
    HostName 203.0.113.10
    User simone
    IdentityFile ~/.ssh/id_ed25519_vps
    IdentitiesOnly yes

IdentitiesOnly yes dice a ssh di usare soltanto le identità configurate, “even if ssh-agent(1) or a PKCS11Provider or SecurityKeyProvider offers more identities”. Il default è no. Con questa riga il client fa un tentativo solo, quello giusto.

Il rumore di fondo

LoginGraceTime 30
MaxStartups 10:30:60

LoginGraceTime è il tempo che il server concede prima di chiudere una connessione in cui nessuno si è ancora autenticato: il default è 120 secondi. Trenta bastano abbondantemente a un essere umano e riducono il numero di connessioni a metà strada che restano appese.

MaxStartups limita le connessioni non autenticate in parallelo. Il default è 10:30:100 e i tre numeri sono start:rate:full: raggiunte 10 connessioni non autenticate, il server comincia a rifiutarne il 30%; la probabilità cresce linearmente e a 100 le rifiuta tutte. Abbassare l’ultimo valore riduce l’impatto delle ondate di scanner, senza fare nulla contro un attacco mirato.

Spegnere quello che non usi

X11Forwarding no
AllowTcpForwarding no

X11Forwarding ha default no upstream, ma Debian lo imposta a yes nel proprio sshd_config: su un server senza interfaccia grafica è superficie di attacco regalata.

Su AllowTcpForwarding fermati a ragionare: il default è yes e serve per i tunnel. Se usi ssh -L per raggiungere il database o la dashboard di un servizio che ascolta solo su localhost, metterla a no ti taglia fuori. Accetta anche local e remote, per chiudere solo una delle due direzioni.

Quello che non serve scrivere

  • PermitEmptyPasswords no — è già il default.
  • Protocol 2 — il protocollo 1 non esiste più da anni.
  • Cambiare la porta. Sposta il servizio su 2222 e i log si svuotano, ma chi fa una scansione delle porte ti trova lo stesso. Non è un danno, non chiamarla sicurezza: è meno rumore nei log, e ha il costo di dover ricordare -p 2222 ovunque.

Non riavviare mai al buio

Prima di toccare il servizio:

sudo sshd -t

“Test mode. Only check the validity of the configuration file and sanity of the keys.” Se sbagli una direttiva, lo scopri qui invece che a servizio fermo e sessione chiusa.

Per vedere la configurazione effettiva, quella che risulta dopo tutti gli Include e con i default espliciti:

sudo sshd -T

È il comando che risolve la maggior parte dei “ma io l’avevo scritto”. Stampa il valore reale di ogni parametro. Per esempio, per verificare cosa succede a un utente specifico da un certo indirizzo si usa -C, che applica anche le regole Match:

sudo sshd -T -C user=deploy,host=example.com,addr=203.0.113.10

E infine la precauzione che vale più di tutte le direttive messe insieme: tieni aperta la sessione SSH corrente mentre riavvii il servizio, e prova a entrare da un secondo terminale. Se la nuova configurazione ti chiude fuori, la vecchia sessione è ancora lì per rimediare. Senza quella, restano la console del provider e mezz’ora di lavoro.

Il file minimo

Messo insieme, per un VPS che serve siti web e a cui accedono due o tre persone:

PermitRootLogin no
PubkeyAuthentication yes
PasswordAuthentication no
KbdInteractiveAuthentication no
AllowGroups ssh-users
MaxAuthTries 3
LoginGraceTime 30
MaxStartups 10:30:60
X11Forwarding no

Nove righe. Non è tutto quello che si può fare su un server esposto — restano il firewall, gli aggiornamenti automatici, i log da guardare ogni tanto — ma è la parte che riguarda SSH, ed è quella che i bot provano per prima.

Un’ultima nota: verifica i default della tua versione con man sshd_config sulla macchina che stai configurando, non su quella da cui leggi. Cambiano fra versioni di OpenSSH e, come si è visto, fra distribuzioni.


Fonti: sshd_config(5) e sshd(8) su man.openbsd.org, ssh_config(5) per IdentitiesOnly, e la pagina di manuale sshd_config(5) di Debian per le differenze di pacchetto.