
Drupal 11.4.8 - 高 - リモート - SSRF - oEmbedディスカバリーがスコープ外のセカンドホップURLをGETする
abraxaslabs.tech · github.com/abraxas · @abraxas_null · [email protected] · drupal-oembed-ssrf
クラス: SSRF 到達範囲: リモート
Drupal core 11.4.8 (61044508) - Drupal
SA-CORE-2026-008 は discovery document のホストを media_oembed_discovery_trusted_host_patterns で制限した。UrlResolver::findUrl は依然として生の link href を返す。ResourceFetcher::fetchResource はその後、その URL をホストやプライベート IP のチェックなしに GET する。oEmbed の thumbnail_url も同種の GET である。デフォルトのパターンは空であるため、管理者が有効化するまで discovery はオフである。
管理者が discovery を有効化した後、oEmbed メディアを作成できるユーザーは、JSON oEmbed の link href がループバック、RFC1918、またはメタデータを指す許可リスト済みページに Drupal を誘導できる。
| ID | CVE 未採番 |
| クラス | SSRF (SA-CORE-2026-008 のセカンドホップ残存) |
| 到達範囲 | リモート (管理者が discovery ホストを設定した後の、認証済みメディア作成) |
| CWE | CWE-918 |
| CVSS | Medium: 6.1 CVSS:3.1/AV:N/AC:H/PR:L/UI:N/S:C/C:H/I:N/A:N |
| 製品 | Drupal core |
| 影響を受けるバージョン | 11.4.8 (61044508071749629aaf3380aa85971e94df9e84) |
| 認証 | 管理者が discovery ホストを設定した後の、認証済みメディア作成。AC:H はその管理者トグルを指す。 |
| ライセンス | GNU Affero GPL v3.0 |
| ラボ | 127.0.0.1 のみ |
管理者が discovery の trusted-host パターンを設定した後のみ。その後はメディア作成者で十分である。アカウントを持たない完全な部外者はデフォルトではこれを実行できない。これは SSRF であり、RCE でも管理者ログインでもない。
| 権限 | メソッド / シンク | 種類 | ラボ実証または推測 |
|---|---|---|---|
| Drupal に攻撃者が選んだ URL (このラボではループバック) を GET させる | UrlResolver::findUrl の生 href、その後 ResourceFetcher::fetchResource の GET | 偵察 (サーバーサイドフェッチ) | ラボ実証済み。http://127.0.0.1:18080/oembed が DRUPAL-OEMBED-SSRF-WITNESS を返した |
| その URL の HTTP ボディを PHP に読み込む | 同じ GET。JSON/XML が oEmbed としてパースされる | 偵察 | ラボ実証済み。Witness は 200 ボディ内にあった |
リソース構築後の thumbnail_url に対する同じ GET | OEmbed::getLocalThumbnailUri 約 417-456 行 | 偵察 | ピン留めバージョンからの推測。このラボは discovery された href で停止した |
| PHP の netns が到達可能なら RFC1918 / link-local / メタデータに到達 | スコープなしの Guzzle GET | 偵察 | 推測。ラボは 169.254.169.254 ではなくループバックを使用した |
この残存部分でクローズ済み: パターンが空の場合のファーストホップ (ラボ実証済み)、HTML ホストが一致しない場合のファーストホップ (ラボ実証済み)、メディアロールなしの Standard での未認証メディア作成、RCE、管理者セッション窃取。
SA-CORE-2026-008 の後、Drupal 11.4.8 をピン留めした。ファーストホップは制限されている:
$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 は生の href を返す。ResourceFetcher はタイムアウトのみを指定してそれを GET する:
$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,
]);
ラボはこれらの 11.4.8 関数を忠実に再現したガジェットであり、実際に稼働する drupal:11.4.8 サイトのインストールではない。最終行は how=gadget を使用している。空のパターンではフェッチしなかった。一致しないホストではフェッチしなかった。^firsthop$ を許可した状態で、discoverResourceUrl は http://127.0.0.1:18080/oembed を返し、スコープなしの GET が witness を返した。
すでに記録済みの誤った道: デフォルトオフをミスとみなすこと (これは意図されたファーストホップのゲートであり、残存部分はホップ 2 である)、稼働中の Standard インストールをオラクルと呼ぶこと (このパックはピン留め関数である)、Guzzle が実行されたからといって RCE と主張すること。茶番: リバースシェル。オラクルはデフォルトオフ + 誤ホストブロック + セカンドホップ witness である。
同じピンの残存部分: CKEditor 5 FileIsImage skip。
python3 drupal-oembed-ssrf-Abraxas-Labs.py
または
cd lab
./run.sh
ループバックのみ: ファーストホップ HTML は http://127.0.0.1:19510。セカンドホップはガジェット netns 内の http://127.0.0.1:18080/oembed。
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
discovery されたエンドポイントと thumbnail_url に対して同じホスト / プライベート IP ポリシーを適用せよ。ファーストホップで失敗するはずのホストを持つ href をフェッチしてはならない。
61044508071749629aaf3380aa85971e94df9e84)UrlResolver.php · ResourceFetcher.phpGNU Affero GPL v3.0。LICENSE を参照。ループバックラボのみ。無保証。