a-blog cms PHP API Reference

SsrfGuard
in package

外部 URL をサーバー側で取得する経路の SSRF ガード。

取得先ホストを名前解決し、ループバック・プライベート・リンクローカルなどの 予約済みアドレスへ向かうリクエストを拒否する。社内 API のように意図的に 内部宛の取得を許可したい場合は、許可リスト(IP アドレスまたは CIDR)で免除する。

Table of Contents

Constants

DEFAULT_WHITE_LIST_ENV_KEY  = 'ACMS_SSRF_WHITE_LIST'
許可リストを解決する既定の環境変数キー
ALLOWED_SCHEMES  = ['http', 'https']
取得を許可するスキーム
DENIED_SUBNETS  = [ '::ffff:0:0/96', // IPv4-mapped(PHP 8.1/8.2 の filter_var は弾かない) '::/96', // IPv4-compatible '2002::/16', // 6to4 '2001::/32', // Teredo '64:ff9b::/96', // NAT64 well-known prefix '64:ff9b:1::/48', ]
filter_var() の NO_PRIV_RANGE / NO_RES_RANGE では弾けない、内部アドレスを埋め込める IPv6 の遷移表記。

Properties

$dnsResolver  : callable(string): array<string|int, string>
ホスト名から IP アドレス一覧を解決するリゾルバ
$whiteList  : array<string|int, string>
SSRF ガードを免除する IP アドレス/CIDR 一覧

Methods

__construct()  : mixed
assertSafeUri()  : void
URI の取得先が内部/予約済みアドレスを指していないか検証する
isAllowedHttpScheme()  : bool
取得を許可するスキームかどうかを判定する
parseWhiteList()  : array<string|int, string>
カンマ区切りの設定値を許可リストへ変換する
resolveSafeIpAddresses()  : array<string|int, string>
URI の取得先を検証し、検証を通った IP アドレス一覧を返す。
ipMatchesEntry()  : bool
IP アドレスが許可リストの 1 エントリ(単一 IP または CIDR 表記)に一致するか判定する
isSafeIpAddress()  : bool
プライベート/予約済みアドレスでないか確認する
isWhitelistedIpAddress()  : bool
IP アドレスが許可リストのいずれかのエントリに一致するか確認する
resolveIpAddressesByDns()  : array<string|int, string>
ホスト名を解決して、検証対象の IP アドレス一覧を返す既定のリゾルバ

Constants

DEFAULT_WHITE_LIST_ENV_KEY

許可リストを解決する既定の環境変数キー

public mixed DEFAULT_WHITE_LIST_ENV_KEY = 'ACMS_SSRF_WHITE_LIST'

ALLOWED_SCHEMES

取得を許可するスキーム

private mixed ALLOWED_SCHEMES = ['http', 'https']

DENIED_SUBNETS

filter_var() の NO_PRIV_RANGE / NO_RES_RANGE では弾けない、内部アドレスを埋め込める IPv6 の遷移表記。

private mixed DENIED_SUBNETS = [ '::ffff:0:0/96', // IPv4-mapped(PHP 8.1/8.2 の filter_var は弾かない) '::/96', // IPv4-compatible '2002::/16', // 6to4 '2001::/32', // Teredo '64:ff9b::/96', // NAT64 well-known prefix '64:ff9b:1::/48', ]

これらは IPv4 アドレスを IPv6 表記の中に埋め込むもので、filter_var() はビット列を 予約帯と比較するだけなので埋め込まれた IPv4 を見ない。たとえば ::7f00:1 / 2002:7f00:1:: / 64:ff9b::7f00:1 はいずれも 127.0.0.1 を指すが素通りする (Symfony の NoPrivateNetworkHttpClient も同じ穴で CVE-2026-48736 が出ている)。 6to4 は RFC 7526 で廃止済み、Teredo・NAT64 も外部コンテンツの取得先として 正当な用途が無いため、帯ごと拒否する。

IPv4-mapped(::ffff:0:0/96)は PHP 8.3 以降の filter_var では弾かれるが 8.1 / 8.2 では 通過する(CI で実測)。サポート対象の全バージョンで同じ判定にするため明示的に持つ。

Properties

$dnsResolver

ホスト名から IP アドレス一覧を解決するリゾルバ

private callable(string): array<string|int, string> $dnsResolver

$whiteList

SSRF ガードを免除する IP アドレス/CIDR 一覧

private array<string|int, string> $whiteList

Methods

__construct()

public __construct([array<string|int, string>|null $whiteList = null ][, callable(string): array<string|int, string>|null $dnsResolver = null ]) : mixed
Parameters
$whiteList : array<string|int, string>|null = null

免除する IP アドレス/CIDR 一覧。null なら既定の環境変数から解決する

$dnsResolver : callable(string): array<string|int, string>|null = null

名前解決の実装(テスト時の差し替え用)

assertSafeUri()

URI の取得先が内部/予約済みアドレスを指していないか検証する

public assertSafeUri(string $uri) : void
Parameters
$uri : string
Tags
throws
RuntimeException

取得を許可できない URI の場合

isAllowedHttpScheme()

取得を許可するスキームかどうかを判定する

public static isAllowedHttpScheme(string $uri) : bool
Parameters
$uri : string
Return values
bool

parseWhiteList()

カンマ区切りの設定値を許可リストへ変換する

public static parseWhiteList(string $value) : array<string|int, string>
Parameters
$value : string

カンマ区切りの IP アドレス/CIDR

Return values
array<string|int, string>

resolveSafeIpAddresses()

URI の取得先を検証し、検証を通った IP アドレス一覧を返す。

public resolveSafeIpAddresses(string $uri) : array<string|int, string>

Why not: 検証だけで IP を捨てないのは、検証と実接続で別々に名前解決すると、 その間に DNS 応答を切り替えられる(DNS リバインディングによる TOCTOU)ため。 呼び出し側は返された IP をそのまま接続先に固定(DNS ピンニング)すること。

Parameters
$uri : string
Tags
throws
RuntimeException

取得を許可できない URI の場合

Return values
array<string|int, string> —

検証を通った IP アドレス一覧

ipMatchesEntry()

IP アドレスが許可リストの 1 エントリ(単一 IP または CIDR 表記)に一致するか判定する

private static ipMatchesEntry(string $ipAddress, string $entry) : bool

IPv4/IPv6 の両方に対応する。

Parameters
$ipAddress : string
$entry : string
Return values
bool

isSafeIpAddress()

プライベート/予約済みアドレスでないか確認する

private isSafeIpAddress(string $ipAddress) : bool
Parameters
$ipAddress : string
Return values
bool

isWhitelistedIpAddress()

IP アドレスが許可リストのいずれかのエントリに一致するか確認する

private isWhitelistedIpAddress(string $ipAddress) : bool
Parameters
$ipAddress : string
Return values
bool

resolveIpAddressesByDns()

ホスト名を解決して、検証対象の IP アドレス一覧を返す既定のリゾルバ

private static resolveIpAddressesByDns(string $host) : array<string|int, string>

dns_get_record() と gethostbynamel() の両方を引き、結果を合併して返す。

Why not: どちらか一方を優先してもう一方をフォールバックにしないのは、両者でホストの 解釈が違い、片方だけ見ると実接続先を取り逃がすため。たとえば "0177.0.0.1" は dns_get_record が DNS 問い合わせとして扱って 177.0.0.1(公開 IP)を返す一方、 gethostbynamel は inet_aton の 8 進数解釈で 127.0.0.1 を返す。curl が実際に繋ぐのは 後者なので、dns_get_record だけを見ると内部宛の接続を許してしまう(実測で確認)。 合併して全件を検証すれば、どちらの解釈で内部アドレスになる場合も拒否できる。

両方が空のときだけ「解決不能」として拒否する。dns_get_record が無効化されている 環境でも gethostbynamel が効けば従来どおり取得できる。

Parameters
$host : string
Return values
array<string|int, string>

        
On this page

Search results