
Drupal 11.4.8 - Alta - Remoto - SSRF - oEmbed discovery esegue GET su un URL di secondo hop non limitato
abraxaslabs.tech · github.com/abraxas · @abraxas_null · [email protected] · drupal-oembed-ssrf
Classe: SSRF Portata: Remota
Drupal core 11.4.8 (61044508) - Drupal
SA-CORE-2026-008 ha vincolato l'host del documento di discovery con media_oembed_discovery_trusted_host_patterns. UrlResolver::findUrl restituisce comunque l'href grezzo del link. ResourceFetcher::fetchResource esegue poi una GET su quell'URL senza alcun controllo sull'host o sugli IP privati. Il thumbnail_url di oEmbed è la stessa classe di GET. I pattern predefiniti sono vuoti, quindi la discovery è disattivata finché un amministratore non la abilita.
Dopo che un amministratore attiva la discovery, un utente che può creare media oEmbed può far puntare Drupal verso una pagina in allowlist il cui href del link oEmbed JSON è loopback, RFC1918 o metadata.
| ID | nessuna CVE al momento |
| Classe | SSRF (residuo del secondo hop di SA-CORE-2026-008) |
| Portata | Remota (creazione media autenticata, dopo che l'amministratore imposta gli host di discovery) |
| CWE | CWE-918 |
| CVSS | Medio: 6.1 CVSS:3.1/AV:N/AC:H/PR:L/UI:N/S:C/C:H/I:N/A:N |
| Prodotto | Drupal core |
| Affetto | 11.4.8 (61044508071749629aaf3380aa85971e94df9e84) |
| Auth | creazione media autenticata, dopo che un amministratore imposta gli host di discovery. AC:H è quel toggle dell'amministratore. |
| Licenza | GNU Affero GPL v3.0 |
| Lab | solo 127.0.0.1 |
Solo dopo che un amministratore ha impostato i pattern degli host attendibili per la discovery. Dopo di che basta un creatore di media. Un completo estraneo senza account non può farlo con la configurazione predefinita. Questa è SSRF, non RCE e non un login da amministratore.
| Privilegio | Metodo / sink | Tipo | Provato in lab o dedotto |
|---|---|---|---|
| Far eseguire a Drupal una GET verso un URL scelto dall'attaccante (loopback in questo lab) | UrlResolver::findUrl con href grezzo poi GET di ResourceFetcher::fetchResource | ricognizione (fetch lato server) | Provato in lab. http://127.0.0.1:18080/oembed ha restituito DRUPAL-OEMBED-SSRF-WITNESS |
| Leggere il corpo HTTP di quell'URL in PHP | Stessa GET; JSON/XML interpretati come oEmbed | ricognizione | Provato in lab. Il witness era nel corpo della 200 |
Stessa GET per thumbnail_url dopo che una risorsa è stata costruita | OEmbed::getLocalThumbnailUri ~417-456 | ricognizione | Dedotto dal pin. Questo lab si è fermato all'href scoperto |
| Raggiungere RFC1918 / link-local / metadata se il netns PHP può | GET Guzzle senza restrizioni | ricognizione | Dedotto. Il lab ha usato il loopback, non 169.254.169.254 |
Chiusi su questo residuo: primo hop quando i pattern sono vuoti (provato in lab); primo hop quando l'host HTML non corrisponde (provato in lab); creazione media non autenticata su Standard senza un ruolo media; RCE; furto di sessione amministratore.
Ho fissato Drupal 11.4.8 dopo SA-CORE-2026-008. Il primo hop è vincolato:
$patterns = \array_map(static fn ($hostPattern) => \sprintf('{%s}i', $hostPattern), Settings::get('media_oembed_discovery_trusted_host_patterns', []));
// ...
if (!$allowed) {
return FALSE;
}
$response = $this->httpClient->get($url);
return $this->findUrl($xpath, 'json') ?: $this->findUrl($xpath, 'xml');
findUrl restituisce l'href grezzo. ResourceFetcher esegue una GET su di esso con un timeout e nient'altro:
$result = $xpath->query("//link[@type='application/$format+oembed']");
return $result->length ? $result->item(0)->getAttribute('href') : FALSE;
$response = $this->httpClient->request('GET', $url, [
RequestOptions::TIMEOUT => $this->timeout,
]);
Il lab è un gadget fedele di quelle funzioni di 11.4.8, non un'installazione live di un sito drupal:11.4.8. L'ultima riga usa how=gadget. I pattern vuoti non hanno eseguito il fetch. Un host non corrispondente non ha eseguito il fetch. Con ^firsthop$ consentito, discoverResourceUrl ha restituito http://127.0.0.1:18080/oembed e la GET senza restrizioni ha restituito il witness.
Vicoli ciechi già registrati: considerare il default-off come un mancato riscontro (è il gate previsto per il primo hop; il residuo è il secondo hop); chiamare un'installazione Standard live l'oracolo (questo pacchetto è le funzioni del pin); affermare RCE perché Guzzle è stato eseguito. Teatro: una reverse shell. L'oracolo è default-off più host errato bloccato più il witness del secondo hop.
Altri residui dello stesso pin: CKEditor 5 FileIsImage skip.
python3 drupal-oembed-ssrf-Abraxas-Labs.py
oppure
cd lab
./run.sh
Solo loopback: HTML del primo hop su http://127.0.0.1:19510. Il secondo hop è http://127.0.0.1:18080/oembed all'interno del netns del gadget.
SUCCESS drupal-oembed-ssrf how=gadget pin=11.4.8 first-hop=allowed second-hop=127.0.0.1:18080 default-off=blocked DRUPAL-OEMBED-SSRF-WITNESS
Applicare la stessa policy su host / IP privati all'endpoint scoperto e a thumbnail_url. Non eseguire il fetch di un href il cui host avrebbe fallito il primo hop.
61044508071749629aaf3380aa85971e94df9e84)UrlResolver.php · ResourceFetcher.phpGNU Affero GPL v3.0. Vedi LICENSE. Solo lab in loopback. Nessuna garanzia.