
Drupal 11.4.8 - Hoch - Remote - SSRF - oEmbed-Discovery führt GET auf eine nicht eingegrenzte Second-Hop-URL aus
abraxaslabs.tech · github.com/abraxas · @abraxas_null · [email protected] · drupal-oembed-ssrf
Write-up: https://abraxaslabs.tech/research/drupal-oembed-ssrf
Klasse: SSRF Reichweite: Remote
Drupal core 11.4.8 (61044508) - Drupal
SA-CORE-2026-008 hat den Host des Discovery-Dokuments mit media_oembed_discovery_trusted_host_patterns abgesichert. UrlResolver::findUrl gibt weiterhin das rohe link-href zurück. ResourceFetcher::fetchResource führt dann ein GET auf diese URL aus, ohne Host- oder Private-IP-Prüfung. oEmbed thumbnail_url ist dieselbe Art von GET. Die Standardmuster sind leer, sodass Discovery deaktiviert ist, bis ein Administrator sie aktiviert.
Nachdem ein Administrator Discovery aktiviert hat, kann ein Benutzer, der oEmbed-Medien erstellen darf, Drupal auf eine allowlistete Seite zeigen lassen, deren JSON-oEmbed-Link-href auf Loopback, RFC1918 oder Metadaten verweist.
| ID | noch keine CVE |
| Klasse | SSRF (Second-Hop-Überbleibsel von SA-CORE-2026-008) |
| Reichweite | Remote (authentifiziertes Erstellen von Medien, nachdem ein Administrator Discovery-Hosts festgelegt hat) |
| CWE | CWE-918 |
| CVSS | Mittel: 6.1 CVSS:3.1/AV:N/AC:H/PR:L/UI:N/S:C/C:H/I:N/A:N |
| Produkt | Drupal core |
| Betroffen | 11.4.8 (61044508071749629aaf3380aa85971e94df9e84) |
| Auth | authentifiziertes Erstellen von Medien, nachdem ein Administrator Discovery-Hosts festgelegt hat. AC:H ist dieser Administrator-Schalter. |
| Lizenz | GNU Affero GPL v3.0 |
| Lab | nur 127.0.0.1 |
Erst nachdem ein Administrator Discovery-Trusted-Host-Muster festgelegt hat. Danach genügt ein Ersteller von Medien. Ein völlig Fremder ohne Konto kann dies in der Standardkonfiguration nicht tun. Dies ist SSRF, kein RCE und kein Admin-Login.
| Berechtigung | Methode / Sink | Art | Im Lab nachgewiesen oder abgeleitet |
|---|---|---|---|
| Drupal dazu bringen, eine vom Angreifer gewählte URL abzurufen (in diesem Lab Loopback) | UrlResolver::findUrl rohes href, dann GET durch ResourceFetcher::fetchResource | Recon (serverseitiger Abruf) | Im Lab nachgewiesen. http://127.0.0.1:18080/oembed gab DRUPAL-OEMBED-SSRF-WITNESS zurück |
| Den HTTP-Body dieser URL in PHP einlesen | Derselbe GET; JSON/XML als oEmbed geparst | Recon | Im Lab nachgewiesen. Der Witness befand sich im 200-Body |
Derselbe GET für thumbnail_url, nachdem eine Ressource aufgebaut wurde | OEmbed::getLocalThumbnailUri ~417-456 | Recon | Aus dem Pin abgeleitet. Dieses Lab endete beim entdeckten href |
| RFC1918 / Link-Local / Metadaten erreichen, falls der PHP-Netns dies kann | Ungescopeter Guzzle-GET | Recon | Abgeleitet. Das Lab verwendete Loopback, nicht 169.254.169.254 |
Bei diesem Überbleibsel geschlossen: erster Hop, wenn die Muster leer sind (im Lab nachgewiesen); erster Hop, wenn der HTML-Host nicht übereinstimmt (im Lab nachgewiesen); unauthentifiziertes Erstellen von Medien auf Standard ohne Medienrolle; RCE; Diebstahl der Admin-Sitzung.
Ich habe Drupal 11.4.8 nach SA-CORE-2026-008 gepinnt. Der erste Hop ist abgesichert:
$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 gibt das rohe href zurück. ResourceFetcher ruft es mit einem Timeout per GET ab und sonst nichts:
$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,
]);
Das Lab ist ein originalgetreues Gadget dieser 11.4.8-Funktionen, keine Live-Installation einer drupal:11.4.8-Seite. Die letzte Zeile verwendet how=gadget. Leere Muster haben nichts abgerufen. Ein nicht passender Host hat nichts abgerufen. Mit erlaubtem ^firsthop$ gab discoverResourceUrl http://127.0.0.1:18080/oembed zurück, und der ungescopte GET lieferte den Zeugen.
Bereits festgehaltene Fehlversuche: Default-off als Fehlschlag zu werten (es ist das beabsichtigte First-Hop-Gate; der Rest ist Hop zwei); eine Live-Standard-Installation als Orakel zu bezeichnen (dieses Pack sind die Pin-Funktionen); RCE zu behaupten, weil Guzzle lief. Theater: eine Reverse Shell. Das Orakel ist Default-off plus blockierter falscher Host plus der Zeuge des zweiten Hops.
Dieselben Pin-Reste: CKEditor 5 FileIsImage skip.
python3 drupal-oembed-ssrf-Abraxas-Labs.py
oder
cd lab
./run.sh
Nur Loopback: erster Hop HTML auf http://127.0.0.1:19510. Zweiter Hop ist http://127.0.0.1:18080/oembed innerhalb des Gadget-Netns.
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
Wenden Sie dieselbe Host-/Private-IP-Richtlinie auf den ermittelten Endpunkt und auf thumbnail_url an. Rufen Sie keinen href ab, dessen Host beim ersten Hop fehlgeschlagen wäre.
61044508071749629aaf3380aa85971e94df9e84)UrlResolver.php · ResourceFetcher.phpGNU Affero GPL v3.0. Siehe LICENSE. Nur Loopback-Lab. Keine Gewährleistung.