摘要

  • CGI/1.1 定义了 HTTP 服务器与脚本之间通用的请求和响应交接,同时承认某些细节由系统或具体实现决定。
  • RFC 3875 把 TLS 安全模型限定在客户端与服务器之间:客户端认证的是服务器;脚本没有内建机制验证谁调用了它,也没有获得完整性受保护的 CGI 消息。

连接是安全的,但应用程序未必就在这条连接的另一端。

这正是 2004 年 Common Gateway Interface(CGI)1.1 规范 RFC 3875 中那条不显眼的边界。浏览器可以通过 TLS 连接 Web 服务器,服务器随后把请求数据交给 CGI 程序。但 RFC 描述的安全关系不会完整穿过这第二次交接。服务器是网络对端;脚本则是位于其后的应用进程。

在 RFC 3875 出现之前,CGI 已经发挥作用多年。HTTP 服务器可以作为网关连接数据库或其他既有信息系统,再由脚本根据请求生成响应。W3C 留存的 CGI 历史页面把它称为服务器实现者之间的一项约定,用于集成网关脚本和程序。页面记录了 1995 年更新 CGI 1.1 的尝试,也记载了 1997 年 11 月重启的一项工作:把事实上的接口整理成一份 Informational RFC。最终文本于 2004 年 10 月通过 Independent Stream 发布。

这段时间差的重要性,不在于标准制定流程的轶事,而在于它说明了文件试图捕捉什么。CGI 早已是实践约定。RFC 3875 没有发明动态 Web 应用,而是为一项既有交接写下可跨实现的契约:请求从面向网络的服务器进入应用程序。

契约为 REQUEST_METHOD、QUERY_STRING、CONTENT_LENGTH、PATH_INFO 和 REMOTE_ADDR 等变量定义了共同词汇,也规定脚本如何返回响应头和正文,包括文档响应与重定向。运行在不同服务器上的程序由此拥有相同的接口语言。职责也被划分:服务器负责客户端连接、数据传输和网络协议;脚本负责数据访问、文档处理等应用工作。

这项划分并不意味着服务器可以转交自己的义务。RFC 3875 明确指出,即使脚本不符合规范,服务器仍有责任向客户端提供符合网络协议的响应。若服务器执行访问认证,它还必须先确认请求通过定义的访问控制,之后才能运行脚本。服务器并非一根透明管道,不能把格式错误的响应或遗漏的门禁归咎于子进程。

第 9.4 节随后收紧了信任边界:对 TLS 客户端连接而言,安全模型适用于客户端与服务器之间,而非客户端与脚本之间。客户端认证的是服务器。规范没有提供让脚本认证调用它的服务器的机制,也没有对 CGI 请求和响应消息强制完整性保护。

这不是说脚本必然不可信,也不是说运营者不能保护本地进程边界。服务器可以使用操作系统权限、私有通道或其他本地控制。更精确的结论是:HTTP 边缘上的 TLS 本身不能证明下游脚本就是同一个经过认证的网络对端。如果脚本从环境变量收到 HTTPS=on 或用户名,它收到的是数值;CGI 接口没有提供密码学凭据,将这些值绑定到经过认证的客户端会话。

规范也没有把 CGI 限定成一种进程模型。RFC 3875 将以服务器用户和组身份运行的子进程描述为最常见实现,同时也承认脚本可以链接进服务器本身。它区分“system-defined”和“implementation-defined”行为。共同接口可以跨服务器使用,但这类可移植性有边界。

Apache 的 mod_cgi 文档可作为一个实现实例,而不是整个 Web 的普查。它展示了某一服务器家族如何通过 handler 或 ScriptAlias 选取脚本,并把脚本输出返回给客户端。这个机制有助于理解交接,却不会改变 RFC 对 TLS 终点的表述,也不能证明所有服务器都以同样方式配置。

CGI 的历史贡献是一条共享接口边界,而不是共同的进程模型或端到端安全通道。服务器与脚本可以通过命名接口协作,同时仍是职责不同的主体。按此理解 RFC,可以避免一个类别错误:浏览器到服务器的受保护请求,不会自动变成浏览器到应用程序的受保护关系。RFC 3875 把分离说清了,却没有将它消除。

来源