尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

FrankenPHP 安全模型:Go 与 PHP 之间的信任边界解析

FrankenPHP 安全模型:Go 与 PHP 之间的信任边界解析 FrankenPHP 安全模型Go 与 PHP 之间的信任边界解析【免费下载链接】frankenphp The modern PHP app server项目地址: https://gitcode.com/GitHub_Trending/fr/frankenphp本篇技术指南系统梳理 FrankenPHP 的信任模型trust model哪些输入是可信任的、哪些是不可信任的信任边界究竟落在哪里。FrankenPHP 以 CGO 方式把 PHP 解释器嵌入 Go/Caddy 服务器见 README.md 与 docs/internals.md因此其攻击面横跨 Go、C、PHP 三层。读完本文你将能够为安全审计、自动化扫描器与渗透测试正确划定 FrankenPHP 自身的责任范围并理解请求映射、脚本路径解析、Worker 状态隔离、环境变量沙箱、慢速请求防御等关键防线背后的源码实现。本文聚焦于FrankenPHP 本身的安全边界而非它所承载的 PHP 应用程序。信任边界四个参与者FrankenPHP 运行的整个技术栈由四个截然不同的参与者构成它们各自的信任级别决定了安全审计时需要检视的对象参与者信任级别说明远程客户端Remote client不可信任HTTP 请求方法、URI、请求头、Cookie、请求体、上传文件是污染tainted输入的主要来源运维人员Operator可信任提供部署配置Caddyfile、环境变量、php.ini、已安装的 PHP 扩展与 Caddy 模块以及应用代码本身PHP 应用代码按来源可信任trustedby provenance由运维人员部署因此 FrankenPHP 永远不会执行攻击者提供的代码但这段代码会消费不可信任的请求数据FrankenPHPGo C可信计算基TCB内嵌 PHP负责数据进出传输并隔离请求与线程。它自身的缺陷才是本文档所界定的审计范围这一分层明确回答了安全审计中最常见的问题我们到底该信任 PHP 吗——答案取决于你问的是代码还是数据。代码来源可信 vs. 数据污染最关键的一对概念这是整个信任模型中最重要、也是最能化解我们到底信不信任 PHP这一困惑的区分代码来源Code provenance是可信任的。FrankenPHP 只执行由运维人员部署的 PHP 文件文档根目录下解析出的脚本或配置好的 Worker 脚本。它从不评估请求本身携带的代码请求体、查询字符串、请求头SAPI 边界传递的是数据永远不会是不可信任的代码。请求路径的分割逻辑位于 cgi.go 的splitCgiPath最终脚本路径由sanitizedPathJoincgi.go确定——这部分在后文脚本路径解析小节展开。请求数据是污染的。PHP 代码从请求中读取的一切$_GET、$_POST、$_COOKIE、$_FILES、$_SERVER、php://input都是不可信任的这与任何 PHP SAPI 完全一致。清洗这些数据是应用层的责任。因此我们信任来自 PHP 的东西对代码成立而SAPI 承载不可信任的输入对数据成立二者并不矛盾。FrankenPHP 的职责是忠实地搬运这些污染数据并确保一个请求的数据不会泄漏到另一个请求中去。FrankenPHP 的三大职责可信计算基TCB承担三项任务FrankenPHP 自身的安全缺陷必居其一忠实传输Faithful transport将请求映射为 PHP 超全局变量与php://input并把 PHP 的输出与响应头带回客户端过程中不得引入注入请求头/CRLF 注入、请求走私、执行错误文件。隔离Isolation防止请求级状态在请求之间、PHP 线程之间以及 Worker 迭代之间发生串扰。内存安全Memory safety管理 Go ↔ C/PHP 之间的 CGO 边界不产生内存破坏。范围内FrankenPHP 自身的攻击面以下表面归 FrankenPHP 所有这里的漏洞即 FrankenPHP 漏洞1. 请求到超全局变量的映射$_SERVER、REMOTE_ADDR、SCRIPT_NAME、PATH_INFO等 CGI 变量的构造由 cgi.go 的addKnownVariablesToServer与 frankenphp.c 中的frankenphp_register_server_vars协同完成。值得注意的实现细节包括Go 侧通过 go_register_server_variables 这个 cgo 导出回调将已知 CGI 变量、请求头以及PreparedEnvfc.env合并进 PHP 的track_vars_array其中环境变量最后注册并允许覆盖前面的值。对于非常见请求头addHeadersToServer 走frankenphp_register_variable_safe路径交由 PHP 侧做额外消毒而非使用缓存的常见请求头快路径。splitRemoteAddr 对畸形地址例如单独的[做了防御性处理net.SplitHostPort失败后降级为手工解析避免在 cgo 回调中 panic 展开而拖垮整个进程。HTTPS 场景下会按 Apache mod_ssl 兼容格式填充SSL_PROTOCOL、SSL_CIPHER等变量tlsProtocol。2. PHP 脚本路径解析防目录穿越与错误文件执行请求路径首先经split_path默认.php拆分为SCRIPT_NAME/PATH_INFO再与文档根目录通过sanitizedPathJoin拼接// cgi.go path : filepath.Join(root, filepath.Clean(/reqPath))这个实现借鉴了 Go 标准库http.Dir的防护逻辑root被视为可信路径而reqPath不可信拼接结果永远不会逃出 root从而阻断路径穿越path traversal。同时 splitPos 采用严格 ASCII 大小写不敏感匹配字节值 utf8.RuneSelf的路径段永远不会命中任何 split 项——这是针对曾经出现过的 Unicode 等价字符绕过如全角或数学字母折叠为 ASCII导致攻击者上传的文件被当作 PHP 执行的修复源码注释中明确引用了 GHSA-3g8v-8r37-cgjm 与 GHSA-v4h7-cj44-8fc8 两个安全公告。此外php_server指令还会设置默认的try_files重写规则将请求路由到已存在的文件或前端控制器从而缓解经典 PHP-FPM 陷阱——执行了错误的文件详见 docs/config.md 与 docs/performance.md 中的等价 route 配置。3. Worker 模式的请求级状态隔离Worker 模式让 PHP 进程常驻内存因此请求之间的状态隔离是安全关键。C 层的 frankenphp_reset_super_globals 在每个请求之间显式刷新$_FILES$_GET、$_POST、$_COOKIE、$_SERVER在重新导入时被刷新但$_ENV不被刷新$_SESSION必须显式从符号表中删除——因为它存储在EG(symbol_table)中且带有对PS(http_session_vars)的引用而会话的 RSHUTDOWN 只减少引用计数却不将其从符号表移除否则会导致数据在请求间泄漏frankenphp.c。此外frankenphp_reset_session_statefrankenphp.c会刷新活动会话、关闭会话模块、释放会话 ID 并保留用户自定义处理器。需要强调的是putenv()写入、static变量、类静态属性以及全局变量在同一线程的多个请求之间会持续存在。请求相关或用户特有的数据一旦残留在这些状态里就可能泄漏给后续请求。关于状态持久化的完整说明与示例见 docs/worker.md。4. 每线程环境变量沙箱由于 PHP 的 ZTSZend 线程安全模型要求每个 PHP 执行运行在真实 POSIX 线程上若多个线程直接操作全局 C 环境setenv/getenv将产生数据竞争。因此 frankenphp.c 实现了线程局部的环境沙箱主线程启动时把os.Environ()快照进main_thread_envfrankenphp_putenv()frankenphp.c与frankenphp_getenv()frankenphp.c都作用于懒初始化自main_thread_env的线程局部sandboxed_env声明于 frankenphp.creset_sandboxed_environment()frankenphp.c在每次 PHP 脚本执行后释放sandboxed_env普通模式即每次请求Worker 模式则只在 Worker 脚本自身退出时执行——因此putenv()的写入在同一线程的后续 Worker 请求中可见直到脚本重启。环境变量的填充时序与$_ENV的差异详见 docs/internals.md。5. CGO 内存边界Go 与 C/PHP 之间的字符串生命周期管理是典型的内存安全风险点Go → C 字符串经C.CString()以malloc()分配由 C 侧负责释放如frankenphp_free_request_context()释放 Cookie 数据为减少拷贝phpthread.go 中的phpThread内嵌 Go 的runtime.Pinner通过thread.Pin()/thread.Unpin()钉住 Go 内存供 C 引用每次脚本执行后解除钉住PHP 侧内存由 Zend 内存管理器emalloc/efree管理在请求关闭时自动释放。6. Caddy Admin API/frankenphp/workers/restart与/frankenphp/threads两个端点通过 Caddy 的 Admin API 暴露caddy/admin.go而 Caddy Admin API 默认监听localhost:2019。是否将该端点暴露到 localhost 之外属于运维人员的决策。Worker 的优雅重启调用方式参见 docs/worker.mdcurl -X POST http://localhost:2019/frankenphp/workers/restart7. 可信代理处理Trusted Proxies传入的X-Forwarded-*请求头始终以污染的$_SERVER[HTTP_X_FORWARDED_*]值到达 PHP只有当配置了trusted_proxies时它们才会被信任用于推导真实客户端 IP 与协议。若不配置X-Forwarded-For、X-Forwarded-Proto等头将被忽略可能导致 HTTPS 误判或客户端 IP 错误——此时 PHP 框架侧通常还需要同步配置受信代理如 Symfony 的TRUSTED_PROXIES或 Laravel 的trustedproxies中间件。8. 慢速请求体Slow-POST DoS一个声明了请求体却慢慢滴水式发送或干脆停滞的客户端会长时间占用处理线程。在线程池有上限的情况下足够多的此类连接即可耗尽线程池典型的 slow-POST DoS。FrankenPHP 的默认防线是对请求体读取施加60 秒空闲超时request_body_timeoutphp_server { request_body_timeout 60s # 默认值设置为 0 可禁用 }其原理是在每次读取前重置 deadlinedeadline 重置机制见 requestbodytimeout_test.go测试覆盖 HTTP/1 与 HTTP/2 两个版本因此持续稳定上传的请求体无论多大可以成功完成停滞的请求会被切断线程被释放。该默认值定义于 caddy/caddy.godefaultRequestBodyTimeout 60 * time.Second并在 caddy/module.go 中作为RequestBodyTimeout字段暴露0表示禁用。范围外不属于 FrankenPHP 的缺陷应用 PHP 代码中的漏洞SQL 注入、XSS、不安全反序列化等。FrankenPHP 将不可信任的请求数据原样送达应用防御它们是应用的责任与任何其他 SAPI 无异。上游组件的缺陷FrankenPHP 依赖的 PHP、Caddy、Go 本身的缺陷以及构建在其之上的项目Laravel Octane、Symfony Runtime的问题应上报给相应项目。这一点在 SECURITY.md 中有明确呼应只有直接影响 FrankenPHP 本身的漏洞才应向本项目报告影响其依赖或被其使用组件的缺陷应上报给相关项目。上报安全漏洞如果你认为发现了直接影响 FrankenPHP 的安全问题请不要公开披露。完整的上报流程、受支持版本策略仅最新版本受支持二进制与 Docker 镜像每晚基于最新依赖重建见 SECURITY.md。延伸阅读docs/internals.md线程模型、状态机与 CGO 边界的内部机制环境沙箱、线程状态机、请求流程docs/worker.md长驻进程的状态持久化与超全局变量行为docs/config.mdrequest_body_timeout、split_path、trusted_proxies等配置项的完整说明docs/production.md反向代理场景下的可信代理配置SECURITY.md安全政策与漏洞上报渠道【免费下载链接】frankenphp The modern PHP app server项目地址: https://gitcode.com/GitHub_Trending/fr/frankenphp创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表