跳到主要内容

XSS 攻击

一、认识


XSS攻击(cross-site scripting跨域脚本攻击) 是一种代码注入攻击。恶意代码未经过滤,与网站正常的代码混在一起;浏览器无法分辨哪些脚本是可信的,导致恶意脚本被执行。而由于直接在用户的终端执行,恶意代码能够直接获取用户的信息,或者利用这些信息冒充用户向网站发起攻击者定义的请求。在部分情况下,由于输入的限制,注入的恶意脚本比较短。但可以通过引入外部的脚本,并由浏览器执行,来完成比较复杂的攻击策略。

二、注入


  1. HTML 中内嵌的文本中,恶意内容以 script 标签形成注入

  2. 在内联的 JavaScript 中,拼接的数据突破了原本的限制(字符串,变量,方法名等)

  3. 在标签属性中,恶意内容包含引号,从而突破属性值的限制,注入其他属性或者标签。

  4. 在标签的 hrefsrc 等属性中,包含 javascript: 等可执行代码。

  5. onloadonerroronclick 等事件中,注入不受控制代码。

  6. style 属性和标签中,包含类似 background-image:url("javascript:..."); 的代码(新版本浏览器已经可以防范)。

  7. style 属性和标签中,包含类似 expression(...)CSS 表达式代码(新版本浏览器已经可以防范)。

三、分类


3.1 存储型 XSS

攻击过程

  1. 攻击者将恶意代码提交到目标网站的数据库中。
  2. 用户打开目标网站时,网站服务端将恶意代码从数据库取出,拼接在 HTML 中返回给浏览器。
  3. 用户浏览器接收到响应后解析执行,混在其中的恶意代码也被执行。
  4. 恶意代码窃取用户数据并发送到攻击者的网站,或者冒充用户的行为,调用目标网站接口执行攻击者指定的操作。

攻击场景

这种攻击常见于带有用户保存数据的网站功能,如论坛发帖、商品评论、用户私信等。

3.2 反射型 XSS

攻击过程

  1. 攻击者构造出特殊的 URL,其中包含恶意代码。
  2. 用户打开带有恶意代码的 URL 时,网站服务端将恶意代码从 URL 中取出,拼接在 HTML 中返回给浏览器。
  3. 用户浏览器接收到响应后解析执行,混在其中的恶意代码也被执行。
  4. 恶意代码窃取用户数据并发送到攻击者的网站,或者冒充用户的行为,调用目标网站接口执行攻击者指定的操作。

攻击场景

反射型 XSS 漏洞常见于通过 URL 传递参数的功能,如网站搜索、跳转等。由于需要用户主动打开恶意的 URL 才能生效,攻击者往往会结合多种手段诱导用户点击。POST 的内容也可以触发反射型 XSS,只不过其触发条件比较苛刻(需要构造表单提交页面,并引导用户点击),所以非常少见。

攻击方式区别:

  • 存储型 XSS: 存储型 XSS 的恶意代码存在数据库里
  • 反射型 XSS: 反射型 XSS 的恶意代码存在 URL 里。

3.3 DOM 型 XSS

攻击过程

  1. 攻击者构造出特殊的 URL,其中包含恶意代码。
  2. 用户打开带有恶意代码的 URL。
  3. 用户浏览器接收到响应后解析执行,前端 JavaScript 取出 URL 中的恶意代码并执行。
  4. 恶意代码窃取用户数据并发送到攻击者的网站,或者冒充用户的行为,调用目标网站接口执行攻击者指定的操作。

攻击方式区别:

  • 存储型 XSS: 属于服务端的安全漏洞
  • 反射型 XSS: 属于服务端的安全漏洞。
  • DOM型 XSS: DOM 型 XSS 攻击中,取出和执行恶意代码由浏览器端完成,属于前端 JavaScript 自身的安全漏洞

四、预防


4.1 攻击角度

  1. 攻击者提交恶意代码: 可以进行输入过滤

  2. 浏览器执行恶意代码: 可以防止 HTML 中出现注入恶意代码, 防止 JavaScript 执行时,执行恶意代码

4.2 输入过滤

输入过滤能够在某些情况下解决特定的 XSS 问题,比如说明确的输入类型对于数字、URL、电话号码、邮件地址等内容一定要进行过滤。对于不确定性的输入类型对于用户评论、商品描述等通过富文本内容提交时有很多的不确定性和乱码的问题,所以输入过滤并非完全可靠。

4.3 纯前端渲染

纯前端渲染的过程:

  • 浏览器先加载一个静态 HTML,此 HTML 中不包含任何跟业务相关的数据
  • 然后浏览器执行 HTML 中的 JavaScript
  • JavaScript 通过 Ajax 加载业务数据,调用 DOM API 更新到页面上

在纯前端渲染中,我们会明确的告诉浏览器:下面要设置的内容是文本(.innerText),还是属性(.setAttribute),还是样式(.style)等等。浏览器不会被轻易的被欺骗,执行预期外的代码了。在很多内部、管理系统中,采用纯前端渲染是非常合适的。但对于性能要求高,或有 SEO 需求的页面,我们仍然要面对拼接 HTML 的问题。

4.4 转义HTML

如果拼接 HTML 是必要的,就需要采用合适的转义库,对 HTML 模板各处插入点进行充分的转义。常用的模板引擎,如 doT.js、ejs、FreeMarker 等,对于 HTML 转义通常只有一个规则,就是把 & < > " ' / 这几个字符转义掉,确实能起到一定的 XSS 防护作用,但并不完善.

4.5 前端 JavaScript 代码使用避免

DOM 型 XSS 攻击,实际上就是网站前端 JavaScript 代码本身不够严谨,把不可信的数据当作代码执行了。在使用 .innerHTML、.outerHTML、document.write() 时要特别小心,不要把不可信的数据作为 HTML 插到页面上,而应尽量使用 .textContent、.setAttribute() 等。

如果用 Vue/React 技术栈,并且不使用 v-html/dangerouslySetInnerHTML 功能,就在前端 render 阶段避免 innerHTML、outerHTML 的 XSS 隐患。

DOM 中的内联事件监听器,如 location、onclick、onerror、onload、onmouseover 等,<a> 标签的 href 属性,JavaScript 的 eval()、setTimeout()、setInterval() 等,都能把字符串作为代码运行。如果不可信的数据拼接到字符串中传递给这些 API,很容易产生安全隐患,请务必避免。

4.6 Content Security Policy

确保正确使用 Content Security PolicyCSP)机制,用于减少或消除 XSS 的潜在威胁。通过允许您定义一组允许的资源源和脚本源来工作,从而有效地控制防止 XSS 攻击。

4.7 输入长度限制

对于不受信任的输入,都应该限定一个合理的长度。虽然无法完全防止 XSS 发生,但可以增加 XSS 攻击的难度。

4.8 Cookie设置httponly属性

禁止 JavaScript 读取某些敏感 Cookie,攻击者完成 XSS 注入后也无法窃取此 Cookie

五、模拟


5.1 反射性 XSS 模拟

模拟描述

攻击者发现http://localhost:3000/index?keyword=2222222 这个URL参数的keyword 未经转义直接输出到HTML 。于是攻击者构建出一个URL,并引导用于点击http://localhost:3000/index?keyword=%3Cscript%3Ealert(document.cookie)%3C/script%3E 用户点击这个URL 后,服务端直接将URL参数拼接到HTML响应中,此时浏览器收到响应就会执行alert(document.cookie) 。攻击者可以通过JavaScript 即可窃取当前用户在当前域名下的Cookie

具体模拟

const Koa = require("koa");
const Router = require("koa-router");

const app = new Koa();
const router = new Router({ prefix: "/" });

router.get("index", async (ctx) => {
const { query:{keyword} } = ctx.request;
const html = `<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<h3>${keyword}</h3>
</body>
</html>`;
ctx.body = html;
});
app.use(router.routes());
app.listen(3000, function () {
console.log("XSS 模拟服务启动成功!");
});

浏览器中输入访问http://localhost:3000/index?keyword=%3Cscript%3Ealert(document.cookie)%3C/script%3E 即可,这就是反射性XSS

参考资料


前端安全系列(一):如何防止XSS攻击?