0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

SSRF 対策では URL を `new URL()` で正規化してから検査する

0
Posted at

はじめに

外部入力として URL を受け取り、サーバー側でその URL にアクセスする処理を書くことがあります。

たとえば、次のような機能です。

  • 入力された URL の OGP を取得する
  • Webhook の送信先 URL を登録する
  • 画像 URL を取得してプロキシする
  • 顧客サイトの origin URL に対して Edge Worker から fetch() する
  • 指定された URL の HTML をクロールする

このような処理では、SSRF に注意が必要です。

SSRF は Server-Side Request Forgery の略です。ここでは、「攻撃者が指定した URL に、サーバー側からアクセスさせられてしまう脆弱性」と考えます。

ブラウザから直接アクセスできない内部ネットワークでも、サーバーからならアクセスできることがあります。たとえば、次のような宛先です。

  • 127.0.0.1
  • localhost
  • 169.254.169.254
  • 社内ネットワークの IP
  • クラウドのメタデータエンドポイント
  • 本来外部公開していない管理画面

そのため、外部入力の URL をそのまま fetch(url) するのは危険です。

ここで大事なのは、URL を「文字列の見た目」だけで検査しないことです。

if (url.includes('127.0.0.1')) {
  throw new Error('blocked');
}

このような検査は、一見よさそうに見えます。しかし URL には、同じ宛先を別の見た目で表す書き方があります。

たとえば、次の URL は見た目は違いますが、new URL() に通すと 127.0.0.1 として扱われます。

new URL('http://0x7f000001/').hostname;
// => '127.0.0.1'

new URL('http://2130706433/').hostname;
// => '127.0.0.1'

つまり、127.0.0.1 という文字列だけを探しても足りません。

また、Zod や Valibot の url() のようなバリデーションは、「URL としてパースできるか」を確認するには便利です。しかし、SSRF 対策で必要なのは「その URL をサーバー側でアクセスしてよいか」の判断です。

この2つは別物です。

この記事では、new URL() が URL をどのように解釈し、正規化するのかを確認したうえで、SSRF 対策の第一段階としてどのように検査すればよいかを整理します。

結論

URL を検査するときは、次の順番にします。

ポイントは、生の文字列ではなく、new URL() が返した結果を見ることです。

new URL() に通すと、URL はいくつかのルールに従って正規化されます。代表的には、次のようなものがあります。

  • IPv4 の特殊な表記が 127.0.0.1 のような通常表記にそろう
  • expected.com@attacker.com のような URL で、本当の hostname が取り出せる
  • IPv6 の表記がそろう
  • 大文字小文字や国際化ドメイン名がそろう
  • デフォルトポートが空文字に正規化される

この結果を使えば、URL の見た目に引っ張られにくい検査を書けます。

ただし、new URL() は SSRF 対策の全部ではありません。あくまで入口です。

一般的なバリデーションライブラリが主に見るのは、次のような内容です。

  • URL として構文的に妥当か
  • protocol が許可されたものか
  • host の形式が妥当か
  • userinfo を禁止するか
  • hostname の allowlist / denylist に合うか

一方で、SSRF ガードでは、さらに次のようなことを考える必要があります。

  • new URL() 後の正規化結果を見る
  • IP リテラルを拒否する
  • localhost / .localhost を拒否する
  • DNS 解決後の IP が private / loopback / link-local / metadata 宛てでないか確認する
  • リダイレクト先を再検査する
  • fetch 実行環境の egress をネットワーク側で制限する
  • 可能なら allowlist 方式にする

そのため、この記事で扱うのは「URL として正しいか」ではなく、「サーバー側でその URL にアクセスしてよいか」を考えるための最初のガードです。

なぜ「文字列の見た目」だけでは足りないのか

まず、不十分な例を見ます。

次のコードは、expected.com だけを許可したい気持ちで書かれています。

function assertExpectedDomain(rawUrl: string): void {
  if (!rawUrl.includes('expected.com')) {
    throw new Error('blocked');
  }
}

しかし、これは危険です。

const rawUrl = 'http://expected.com@attacker.com/';

この URL には expected.com という文字列が含まれています。けれども、実際の接続先 hostname は expected.com ではありません。

const u = new URL('http://expected.com@attacker.com/');

console.log(u.username);
// => 'expected.com'

console.log(u.hostname);
// => 'attacker.com'

@ より前は userinfo と呼ばれる部分です。昔の URL では、ユーザー名やパスワードを URL に含めるために使われました。

http://user:password@example.com/

この場合、hostname は example.com です。user:password は hostname ではありません。

つまり、URL の中で「ドメインっぽく見える文字列」が、必ずしも接続先とは限りません。

そのため、SSRF 対策では「URL 文字列に何が含まれているか」ではなく、「URL パーサーが hostname として何を解釈したか」を見る必要があります。

new URL() がやってくれること

new URL() は、URL 文字列をただ分割するだけではありません。

URL の仕様に従ってパースし、いくつかの表記をそろえます。ここでは、SSRF 対策で特に重要なものを 5 つに絞って見ます。

1. IPv4 の特殊表記を通常の IPv4 表記にそろえる

まず重要なのが IPv4 です。

普段よく見る IPv4 は、次のような形式です。

127.0.0.1

これは dotted decimal と呼ばれる表記です。この記事では、わかりやすく「通常の IPv4 表記」と呼びます。4つの数字を . で区切る形です。

しかし、URL パーサーは、これ以外の IPv4 表記も解釈します。

const cases = [
  'http://127.0.0.1/',
  'http://0x7f000001/',
  'http://2130706433/',
];

for (const c of cases) {
  const u = new URL(c);
  console.log(c, '=>', u.hostname);
}

結果は次のようになります。

http://127.0.0.1/   => 127.0.0.1
http://0x7f000001/  => 127.0.0.1
http://2130706433/  => 127.0.0.1

0x7f000001 は 16 進数表記です。2130706433 は 10 進数の単一数値表記です。どちらも new URL() に通すと 127.0.0.1 に正規化されます。

これが、「文字列に 127.0.0.1 が含まれているか」を見るだけでは危ない理由です。

次のような検査は抜けられます。

if (rawUrl.includes('127.0.0.1')) {
  throw new Error('blocked');
}

しかし、new URL() の結果を見るなら、特殊表記が通常の表記にそろった後で検査できます。

const u = new URL(rawUrl);

if (u.hostname === '127.0.0.1') {
  throw new Error('blocked');
}

もちろん実際には 127.0.0.1 だけでなく、プライベート IP やリンクローカル IP なども考える必要があります。ただ、まずは「正規化された hostname を見る」ことが前提になります。

2. userinfo と hostname を分けてくれる

次に userinfo です。

URL には、次のような形式があります。

http://user:password@example.com/

この場合、user:password は接続先ではありません。接続先 hostname は example.com です。

SSRF 対策で問題になりやすいのは、次のような URL です。

http://expected.com@attacker.com/

見た目だけでは expected.com にアクセスしているように見えるかもしれません。しかし実際の hostname は attacker.com です。

const u = new URL('http://expected.com@attacker.com/');

console.log(u.username);
// => 'expected.com'

console.log(u.hostname);
// => 'attacker.com'

new URL() を使えば、userinfo と hostname を分けて取得できます。

SSRF ガードでは、外部入力の URL に userinfo を許可しない方が安全です。

if (u.username !== '' || u.password !== '') {
  throw new Error('URL must not contain userinfo');
}

多くのアプリケーションでは、Webhook 送信先や origin URL に userinfo は不要です。不要なものは拒否しておくと、見た目の紛らわしさを減らせます。

3. IPv6 を bracket 付きの hostname として扱う

IPv6 は [] で囲まれます。

const u = new URL('http://[::1]/');

console.log(u.hostname);
// => '[::1]'

::1 は IPv6 の loopback アドレスです。IPv4 の 127.0.0.1 に近いものです。

IPv6 には、IPv4 よりも表記ゆれがあります。たとえば、0 の連続を :: で省略できます。また、IPv4-mapped IPv6 address(IPv4 アドレスを IPv6 の形で表す形式)のようなものもあります。

const u = new URL('http://[::ffff:127.0.0.1]/');

console.log(u.hostname);
// Node.js では例: '[::ffff:7f00:1]'

SSRF ガードをシンプルに保ちたい場合は、外部入力の URL で IP リテラルそのものを拒否する設計が安全です。

ここでいう IP リテラルとは、ドメイン名ではなく IP アドレスを直接 URL に書く形式です。

http://127.0.0.1/
http://[::1]/

たとえば、次のように判定できます。

function isIpLiteralHostname(hostname: string): boolean {
  if (hostname.startsWith('[')) {
    return true; // IPv6
  }

  // IPv4 は new URL() 後に dotted decimal へ正規化される。
  // この関数は new URL() 後の hostname にだけ使う前提。
  if (/^\d{1,3}(?:\.\d{1,3}){3}$/.test(hostname)) {
    return true; // IPv4 dotted decimal
  }

  return false;
}

この関数は、new URL() で正規化された後の hostname に対して使う前提です。

この正規表現は「IPv4 らしい形」を見るためのものです。生の入力値を検証するための汎用 IPv4 validator ではありません。

生の URL 文字列に対して使うものではありません。

4. ドメイン名の大文字小文字や IDN をそろえる

ドメイン名では、大文字小文字の違いは通常区別されません。

const u = new URL('https://Example.COM/');

console.log(u.hostname);
// => 'example.com'

new URL() に通すと、hostname は小文字に正規化されます。

また、日本語やキリル文字などを含む国際化ドメイン名は、punycode(ASCII 化された表記)に変換されます。

const u = new URL('https://тест.example/');

console.log(u.hostname);
// => 'xn--e1aybc.example'

そのため、ドメインの許可リストと比較するときは、生の URL 文字列ではなく、u.hostname を使う方が扱いやすくなります。

const allowedHosts = new Set([
  'example.com',
  'api.example.com',
]);

const u = new URL(rawUrl);

if (!allowedHosts.has(u.hostname)) {
  throw new Error('host is not allowed');
}

ただし、IDN には見た目が紛らわしい文字の問題もあります。たとえば、ラテン文字に似た別の文字を使うケースです。

重要な送信先を許可する場合は、「任意の外部ドメインを受け付けて後から危険なものを弾く」よりも、「許可した hostname だけを通す」許可リスト方式の方が安全です。

5. デフォルトポートは空文字になる

URL には port もあります。

const u = new URL('http://example.com:8080/');

console.log(u.port);
// => '8080'

ただし、scheme のデフォルトポートは空文字に正規化されます。

console.log(new URL('http://example.com:80/').port);
// => ''

console.log(new URL('https://example.com:443/').port);
// => ''

これは、http://example.comhttp://example.com:80 が同じデフォルトポートを意味するためです。

ポートを検査するときは、この挙動を前提にします。

たとえば、標準ポートだけを許可し、非標準ポートを拒否したいなら、次のように考えます。

function isAllowedPort(u: URL): boolean {
  // http:80 / https:443 のようなデフォルトポートは空文字になる
  if (u.port === '') {
    return true;
  }

  // 非標準ポートを個別に許可したい場合だけ追加する
  return false;
}

http://example.com:80/https://example.com:443/port は空文字になるので、u.port === '80'u.port === '443' だけを見ても判定できません。

実際に確認してみる

ここまでの挙動は、手元で確認できます。

const cases = [
  'http://10.0.0.1/',
  'http://169.254.169.254/',
  'http://127.0.0.1/',
  'http://localhost/',
  'http://expected.com@attacker.com/',
  'http://0x7f000001/',
  'http://2130706433/',
  'http://[::1]/',
  'http://[::ffff:127.0.0.1]/',
  'http://example.com:80/',
  'https://Example.COM./',
  'https://тест.example/',
];

for (const c of cases) {
  const u = new URL(c);

  console.log(c, '=>', {
    protocol: u.protocol,
    username: u.username,
    password: u.password,
    hostname: u.hostname,
    port: u.port,
  });
}

Node.js で実行すると、たとえば次のような結果になります。

http://0x7f000001/
=> { protocol: 'http:', username: '', password: '', hostname: '127.0.0.1', port: '' }

http://2130706433/
=> { protocol: 'http:', username: '', password: '', hostname: '127.0.0.1', port: '' }

http://expected.com@attacker.com/
=> { protocol: 'http:', username: 'expected.com', password: '', hostname: 'attacker.com', port: '' }

http://[::1]/
=> { protocol: 'http:', username: '', password: '', hostname: '[::1]', port: '' }

http://[::ffff:127.0.0.1]/
=> { protocol: 'http:', username: '', password: '', hostname: '[::ffff:7f00:1]', port: '' }

http://example.com:80/
=> { protocol: 'http:', username: '', password: '', hostname: 'example.com', port: '' }

https://Example.COM./
=> { protocol: 'https:', username: '', password: '', hostname: 'example.com.', port: '' }

https://тест.example/
=> { protocol: 'https:', username: '', password: '', hostname: 'xn--e1aybc.example', port: '' }

ここで注目したいのは、見た目が違っても、URL パーサーを通すと検査しやすい形にそろうことです。

0x7f000001127.0.0.1 になります。
expected.com@attacker.com は hostname が attacker.com になります。
Example.COMexample.com になります。
http://example.com:80/ の port は空文字になります。

この結果に対して検査するのが基本です。

SSRF ガードの第一段階を書く

ここから、実際の検査コードを組み立てます。

前提として、これは「SSRF 対策の第一段階」です。このコードだけで SSRF を完全に防げるわけではありません。

特に、hostname を DNS 解決した結果が内部 IP に変わるケースや、DNS rebinding のような問題は、URL parser だけでは防げません。そこは後で説明します。

まずは、外部入力の URL に対して最低限の検査を入れます。

export function assertHttpUrlForServerFetch(rawUrl: string): URL {
  const safeUrlForLog = rawUrl.slice(0, 128);

  let url: URL;

  try {
    url = new URL(rawUrl);
  } catch {
    throw new Error(`invalid URL: ${safeUrlForLog}`);
  }

  // 1. scheme を http / https に限定する
  if (url.protocol !== 'http:' && url.protocol !== 'https:') {
    throw new Error(`URL must use http or https: ${url.protocol}`);
  }

  // 2. userinfo は拒否する
  if (url.username !== '' || url.password !== '') {
    throw new Error(`URL must not contain userinfo: ${safeUrlForLog}`);
  }

  // 3. IP リテラルは拒否する
  if (isIpLiteralHostname(url.hostname)) {
    throw new Error(`URL must not use an IP address directly: ${url.hostname}`);
  }

  // 4. localhost 系の hostname は拒否する
  if (isLoopbackHostname(url.hostname)) {
    throw new Error(`URL must not target localhost: ${url.hostname}`);
  }

  return url;
}

function isIpLiteralHostname(hostname: string): boolean {
  // IPv6 は new URL() 後の hostname では [::1] のように bracket 付きになる
  if (hostname.startsWith('[')) {
    return true;
  }

  // IPv4 は new URL() 後に dotted decimal へ正規化される
  return /^\d{1,3}(?:\.\d{1,3}){3}$/.test(hostname);
}

function isLoopbackHostname(hostname: string): boolean {
  const lower = hostname.toLowerCase();

  return lower === 'localhost' || lower.endsWith('.localhost');
}

使う側はこうです。

const url = assertHttpUrlForServerFetch(inputUrl);

const response = await fetch(url);

このコードでやっていることは、次の4つです。

  1. http: / https: 以外を拒否する
  2. userinfo を拒否する
  3. IP アドレスを直接書いた URL を拒否する
  4. localhost / *.localhost を拒否する

ここで重要なのは、すべて new URL() の結果に対して検査していることです。

0x7f000001 のような URL は、new URL() の時点で 127.0.0.1 に正規化されます。そのため、isIpLiteralHostname() では dotted decimal の IPv4 だけを見れば検出できます。

バリデーションライブラリだけで十分ではないのか

ここで、「Zod や Valibot の url() でよいのでは」と思うかもしれません。

入力値が URL として妥当かを見るだけなら、それで十分な場面は多いです。

たとえば、フォームで「ユーザーのプロフィール URL」を保存するだけなら、URL としてパースできるか、https: かどうか、長すぎないか、という検査で足りるかもしれません。

しかし、サーバー側でその URL にアクセスする場合は話が変わります。

問題は、「URL として正しいか」ではなく、「その URL にサーバーからアクセスしてよいか」です。

一般的なバリデーションライブラリは、主に次のような検査を助けてくれます。

  • URL の構文として妥当か
  • http: / https: など、許可した protocol か
  • host が存在するか
  • userinfo を許可するか禁止するか
  • 文字列として許可リストや拒否リストに合うか

これらは有用です。

ただし、SSRF 対策では、それだけでは足りません。

たとえば、次のような観点は、通常の url() バリデーションだけでは閉じません。

  • hostname を DNS 解決した結果が内部 IP ではないか
  • DNS rebinding で解決結果が変わらないか
  • リダイレクト先が内部 IP へ向かわないか
  • クラウドのメタデータエンドポイントに到達しないか
  • 実行環境から内部ネットワークへ egress できてしまわないか

つまり、バリデーションライブラリは「URL として成立しているか」を確認する道具です。

一方で、SSRF ガードは「サーバー側でアクセスしてよい宛先か」を判断する仕組みです。

両者は重なりますが、同じものではありません。

そのため、バリデーションライブラリを使う場合でも、SSRF 対策としては追加の検査やネットワーク制御が必要になります。

許可リスト方式にできるなら、その方が安全

ここまでのコードは、危険そうな URL を弾く方式です。

しかし、安全性を高めたいなら、できるだけ許可リスト方式に寄せた方がよいです。

つまり、「危険なものを探して拒否する」のではなく、「許可したものだけを通す」方式です。

たとえば、Webhook 送信先や origin URL が事前に決まっているなら、hostname を許可リストで比較します。

const allowedHosts = new Set([
  'example.com',
  'api.example.com',
]);

export function assertAllowedOrigin(rawUrl: string): URL {
  const url = assertHttpUrlForServerFetch(rawUrl);

  if (!allowedHosts.has(url.hostname)) {
    throw new Error(`host is not allowed: ${url.hostname}`);
  }

  return url;
}

OWASP でも、SSRF 対策では scheme、port、destination を positive allowlist、つまり許可リストで制限することが推奨されています。特に、接続先が事前に決まっているシステムでは許可リストが強いです。

一方で、OGP 取得や一般的な URL クロールのように、任意の外部 URL を受け付けたい機能もあります。その場合は許可リストが難しいため、URL パーサーによる検査、DNS 解決後の IP 検査、egress 制御、リダイレクト制御などを組み合わせる必要があります。

URL パーサーの挙動をテストで固定する

ここまでの検査は、new URL() の挙動に依存しています。

たとえば、次の前提があります。

  • 0x7f000001127.0.0.1 に正規化される
  • 2130706433127.0.0.1 に正規化される
  • expected.com@attacker.com は hostname が attacker.com になる
  • IPv6 は bracket 付きの hostname になる
  • デフォルトポートは空文字になる

これらは WHATWG URL Standard に沿った挙動ですが、自分のコードの前提として使っているなら、テストで固定しておくと安心です。

import { describe, expect, it } from 'vitest';

describe('URL parser assumptions', () => {
  it('IPv4 hex 表記は dotted decimal に正規化される', () => {
    expect(new URL('http://0x7f000001/').hostname).toBe('127.0.0.1');
  });

  it('IPv4 decimal 表記も dotted decimal に正規化される', () => {
    expect(new URL('http://2130706433/').hostname).toBe('127.0.0.1');
  });

  it('userinfo は hostname から分離される', () => {
    const u = new URL('http://expected.com@attacker.com/');

    expect(u.username).toBe('expected.com');
    expect(u.hostname).toBe('attacker.com');
  });

  it('IPv6 は bracket 付きの hostname になる', () => {
    expect(new URL('http://[::1]/').hostname).toBe('[::1]');
  });

  it('default port は空文字になる', () => {
    expect(new URL('http://example.com:80/').port).toBe('');
    expect(new URL('https://example.com:443/').port).toBe('');
  });
});

これは、一見すると「自分のコードではなく、標準ライブラリをテストしている」ように見えます。

しかし、目的は標準ライブラリの正しさを検証することではありません。

自分の SSRF ガードが依存している前提を、CI 上で見える形にすることです。Node.js や Edge runtime を更新したときに、もし URL パーサーの挙動が変われば、このテストが気づくきっかけになります。

この検査だけでは防げないもの

ここまでの話は、URL 文字列をパースする段階の防御です。

ただし、SSRF は URL パーサーだけでは閉じません。

ここは重要です。

DNS rebinding

DNS rebinding は、hostname の解決結果があとから変わる問題です。

たとえば、最初は外部 IP を返す hostname が、次の DNS 解決では 127.0.0.1 やプライベート IP を返すようなケースです。

URL パーサーは DNS 解決をしません。

const u = new URL('http://evil.example.com/');
console.log(u.hostname);
// => 'evil.example.com'

この時点では、evil.example.com がどの IP に解決されるかはわかりません。

つまり、new URL() による検査を通っても、実際の fetch() 時に内部 IP へ向かう可能性は残ります。

対策としては、次のような設計が必要です。

  • DNS 解決後の IP アドレスを検査する
  • 内部 IP・リンクローカル IP・メタデータ IP への egress をネットワーク側で止める
  • リダイレクト先も再検査する
  • 可能なら接続先を許可リストで制限する

クラウドのメタデータエンドポイント

クラウド環境では、メタデータエンドポイントにも注意が必要です。

代表例として、AWS では 169.254.169.254 がインスタンスメタデータサービスのアドレスとして使われます。

このような IP を直接 URL に書かれた場合、先ほどの IP リテラル拒否で止められます。

http://169.254.169.254/

しかし、hostname 経由で内部のメタデータサービスに到達できる環境もあります。

たとえば、クラウド環境によっては metadata.google.internal のような hostname がメタデータサービスに使われます。

このような名前は、IP アドレスを直接書いた URL ではないため、単純な IP リテラル拒否では止まりません。

そのため、メタデータエンドポイントへのアクセスは、アプリケーションの URL 検査だけでなく、クラウド側・ネットワーク側の egress 制御、つまりサーバーから外向きに出ていく通信の制御でも防ぐ必要があります。

リダイレクト

URL の最初の宛先だけを検査しても、リダイレクト先が危険な場合があります。

https://safe.example/redirect?to=http://127.0.0.1/

最初の URL は安全そうに見えても、fetch が自動的にリダイレクトをたどると、内部 IP に向かう可能性があります。

そのため、SSRF 対策ではリダイレクトにも注意します。

  • 自動リダイレクトを無効にする
  • リダイレクト先 URL を再度検査する
  • リダイレクト回数に上限を設ける

new URL() の検査は、最初の URL だけでなく、リダイレクト先にも必要です。

末尾ドット付きのホスト名

ドメイン名には、末尾に . が付くことがあります。

const u = new URL('https://Example.COM./');

console.log(u.hostname);
// => 'example.com.'

example.comexample.com. は、DNS の文脈では近い意味を持つことがあります。しかし、文字列比較では別物です。

許可リストと比較するときに、末尾の dot をどう扱うかは設計で決める必要があります。

たとえば、許可リストに example.com だけを入れている場合、example.com. を同じものとして扱うのか、それとも拒否するのかを決めます。

単純に扱うなら、外部入力では trailing dot を拒否してもよいです。

if (url.hostname.endsWith('.')) {
  throw new Error(`hostname must not end with a dot: ${url.hostname}`);
}

または、末尾の dot を取り除いてから許可リストと比較する設計もあります。

どちらにするかは、サービスの要件次第です。

まとめ

URL を検査するときは、文字列の見た目だけで判断しない方が安全です。

0x7f000001 のような IPv4 の特殊表記は、new URL() に通すと 127.0.0.1 に正規化されます。
expected.com@attacker.com のような URL は、expected.com ではなく attacker.com が hostname として扱われます。
国際化ドメイン名や大文字小文字、デフォルトポートも、URL パーサーの規則に従ってそろえられます。

そのため、SSRF ガードでは次の順番が基本になります。

  1. 生の URL 文字列を new URL() に通す
  2. protocol / username / password / hostname / port を見る
  3. http: / https: 以外を拒否する
  4. userinfo を拒否する
  5. IP リテラルや localhost を拒否する
  6. 可能なら hostname を許可リストで制限する
  7. DNS 解決後の IP、リダイレクト、egress 制御も別途考える

一般的なバリデーションライブラリは、URL として妥当かを確認するには便利です。

しかし、サーバー側で fetch する URL では、それだけでは足りません。必要なのは、「URL として正しいか」だけでなく、「サーバーからアクセスしてよい宛先か」を判断することです。

new URL() は SSRF 対策の全部ではありません。

しかし、最初に URL を正規化し、URL パーサーが解釈した結果を見ることで、文字列ベースの検査よりも URL の意味に沿ったガードを書きやすくなります。

SSRF 対策では、「URL 文字列をそのまま見ない」ことが出発点になります。まずパースし、正規化された結果を見て判断します。

参考資料

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?