PHP 应用 security.txt 漏洞披露实践

简介: security.txt 是 RFC 9116 标准的纯文本文件,置于 `/.well-known/security.txt`,为安全研究员提供统一、机器可读的漏洞报告入口(如 Contact、Policy、Expires 等字段),显著降低披露门槛,提升响应效率。本文详解其原理,并以 CakePHP 中间件实现“永不过期”的自动化集成。(239字)

PHP 应用 security.txt 漏洞披露实践

大多数安全事件的起点不是天才攻击者,而是一个发现了问题、却不知道该如何报告的人。如果上报缺陷比发条推文还麻烦,客观上你就已经替他们选了公开披露。

与此同时,AI 让自动化安全测试达到了前所未有的规模。每一天,都有漏洞在某个角落被发现、被上报。

security.txt 解决的正是这个问题里最乏味的那部分:直接告诉发现者报告该发到哪。本文会介绍这个标准是什么、为什么降低报告门槛很重要、以及如何在 CakePHP 应用中一行代码接入一个永不过期的中间件。

问题:一扇没有门铃的锁着的门

站在安全研究人员的角度想想:你在某个站点上发现了一个暴露的端点,想做正确的事——私下报告。于是你开始找:

  • 有没有 /security 页面?通常没有。
  • 有没有 security@ 邮箱?也许有,也许真有人在看。
  • 有没有联系表单?消息限 500 字符,附件也吞掉。
  • 去社交媒体上找那家公司?现在你是在公开场合讨论漏洞。

每撞上一次死胡同,要么报告者放弃,要么漏洞细节最终被公开。两种结果你都不想要。

security.txt 是什么(RFC 9116)

security.txt 是 RFC 9116 "A File Format to Aid in Security Vulnerability Disclosure"(信息性标准,2022 年,Edwin Foudil 与 Yakov Shafranovich 编写)定义的一种纯文本小文件,在已知路径以 text/plain 格式通过 HTTPS 对外提供:

https://example.com/.well-known/security.txt

文件内部只有 字段: 值 这样的行。两个字段是必填的,其余选填:

字段 是否必需 用途
Contact 联系方式:https: URL、mailto:tel:。可按优先级重复出现。
Expires 文件数据不再受信任的时间点(ISO 8601 格式)。
Encryption 公钥获取地址,方便对报告进行加密。
Policy 披露策略链接。
Acknowledgments 过往报告者的荣誉榜。
Preferred-Languages 能阅读的语言,比如 ende
Canonical 本文件的规范 URL。
Hiring 安全相关岗位招聘。

实践中有两个点值得注意:

  • 不同字段之间顺序不重要。只有一处例外:多条 Contact 行从上到下按优先级读取。
  • Expires 是真正的坑。它必须是一个未来日期。两年前写好就丢在那儿的 security.txt,按规范已经过期——一份过期文件给人的信号是:这个团队对安全不上心。

为什么要有一个标准?安全研究人员不用再去猜你的组织架构。一条确定的路径,机器能读,人和扫描器都知道去那里查。

为什么重要:降低门槛,把人引到对的渠道

添加 security.txt 成本极低,收益至少有两条。

第一,降低摩擦。发现者越快抵达正确的收件箱,就越有可能完成报告——漏洞在他们翻找联系方式期间泄露的概率也就越低。你在堵上公开披露的借口。

第二,把人引到正确的渠道。这是很多团队不当回事的地方。"正确渠道"意味着私密、有人监控、预期明确:

  • 一个私密的入口(专用邮箱或 GitHub 私密漏洞报告通道),不要用公开 Issue 区。
  • 一份写清楚的 Policy,让报告者知道该期待什么:会不会给安全港?披露时间线?会不会致谢?
  • 另一端有个真人,职责就是接收这些报告。

让这条路显而易见,你就能把一个 Twitter 零日危机变成一次安静的协同修复。核心就一句话:让审计和报告变简单,让简单的路成为安全的路。

CakePHP 中间件:一个永不过期的实现

在 CakePHP 应用中同样想集成 security.txt,但放一个静态文件有 Expires 过期问题——得有人一直记得去更新那个日期。于是把它做成了 cakephp-setup 插件(3.21.0+)里一个轻量的 PSR-15 中间件。

在线示例:sandbox.dereuromark.de/.well-known/security.txt

下面几个设计选择让它用起来很顺手:

  • 做成中间件,而不是路由。文件公开且格式固定,在路由和认证执行之前就能直接返回,不需要控制器、不需要路由配置、不需要认证例外。
  • Expires 每次请求时重新计算。默认值是一年之后,所以它永远有效。维护的问题就这么没了。
  • 配置用类型化的值对象。通过 SecurityTxt 对象来描述文档——具名参数、IDE 自动补全、类型检查——而不是丢一个松散的魔术字符串数组。
  • Contact 强制必填。RFC 9116 要求有它,漏了就直接抛异常。配错在启动时就暴露,不用等到运行时悄悄坏掉。

在应用中间件栈里,一行代码接入:

use Setup\Middleware\SecurityTxt;
use Setup\Middleware\SecurityTxtMiddleware;

public function middleware(MiddlewareQueue $middlewareQueue): MiddlewareQueue
{
    $middlewareQueue->add(new SecurityTxtMiddleware(new SecurityTxt(
        contact: 'https://github.com/owner/repo/security/advisories/new',
        canonical: 'https://example.com/.well-known/security.txt',
        policy: 'https://github.com/owner/repo/security/policy',
        preferredLanguages: 'en, de',
    )));

    // ... the rest of your stack

    return $middlewareQueue;
}

它同时在 /.well-known/security.txt 和老的 /security.txt 两个路径返回如下内容,每次都带上最新的 Expires:

Contact: https://github.com/owner/repo/security/advisories/new
Policy: https://github.com/owner/repo/security/policy
Canonical: https://example.com/.well-known/security.txt
Preferred-Languages: en, de
Expires: 2027-05-23T00:00:00.000Z

其他细节:HEAD 请求只返回头部,不带响应体;路径匹配感知基础路径,子目录挂载的应用也能用;另外给值对象还没覆盖的字段留了原始数组兜底。

配合 SECURITY.md 使用

如果项目有公开的 Git 仓库,SECURITY.md 可以锦上添花。

security.txt 说的是报告发到哪,SECURITY.md 说的是怎么报、该期待什么。在 GitHub 上,SECURITY.md(放在仓库根目录、.github/docs/)会在 /security/policy 渲染,并出现在仓库的 Security 选项卡里——正好是前面 Policy 字段指的那个地址。

两者互为补充:

  • Contact 指向 GitHub 的私密漏洞报告通道,不是公开 Issue。
  • Policy 指向 SECURITY.md,里面写着流程和时间线。

这样,扫描器和人类都会抵达同一个私密的、该去的地方。

今天就开始用

给公开应用加 security.txt,投入产出比极高。寥寥几行,就能告诉外界你愿意听问题。

  • 读标准:RFC 9116
  • 生成或校验文件:securitytxt.org
  • CakePHP 用户:security.txt 中间件文档

让门好找,装上铃,铃响就接。

框架无关的中间件代码

这个 PSR-15 中间件很容易移植到任何兼容 PSR-15 的框架。需要的话复制粘贴进自己的生态就行(中间件 + DTO)。Response 类换成你实际用的实现,InstanceConfigTrait 也要调整。下面是一个尽量去框架化的版本:

<?php

declare(strict_types=1);

/**
 * Serves an RFC 9116 security.txt. Pure PSR-15 + PSR-17: it depends only on the
 * injected factories, so it runs in any compliant stack.
 *
 * The required `Expires` field is computed on every request, so it never goes
 * stale. `Contact` is required; constructing without one throws.
 */
class SecurityTxtMiddleware implements MiddlewareInterface
{
    /** @var array<string, string|array<string>> */
    private array $fields;

    private string $expiresInterval;

    public function __construct(
        SecurityTxt $document,
        private readonly ResponseFactoryInterface $responseFactory,
        private readonly StreamFactoryInterface $streamFactory,
        private readonly string $path = '/.well-known/security.txt',
        private readonly bool $serveRootFallback = true,
        private readonly int $cacheMaxAge = 86400, // 1 day; was CakePHP's DAY constant
    ) {
        $this->fields = $document->toFields();
        $this->expiresInterval = $document->expiresInterval;

        if (SecurityTxt::normalize($this->fields['Contact'] ?? null) === []) {
            throw new InvalidArgumentException(
                'SecurityTxtMiddleware requires at least one non-empty Contact field (RFC 9116).',
            );
        }
    }

    public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface
    {
        $method = $request->getMethod();
        if ($method !== 'GET' && $method !== 'HEAD') {
            return $handler->handle($request);
        }

        if (!$this->matches($this->relativePath($request))) {
            return $handler->handle($request);
        }

        $rendered = $this->render();

        $response = $this->responseFactory->createResponse()
            ->withHeader('Content-Type', 'text/plain; charset=utf-8')
            ->withHeader('Content-Length', (string)strlen($rendered)); // correct even for HEAD

        if ($this->cacheMaxAge > 0) {
            $response = $response->withHeader('Cache-Control', 'max-age=' . $this->cacheMaxAge);
        }

        // HEAD: same headers as GET, but no body.
        $body = $method === 'HEAD' ? '' : $rendered;

        return $response->withBody($this->streamFactory->createStream($body));
    }

    private function matches(string $path): bool
    {
        return $path === $this->path
            || ($this->serveRootFallback && $path === '/security.txt');
    }

    /**
     * Resolve the request path relative to the application base path. The `base`
     * attribute is set by some frameworks (e.g. CakePHP) for subdirectory installs;
     * elsewhere getAttribute() returns '' and this is a pure-PSR-7 no-op.
     */
    private function relativePath(ServerRequestInterface $request): string
    {
        $path = $request->getUri()->getPath();
        $base = (string)$request->getAttribute('base', '');
        if ($base !== '' && str_starts_with($path, $base)) {
            $path = substr($path, strlen($base));
        }

        return $path !== '' ? $path : '/';
    }

    private function render(): string
    {
        $lines = [];
        foreach (SecurityTxt::normalize($this->fields['Contact'] ?? null) as $contact) {
            $lines[] = 'Contact: ' . $contact;
        }
        foreach ($this->fields as $name => $value) {
            if ($name === 'Contact' || $name === 'Expires') {
                continue;
            }
            foreach (SecurityTxt::normalize($value) as $item) {
                $lines[] = $name . ': ' . $item;
            }
        }
        $lines[] = 'Expires: ' . $this->expires();

        return implode("\n", $lines) . "\n";
    }

    private function expires(): string
    {
        $timestamp = strtotime($this->expiresInterval) ?: strtotime('+1 year');

        return gmdate('Y-m-d\TH:i:s.000\Z', (int)$timestamp);
    }
}

PHP 应用 security.txt 漏洞披露实践

目录
相关文章
|
3月前
|
编译器 Go PHP
FrankenPHP 原生支持 Windows 了
FrankenPHP 正式原生支持 Windows!基于 Go 1.26 对 MSVC Clang 的 CGO 支持,成功打通与官方 PHP(MSVC 编译)的链接难题,实现 100% 特性兼容——含 Worker Mode、Hot Reloading 及全部扩展。性能较 Nginx/PHP-FPM 提升超 260%,开箱即用。(239字)
466 157
|
3月前
|
缓存 NoSQL API
当你的 PHP 应用的 API 没有限流时会发生什么?
PHP API若无限流,易遭流量洪峰、爬虫或攻击冲击,导致服务器过载、响应延迟甚至宕机。本文剖析限流缺失的性能与安全风险,指出常见误区(如忽略用户分级、算法低效、云环境适配失败),并提供基于Redis的中间件实现方案及生产部署要点,助你构建健壮、可扩展的API防护体系。(239字)
372 168
|
2月前
|
人工智能 安全 数据可视化
GPT-5.5 开启更强的智能体工作方式
OpenAI发布GPT-5.5:迄今最强智能体模型,兼具更高智能与GPT-5.4级响应速度。擅长代码编写调试、多工具协同、数据分析与文档生成,在编码、科研、知识工作等长程任务中表现卓越,支持复杂意图理解与自主推进,现已面向Plus/Pro/企业用户开放。(239字)
312 4
GPT-5.5 开启更强的智能体工作方式
|
2月前
|
人工智能 JavaScript PHP
Laravel + Vue 免费可商用 PHP 管理后台 CatchAdmin V5.3.0 发布:支持 AI Agent 开发
CatchAdmin V5.3.0 是基于 Laravel 13 + Vue3 的免费可商用后台框架,支持企业级权限、动态路由、代码生成等;本次重磅升级AI开发体验,内置AGENTS指引、9大AI Skills、多平台兼容及Context7实时文档,大幅提升后台开发效率。(239字)
277 1
Laravel + Vue 免费可商用 PHP 管理后台 CatchAdmin V5.3.0 发布:支持 AI Agent 开发
|
2月前
|
JavaScript 前端开发 PHP
Laravel13 + Vue3 的免费可商用 PHP 管理后台 CatchAdmin V5.2.0 发布
CatchAdmin V5.2.0发布!基于Laravel 13 + Vue3的免费可商用后台系统,支持Token鉴权、RBAC权限、动态路由、代码生成、多云上传等,模块化设计,开箱即用,助力快速构建CMS/OA/CRM等企业级应用。(239字)
285 1
|
3月前
|
人工智能 PHP 开发工具
用 Laravel AI SDK 构建多智能体工作流
Laravel AI SDK 提供轻量、灵活的多智能体工作流实现方案,支持 Prompt Chaining、Routing、Parallelization、Orchestrator-Workers 和 Evaluator-Optimizer 五种经生产验证的模式,一行 `agent()` 即可启动,Pipeline/Concurrency 等 API 让复杂任务模块化、可编排。(239字)
242 7
|
3月前
|
JSON Go PHP
告别阻塞!用 PHP TrueAsync 实现 PHP 脚本提速 10 倍
本文介绍如何用 PHP TrueAsync 构建高性能进程池:基于 `proc_open` 与 NDJSON 协议实现非阻塞进程通信,结合 Channel 任务分发、TaskGroup 协程管理及 Supervisor 自愈机制,让同步 PHP 函数在协程中高效并行,轻松提速 10 倍。
205 19
|
5月前
|
缓存 JavaScript PHP
Laravel12 + Vue3 的免费可商用商业级管理后台 CatchAdmin V5 正式发布
CatchAdmin V5 是基于 Laravel 12 + Vue3 的免费可商用企业级后台系统,支持前后端分离。内置权限管理、动态路由、代码生成、插件系统(直连 Composer)、Excel 导入导出、SFC 远程加载等核心能力,开箱即用,模块化设计,助力快速构建 CMS/CRM/OA 等系统。(239字)
436 5
Laravel12 + Vue3 的免费可商用商业级管理后台 CatchAdmin V5 正式发布
|
5月前
|
人工智能 缓存 JavaScript
Laravel12 + Vue3 的免费可商用商业级管理后台 CatchAdmin V5.1.0 发布 新增 AI AGENTS 配置
CatchAdmin V5.1.0发布!基于Laravel 12 + Vue3的免费可商用企业级后台,新增AI AGENTS配置、系统缓存命令,优化首屏加载(2~3s)、多语言切换与打包分包,内置权限、代码生成、文件管理等全功能模块。
320 4
Laravel12 + Vue3 的免费可商用商业级管理后台 CatchAdmin V5.1.0 发布 新增 AI AGENTS 配置
|
4月前
|
人工智能 PHP Android开发
PHP 现在可以零成本构建原生 iOS 和 Android 应用 NativePHP for Mobile v3 发布
NativePHP for Mobile v3(即NativePHP Air)正式发布!核心框架MIT协议免费开源,Laravel开发者可零成本构建原生iOS/Android应用。全新插件化架构,按需加载功能;内置Jump真机即时调试、Mimi AI辅助编码,PHP≥8.3即可快速上手。(239字)
697 1
PHP 现在可以零成本构建原生 iOS 和 Android 应用 NativePHP for Mobile v3 发布

热门文章

最新文章