Scroll to top

SanDssrf — Node.js のための SSRF 封じ込め

SanDssrf — the new era of SSRF defense. SSRF attempts against 169.254.169.254, 10.0.0.1, 127.0.0.1 and [::1] are contained inside a simulated internal network in a Linux namespace, while public internet traffic is unaffected.
Secure development

プロジェクト概要

遮断するのではなく、封じ込める。SSRF 防御の新時代。

SanDssrf は、私自身の DSSRF を含む SSRF 対策を攻撃するなかで何度も突き当たった問題への答えです。DSSRF のようなライブラリは URL を検証してからリクエストを通しますが、どれもパーサーディファレンシャル、DNS リバインディング、あるいは誰もリストに載せていなかった IPv6 表記で破られ得ます。SanDssrf は検証をやめ、封じ込めます。ユーザーが指定した URL への接続は、シミュレートされた内部ネットワークを持つ Linux ネットワーク名前空間の内側から行われます。宛先が内部アドレスなら Linux カーネルが無害なおとりへルーティングし、公開アドレスなら通常どおり送信されます。誤り得る範囲チェックも勝てる競合も存在しないため、バイパスの攻撃クラスそのものが消滅します。しかもアプリケーション自身のデータベース、キャッシュ、内部 API はそのまま動作します。依存関係ゼロの純粋な Node.js 実装で、MIT ライセンスのもと npm に @insitetechjp/sandssrf として公開しています。

0依存関係 ・ 純粋な Node.js
16.7M10.0.0.0/8 だけでおとり化されるアドレス数
Kernel内部かどうかを判断
MIT無料・オープンソース

課題

  1. バリデータと HTTP クライアントが同じ URL を異なる解釈で読み取ること。これが私自身の DSSRF を含む多くの SSRF 対策ライブラリを破る「パーサーディファレンシャル」という攻撃クラスです。
  2. DNS リバインディング:チェックから接続までの間に DNS の応答が変わり得るため、「確認してから接続する」設計には必ず負け得る競合が存在します。
  3. ユーザー空間での範囲チェックは、IPv6 表記をひとつ見落とすか「未知なので許可」という分岐がひとつあるだけでバイパスされます。
  4. SSRF は HTTP だけの問題ではありません。Redis や PostgreSQL などの生の TCP プロトコルにも同じ保護が必要です。
  5. 信頼できないリクエストを封じ込めつつ、アプリケーション自身のデータベース、キャッシュ、内部 API は壊さないこと。

アプローチ

  1. 補助プロセスを独自のユーザー+ネットワーク名前空間(unshare -Ur -n)で起動し、その中で内部レンジをすべて Linux AnyIP のローカルルートとして登録します。
  2. 親プロセスがホスト名を一度だけ名前解決し、得られたアドレスだけをサンドボックスへ渡します。改ざんされ得る二度目の名前解決は存在しません。
  3. 何が内部かはカーネルの最長一致ルーティングが判断します。ユーザー空間のパーサーも、誤り得る範囲リストも、勝てる競合もありません。
  4. 接続済みソケットは SCM_RIGHTS で親に返されます。ソケットの名前空間はファイルディスクリプタとともに移動するため、サンドボックス以外には到達できません。
  5. 内部宛ての通信は x-sandssrf: mock ヘッダ付きの無害なおとりに届き、ホスト・メソッド・パスを含む blocked イベントが発火します。公開宛先には通常どおり接続します。

数行で導入

信頼できない URL を取得する箇所だけに、サンドボックスの Agent を渡します。http/https 向けの Agent に加え、HTTP 以外のプロトコル向けに sandbox.connect() も提供しています。TLS 証明書は引き続きホスト名で検証されます。

const { createSSRFSandbox } = require('@insitetechjp/sandssrf');

const sandbox = createSSRFSandbox();
const agent   = sandbox.Agent();

sandbox.on('blocked', (e) => log.warn('SSRF attempt', e));

// user-supplied URL — contained
await fetchWith(agent, userUrl);

// everything else is untouched
await pgcon.query('select 1');
npm install @insitetechjp/sandssrf

限界を正直に明記

「限界を隠すセキュリティライブラリは、存在しないよりも悪い。」

  • Linux 専用です。unshare(util-linux)と ip(iproute2)、および非特権ユーザー名前空間が必要です。
  • Docker の既定の seccomp プロファイルではブロックされる場合があります。その場合 ready() は黙って素通しせず、SANDSSRF_UNAVAILABLE で明示的に失敗します。
  • 封じ込められるのはサンドボックスの Agent 経由で作られたソケットだけです。直接の fetch()、サブプロセス、ネイティブアドオンは対象外です。
  • 公開ホストへの通信は制限しません。それには宛先の許可リストという別の対策が必要です。

SanDssrf は DSSRF の後継となる設計です。DSSRF に寄せられたアドバイザリ、そして私自身が発見し公開してきたバイパスから得た教訓を、検証ではなく封じ込めという形で実装しました。