null, 'blockReason' => 'Некорректный URL webhook.']; } $host = trim($host, '[]'); $ips = self::resolve($host); foreach ($ips as $ip) { if (! self::isPublicIp($ip)) { return ['ip' => null, 'blockReason' => 'URL webhook ведёт во внутреннюю/зарезервированную сеть — запрещено.']; } } // Все записи публичны (или хост не резолвится → ip=null, пиннинг не // применяется, запрос упадёт сам — как и в blockReason). return ['ip' => $ips[0] ?? null, 'blockReason' => null]; } /** @return list Все IP, в которые разрешается хост (пусто, если не разрешается). */ private static function resolve(string $host): array { if (filter_var($host, FILTER_VALIDATE_IP) !== false) { return [$host]; // IP-литерал — без DNS } $ips = []; $v4 = gethostbynamel($host); if (is_array($v4)) { $ips = array_merge($ips, $v4); } $aaaa = @dns_get_record($host, DNS_AAAA); if (is_array($aaaa)) { foreach ($aaaa as $rec) { if (isset($rec['ipv6']) && is_string($rec['ipv6'])) { $ips[] = $rec['ipv6']; } } } return array_values(array_unique($ips)); } private static function isPublicIp(string $ip): bool { if (filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4) !== false) { return filter_var( $ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4 | FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE ) !== false; } if (filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6) !== false) { $lower = strtolower($ip); // loopback / unspecified if ($lower === '::1' || $lower === '::') { return false; } // link-local fe80::/10 if (preg_match('/^fe[89ab]/', $lower) === 1) { return false; } // unique-local fc00::/7 if ($lower[0] === 'f' && in_array($lower[1], ['c', 'd'], true)) { return false; } // IPv4-mapped ::ffff:a.b.c.d — проверить встроенный IPv4 if (str_contains($lower, '::ffff:')) { $v4 = substr($lower, (int) strrpos($lower, ':') + 1); if (filter_var($v4, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4) !== false) { return self::isPublicIp($v4); } } return filter_var( $ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6 | FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE ) !== false; } return false; } }