PHP 内核层网络请求安全,最佳方式是“默认全拒绝,按白名单放行”,并且在4层一起做:内核/扩展拦截 + PHP 配置 + 业务 SDK +forshortcuts 系统防火墙。───────────────────────────────────────────────────────────────────────────────────────────────────────── 下面我给你一套“能落地”的完整方案(大白话 + 代码模板)。 --- 先看风险入口(你要管住这些): -curl发 HTTP/HTTPS - file_get_contents("http://...")这类 stream wrapper - socket 直连(fsockopen, stream_socket_client) - 扩展偷偷出网(redis、amqp、grpc、第三方 SDK) --- 最佳架构(生产推荐)1. 第1层(最硬):自定义 PHP 扩展,在网络函数入口做白名单校验2. 第2层(最省事):php.ini 关掉高风险默认能力3. 第3层(最好维护):业务统一走“安全 HTTP 客户端”4. 第4层(兜底):iptables/nftables 只允许指定 IP:Port 出网 --- 一、php.ini 基线(先收口);关掉 URL 文件包装器(防 file_get_contents 远程拉取) allow_url_fopen=Off allow_url_include=Off;隐藏版本信息 expose_php=Off;可选:禁用直接 socket(按业务决定) disable_functions=fsockopen,pfsockopen,stream_socket_client,stream_socket_server;错误日志 display_errors=Off log_errors=On --- 二、业务统一安全客户端(完整 PHP 代码) 文件:SafeHttpClient.php<?php declare(strict_types=1);final class SafeHttpClient{private array$allowHosts;private array$allowPorts;private int$connectTimeout;private int$totalTimeout;publicfunction__construct(array$allowHosts, array$allowPorts=[80,443], int$connectTimeout=3, int$totalTimeout=10){$this->allowHosts=array_map('strtolower',$allowHosts);$this->allowPorts=$allowPorts;$this->connectTimeout=$connectTimeout;$this->totalTimeout=$totalTimeout;}publicfunctionget(string$url, array$headers=[]): array{$parsed=$this->validateUrl($url);$ch=curl_init();curl_setopt_array($ch,[CURLOPT_URL=>$url, CURLOPT_RETURNTRANSFER=>true, CURLOPT_FOLLOWLOCATION=>false, // 禁止自动跳转,防跳白名单外 CURLOPT_MAXREDIRS=>0, CURLOPT_CONNECTTIMEOUT=>$this->connectTimeout, CURLOPT_TIMEOUT=>$this->totalTimeout, CURLOPT_PROTOCOLS=>CURLPROTO_HTTP|CURLPROTO_HTTPS, CURLOPT_REDIR_PROTOCOLS=>CURLPROTO_HTTP|CURLPROTO_HTTPS, CURLOPT_SSL_VERIFYPEER=>true, CURLOPT_SSL_VERIFYHOST=>2, CURLOPT_HTTPHEADER=>$headers,]);$body=curl_exec($ch);$errno=curl_errno($ch);$error=curl_error($ch);$code=(int)curl_getinfo($ch, CURLINFO_HTTP_CODE);curl_close($ch);if($errno!==0){throw new RuntimeException("HTTP request failed: {$error}");}return['host'=>$parsed['host'],'status'=>$code,'body'=>(string)$body,];}privatefunctionvalidateUrl(string$url): array{$p=parse_url($url);if(!is_array($p)||empty($p['scheme'])||empty($p['host'])){throw new InvalidArgumentException('Invalid URL');}$scheme=strtolower($p['scheme']);$host=strtolower($p['host']);$port=isset($p['port'])?(int)$p['port']:($scheme==='https'?443:80);if(!in_array($scheme,['http','https'],true)){throw new RuntimeException("Blocked scheme: {$scheme}");}if(!in_array($host,$this->allowHosts,true)){throw new RuntimeException("Blocked host: {$host}");}if(!in_array($port,$this->allowPorts,true)){throw new RuntimeException("Blocked port: {$port}");}// 防 SSRF:阻止内网/回环 IP(域名解析后校验)$ips=gethostbynamel($host)?:[];foreach($ipsas$ip){if($this->isPrivateOrLoopbackIp($ip)){throw new RuntimeException("Blocked private/loopback target: {$ip}");}}return['scheme'=>$scheme,'host'=>$host,'port'=>$port];}privatefunctionisPrivateOrLoopbackIp(string$ip): bool{if(!filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4)){returntrue;}$long=ip2long($ip);if($long===false)returntrue;$ranges=[['0.0.0.0','0.255.255.255'],['10.0.0.0','10.255.255.255'],['127.0.0.0','127.255.255.255'],['169.254.0.0','169.254.255.255'],['172.16.0.0','172.31.255.255'],['192.168.0.0','192.168.255.255'],];foreach($rangesas[$start,$end]){if($long>=ip2long($start)&&$long<=ip2long($end)){returntrue;}}returnfalse;}}使用示例:<?php require'SafeHttpClient.php';$client=new SafeHttpClient(allowHosts:['api.example.com','auth.example.com'], allowPorts:[443]);$result=$client->get('https://api.example.com/v1/ping');echo$result['status'].PHP_EOL;--- 三、内核/扩展层拦截(C 扩展模板) 你要“底层管控”,这个才是关键。思路是 hook 执行过程,拦截高风险网络函数调用(curl_exec, fsockopen, stream_socket_client 等)。 文件:php_netguard.c(简化模板)#ifdef HAVE_CONFIG_H#include "config.h"#endif#include "php.h"#include "zend_extensions.h"#include "ext/standard/info.h"static zend_execute_ex_hook_t old_execute_ex=NULL;static int netguard_enabled=1;static int is_blocked_function(const zend_string *fname){if(!fname)return0;const char *deny[]={"fsockopen","pfsockopen","stream_socket_client","stream_socket_server","curl_exec", NULL};for(int i=0;deny[i]!=NULL;i++){if(zend_string_equals_literal_ci(fname, deny[i])){return1;}}return0;}static void netguard_execute_ex(zend_execute_data *execute_data){if(netguard_enabled&&execute_data&&execute_data->func){zend_function *func=execute_data->func;if(func->common.function_name&&is_blocked_function(func->common.function_name)){zend_throw_error(NULL,"netguard blocked function: %s", ZSTR_VAL(func->common.function_name));return;}}old_execute_ex(execute_data);}PHP_INI_BEGIN()STD_PHP_INI_BOOLEAN("netguard.enabled","1", PHP_INI_SYSTEM, OnUpdateBool, netguard_enabled, zend_netguard_globals, netguard_globals)PHP_INI_END()PHP_MINIT_FUNCTION(netguard){REGISTER_INI_ENTRIES();old_execute_ex=zend_execute_ex;zend_execute_ex=netguard_execute_ex;returnSUCCESS;}PHP_MSHUTDOWN_FUNCTION(netguard){zend_execute_ex=old_execute_ex;UNREGISTER_INI_ENTRIES();returnSUCCESS;}PHP_MINFO_FUNCTION(netguard){php_info_print_table_start();php_info_print_table_header(2,"netguard support","enabled");php_info_print_table_end();}zend_module_entry netguard_module_entry={STANDARD_MODULE_HEADER,"netguard", NULL, PHP_MINIT(netguard), PHP_MSHUTDOWN(netguard), NULL, NULL, PHP_MINFO(netguard),"0.1.0", STANDARD_MODULE_PROPERTIES};#ifdef COMPILE_DL_NETGUARDZEND_GET_MODULE(netguard)#endifconfig.m4: PHP_ARG_ENABLE(netguard, whether toenablenetguard extension,[--enable-netguard Enable netguard extension], no)iftest"$PHP_NETGUARD"!="no";thenPHP_NEW_EXTENSION(netguard, php_netguard.c,$ext_shared)fi编译安装: phpize ./configure --enable-netguardmake-j"$(nproc)"makeinstallphp.ini:extension=netguard.sonetguard.enabled=1--- 四、系统层兜底(必须做) 示例(只允许访问固定出口 IP):# 默认拒绝出站iptables-POUTPUT DROP# 放行 DNS 到内网DNSiptables-AOUTPUT-pudp--dport53-d10.0.0.53-jACCEPT# 放行到指定 APIiptables-AOUTPUT-ptcp-d203.0.113.10--dport443-jACCEPT# 放行本机必要通信iptables-AOUTPUT-olo-jACCEPT --- 五、最佳上线方式(避免误伤)1. 先审计模式(只记录不拦)3-7 天2. 统计真实目标域名/IP/端口3. 生成白名单4. 切阻断模式5. 告警监控拦截日志(来源文件、函数、目标地址) --- 最终落地一句话: “内核拦截负责硬限制,业务 SDK 负责可维护,系统防火墙负责兜底”,三层同开,PHP 网络请求安全才真正稳。