
Drupal 11.4.8 - Alta - Remota - SSRF - El descubrimiento de oEmbed realiza GETs a una URL de segundo salto sin alcance
abraxaslabs.tech · github.com/abraxas · @abraxas_null · [email protected] · drupal-oembed-ssrf
Clase: SSRF Alcance: Remoto
Drupal core 11.4.8 (61044508) - Drupal
SA-CORE-2026-008 restringió el host del documento de descubrimiento con media_oembed_discovery_trusted_host_patterns. UrlResolver::findUrl sigue devolviendo el href del link sin procesar. ResourceFetcher::fetchResource luego hace GET a esa URL sin ninguna comprobación de host ni de IP privada. El thumbnail_url de oEmbed es la misma clase de GET. Los patrones predeterminados están vacíos, por lo que el descubrimiento está desactivado hasta que un administrador lo habilite.
Después de que un administrador active el descubrimiento, un usuario que pueda crear medios oEmbed puede apuntar Drupal a una página en la lista de permitidos cuyo href del enlace oEmbed en JSON sea loopback, RFC1918 o metadata.
| ID | sin CVE todavía |
| Clase | SSRF (residuo del segundo salto de SA-CORE-2026-008) |
| Alcance | Remoto (creación de medios autenticada, después de que el administrador configure los hosts de descubrimiento) |
| 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 |
| Producto | Drupal core |
| Afectado | 11.4.8 (61044508071749629aaf3380aa85971e94df9e84) |
| Auth | creación de medios autenticada, después de que un administrador configure los hosts de descubrimiento. AC:H es ese interruptor del administrador. |
| Licencia | GNU Affero GPL v3.0 |
| Lab | solo 127.0.0.1 |
Solo después de que un administrador haya configurado los patrones de hosts de confianza para el descubrimiento. Después de eso, basta con un creador de medios. Un completo desconocido sin cuenta no puede hacer esto de forma predeterminada. Esto es SSRF, no RCE y no un inicio de sesión de administrador.
| Privilegio | Método / sink | Tipo | Probado en lab o inferido |
|---|---|---|---|
| Hacer que Drupal haga GET a una URL elegida por el atacante (loopback en este lab) | UrlResolver::findUrl con href sin procesar y luego GET de ResourceFetcher::fetchResource | reconocimiento (fetch del lado del servidor) | Probado en lab. http://127.0.0.1:18080/oembed devolvió DRUPAL-OEMBED-SSRF-WITNESS |
| Leer el cuerpo HTTP de esa URL en PHP | El mismo GET; JSON/XML analizado como oEmbed | reconocimiento | Probado en lab. El testigo estaba en el cuerpo 200 |
El mismo GET para thumbnail_url después de construir un recurso | OEmbed::getLocalThumbnailUri ~417-456 | reconocimiento | Inferido del pin. Este lab se detuvo en el href descubierto |
| Alcanzar RFC1918 / link-local / metadata si el netns de PHP puede | GET de Guzzle sin restricciones | reconocimiento | Inferido. El lab usó loopback, no 169.254.169.254 |
Cerrado en este residuo: primer salto cuando los patrones están vacíos (probado en lab); primer salto cuando el host HTML no coincide (probado en lab); creación de medios no autenticada en Standard sin un rol de medios; RCE; robo de sesión de administrador.
Fijé Drupal 11.4.8 después de SA-CORE-2026-008. El primer salto está restringido:
$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 devuelve el href sin procesar. ResourceFetcher hace GET con un timeout y nada más:
$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,
]);
El lab es un gadget fiel de esas funciones de 11.4.8, no una instalación real de un sitio drupal:11.4.8. La última línea usa how=gadget. Los patrones vacíos no hicieron fetch. Un host que no coincide no hizo fetch. Con ^firsthop$ permitido, discoverResourceUrl devolvió http://127.0.0.1:18080/oembed y el GET sin restricciones devolvió el testigo.
Errores ya registrados: tratar el desactivado por defecto como un fallo (es la puerta prevista del primer salto; el residuo es el segundo salto); llamar oráculo a una instalación Standard en vivo (este paquete son las funciones fijadas); afirmar RCE porque Guzzle se ejecutó. Teatro: una reverse shell. El oráculo es desactivado por defecto más host incorrecto bloqueado más el testigo del segundo salto.
Otros residuos del mismo pin: CKEditor 5 FileIsImage skip.
python3 drupal-oembed-ssrf-Abraxas-Labs.py
o
cd lab
./run.sh
Solo loopback: HTML del primer salto en http://127.0.0.1:19510. El segundo salto es http://127.0.0.1:18080/oembed dentro 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
Aplicar la misma política de host / IP privada en el endpoint descubierto y en thumbnail_url. No hacer fetch de un href cuyo host habría fallado el primer salto.
61044508071749629aaf3380aa85971e94df9e84)UrlResolver.php · ResourceFetcher.phpGNU Affero GPL v3.0. Ver LICENSE. Solo lab de loopback. Sin garantía.