
Advisory per CVE-2026-51385. Necessario per pubblicarlo perché GRAPHIFY non ha riconosciuto l'advisory né l'ha pubblicata, e MITRE ha assegnato CVE-2026-51385; questa è l'advisory relativa.
Advisory per CVE-2026-51385. Era necessario pubblicarlo perché GRAPHIFY non ha riconosciuto l'advisory e non lo ha pubblicato, e MITRE ha assegnato CVE-2026-51385, questo è l'advisory per essa.
ES: Questa è stata la mia prima CVE, per essere onesto ho trovato un modo per rompere la logica nella funzione ANTI SSRF, e volevo davvero pubblicare la mia prima CVE così ho pensato a come potesse influire sulla sicurezza per dimostrarne l'impatto, anche se fosse complessa da eseguire (è complicato che questo accada in un'istanza reale, ma possibile, per questo ha complexity high). L'ho inviata a MITRE e l'hanno accettata. Questo è l'advisory.
EN: Questa è stata la mia prima CVE, a essere onesto ho trovato da solo il modo di rompere la logica nella funzione anti-SSRF, e volevo davvero pubblicare la CVE così ho pensato a come potesse influire sulla sicurezza, ho trovato una giustificazione, l'ho inviata a MITRE e l'hanno accettata. Questo è l'advisory.
Tieni presente che il report è stato realizzato in parte con l'AI, ma supervisionato da un essere umano
SSRF tramite DNS rebinding (TOCTOU) in graphify.
Pacchetto: graphify (PyPI: graphifyy), repo Graphify-Labs/graphify
Componente interessato: il percorso di ingestione delle URL, graphify add <url>
Versioni interessate: >=0.3.2, <=0.4.29
Corretta in: 0.5.4 (commit dd86271, PRs #591 / #592)
CVSS 3.1: 8.3 (Alta), CVSS:3.1/AV:N/AC:H/PR:N/UI:R/S:C/C:H/I:H/A:H
CWE: CWE-918 (SSRF), CWE-367 (gara TOCTOU)
Segnalatore: Arturo Melgarejo Galindo, ricercatore di sicurezza indipendente (@Arturo0x90)
graphify add <url> cerca di proteggersi dalla SSRF come fanno molti fetcher: risolve l'hostname, verifica l'IP risultante contro una blocklist (loopback, RFC1918, link-local, riservati), e scarica il contenuto solo se il controllo passa.
Il problema è che l'IP validato non è l'IP a cui si connette. La validazione esegue una query DNS, e poi requests.get(hostname) ne esegue una seconda, indipendente. Se si controlla la zona DNS dell'hostname e si restituisce un TTL molto basso con due record A, uno pubblico e uno interno, il resolver può legalmente fornire una risposta diversa a ogni query. La prima risposta supera la blocklist. La seconda risposta è dove finisce realmente il socket. Questo è l'intero bug. È il classico bypass da rebinding contro la logica "controlla l'IP, poi connettiti per nome", e la correzione consiste nel vincolare l'IP validato alla connessione, che è ciò che fa 0.5.4.
Voglio essere onesto su questo, perché da sola sembra più debole di quanto non sia.
Se un essere umano si siede e digita in graphify add un URL di cui si fida già, questo è quasi inutile. Quella persona può già puntare graphify al proprio 127.0.0.1 se lo desidera. Non ha bisogno di un trucco di rebinding per farlo, e in quello scenario non c'è alcun attaccante.
Diventa una vera vulnerabilità nel momento in cui graphify viene eseguito su un URL che l'operatore non ha scelto. E per questo strumento non è un caso limite, è il modo normale in cui viene usato. graphify costruisce knowledge graph che vengono consumati da assistenti AI, quindi il flusso realistico è un qualche processo che chiama graphify add per conto tuo: un agente, un job CI, uno script che scorre una lista di URL da un README, o un URL uscito direttamente dall'output di un altro modello. In tutti questi casi, l'attaccante controlla la stringa di input e l'umano non l'ha mai ispezionata.
Questo è il caso che conta. Una volta che l'input è inaffidabile e la gara di rebinding viene vinta, il fetch finisce su un indirizzo interno invece che su quello pubblico che era stato validato. Concretamente può raggiungere:
127.0.0.1 e qualsiasi cosa in ascolto su loopback,169.254.169.254 (metadati dell'istanza cloud),100.64.0.0/10, che la blocklist non copre affatto, quindi quello non richiede nemmeno la gara, basta un semplice record A che punta al suo interno.E raggiungere questi indirizzi si trasforma in danno solo per ciò che vi risiede. Molti servizi interni e di sviluppo espongono endpoint GET che o restituiscono dati o modificano lo stato con una semplice GET. Quindi un fetch di graphify che finisce su uno di essi, con il path e la query string sbagliati, non è solo una lettura. Se il target interno è uno scriptText di Jenkins, un debugger Flask/Django in --debug, un pannello di amministrazione o un endpoint di metadati, quella GET malformata è l'attaccante che agisce dall'interno del perimetro. graphify è il delegato che effettua la richiesta per loro.
Non sto affermando che questo includa una RCE. Di per sé non lo fa. Ma una SSRF completa che può essere puntata su loopback, IMDS, RFC1918 e CGN da un percorso di ingestione automatizzato è esattamente la primitiva su cui quegli attacchi sono costruiti, ed è questo il punto della segnalazione.
Ho usato l'harness pubblico rbndr.us di Tavis Ormandy, che fornisce un hostname che alterna due IP a ogni risoluzione. 7f000001.08080808.rbndr.us alterna tra 8.8.8.8 e 127.0.0.1.
Avvia qualcosa sul target interno (qui, loopback, per la demo):
sudo python3 -m http.server 80
Poi attiva l'ingestione e riprova finché la gara non va a buon fine. Riesce circa 1 volta su 4 o 5 tentativi, e un semplice loop porta quella probabilità ben oltre il 99%:
for i in {1..20}; do
graphify add http://7f000001.08080808.rbndr.us/ && break
sleep 1
done
In un tentativo riuscito, graphify ingerisce qualunque cosa il server locale su 127.0.0.1:80 abbia restituito, anche se l'hostname aveva prima superato la blocklist degli IP.
0.5.4 (dd86271), 8 giorni dopo.Arturo Melgarejo Galindo, ricercatore di sicurezza indipendente.