L’elenco delle porte aperte è l’elenco delle vie che portano al suo server. Tutto il resto — firewall, WAF, rilevamento delle intrusioni — si costruisce sopra. Per questo «che cosa è in ascolto qui» viene per primo in qualsiasi verifica, ed è anche il controllo che più spesso dà un risultato spiacevole: metà di ciò che salta fuori non l’ha aperto lei, ma l’installatore di qualche pacchetto.
Leggere l’output
sudo ss -tulpn
Le opzioni: t per TCP, u per UDP, l solo ciò che è in ascolto, p il processo, n perché i numeri restino numeri. Senza sudo la colonna del processo resta vuota e l’esercizio perde senso.
Conta la colonna Local Address:Port, e la differenza lì è sostanziale:
127.0.0.1:3306: il servizio è raggiungibile solo dalla macchina stessa. Va bene;0.0.0.0:3306: da qualsiasi indirizzo IPv4, cioè da internet. È questo che va controllato;[::]:3306: lo stesso per IPv6. Una riga a parte, e la si trascura con regolarità;203.0.113.25:443: su un indirizzo preciso, di norma voluto.
Poi, per ogni riga con 0.0.0.0 o [::], si ponga una domanda: uno sconosciuto deve poter arrivare fin qui? Per 80 e 443 la risposta è sì. Per quasi tutto il resto, no.
I ritrovamenti abituali
Redis, porta 6379. La riga più pericolosa che esista. Di default Redis non richiede password, e i suoi comandi permettono di scrivere un file su disco, cioè una chiave altrui in authorized_keys. Fra la comparsa di Redis su un indirizzo pubblico e il suo sfruttamento passano ore, a volte meno. Verifichi bind 127.0.0.1 e protected-mode yes nella configurazione.
Memcached, 11211/UDP. Anche se dentro non c’è nulla di prezioso, il suo server diventa un amplificatore per attacchi altrui, e il reclamo arriverà dal provider.
MySQL e PostgreSQL, 3306 e 5432. Una password c’è, ma la si tenta di continuo, e le versioni dei database si aggiornano meno spesso di quanto si vorrebbe. Quasi mai devono essere esposti: l’applicazione vive sulla stessa macchina, e per il suo lavoro basta un tunnel SSH.
Elasticsearch 9200, MongoDB 27017. Storicamente senza autenticazione di default. Le istanze pubbliche di questi servizi sono una fonte costante di notizie sulle fughe di dati.
L’API di Docker, 2375. Una porta di comando di Docker aperta è root sull’host senza alcuna password. Compare di solito dopo esperimenti con l’accesso remoto a Docker.
Pannelli di gestione e phpMyAdmin sulle loro porte: 8080, 8083, 10000. Non che non si debbano mai aprire, ma sono proprio loro a concentrare il grosso dei tentativi.
Correggere sul servizio, non sul firewall
La voglia di chiudere ogni ritrovamento con una regola di UFW si capisce, ma quella è la seconda linea, non la prima. Una regola può essere cancellata per sbaglio, un firewall spento per un po’ durante il debug, e Docker pubblica le porte scavalcando del tutto UFW. Un’impostazione di associazione nella configurazione del servizio sopravvive a tutto questo:
- MySQL/MariaDB:
bind-address = 127.0.0.1; - PostgreSQL:
listen_addresses = 'localhost'; - Redis:
bind 127.0.0.1 ::1; - Docker Compose: pubblicazione come
"127.0.0.1:5432:5432".
Il firewall si mette sopra come assicurazione, non al posto di questo.
Trovare il processo quando non è chiaro
ss dà il nome e il pid. Poi:
sudo systemctl status <pid>
sudo lsof -i :8080
Il primo comando nomina l’unità systemd a cui appartiene il processo, e di solito basta per capire che cos’è e se serve. Un processo sconosciuto in ascolto su una porta alta e avviato fuori dalle cartelle di sistema — da /tmp o /dev/shm, per esempio — non è più una questione di configurazione ma motivo di un’indagine a sé.
La verifica dall’esterno è obbligatoria
ss risponde a «che cosa è in ascolto», non a «a che cosa si può arrivare». Fra le due cose stanno il firewall, il NAT e le regole del provider. L’unica risposta onesta viene da una scansione da un’altra macchina:
nmap -Pn -p- 203.0.113.25
nmap -Pn -p- -6 2001:db8::1
Non trascuri il secondo comando: quasi ogni VPS ha un indirizzo IPv6, le sue regole si scrivono a parte, e il servizio ascolta su entrambe le versioni del protocollo insieme.
Non è un controllo una tantum
L’elenco delle porte aperte cambia da solo. Ha installato un pacchetto e ha portato un servizio che ha aperto una porta. Ha aggiornato un pannello e ha ripristinato un valore predefinito. Ha avviato un container e ha pubblicato una porta scavalcando il firewall. Un controllo una tantum risponde per la giornata di oggi e per nient’altro.
Il valore non sta nell’elenco in sé ma nei suoi cambiamenti: una porta nuova che ieri non c’era è un segnale breve e molto eloquente. È così che conviene guardarlo: come uno snapshot con storico, e non come un output di ss ricostruito a memoria. La pagina dimostrativa qui sotto mostra esattamente questo.