跨站脚本攻击XSS:为什么cookie中有httpOnly属性|浏览器篇
# 跨站脚本攻击XSS:为什么cookie中有httpOnly属性
30 秒速记
- 严格的同源策略能够提升隔离强度,但会限制第三方资源和跨源数据请求等业务能力。
- 浏览器允许页面引用第三方资源,并用
CSP对这种开放进行约束。 XMLHttpRequest和Fetch默认受跨源限制,需要通过CORS支持受控的跨域访问。- 第三方资源引用与
CORS扩展了页面能力,同时也增加了安全暴露面。 XSS是这些开放能力所伴随的典型安全问题之一;本段尚未说明HttpOnly的具体执行机制。
HttpOnly 的作用,是让对应 Cookie 可以随匹配的请求发送,但不能被页面 JavaScript 通过 document.cookie 读取。 这是因为恶意脚本一旦通过 XSS 进入当前页面,就会以本站脚本的身份运行,同源策略无法再把它隔离出去。给会话 Cookie 设置 HttpOnly,能降低凭证被直接窃取的风险。它并不能修复 XSS,脚本仍可能借助浏览器自动携带 Cookie 调用接口,因此还要处理注入点并配合 CSP。
通过上篇文章的介绍,我们知道了同源策略可以隔离各个站点之间的 DOM 交互、页面数据和网络通信,虽然严格的同源策略会带来更多的安全,但是也束缚了 Web。这就需要在安全和自由之间找到一个平衡点,所以我们默认页面中可以引用任意第三方资源,然后又引入 CSP 策略来加以限制;默认 XMLHttpRequest 和 Fetch 不能跨站请求资源,然后又通过 CORS 策略来支持其跨域。
不过支持页面中的第三方资源引用和 CORS 也带来了很多安全问题,其中最典型的就是 XSS 攻击。
原理拆解: 同源策略无法阻止已经进入当前站点执行环境的恶意脚本。若服务端把用户输入直接拼入 HTML,或前端把不可信字符串交给 innerHTML、eval 等执行型接口,攻击代码会以本站源的身份运行,可以读取页面数据、调用本站接口,并读取未受保护的 Cookie。HttpOnly 由服务器通过 Set-Cookie 设置,使对应 Cookie 仍可随匹配的 HTTP 请求发送,但不会通过 document.cookie 暴露给 JavaScript,从而降低会话标识被 XSS 直接窃取的风险。
最小验证: 将以下代码保存为 xss-cookie.js 并用 Node.js 运行。输入是查询参数 name;要验证服务端转义与 HttpOnly 分别保护 HTML 注入点和会话 Cookie。
const http = require('node:http');
function escapeHtml(value) {
return value.replace(/[&<>"']/g, char => ({
'&': '&', '<': '<', '>': '>',
'"': '"', "'": '''
})[char]);
}
http.createServer((req, res) => {
const url = new URL(req.url, 'http://localhost:3000');
const name = url.searchParams.get('name') || '访客';
res.setHeader('Set-Cookie', [
'session=secret123; HttpOnly; SameSite=Lax; Path=/',
'theme=dark; SameSite=Lax; Path=/'
]);
res.setHeader('Content-Type', 'text/html; charset=utf-8');
res.end(`<!doctype html><meta charset="utf-8">
<p id="safe">你好,${escapeHtml(name)}</p>
<script>console.log(document.cookie)</script>`);
}).listen(3000, () => console.log('http://localhost:3000'));
访问 http://localhost:3000/?name=%3Cimg%20src=x%20onerror=alert(1)%3E,页面应把输入显示为文本,不执行 onerror;控制台能看到 theme=dark,但看不到 session=secret123。关键点是输出编码阻断注入,HttpOnly 只限制脚本读取 Cookie。
边界与排查: HttpOnly 不是 XSS 修复方案。恶意脚本即使拿不到会话值,仍可能在当前页面内发起携带 Cookie 的请求、读取可见数据或修改界面。工程上应同时检查输入进入 HTML、属性、URL 和脚本字符串时是否采用对应上下文的编码,优先使用 textContent 等非执行型接口,并结合 CSP、依赖审计和 Cookie 的 Secure、SameSite 属性缩小攻击面。
面试官追问
追问 1登录页的会话 Cookie 已设置 HttpOnly,产品负责人因此断言即使 innerHTML 存在注入也不会产生严重 XSS,你认同吗?
不认同,HttpOnly 只阻止脚本通过 document.cookie 读取对应 Cookie,并没有修复注入点。恶意脚本仍可读取页面可见数据、修改界面,并利用浏览器自动携带会话 Cookie 发起业务请求,因此风险依然存在。
追问 2用户昵称会同时进入欢迎语、头像链接和一段配置脚本,开发只写了一个通用转义函数,你会要求怎样处理这些输出点?
应按数据进入的上下文分别处理 HTML、属性、URL 和脚本字符串,纯文本位置优先使用 textContent 等非执行型接口。单一转义规则无法覆盖所有语法边界;确需富文本时还要采用经过验证的白名单净化方案。
追问 3系统把 session 改成 HttpOnly; Secure; SameSite=Lax 后,安全团队准备移除现有 CSP,理由是脚本已无法盗取会话值,你会如何评估?
不应移除 CSP,这些 Cookie 属性只缩小部分利用面,不能阻止恶意脚本在当前页面上下文执行。CSP、输出编码和非执行型 DOM 接口承担不同职责;即使会话值不可读,脚本仍可能操作业务或窃取其他可见信息。
追问 4测试环境里 document.cookie 只打印 theme=dark,看不到服务端设置的 session=secret123,但请求头仍携带会话,你会如何判断是否异常?
若 session 由服务器设置了 HttpOnly,脚本不可见而匹配请求仍自动携带正是预期行为。应检查实际 Set-Cookie 属性、作用域和请求条件,而不是据此认定 Cookie 丢失;这种保护也不能证明页面不存在 XSS。
追问 5修复排期只能先做一项,团队在“给会话加 HttpOnly”和“修复把查询参数拼入 HTML 的注入点”之间争执,你会如何定优先级?
注入点是攻击代码得以执行的根因,应优先采用正确的输出编码或安全 DOM 接口修复,同时尽快给会话设置 HttpOnly 作为纵深防护。只做后者会保留页面操控和代用户请求能力,只做前者则缺少会话泄露的额外缓冲。
# 什么是 XSS 攻击
30 秒速记
XSS是攻击者把恶意脚本注入HTML或DOM,并让脚本在用户浏览页面时执行。- 名称源于早期的跨域脚本攻击,但现代注入方式并不要求脚本一定来自其他域。
- 浏览器无法仅凭运行状态区分恶意脚本与正常脚本,因此注入代码会获得页面脚本可用的权限。
- 恶意脚本可能读取
document.cookie、监听键盘事件,或伪造登录界面收集敏感信息。 - 攻击代码还可修改页面内容、生成广告,并通过
XMLHttpRequest或Fetch将获取的数据发送出去。 - 核心代价是页面数据、用户输入和交互能力暴露;本段未给出
HttpOnly等防护机制的具体规则。
XSS 是攻击者把恶意脚本注入页面,使其在用户浏览时以当前站点脚本的身份执行。 浏览器无法仅凭运行状态区分它和正常代码,所以它可以读取可访问的页面数据、监听输入、修改 DOM,还可能发起请求把信息传出去。比如把不可信内容交给 innerHTML,其中的事件属性可能变成可执行节点;使用 textContent 则只会显示文本。HttpOnly 只能阻止脚本直接读取部分 Cookie,不能消除注入点或阻止脚本利用当前会话操作业务。
