📘 第六章 HTTP首部
📄 一句话概括

Server 报的是「我是谁、哪个版本、跑在什么系统上」。 它本来是排障用的,实际却成了给攻击者的免费情报——版本号一露,对应的 CVE 就能直接查。

📝 本节和前面几节的关系

这是响应首部里最不起眼、也最容易泄露信息的一个。前面几节讲的是功能,这一节讲的是副作用。

一、它的格式

产品名/版本 (注释),可以并列多个产品。 Demo 里那行 Apache/2.4.52 (Unix) OpenSSL/1.1.1n 就是三段信息叠在一起:Web 服务器、操作系统大类、 TLS 库。 括号里是注释,全凭实现填。 有的写系统名,有的写编译参数,有的干脆写主机名——见过最糟的情况是连内网 IP 都漏出来。 实测五种写法的字节数:43、29、26、13、0。从「什么都报」到「什么都不报」,长度差了三倍多。

二、它泄的到底是什么

三件事。第一,用什么软件——决定了攻击者用哪套漏洞库;第二,哪个版本——决定了具体哪几个 CVE 有效; 第三,什么环境——Unix 还是 Windows,会影响后续提权路线。 这三点加起来,等于把「扫指纹」这一步替攻击者省掉了。 正常情况下他得发一堆畸形请求去试探,现在看一眼响应就行。 所以 Server 是信息泄露里最典型的「低危但高价值」:单看它不致命,但它让后面每一步都变便宜。

三、关掉它,也要接受代价

nginx 是 server_tokens off;,Apache 是 ServerTokens Prod 加 ServerSignature Off。 改完只剩软件名,甚至整行消失。 代价是排障变难。出问题时你没法从响应看出后端是哪台、哪个版本,只能去翻访问日志或者内部监控。 所以这事得配套做——关掉之前先把审计链路补上。 最后一句实话:Server 不是安全边界。真被打了,靠的是补丁和网络隔离,不是把版本号藏起来。 它只让对手少一条线索,挡不住他。

四、动手 Demo:五种 Server 写法,量一量泄露多少

同一份正文,分别配上五种 Server 值:全报、报软件加发行版、只报产品、只报名字、完全不报。 看每一行有多少字节、暴露到哪一层。

cands = [
    (u'Apache/2.4.52 (Unix) OpenSSL/1.1.1n', u'Apache 版本 + 系统 + TLS 库'),
    (u'nginx/1.18.0 (Ubuntu)', u'nginx 版本 + 发行版'),
    (u'Microsoft-IIS/10.0', u'IIS 版本'),
    (u'nginx', u'只报软件名'),
    (u'', u'完全不报'),
]
def fn_of(val):
    def fn(raw, st):
        hd = [(u'Content-Type', u'text/plain')]
        if val:
            hd.insert(0, (u'Server', val))
        return X.reply(u'HTTP/1.1 200 OK', hd, TEXT)
    return fn

实跑输出:

--------------------------------------------------------------------
Server:报身份,也报漏洞
--------------------------------------------------------------------
  Server 值                                  字节   泄露信息
  Apache/2.4.52 (Unix) OpenSSL/1.1.1n        43     Apache 2.4.52 + OpenSSL 1.1.1n
  nginx/1.18.0 (Ubuntu)                      29     nginx 1.18.0 + 发行版
  Microsoft-IIS/10.0                         26     IIS 10.0
  nginx                                      13     只报软件名
  (无)                                     0      完全不报

  最上面一行 43 个字节,把好几件事全说出去了:
  用什么软件、哪个版本、跑在什么系统上、连 OpenSSL 的版本都给了。
  攻击者不用扫,直接照着版本号找对应的 CVE 就行。

  所以常见的加固做法是改配置,只留软件名甚至全删:
    nginx     server_tokens off;
    Apache    ServerTokens Prod  /  ServerSignature Off
  代价是排障时看不出后端是哪个——审计日志里得另找办法补上。

  反过来说,`Server` 不是安全边界。真被打了,靠的是补丁和隔离,
  不是把版本号藏起来。它只是「少给对手一条线索」,不是「挡住他」。

第一行整行 43 字节,把软件、版本、系统、TLS 库四层信息全给了。 第二行 29 字节,软件加版本加发行版,够用来查 CVE 了。 第三行 26 字节,只差系统名,但 IIS 本来就只有 Windows 一个平台,等于全说了。 第四行 13 字节,只剩名字;第五行 0 字节,整行都不发。 43 和 13 之间差的那 30 个字节,全是给攻击者省事的。 关掉它们只需要改一行配置,但排障能力得另外补回来。

30 秒实验

  1. 把第一行的 OpenSSL 版本删掉,看字节数少多少。
  2. 试着往括号里写一句中文,看它会不会变成乱码(提示:首部默认是 latin-1)。
  3. 想一想:如果完全删掉 Server,运维靠什么判断后端是哪台机器?

常见误区

  1. 「Server 是必填首部」——不是,可以整行不发。
  2. 「藏了版本号就安全了」——补丁和隔离才是防线,藏只是少给线索。
  3. 「所有服务器都报同样的格式」——格式只是约定,各实现自己填。
自测三问(点开对答案) 1. `Apache/2.4.52 (Unix) OpenSSL/1.1.1n` 里的括号是什么? —— 注释字段,内容由实现自定,常写系统或编译参数。 2. 为什么说它是「低危但高价值」的信息泄露? —— 单看不要命,但它省掉了攻击者扫指纹这一步,让后续每一步都变便宜。 3. nginx 里关掉版本号用哪条配置? —— `server_tokens off;`

Server 说的是「我是谁」。下一个字段说的是「这份东西有几种版本」——Vary。

相关笔记