Skip to main content
  1. Concetti/

Socket (Endpoint di Comunicazione)

·11 mins
Alessio Barnini
Author
Alessio Barnini
Table of Contents

Cos'è
#

Un socket è un endpoint di comunicazione che permette a due processi di scambiare dati, sia sulla stessa macchina che attraverso la rete. In Linux, un socket è un file con un proprio File Descriptor, gestito dal Kernel.

Un socket è un endpoint di comunicazione. Pensalo come una presa elettrica: da sola non fa nulla, ma quando due prese vengono collegate da un cavo, l'energia (i dati) può fluire.

In pratica, quando un servizio come SSH vuole ricevere connessioni, fa tre cose:

  • crea un socket,
  • lo "aggancia" a un indirizzo IP + porta (questo si chiama bind),
  • e poi si mette in ascolto (listen).
  • Da quel momento, quel socket diventa raggiungibile da chiunque conosca quell'indirizzo e porta. ![Pasted image 20260311191936.webp](/assets/Pasted image 20260311191936.webp)

Cos'è un Socket a Livello Kernel
#

Un socket non è un dispositivo fisico. È una struttura dati (struct socket) che il kernel crea in memoria quando un processo chiama la system call socket(). Questa struct contiene tutto il necessario per comunicare:

  struct socket (semplificata)
  ┌──────────────────────────────────────┐
  │  Protocollo:    TCP (o UDP)          │
  │  IP locale:     0.0.0.0              │
  │  Porta locale:  22                   │
  │  IP remoto:     (vuoto se LISTEN)    │
  │  Porta remota:  (vuoto se LISTEN)    │
  │  Stato:         LISTEN / ESTABLISHED │
  │  Buffer invio:  [dati in uscita]     │
  │  Buffer ricezione: [dati in arrivo]  │
  │  File Descriptor:  FD 3              │
  └──────────────────────────────────────┘

Qualsiasi processo può crearne uno: un server web, il demone SSH, nc, uno script Python, il browser (uno per ogni tab), un malware. La system call è sempre la stessa: socket(). Dopo la creazione, il kernel assegna un File Descriptor (FD) e da quel momento il processo legge e scrive sul socket come se fosse un file normale.

Come funziona
#

Il Ciclo di Vita di un Socket (Server-side)
#

  PROCESSO (es. sshd)            KERNEL               RETE
        │                           │                    │
   1. socket()  ──────────────►  Crea FD (es. FD 3)        │                           │                    │
   2. bind(FD 3, IP:22)  ────►  Aggancia a 0.0.0.0:22    │
        │                           │                    │
   3. listen(FD 3)  ──────────►  Stato: LISTEN           │
        │                           │                    │
   4. accept()  ◄─────────────  Nuova connessione!  ◄─── │ (client si connette)
        │                           │                    │
     Crea FD 4  ◄─────────────  Socket dedicato          │
        │                           │                    │
   5. read(FD 4) / write(FD 4)  Scambio dati ◄────────►  │
        │                           │                    │
   6. close(FD 4)  ───────────►  Chiude connessione      │

Punto chiave: Il socket in LISTEN (FD 3) resta aperto per accettare nuovi client. Per ogni client che si connette, il kernel crea un nuovo socket (FD 4, 5, 6...) dedicato a quella conversazione.

FD = file Descriptor

Perché "Everything is a File" conta qui
#

  File Descriptor Table (processo sshd)
  ┌─────┬──────────────────────────────────┐
  │ FD  │ Cosa punta                       │
  ├─────┼──────────────────────────────────┤
  │  0  │ stdin  (standard input)          │
  │  1  │ stdout (standard output)         │
  │  2  │ stderr (standard error)          │
  │  3  │ SOCKET TCP *:22 (LISTEN)         │  ◄── il "centralino"
  │  4  │ SOCKET TCP 192.168.1.5:22→       │  ◄── client connesso
  │     │         192.168.1.10:54321       │
  │  5  │ /var/log/auth.log (file aperto)  │
  └─────┴──────────────────────────────────┘

Leggere da un socket (FD 4) usa la stessa system call read() di leggere da un file (FD 5). Scrivere su un socket usa la stessa write(). Il kernel sa internamente che FD 4 è un socket e instrada i dati sulla rete, ma per il processo non fa differenza.

Due Famiglie di Socket
#

  ┌─────────────────────────────────────────────────────────────┐
  │                        SOCKET                               │
  │                                                             │
  │   ┌─────────────────────┐    ┌────────────────────────────┐ │
  │   │   NETWORK SOCKET    │    │   UNIX DOMAIN SOCKET       │ │
  │   │   (TCP / UDP)       │    │   (IPC locale)             │ │
  │   │                     │    │                            │ │
  │   │ Identificato da:    │    │ Identificato da:           │ │
  │   │   IP + Porta        │    │   Percorso su filesystem   │ │
  │   │                     │    │                            │ │
  │   │ Es: 0.0.0.0:22      │    │ Es: /var/run/docker.sock   │ │
  │   │     127.0.0.1:30000 │    │     /run/systemd/journal/  │ │
  │   │                     │    │                            │ │
  │   │ Passa dallo stack   │    │ Bypass stack di rete       │ │
  │   │ TCP/IP del kernel   │    │ (più veloce, solo locale)  │ │
  │   └─────────────────────┘    └────────────────────────────┘ │
  └─────────────────────────────────────────────────────────────┘

Network socket (INET): Usano IP + porta. Possono comunicare tra macchine diverse. Sono quelli che vedi con lsof -i e ss -tlnp. Esempio: SSH su porta 22, il servizio di Bandit 14 su porta 30000.

Unix domain socket (UNIX): Usano un percorso sul filesystem come indirizzo. Comunicazione solo tra processi sulla stessa macchina. Più veloci perché non attraversano lo stack di rete. Esempio: Docker usa /var/run/docker.sock per ricevere comandi dal client docker.

Presa Esterna vs Presa Interna: il confronto pratico
#

  NETWORK SOCKET (presa esterna)     UNIX DOMAIN SOCKET (presa interna)
  ─────────────────────────────────  ─────────────────────────────────
  Indirizzo: IP + Porta              Indirizzo: percorso su filesystem
  Esce dalla macchina: SÌ            Esce dalla macchina: MAI
  Visibile con: lsof -i              Visibile con: lsof -U
                ss -tlnp                            ss -xl

  Esempi:                            Esempi:
  sshd → 0.0.0.0:22                 Docker → /var/run/docker.sock
  nginx → 0.0.0.0:443               systemd → /run/systemd/journal/stdout
  nc → 127.0.0.1:30000              MySQL → /var/run/mysqld/mysqld.sock

Caso Concreto: docker.sock
#

Quando scrivi docker ps, il client Docker non apre una connessione TCP verso una porta. Scrive sul file /var/run/docker.sock, e il demone Docker legge da quel file. Tutto locale, nessun traffico di rete.

  [ docker ps ]                     [ dockerd ]
  (client CLI)                      (demone)
       │                                 │
       │── write() ──►  /var/run/docker.sock  ◄── read() ──│
       │                (Unix Domain Socket)               │
       │◄── read() ───  /var/run/docker.sock  ──► write() ─│
       │                                                   │
       ▼                                                   ▼
  Stampa la lista                              Interroga i container
  dei container                                e restituisce i dati

Perché docker.sock è un rischio Blue Team
#

Docker usa un Unix socket invece di una porta TCP perché è più veloce e non espone nulla sulla rete. Ma il socket è un file, quindi ha permessi Unix:

$ ls -la /var/run/docker.sock
srw-rw---- 1 root docker 0 Mar 11 06:00 /var/run/docker.sock
#  │            │     │
#  │            │     └─ Solo il gruppo "docker" può accedere
#  │            └─ Proprietario: root
#  └─ "s" = socket (tipo file speciale)

Se un attaccante ottiene accesso al gruppo docker o i permessi vengono allargati (es. chmod 777), può controllare Docker completamente: creare container privilegiati, montare il filesystem dell'host, e scalare a root. Verificare i permessi su /var/run/docker.sock è hardening base.

Analogia Mondo Reale: La Prolunga Elettrica
#

  CLIENT (il tuo PC)                                    SERVER (es. web server)
  ┌──────────────┐                                      ┌──────────────┐
  │ Socket       │         PROLUNGA (il cavo)           │ Socket       │
  │ 192.168.1.5  │ ═══════════════════════════════════  │ 93.184.1.10  │
  │ :54321       │  ◄── qui dentro passa il traffico ►  │ :80          │
  │ (presa |_| ) │                                      │ (presa |_| ) │
  └──────────────┘                                      └──────────────┘
     IP + Porta                                            IP + Porta

Il socket è la presa (endpoint). Il tool che usi è il cavo (prolunga). Il traffico che passa dentro può essere in chiaro o cifrato, a seconda del cavo scelto:

  CAVO (Tool)          CIFRATURA        ANALOGIA
  ────────────────────────────────────────────────────────────
  nc                   Nessuna          Prolunga senza guaina
  telnet               Nessuna          Prolunga senza guaina (legacy)
  ssh / ssh -L         AES cifrato      Prolunga dentro tubo blindato
  openssl s_client     TLS cifrato      Prolunga dentro tubo blindato

  TESTER (Tool)        COSA FA          ANALOGIA
  ────────────────────────────────────────────────────────────
  ss / netstat         Mostra socket    Multimetro: misura i cavi
  lsof -i              Mostra chi usa   Multimetro + etichette
  tcpdump              Intercetta dati  Pinza amperometrica sul cavo

Il cavo non cambia le prese. Le prese (socket) sono sempre le stesse: IP + porta da un lato, IP + porta dall'altro. Cambia solo cosa passa dentro — byte in chiaro (leggibili da tcpdump) o byte cifrati (illeggibili senza la chiave).

Analogia Mondo Reale: Prese Elettriche e Compatibilità
#

Un socket è una presa. Il tool che usi (nc, ssh, telnet) è il programma che crea la presa e stende il cavo in un colpo solo. Ma le prese devono essere compatibili per agganciarsi.

Livello 1 — La forma della presa (Protocollo: TCP vs UDP)
#

  Server SSH :2222 (TCP)            Client
  ┌─────────────────┐
  │  ○ ○ ○  TCP     │ ◄──────────  nc server 2222        ✅ TCP ↔ TCP
  │  (presa Schuko) │ ◄──────────  ssh -p 2222 server    ✅ TCP ↔ TCP
  │                 │ ◄──────────  telnet server 2222    ✅ TCP ↔ TCP
  │                 │ ◄── ✗ ────  nc -u server 2222     ❌ UDP ↔ TCP
  └─────────────────┘
                                   Presa italiana in Schuko = non entra

Se il server ascolta in TCP, il client deve creare un socket TCP. Un socket UDP (flag -u in nc) è come una spina italiana a tre poli in una presa Schuko tedesca: non si aggancia.

Livello 2 — Il voltaggio (Protocollo applicativo)
#

Anche se la presa entra (TCP ↔ TCP), il server potrebbe rifiutarti perché non parli la sua lingua:

  PRESA COMPATIBILE, MA VOLTAGGIO DIVERSO?

  nc server 2222        → Presa entra (TCP ✅)
                           Ma nc parla raw bytes, SSH aspetta
                           handshake crittografico → il server
                           manda il banner e poi chiude.
                           (utile per banner grabbing!)

  ssh -p 2222 server    → Presa entra (TCP ✅)
                           ssh parla il protocollo SSH → handshake,
                           cifratura, sessione aperta ✅

  openssl s_client      → Presa entra (TCP ✅)
                           Parla TLS → si aggancia a servizi
                           HTTPS/SSL sulla porta 443 ✅

È come attaccare un forno (230V) a una presa da lampada (230V): la spina entra, ma il circuito non regge il carico. La forma (protocollo di trasporto) è giusta, il voltaggio (protocollo applicativo) no.

Riepilogo Tool
#

  TOOL               CREA SOCKET + CAVO    CIFRATURA     PROTOCOLLO APP
  ──────────────────────────────────────────────────────────────────────
  nc                 TCP (o UDP con -u)     Nessuna       Raw bytes
  telnet             TCP                    Nessuna       Raw bytes (legacy)
  ssh / ssh -L       TCP                    AES           SSH protocol
  openssl s_client   TCP                    TLS           TLS handshake

  TOOL DI ISPEZIONE  COSA FA               ANALOGIA
  ──────────────────────────────────────────────────────────────────────
  ss / netstat       Mostra socket attivi   Multimetro
  lsof -i            Mostra chi usa cosa    Multimetro + etichette
  tcpdump            Intercetta traffico    Pinza amperometrica

Vedere i Socket con lsof
#

lsof -i mostra i network-defense socket. Ecco come interpretare l'output:

$ sudo lsof -i -P | grep LISTEN
sshd     1234  root    3u  IPv4  12345  0t0  TCP *:22 (LISTEN)
#  │       │     │     │    │      │          │    │      │
#  │       │     │     │    │      │          │    │      └─ Stato: in attesa
#  │       │     │     │    │      │          │    └─ Porta 22
#  │       │     │     │    │      │          └─ Protocollo TCP
#  │       │     │     │    │      └─ Inode del socket
#  │       │     │     │    └─ Tipo: IPv4
#  │       │     │     └─ FD 3, modalità u (read/write)
#  │       │     └─ Utente root
#  │       └─ PID del processo
#  └─ Nome del comando

Indagine pratica: chi ascolta su questa macchina?
#

# Tutti i socket TCP in stato LISTEN
sudo lsof -i TCP -P | grep LISTEN

# Output tipico su un server con SSH e un web server:
# sshd      1234 root    3u  IPv4 12345 TCP *:22 (LISTEN)
# nginx     5678 www     6u  IPv4 67890 TCP *:80 (LISTEN)
# nginx     5678 www     7u  IPv4 67891 TCP *:443 (LISTEN)

# Socket specifico: chi occupa la porta 30000?
sudo lsof -i :30000 -P

# Tutti i file aperti da un processo sospetto (socket + file normali)
sudo lsof -p 1234

Connessione attiva vs LISTEN
#

  STATO         SIGNIFICATO                        LSOF OUTPUT
  ─────────────────────────────────────────────────────────────
  LISTEN        "Sto aspettando connessioni"        TCP *:22 (LISTEN)
                Il socket è aperto ma nessuno
                è collegato.

  ESTABLISHED   "Sto parlando con qualcuno"         TCP 192.168.1.5:22->
                Dati fluiscono in entrambe                192.168.1.10:54321
                le direzioni.                       (ESTABLISHED)

  CLOSE_WAIT    "L'altro ha chiuso, io no ancora"   Possibile leak o
                                                    processo bloccato.

Socket e Servizi Linux: il flusso completo
#

  [ systemd ]
       │ legge /lib/systemd/system/ssh.service
  [ sshd ] (PID 1234)
       │ socket() → bind(0.0.0.0:22) → listen()
  [ Socket FD 3: *:22 LISTEN ]  ◄── visibile con: lsof -i :22
       │ un client si connette (nc o ssh)
  [ Socket FD 4: ESTABLISHED ] ◄── visibile con: lsof -i | grep ESTABLISHED
       │ scambio dati (read/write)
  [ journald ] registra l'evento ◄── visibile con: journalctl -u ssh

Questo è il motivo per cui lsof e journalctl sono complementari: lsof ti dice cosa sta succedendo adesso (socket aperti, connessioni attive), journalctl ti dice cosa è successo nel tempo (login riusciti/falliti, errori).

Perché è importante per Blue Team
#

  • Detection: Un socket LISTEN su una porta inaspettata (es. 4444, 8888) è un potenziale indicatore di backdoor. lsof -i -P | grep LISTEN è uno dei primi comandi durante un incident response.
  • Lateral Movement: Un attaccante che apre un reverse shell crea un socket ESTABLISHED verso il suo server C2. lsof -i TCP | grep ESTABLISHED lo rivela.
  • Unix Socket Abuse: Un attaccante con accesso al file /var/run/docker.sock può controllare Docker e scalare privilegi. Verificare i permessi su questi file è hardening base.
  • Persistenza: Un servizio malevolo registrato in systemd apre un socket ad ogni boot. systemctl list-units --type=socket mostra i socket gestiti da systemd.

Scenario Reale
#

Durante un incident response, noti traffico anomalo in uscita. Esegui sudo lsof -i TCP -P | grep ESTABLISHED e trovi un processo chiamato update-check con PID 9876 che ha un socket aperto verso 45.33.xx.xx:443. Non riconosci il processo. Indaghi con lsof -p 9876 per vedere tutti i file che ha aperto (binario, librerie, file di configurazione) e con systemctl show update-check -p FragmentPath per scoprire da dove viene caricato. Se il path non è in /lib/systemd/system/, hai probabilmente trovato una backdoor persistente.

Dove l'ho incontrato
#

  • bandit-14 — Connessione TCP a un socket locale sulla porta 30000 con nc
  • progetto-lab-vm — Socket SSH in LISTEN sulla VM Ubuntu

Note personali
#

Il concetto "tutto è un file" diventa concreto con i socket: puoi fare echo "GET / HTTP/1.0" | nc google.com 80 e funziona perché nc apre un socket, ci scrive sopra con write() come se fosse un file, e legge la risposta con read(). Unix domain socket li incontrerai di più quando installerai Docker e Wazuh — sono il modo in cui i componenti comunicano sulla stessa macchina senza esporre porte di rete.

Collegato a
#

  • system — categoria (Hub)
  • lsof — comando principale per ispezionare socket aperti
  • nc — crea socket client per connettersi a servizi
  • systemctl — gestisce servizi che aprono socket
  • standard-streams — i socket usano File Descriptor come stdin/stdout/stderr
  • inode-anatomy — i socket sono "file" nel senso Unix del termine
  • linux-services — i servizi (daemon) comunicano tramite socket
  • ssh-protocol — SSH è un servizio che ascolta su un socket TCP

Related