跨站脚本攻击的英文全称是 Cross-Site Scripting,简称 XSS。之所以简称为 XSS 而非 CSS,是因为 CSS 已被层叠样式表占用,业界约定以 X 作为 Cross 的谐音替代。在软考信息安全工程师考试大纲里,XSS 被归入 Web 应用安全核心模块,是每年选择题几乎必出的高频考点,也是案例题的常考攻击类型。理解 XSS 的第一要务,是把它与 SQL 注入在概念上彻底切开:SQL 注入攻击的目标是后端数据库服务器,攻击者通过在输入参数中拼接恶意语句诱使数据库越权执行;而 XSS 攻击的目标是前端浏览器,攻击者通过在网页中植入恶意脚本,让受害者浏览器在访问正常页面时不知不觉地执行。一句话概括:SQL 注入打的是服务器,XSS 打的是客户端浏览器。
从攻击对象看,XSS 的受害者从来不是网站服务器,而是访问该网站的其他用户。攻击者之所以能得逞,根本原因在于浏览器无法区分一段脚本究竟是开发者写的合法代码,还是攻击者塞进来的恶意代码。当网站把用户提交的数据不加过滤、不加转义地原样拼接到页面再返回时,攻击者就能构造一段包含脚本标签的数据,让浏览器误以为它是页面本身的合法内容而去执行。国际权威安全组织 OWASP 在其十大 Web 应用安全风险榜单中,长期将注入类攻击列为最高风险等级,XSS 正是注入类攻击在客户端方向的典型代表。软考教材给出的标准定义是:攻击者利用网站未对用户输入严格过滤转义的漏洞,把恶意脚本注入到其他用户浏览的网页中,当用户浏览该网页时脚本便在其浏览器中执行,从而实现窃取 Cookie、劫持会话、篡改页面等目的。这个定义有三个关键词必须抓住:注入、执行、窃取,分别对应攻击的三个动作环节。
还需明确一个易被忽视的边界:XSS 与跨站请求伪造虽都带跨站二字,也常被考生混淆,但它们是两种完全不同的攻击。跨站请求伪造的英文是 CSRF,攻击者利用浏览器自动携带 Cookie 的机制,诱使受害者在不知情时向目标网站发出伪造请求,并不需要往页面里注入脚本;而 XSS 则是实打实地往页面里写脚本并让其执行。这一区分常以辨析题出现,命题人尤其喜欢把两者的防护手段张冠李戴,让考生在 HttpOnly、同源策略、验证码、Referer 校验之间徘徊。
要理解 XSS 为何能成立,必须先理解浏览器赖以维持安全秩序的基本法则——同源策略。同源策略规定,一段脚本只能读取与它同源的文档数据,同源指协议、域名、端口三者完全相同。开发者写的脚本与页面天然同源,可自由操作 Cookie、表单和接口数据。而 XSS 的巧妙之处在于,恶意脚本虽由攻击者编写,但最终被注入到目标网站的页面里,在浏览器看来它与目标网站同源,于是便借着目标网站的信誉绕过了同源策略对敏感数据读取的限制。换句话说,XSS 并未攻破同源策略,而是通过代码注入让自己伪装成了目标网站的合法脚本。
XSS 的危害并非单一动作,而是一条可无限延伸的利用链条。第一层是信息窃取,攻击者最常用脚本读取 document.cookie,把会话标识发送到攻击者控制的服务器,一旦拿到即可冒充受害者登录,这就是经典的会话劫持。第二层是页面篡改与钓鱼,攻击者可在受害者眼前修改页面、伪造登录框、插入钓鱼表单,诱导其主动交出账号密码。第三层是浏览器控制与横向渗透,脚本可记录键盘、截取屏幕、探测内网端口,甚至配合浏览器历史漏洞实现远程代码执行,把攻击面从单台浏览器延伸到整个内网。这三层危害层层递进,命题人在案例题中常给出具体攻击场景,让考生判断属于哪一种危害并给出防护建议。
XSS 之所以能够发生,核心机制在于浏览器对 HTML 与 JavaScript 的解析执行模型,与网站对用户输入的信任之间出现了不可调和的矛盾。浏览器渲染引擎在加载页面时,会自上而下解析 HTML 文档,当遇到一对 script 标签时,它不会判断这段脚本是开发者写的还是攻击者注入的,而是机械地调用 JavaScript 引擎去执行标签之间的代码。浏览器秉持的铁律是:页面里的脚本默认可信。这条铁律既是 Web 应用得以运转的基础,也是 XSS 存活的温床。网站开发者在输出用户数据时,只要没有对尖括号、引号等具有语法意义的特殊字符进行转义,攻击者提交的一段看似人畜无害的文本,就能在返回浏览器的那一刻从普通数据摇身一变成为可执行脚本。
一次完整的 XSS 攻击通常包含四个环节。第一是寻找注入点,攻击者遍历网站所有接收用户输入的地方,包括搜索框、留言板、评论栏、URL 参数、HTTP 请求头等,凡输入最终被服务器原样返回到页面的位置都可能成为注入点。第二是构造载荷,攻击者根据注入点上下文,设计一段能闭合原有 HTML 标签或 JavaScript 语法的恶意代码,这段代码在安全术语里称为载荷,英文写作 payload。第三是触发,攻击者需让受害者访问携带载荷的页面,反射型通常靠社交工程诱导点击恶意链接,存储型则等待受害者自然访问被污染页面。第四是执行与回传,浏览器执行恶意脚本,脚本把窃取的数据通过指向攻击者服务器的请求回传出去,攻击链就此闭合。
把上面四个环节放到一个具体而抽象的场景里,可以看得更清楚。假设某网站的搜索功能存在缺陷,用户输入的关键词会被原样显示在结果页面上。攻击者构造一段以尖括号开头、包含 script 标签、内部写入读取 Cookie 代码的文本,把它拼接到搜索页面的 URL 参数里,再通过短链接伪装后发给受害者。受害者点击链接,浏览器向网站发起搜索请求,网站把这段恶意文本当作关键词原样嵌入返回的 HTML,浏览器解析到其中的 script 标签便执行了代码,受害者的 Cookie 随即被发送到攻击者的服务器。整个过程中,网站服务器始终认为自己做的是正常搜索,攻击者的代码也未碰服务器端一行记录,但受害者的会话已被窃取。这正是 XSS 最危险之处:它利用了信任链中最薄弱的一环——浏览器对页面脚本的无条件信任。
理解了攻击链,也就理解了主流防护手段的发力点。HttpOnly 属性是对抗 Cookie 窃取的第一道闸门,服务器在设置 Cookie 时附加 HttpOnly 标记后,浏览器只允许该 Cookie 随 HTTP 请求自动发送,而禁止 JavaScript 通过 document.cookie 读取它,这样即便脚本成功执行,也无法窃取被保护的会话标识,攻击链在最后一步被硬生生掐断。内容安全策略则是另一条防线,英文缩写 CSP,它通过 HTTP 响应头告诉浏览器哪些来源的脚本允许执行,凡不在名单内的一律拒绝,相当于给浏览器戴上白名单眼镜,让注入的内联脚本即使进入页面也无法执行。需强调,HttpOnly 和 CSP 都属于纵深防御手段,无法替代最根本的修复措施——输入过滤与输出转义,但软考命题中它们常作为正确选项出现。
按照恶意脚本在攻击链中所处位置和持久程度的不同,XSS 被划分为三大类:反射型、存储型和 DOM 型。这一分类是软考信息安全工程师考试中考察频率最高的知识点之一,命题人不仅要求考生记住三个名称,更要求在给定场景中准确判断攻击类型,并据此选择对应防护方案。
反射型 XSS 又被称为非持久型 XSS,其特点是恶意脚本不落库,只存在于攻击者构造的那一条 URL 或请求参数里。攻击者把载荷拼接到链接中发给受害者,受害者点击后,网站服务器把参数里的恶意内容反射回响应页面,浏览器随即执行。因为脚本只在这一次请求响应的往返中存在,页面关闭即消失,所以被称为反射型。这类攻击的触发高度依赖社交工程,必须诱导受害者主动点击恶意链接,因此常与钓鱼邮件、即时通信中的可疑链接绑定出现。反射型 XSS 多发生在搜索、错误提示、登录回显等把请求参数直接输出的功能点上,攻击边界受限于单次交互,杀伤力相对可控,但胜在隐蔽性强、难以被服务器端日志察觉。
存储型 XSS 又被称为持久型 XSS,是三类中危害最大的一种。其特点是恶意脚本被提交后由服务器存储进数据库,凡后续访问相关页面的用户,都会从服务器拿到这段脚本并在浏览器中执行。典型落点包括留言板、评论区、用户资料、论坛帖子等。因为脚本持久化,攻击者只需注入一次就能持续攻击所有访问者,甚至实现蠕虫式自我传播。存储型 XSS 的杀伤力在于攻击面从单个受害者扩大到全体用户,且无需针对每个受害者重新构造链接,攻击成本极低而危害极大,软考案例题中涉及用户数据大面积泄露的场景,多数指向存储型 XSS。
DOM 型 XSS 是三类中最特殊的一种,它全程不经过服务器端。其原理是攻击者构造的恶意数据进入前端 JavaScript 代码,被页面脚本读取后,通过修改文档对象模型的方式动态写入页面而触发执行。文档对象模型的英文是 Document Object Model,简称 DOM,是浏览器对页面结构的可编程表示。DOM 型 XSS 的恶意脚本既不存储在服务器数据库里,也不经过服务器反射,而是完全在浏览器本地完成注入与执行,因此服务器端的日志与输入过滤对它完全失效,防御最为棘手。这类攻击常见于使用前端框架通过 URL 片段、锚点参数动态渲染内容的单页应用场景,命题人常以一段看似无害的 JavaScript 代码为题干,考察考生能否识别出其中对不可信数据的危险使用。
反射型 XSS 的成立需要两个条件同时满足:其一,网站存在把请求参数原样输出的功能点;其二,攻击者能把构造好的链接送达受害者并使其点击。缺一不可。这解释了为何命题人在选择题中会设置干扰项,声称反射型
本篇完!