X-XSS-Protection 是当年让浏览器开启「XSS 过滤器」的开关。
如今 Chrome、Edge 已移除该功能,Firefox 从没实现过——这个首部发出去基本等于白发。
上一节防点击劫持,这一节防 XSS。两个字段放一起看很有意思:一个还有效,一个已经被主流实现抛弃。
一、三种取值
0 是关掉过滤器,1 是开着并在发现可疑时改页面,1; mode=block 是开着并在发现可疑时整页拦掉。
不发就是不表态。
Demo 里四种情况:111、132、132、144 字节。0 和 1 一样长,只有带 mode=block 的多 12 字节。
注意 1 那个「改页面」的做法:它会试图把可疑的脚本片段从响应里剔除。
这种「猜哪一段像攻击」的思路本身就很脆——攻击者可以构造出让过滤器误判的输入,把正常内容改坏,
甚至绕过。
二、为什么它退场了
Chrome 从 78 版起移除 XSS Auditor,Edge 跟着做。
理由是这套机制误报多、可绕过,而且它带来的虚假安全感比它能防的漏洞更糟。
Firefox 从头到尾就没实现过这一套,它走的是另一条路(CSP)。所以今天主流浏览器全部忽略这个首部。
于是出现一个反直觉的建议:现在很多安全扫描会要求把它显式写成 0。
不是因为它有用,而是为了关掉老浏览器里那个会帮倒忙的过滤器。
三、真正管用的是 CSP
CSP(Content-Security-Policy)管的是「哪些脚本可以跑」,
比如 script-src 'self' 就只允许同源脚本。
差别在思路上:X-XSS-Protection 是「猜页面里有没有像攻击的字符串」,CSP 是「列出允许的,
其余一律不许」。前者是黑名单加猜测,后者是白名单。
白名单当然也有代价——配错了会把正常功能挡掉,而且上线后要盯着报告(report-uri)慢慢收。
但这是工程问题,不是机制缺陷。
四、动手 Demo:四种取值,只差几十字节
同一个页面分别不发、写 0、写 1、写 1; mode=block,量出整条响应的大小,顺带看清每个值的语义。
cases = [(None, u'(不发)'),
(u'0', u'关掉过滤器'),
(u'1', u'发现可疑就改页面'),
(u'1; mode=block', u'发现可疑就整页拦掉')]
def make(val):
def fn(raw, st):
hd = [(u'Content-Type', u'text/html; charset=utf-8')]
if val is not None:
hd.insert(0, (u'X-XSS-Protection', val))
return X.reply(u'HTTP/1.1 200 OK', hd, PAGE)
return fn
实跑输出:
--------------------------------------------------------------------
X-XSS-Protection:一个已经退场的开关
--------------------------------------------------------------------
X-XSS-Protection 整条响应 含义
(不发) 111 不表态
0 132 关掉过滤器
1 132 开着,发现可疑就改页面
1; mode=block 144 开着,发现可疑就整页拦掉
这四行的差别只在几十字节,但它描述的那个机制已经不存在了:
Chrome 从 78 版起移除 XSS Auditor,Edge 跟着做;
Firefox 从来没实现过这一套。也就是说今天主流浏览器全部忽略它。
更麻烦的是 `1`(不带 mode)这个值:老 Chrome 的「改页面」做法
本身会引入新的绕过手法,反而增加攻击面。所以现在很多安全扫描
会建议**显式写成 `0`**,把它彻底关掉,免得老浏览器帮倒忙。
真正管用的是 CSP:`Content-Security-Policy: script-src 'self'`。
它管的是「哪些脚本能跑」,而不是「页面里有没有像攻击的字符串」——
前者是白名单,后者是猜,差别就在这里。
四行响应 111、132、132、144 字节。0 和 1 都是 132,只有 1; mode=block 那行多 12 字节。
字节上差得不多,但语义差得远:0 是「别管」,1 是「管,但要改我的页面」,
1; mode=block 是「管,宁可整页不显示」。
本机没有浏览器,所以这四行只能验证「首部发出去了」,
验证不了「浏览器到底做了什么」——因为主流浏览器现在什么都不做。
这也是这一节的核心:一个首部格式正确、语义清晰,仍然可能已经没有实现。
看规范之前,先确认还有谁在实现它。
30 秒实验
- 查一下自己常用浏览器当前版本还认不认这个首部。
- 写一条
Content-Security-Policy: script-src 'self',比较两者思路的差别。 - 想一想:为什么「猜哪段像攻击」这种思路天生容易误报?
常见误区
- 「它还在保护我的站点」——主流浏览器已移除该机制。
- 「
1比0安全」——1的改写行为本身会引入新问题。 - 「有它就够了,不用 CSP」——方向错了,CSP 才是现在的手段。
自测三问(点开对答案)
1. `1; mode=block` 和 `1` 的区别是什么? —— 前者发现可疑就整页拦掉,后者只是试图改写页面。 2. 为什么现在建议显式写 `0`? —— 为了关掉老浏览器里那个会误判、可能帮倒忙的过滤器。 3. 替代它的是什么机制? —— CSP,走白名单路线,管「哪些脚本能跑」。两个防攻击的字段讲完,剩下两个更尴尬:一个是「请求对方别跟踪」,一个是「声明自己的隐私承诺」。
- ☐ (本节完,下一节 6.8.3 DNT)