コース目次 / 第1章

SSRF — サーバに、内部への使いを頼む

外から直接は届かない内部専用API(:3901)に、公開URLフェッチ機能(:3900)を踏み台にして到達します。サーバが利用者の代わりにリクエストを送る仕組みと、その悪用を体で確かめます。

第1章 / 全5章目安 約12分この章のゴール: URLフェッチ機能を悪用し、外から直接届かない内部APIに到達できると確認する

2つのサービスを立てます。内部専用のAPI(あなたの組織の中だけで使う想定)と、公開の画像取得API(外部URLの画像を取ってきて表示する、よくある機能)です。

法と倫理ゲート

手を動かす前に

このコースは攻撃の技術に触れます。実習してよい相手は、次の3つだけです。

  • (a) 自分で作った練習用のやられ環境(このサイトが配る自作ターゲット)
  • (b) あなた自身の許可された環境(自分の Thousand SKY・secret-notes-ctf など)
  • (c) CTF 競技の中(規約の範囲で)

実在する第三者のシステムを許可なく触ることは、不正アクセス禁止法に触れます。 このゲートは、その線引きを毎回の入口で確かめるためのものです。詳しくは「セキュリティの法と倫理」へ。

内部専用API(:3901)— 本来、外から直接は使わせない想定

これは「社内ネットワークの中でだけ使われる」という想定のAPIです。internal-api.js として保存します。

// internal-api.js — 内部専用の想定のAPI。127.0.0.1:3901。
// 本来なら「社内ネットワークの中からしか届かない」場所にあるという設定。
const http = require('node:http');

http.createServer((req, res) => {
  res.setHeader('Content-Type', 'application/json; charset=utf-8');
  if (req.url === '/admin/config') {
    res.end(JSON.stringify({ dbPassword: 'sup3r-secret-db-pass', apiKey: 'internal-key-xyz' }));
    return;
  }
  res.end(JSON.stringify({ hint: '/admin/config' }));
}).listen(3901, '127.0.0.1', () => console.log('内部API: http://127.0.0.1:3901 (Ctrl+C で停止)'));

公開API(:3900)— URLを渡すと、取りに行ってくれる

こちらは「利用者が渡したURLの画像を取得して表示する」、よくあるサムネイル生成のような機能です。public-api.js として保存します。

// public-api.js — 公開の画像取得API。127.0.0.1:3900。渡されたURLをサーバが取りに行く。
const http = require('node:http');

http.createServer((req, res) => {
  const url = new URL(req.url, 'http://127.0.0.1:3900');
  if (url.pathname === '/fetch-image') {
    const target = url.searchParams.get('url') || '';
    // 【穴: SSRF】渡された URL を、検証せずそのまま fetch している。
    fetch(target)
      .then((r) => r.text())
      .then((body) => {
        res.setHeader('Content-Type', 'text/plain; charset=utf-8');
        res.end('取得結果:\n' + body);
      })
      .catch((e) => { res.writeHead(502); res.end('取得失敗: ' + e.message); });
    return;
  }
  res.end('使い方: /fetch-image?url=https://example.com/photo.jpg');
}).listen(3900, '127.0.0.1', () => console.log('公開API: http://127.0.0.1:3900 (Ctrl+C で停止)'));

正規の使い方 — 外の画像を取ってくる

両方起動します。

node internal-api.js     # 1つめのターミナル
node public-api.js       # 2つめのターミナル

普通の使い方は、こうです。

curl "http://127.0.0.1:3900/fetch-image?url=https://example.com"

example.com の中身が返ってきます。「利用者が指定した外部URLを、サーバが代わりに取りに行く」——ここまでは、便利な機能です。

攻撃 — 「代わりに」を、内部へ向ける

まず、内部APIに直接アクセスできるか確かめます。

curl http://127.0.0.1:3901/admin/config

もちろん、あなたの手元ではポートを開けているので届きますが——本来の想定は「これは社内ネットワークの中だけ」です。外部の攻撃者からは直接は届かない、という前提のAPI。では、公開API(:3900)を踏み台にしたら?

curl "http://127.0.0.1:3900/fetch-image?url=http://127.0.0.1:3901/admin/config"

公開API経由で、内部APIの秘密の設定(dbPassword・apiKey)が取れてしまいました。攻撃者は内部ネットワークに直接は入れません。でも——「代わりに取りに行く」機能を持つサーバ(:3900)自身は、内部にいる。だから、そのサーバに「これを取ってきて」と頼めば、サーバの立ち位置を借りて内部に届く。これが SSRF です。攻撃者は自分では動かず、サーバを“使い”として動かします。

実務では、この的が「クラウドの認証情報を配るメタデータエンドポイント」だったりします。SSRF がクラウド環境で特に恐れられるのは、この一撃でクラウドの管理権限そのものが奪われうるからです。

持ち帰る一言

サーバは、内側にいる特権的な使い。 「URLを渡すと取りに行く」機能は、その特権を利用者の入力に渡してしまいます。渡した先を検証しなければ、外から直接届かない内部にまで手が届く。次の章で、この使いに「行ってよい場所」を教えます。

こうなっていればOK

卒業まであと3章です。

この章はまだ完了していません。