
CVE-2026-13233 (Drupal OpenAI Provider, SA-CONTRIB-2026-053): SSRF tramite response-URL / lettura di file locale. Upstream non attendibile, non il prompt. Reproducer sicuro + rilevamenti. Corretto nella 1.1.1/1.2.2.
Upstream non affidabile: il provider che ha recuperato l'URL indicato dalla propria risposta.
Drupal OpenAI Provider (ai_provider_openai) · SSRF (CWE-918) · CVE-2026-13233 · SA-CONTRIB-2026-053
Stato: Divulgazione coordinata completa (2026-07-10). Segnalato privatamente al Drupal Security Team, corretto e pubblicato come CVE-2026-13233 / advisory SA-CONTRIB-2026-053 (valutato Moderately critical). Autore accreditato come finder & remediation developer.
Uno studio di un classico difetto di autorizzazione / validazione degli input su un "punto di connessione" AI: un
provider di generazione immagini AI recuperava l'URL contenuto nella risposta API upstream usando una
funzione generica senza allowlist di schemi. Nelle distribuzioni in cui l'upstream configurato
non è il default fidato (bring-your-own-endpoint / proxy / gateway self-hosted / relay
compromesso), la risposta poteva puntare il server a file:// o a un indirizzo interno.
L'input non affidabile che raggiunge il sink è l'URL della risposta upstream — non il prompt dell'utente.
file://; l'analisi
interna dell'autore l'ha mappata anche su CWE-73 / CWE-441.3.1 / condizionale 5.3
(Medium); i precedenti 6.5 / 7.4 sono stati ritrattati, e la valutazione media lo ha confermato.ai_provider_openai) < 1.1.1 e 1.2.0–1.2.1 (verificata 1.2.1) su Drupal core 11.2Tutte le verifiche sono state eseguite in una sandbox di proprietà, isolata dalla rete, contro un upstream mock. La vera API OpenAI non è mai stata chiamata. Gli oracoli di leak erano esclusivamente sintetici.
Confermato (dimostrato dinamicamente):
settings.php confermata tramite byte-match sha256 (35.182 B) — contenuto mai stampato; le credenziali del DB
sono quindi in scope.Non verificato (esplicitamente non rivendicato):
Non interessato: api.openai.com predefinito su TLS. L'impostazione host è controllata da admin / config-sync / deploy
— un attaccante non-admin in esecuzione non può modificarla.
L'URL della risposta veniva passato a un fetch generico che onora ogni stream wrapper PHP registrato
(file, php, http, data, phar, …). response_format ha come default url, quindi il ramo vulnerabile
è il percorso predefinito. file:// è il filesystem wrapper predefinito di PHP e non è limitato da
allow_url_fopen (una precedente affermazione su "richiede allow_url_fopen" era sbagliata ed è stata ritrattata).
Correggi la primitiva, non solo il punto di chiamata:
http/https prima della richiesta; disabilita i redirect (un 30x può rimbalzare su file://).file://…, http://169.254.169.254/… e un redirect 302→file://
devono essere tutti rifiutati.Direzione completa: docs/fix-direction.md.
.
├── README.md # this file (English, canonical)
├── README_ja.md # Japanese mirror
├── SECURITY.md # reporting policy + responsible-disclosure disclaimer
├── .gitignore # blocks secrets / primary evidence from being committed
├── docs/
│ └── fix-direction.md # root-cause fix + variant hunt + minimal-diff sketch (EN)
├── reproducer/ # SAFE reproducer only (sandbox, mock upstream, benign oracle)
│ ├── README.md # how to stand up the network-isolated sandbox
│ ├── verdict.md # what "success" means: raw-byte match + 200-vs-403 + exit codes
│ ├── mock-openai-server.py # benign mock; default happy-path PNG, ssrf_demo → file:///etc/hostname only
│ └── docker-compose.yml # TEMPLATE: isolated Drupal sandbox + mock (you add the vulnerable target)
├── detections/
│ ├── README.md
│ ├── sigma/web-egress-to-internal-after-imagegen.yml
│ ├── sigma/php-sensitive-file-open.yml
│ ├── sentinel/imagegen-egress-correlation.kql
│ └── splunk/imagegen-content-type-mismatch.spl
└── timeline.md # disclosure timeline (single source of truth)
La documentazione è attualmente in giapponese (
.ja.md); le traduzioni canoniche in inglese sono un TODO. Non inclusi in questo repo: payload armati e qualsiasi PoC pronto all'uso di lettura file. Ilreproducer/pubblico dimostra il meccanismo del difetto contro un mock usando un singolo oracolo benigno (/etc/hostname) — nessuna lettura a percorso arbitrario, nessun recupero disettings.php, nessun IMDS. Non è un'arma contro i siti live. Aggiorna a 1.1.1 / 1.2.2.
Obiettivo: consentire a un ricercatore di confermare la classe del difetto senza fornire un exploit per target live o esporre segreti. Tutto viene eseguito offline.
docker compose con l'app e un upstream mock su una rete isolata (--network none
per i controlli delle primitive del linguaggio). Nessuna credenziale montata. Il mock non parla mai con la vera API.host che punta a un proxy / gateway self-hosted
non affidabile." Viene predisposto out-of-band (non tramite il prompt) per restituire un URL target sintetico — questo codifica
il modello di attaccante corretto (l'upstream sceglie l'URL, l'utente si limita a triggerare).file:///etc/hostname e un marcatore unico per esecuzione scritto solo all'interno del container dell'app → dimostra
che l'app ha letto un percorso scelto dall'upstream (marcatore assente sul mock).settings.php verificato tramite byte-match sha256, contenuto mai emesso → dimostra "credenziali DB in scope"
senza divulgarle.0 sfruttabile / 1 precondizione /
2 non riprodotto / 3 pulizia fallita / 99 errore). Vedi reproducer/verdict.md.Vedi reproducer/README.md.
Vedi timeline.md. Riepilogo:
| Data | Evento |
|---|---|
| 2026-06-10 | La revisione statica individua il sink; lettura file:// a percorso fisso confermata dinamicamente |
| 2026-06-11 | Lettura a percorso arbitrario + match sha256 di settings.php + SSRF HTTP interna confermati |
| 2026-06-11 | Raggiungibilità dei consumer in produzione (automator percorso-interno; esfiltrazione FWA via HTTP legittimo) |
| 2026-06-11 | Una peer review indipendente corregge affermazioni eccessive (modello di attaccante, CVSS, allow_url_fopen); invio messo in HOLD |
| 2026-06/07 | Elementi HOLD risolti; segnalato privatamente al Drupal Security Team; gemello della classe base segnalato a drupal/ai |
| 2026-07-10 | Rilasci corretti 1.1.1 / 1.2.2; advisory SA-CONTRIB-2026-053 e CVE-2026-13233 pubblicati; autore accreditato come finder & remediation developer |
Le rilevazioni per i practitioner sono in detections/; la logica in
docs/detection-engineering.md. In breve:
169.254.169.254 / loopback / RFC1918 in una finestra temporale ristretta dopo la chiamata API. Non fare alert sul
solo egress.file:// non toccano mai la rete — rileva aperture di file sensibili da parte dei processi web tramite auditing
dell'host (auditd/EDR), con baseline così che scattino solo letture fuori pattern all'interno di una richiesta di generazione immagini.image/* i cui magic bytes non sono un'immagine.{request_id, actor, target_url, scheme, resolved_ip, content_type}. I log standard da soli non lo catturano.file:// è il filesystem wrapper predefinito; non limitato da allow_url_fopen)reproducer/ è solo sandbox e usa oracoli sintetici. Non è un exploit per target live e
non contiene segreti. Non eseguirlo contro sistemi che non possiedi e che non sei autorizzato a testare.Tutta la ricerca è stata eseguita dall'autore in una sandbox di propria proprietà, isolata dalla rete, contro un upstream mock —
la vera API del vendor non è mai stata contattata e nessun sistema di terze parti è mai stato testato. Il finding è stato segnalato
privatamente al Drupal Security Team e pubblicato solo in sincronia con la correzione e l'advisory
(SA-CONTRIB-2026-053, CVE-2026-13233); la valutazione del vendor è Moderately critical. I valori CVSS dell'autore
sono una autovalutazione provvisoria, non un punteggio ufficiale, e sono stati deliberatamente mantenuti conservativi (nessun
CVSS Critical/High) — coerenti con la valutazione media del vendor. Questo materiale ha scopi difensivi ed educativi.
Vedi SECURITY.md.