NovinkaZverejnili sme 50+ Laravel balíkov ako open source
Custom AI apps, agents and automation — Roundly ConsultingRoundly
Všetky balíky
Safe Fetch for Laravel

URL adresy a blokované siete

Kanonizácia

Každú URL adresu parsuje ochrana sama, nikdy vlastný parser URL v PHP: ten a curl čítajú niektorých hostiteľov rôzne (http://a.com\@127.0.0.1/, http://127.0.0.1#@a.com/), takže ochrana by overila jedného hostiteľa a curl by vytočil iného. Všetko, čo by parser mohol prečítať dvoma spôsobmi, sa odmietne a požiadavka sa pošle na URL prestavanú z overených častí. V tomto poradí, rozhoduje prvé zlyhanie:

  • Riadiaci znak C0 alebo DEL kdekoľvek → invalid_url.
  • Chýba scheme: → invalid_url; iná schéma než http alebo https → scheme.
  • Chýba // alebo je autorita prázdna → invalid_url.
  • Medzera, \, %, Unicode medzera alebo U+FEFF v autorite → non_canonical_host (hostiteľ kódovaný percentami, IPv6 zone id).
  • @ v autorite → credentials.
  • Port, ktorý nie sú číslice → invalid_url; mimo 1–65535 → port.
  • Hostiteľ: IPv6 adresa v hranatých zátvorkách musí byť platná a skráti sa ([0:0:0:0:0:0:0:1] → [::1]); ne-ASCII alebo xn-- časť prejde cez UTS #46 (vyžaduje ext-intl); malé písmená; jedna koncová bodka sa vynechá; hostiteľ, ktorého posledná časť je číselná alebo hexadecimálna, musí mať presne štyri desiatkové časti 0–255 bez úvodných núl, napísané práve tak — inak non_canonical_host.
  • \ v ceste, query alebo fragmente → invalid_url.

Fragment sa vynechá. Výsledkom je CanonicalUrl — toString() vráti schému a hostiteľa malými písmenami, bez používateľských údajov, fragmentu a predvoleného portu, IPv6 skrátenú v zátvorkách, cestu a query bez zmeny (prázdne → /); origin() vráti scheme://host:port s vždy uvedeným portom. Pravidlo schémy sa uplatní až potom, takže kanonická URL http:// sa naparsuje v poriadku a odmietne sa (scheme) pri overení.

Tabuľka blokovaných sietí

RozsahČo
0.0.0.0/8sieť „this“
10.0.0.0/8 · 172.16.0.0/12 · 192.168.0.0/16privátne
100.64.0.0/10carrier-grade NAT (metadáta Alibaba Cloud na 100.100.100.200)
127.0.0.0/8loopback
168.63.129.16/32Azure WireServer (verejný adresný priestor, dostupný z každého Azure VM a uzla AKS)
169.254.0.0/16link-local: metadáta AWS, GCP, Azure, OpenStack na 169.254.169.254
192.0.0.0/24pridelenia protokolov IETF (metadáta Oracle Cloud na 192.0.0.192)
192.0.2.0/24 · 198.51.100.0/24 · 203.0.113.0/24dokumentácia
192.88.99.0/246to4 relay anycast
198.18.0.0/15benchmarking
224.0.0.0/4multicast
240.0.0.0/4rezervované, broadcast
64:ff9b:1::/48NAT64, lokálne použitie
100::/64discard
2001::/23pridelenia protokolov IETF (vrátane Teredo)
2001:db8::/32 · 3fff::/20dokumentácia
5f00::/16SRv6 SID
fc00::/7unique local (metadáta AWS cez IPv6 na fd00:ec2::254)
fe80::/10link-local
fec0::/10site-local
ff00::/8multicast
  • IPv6 adresa, ktorá nesie IPv4 adresu, sa posudzuje podľa tejto IPv4 adresy — podľa prvej zhody z ::ffff:0:0/96 (mapped), ::/96 (compatible, vrátane :: a ::1), 64:ff9b::/96 (NAT64), ::ffff:0:0:0/96 (translated) a 2002::/16 (6to4). [::ffff:127.0.0.1] a [2002:a9fe:a9fe::] sú preto blokované a 64:ff9b::808:808 (NAT64 pre 8.8.8.8) je verejná.
  • localhost a každý *.localhost sa odmietnu podľa mena ešte pred prekladom.
  • Všetko nečitateľné sa považuje za privátne. IPv6 zone id pri adrese sa pri posudzovaní ignoruje — v URL sa odmietne (%).

PrivateNetworks

RoundlyConsulting\SafeFetch\Support\PrivateNetworks obsahuje tabuľku — verejnú, čistú a statickú, bez konfigurácie a bez prekladu. Pravidlá na ňu uplatňuje ochrana; SafeFetch::networks() obaľuje isPrivate(), isBlockedHostname() a BLOCKED pre kód, ktorý uprednostňuje fasádu:

ČlenČo
BLOCKEDlist<string> — 26 rozsahov vyššie.
IPV4_EMBEDDING5 IPv6 prefixov, ktoré nesú IPv4 adresu, a bajt, na ktorom začína.
METADATA6 cloudových metadátových rozsahov a adries.
BLOCKED_HOSTNAMES · BLOCKED_HOSTNAME_SUFFIXES['localhost'] · ['.localhost'] — odmietnuté podľa zápisu.
isPrivate(string $address): boolV tabuľke, alebo nesie adresu, ktorá v nej je; nečitateľná → true; zone id sa ignoruje.
isMetadata(string $address): boolCloudový metadátový koncový bod alebo IPv6 adresa, ktorá ho nesie.
isBlockedHostname(string $host): boollocalhost alebo *.localhost, v akejkoľvek veľkosti písmen, jedna koncová bodka sa vynechá.
carriedIpv4(string $address): ?stringIPv4 adresa, ktorú nesie IPv6 adresa, v bodkovom zápise; null pre IPv4, adresu, ktorá žiadnu nenesie, alebo nečitateľný vstup.
use RoundlyConsulting\SafeFetch\Support\PrivateNetworks;

PrivateNetworks::carriedIpv4('2002:a9fe:a9fe::');        // '169.254.169.254' — 6to4
PrivateNetworks::carriedIpv4('64:ff9b::808:808');        // '8.8.8.8' — NAT64
PrivateNetworks::isMetadata('::ffff:169.254.169.254');   // true

Pomocné metódy na kontrolu

Handly a overený cieľ ukazujú, čo ochrana vypočíta, bez toho, aby sa čokoľvek odoslalo:

SafeFetch::networks()->isPrivate('169.254.169.254');        // true — cloud metadata
SafeFetch::networks()->isPrivate('::ffff:10.0.0.1');        // true — judged by the IPv4 address it carries
SafeFetch::networks()->isBlockedHostname('api.localhost');  // true
SafeFetch::networks()->blockedRanges();                     // the 26 ranges, IPv4 and IPv6

$url = SafeFetch::urls()->canonicalise('HTTPS://Example.COM.:443/a?b#frag');
$url->toString();                                           // 'https://example.com/a?b'
$url->origin();                                             // 'https://example.com:443'
SafeFetch::urls()->resolveRedirect('https://example.com/a/b', '../c'); // 'https://example.com/c'

$vetted = SafeFetch::vet('https://hooks.example/in');
$vetted->addresses;                                         // every address the name resolved to, each judged
$vetted->resolveEntry();                                    // 'hooks.example:443:93.184.216.34,[2606:2800:220:1:248:1893:25c8:1946]'
$vetted->connectsTo('::ffff:93.184.216.34');                // true — the mapped form is the same address

canonicalise() vyhodí DestinationRejected s dôvodom parsera (invalid_url, scheme, credentials, non_canonical_host, port); resolveRedirect() ju vyhodí (invalid_url) pre relatívny odkaz, na ktorom sa parsery nezhodnú.

Vlastný resolver

Každý preklad ide cez Contracts\HostResolver — jedinú metódu resolve(string $host): array, ktorá vráti všetky IPv4 a IPv6 adresy (IPv6 bez zátvoriek), alebo [], ak sa meno nepreloží. Hostiteľ, ktorého dostane, je už kanonický. Predvolený Support\SystemHostResolver používa gethostbynamel() (systémový resolver vrátane súboru hosts) a dns_get_record(DNS_AAAA). Čokoľvek resolver vráti, sa aj tak posúdi a pripne: privátnu adresu nepustí a odpoveď, ktorá nie je adresa, sa odmietne.

use RoundlyConsulting\SafeFetch\Contracts\HostResolver;

final class DnsRecordResolver implements HostResolver
{
    public function resolve(string $host): array
    {
        $addresses = [];

        foreach (@dns_get_record($host, DNS_A | DNS_AAAA) ?: [] as $record) {
            $addresses[] = $record['ip'] ?? $record['ipv6'] ?? null;
        }

        return array_values(array_unique(array_filter($addresses, is_string(...))));
    }
}
// config/safe-fetch.php
'resolver' => App\Support\DnsRecordResolver::class,

// or in a service provider
app()->instance(HostResolver::class, new DnsRecordResolver);

Trieda, ktorá nie je inštancovateľný HostResolver, vyhodí pri vytvorení InvalidConfigurationException. V testoch použite namiesto toho SafeFetch::fakeDns().

Prejavte lásku k open source

Tento balík je zadarmo pod licenciou MIT. Ak vám šetrí čas, jednorazový príspevok alebo členstvo na Patreone nám pomôže ho ďalej udržiavať, testovať a dokumentovať.

Ďalšie spôsoby podpory vrátane kryptomien

Odoslaním daru súhlasíte s našimi podmienkami prijímania darov.

Chcete to zabudovať do svojho produktu?

Naše balíky integrujeme do zákazkových Laravel a AI riešení. Napíšte nám, na čom pracujete, a ozveme sa do 48 hodín.