
Drupal 11.4.8 - Élevée - À distance - SSRF - La découverte oEmbed effectue un GET sur une URL de second saut non limitée
abraxaslabs.tech · github.com/abraxas · @abraxas_null · [email protected] · drupal-oembed-ssrf
Write-up : https://abraxaslabs.tech/research/drupal-oembed-ssrf
Classe : SSRF Portée : Distante
Drupal core 11.4.8 (61044508) - Drupal
SA-CORE-2026-008 a restreint l'hôte du document de découverte avec media_oembed_discovery_trusted_host_patterns. UrlResolver::findUrl renvoie toujours le href brut du link. ResourceFetcher::fetchResource effectue ensuite un GET sur cette URL sans vérification de l'hôte ni des IP privées. Le thumbnail_url oEmbed relève de la même catégorie de GET. Les motifs par défaut sont vides, donc la découverte est désactivée tant qu'un administrateur ne l'active pas.
Après qu'un administrateur a activé la découverte, un utilisateur autorisé à créer des médias oEmbed peut pointer Drupal vers une page autorisée dont le href du lien oEmbed JSON pointe vers le loopback, une adresse RFC1918 ou un service de métadonnées.
| ID | pas encore de CVE |
| Classe | SSRF (résidu du second saut de SA-CORE-2026-008) |
| Portée | Distante (création de média authentifiée, après que l'administrateur a défini les hôtes de découverte) |
| CWE | CWE-918 |
| CVSS | Moyen : 6.1 CVSS:3.1/AV:N/AC:H/PR:L/UI:N/S:C/C:H/I:N/A:N |
| Produit | Drupal core |
| Affecté | 11.4.8 (61044508071749629aaf3380aa85971e94df9e84) |
| Auth | création de média authentifiée, après que l'administrateur a défini les hôtes de découverte. AC:H correspond à ce basculement administrateur. |
| Licence | GNU Affero GPL v3.0 |
| Lab | 127.0.0.1 uniquement |
Uniquement après qu'un administrateur a défini les motifs d'hôtes de confiance pour la découverte. Un créateur de médias suffit ensuite. Un parfait inconnu sans compte ne peut pas faire cela par défaut. Il s'agit d'une SSRF, pas d'une RCE ni d'une connexion administrateur.
| Privilège | Méthode / sink | Type | Prouvé en lab ou déduit |
|---|---|---|---|
| Faire effectuer à Drupal un GET vers une URL choisie par l'attaquant (loopback dans ce lab) | UrlResolver::findUrl avec href brut puis GET via ResourceFetcher::fetchResource | reconnaissance (requête côté serveur) | Prouvé en lab. http://127.0.0.1:18080/oembed a renvoyé DRUPAL-OEMBED-SSRF-WITNESS |
| Lire le corps HTTP de cette URL dans PHP | Même GET ; JSON/XML analysé comme oEmbed | reconnaissance | Prouvé en lab. Le témoin se trouvait dans le corps de la réponse 200 |
Même GET pour thumbnail_url après construction d'une ressource | OEmbed::getLocalThumbnailUri ~417-456 | reconnaissance | Déduit de la version épinglée. Ce lab s'est arrêté au href découvert |
| Atteindre RFC1918 / link-local / métadonnées si le netns PHP le permet | GET Guzzle non restreint | reconnaissance | Déduit. Le lab a utilisé le loopback, pas 169.254.169.254 |
Écarté sur ce résidu : premier saut lorsque les motifs sont vides (prouvé en lab) ; premier saut lorsque l'hôte HTML ne correspond pas (prouvé en lab) ; création de média non authentifiée sur Standard sans rôle média ; RCE ; vol de session administrateur.
J'ai épinglé Drupal 11.4.8 après SA-CORE-2026-008. Le premier saut est restreint :
$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 renvoie le href brut. ResourceFetcher l'envoie via GET avec un timeout et rien d'autre :
$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,
]);
Le lab est un gadget fidèle de ces fonctions 11.4.8, et non une installation de site drupal:11.4.8 en direct. La dernière ligne utilise how=gadget. Les motifs vides n'ont pas déclenché de requête. Un hôte non correspondant n'a pas déclenché de requête. Avec ^firsthop$ autorisé, discoverResourceUrl a renvoyé http://127.0.0.1:18080/oembed et la requête GET non restreinte a renvoyé le témoin.
Fausses pistes déjà consignées : considérer le désactivé par défaut comme un échec (c'est le verrou de premier saut prévu ; le résidu est le deuxième saut) ; appeler une installation Standard en direct l'oracle (ce pack correspond aux fonctions épinglées) ; affirmer une RCE parce que Guzzle s'est exécuté. Théâtre : un reverse shell. L'oracle est le désactivé par défaut, plus l'hôte incorrect bloqué, plus le témoin de deuxième saut.
Mêmes résidus épinglés : CKEditor 5 FileIsImage skip.
python3 drupal-oembed-ssrf-Abraxas-Labs.py
ou
cd lab
./run.sh
Loopback uniquement : premier saut HTML sur http://127.0.0.1:19510. Le second saut est http://127.0.0.1:18080/oembed à l'intérieur du netns du 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
Appliquez la même politique d'hôte / d'IP privée sur le point de terminaison découvert et sur thumbnail_url. Ne récupérez pas un href dont l'hôte aurait échoué au premier saut.
61044508071749629aaf3380aa85971e94df9e84)UrlResolver.php · ResourceFetcher.phpGNU Affero GPL v3.0. Voir LICENSE. Laboratoire en boucle locale uniquement. Aucune garantie.