XSS 初探

569 阅读6分钟

XSS,即 Cross Site Script,是跨站脚本攻击, 其原本缩写是 CSS,但为了和层叠样式表(Cascading Style Sheet)有所区分,因而在安全领域叫做 XSS。

XSS 攻击是指攻击者在网站上注入恶意的客户端代码,通过恶意脚本对客户端网页进行篡改,从而在用户浏览网页时,对用户浏览器进行控制或者获取用户隐私数据的一种攻击方式。

攻击者对客户端网页注入的恶意脚本一般包括 JavaScript,有时也会包含 HTMLFlash。 有很多种方式进行 XSS 攻击,但它们的共同点为:将一些隐私数据像 cookie、session 发送给攻击者,将受害者重定向到一个由攻击者控制的网站,在受害者的机器上进行一些恶意操作。

XSS 攻击可以分为 3 类:

  • 反射型(非持久型)
  • 存储型(持久型)
  • 基于 DOM。

CSRF 和 XSS 傻傻分不清

何伟 信息网络安全 ppt

一句话概括 CSRF 的危害:盗用受害者身份,受害者能做什么,攻击者就能以受害者的身份做什么。

CSRF 与 XSS 的区别:CSRF 攻击者并没有拿到用户的权限,只是借用户的权限来完成攻击;而 XSS 可以通过盗取 cookie 来获取用户权限来进行破坏。

反射型

反射型 XSS 的攻击步骤:

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

反射型 XSS 只是简单地把用户输入的数据 “反射” 给浏览器,这种攻击方式往往需要攻击者诱使用户点击一个恶意链接,或者提交一个表单,或者进入一个恶意网站时,注入脚本进入被攻击者的网站。

反射型

恶意链接的地址指向了 localhost:8001/?q=111&p=222。 服务(Node)处理恶意链接的请求:

const http = require("http");
function handleReequest(req, res) {
  res.setHeader("Access-Control-Allow-Origin", "*");
  res.writeHead(200, { "Content-Type": "text/html; charset=UTF-8" });
  // 直接往请求地址写入脚步
  res.write('<script>alert("反射型 XSS 攻击")</script>');
  res.end();
}

const server = new http.Server();
server.listen(8001, "127.0.0.1");
server.on("request", handleReequest);

当用户点击恶意链接时,页面跳转到攻击者预先准备的页面,会发现在攻击者的页面执行了 js 脚本:

恶意链接

这样就产生了反射型 XSS 攻击。攻击者可以注入任意的恶意脚本进行攻击,可能注入恶作剧脚本,或者注入能获取用户隐私数据(如 cookie)的脚本

存储型

存储型 XSS 的攻击步骤:

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

存储型 XSS 会把用户输入的数据 "存储" 在服务器端,当浏览器请求数据时,脚本从服务器上传回并执行。这种 XSS 攻击具有很强的稳定性。

比较常见的一个场景是攻击者在社区或论坛上写下一篇包含恶意 JavaScript 代码的文章或评论,文章或评论发表后,所有访问该文章或评论的用户,都会在他们的浏览器中执行这段恶意的 JavaScript 代码。

存储型 XSS 跟反射型 XSS 的区别是:存储型 XSS 的恶意代码存在数据库里,反射型 XSS 的恶意代码存在 URL 里。

<input type="text" id="input" />
<button id="btn">Submit</button>

<script>
  const input = document.getElementById("input");
  const btn = document.getElementById("btn");
  let val;
  input.addEventListener(
    "change",
    (e) => {
      val = e.target.value;
    },
    false
  );
  btn.addEventListener(
    "click",
    (e) => {
      fetch("http://localhost:8001/save", {
        method: "POST",
        body: val,
      });
    },
    false
  );
</script>

启动一个 Node 服务监听 save 请求。

const http = require("http");
let userInput = "";
function handleReequest(req, res) {
  const method = req.method;
  res.setHeader("Access-Control-Allow-Origin", "*");
  res.setHeader("Access-Control-Allow-Headers", "Content-Type");
  if (method === "POST" && req.url === "/save") {
    let body = "";
    req.on("data", (chunk) => {
      body += chunk;
    });
    req.on("end", () => {
      if (body) {
        userInput = body;
      }
      res.end();
    });
  } else {
    res.writeHead(200, { "Content-Type": "text/html; charset=UTF-8" });
    res.write(userInput);
    res.end();
  }
}

const server = new http.Server();
server.listen(8001, "127.0.0.1");
server.on("request", handleReequest);

当用户点击提交按钮将输入信息提交到服务端时,服务端通过 userInput 变量保存了输入内容。当用户通过 http://localhost:8001/${id} 访问时,服务端会返回与 id 对应的内容(本示例简化了处理)。如果用户输入了恶意脚本内容,则其他用户访问该内容时,恶意脚本就会在浏览器端执行:

恶意脚本

var element = document.getElementById("root");
element.innerHTML = '<script>alert("XSS Attack");</script>';

基于 DOM

DOM 型 XSS 的攻击步骤:

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

基于 DOM 的 XSS 攻击是指通过恶意脚本修改页面的 DOM 结构,是纯粹发生在客户端的攻击。DOM 型 XSS 跟前两种 XSS 的区别:DOM 型 XSS 攻击中,取出和执行恶意代码由浏览器端完成,属于前端 JavaScript 自身的安全漏洞,而其他两种 XSS 都属于服务端的安全漏洞。

<h2>XSS:</h2>
<input type="text" id="input" />
<button id="btn">Submit</button>
<div id="div"></div>
<script>
  const input = document.getElementById("input");
  const btn = document.getElementById("btn");
  const div = document.getElementById("div");
  let val;
  input.addEventListener(
    "change",
    (e) => {
      val = e.target.value;
    },
    false
  );
  btn.addEventListener(
    "click",
    () => {
      div.innerHTML = `<a href=${val}>testLink</a>`;
    },
    false
  );
</script>

用户提交之后,页面代码就变成了:<a href onlick="alert(/xss/)">testLink</a>

此时,用户点击生成的链接,就会执行对应的脚本:

脚本

防范 XSS

现在主流的浏览器内置了防范 XSS 的措施。但对于开发者来说,也应该寻找可靠的解决方案来防止 XSS 攻击。

HttpOnly 防止劫取 Cookie

HttpOnly 最早由微软提出,至今已经成为一个标准。浏览器将禁止页面的 Javascript 访问带有 HttpOnly 属性的 Cookie。

  1. 什么是 HttpOnly?

    如果 cookie 中设置了 HttpOnly 属性,那么通过 js 脚本将无法读取到 cookie 信息,这样能有效的防止 XSS 攻击,窃取 cookie 内容,这样就增加了 cookie 的安全性;

    即便是这样,也不要将重要信息存入 cookie。XSS 全称 Cross SiteScript,跨站脚本攻击,其原理是攻击者向有 XSS 漏洞的网站中输入(传入)恶意的 HTML 代码,这段 HTML 代码会自动执行,从而达到攻击的目的。主要盗取用户 Cookie、破坏页面结构、重定向到其它网站等。

  2. HttpOnly 的设置样例(服务端代码)

    response.setHeader(
      "Set-Cookie",
      "cookiename=httponlyTest;Path=/;Domain=domainvalue;Max-Age=seconds;HTTPOnly"
    );
    // 例如:设置cookie
    response.addHeader("Set-Cookie", "uid=112; Path=/; HttpOnly");
    //设置多个cookie
    response.addHeader("Set-Cookie", "uid=112; Path=/; HttpOnly");
    response.addHeader("Set-Cookie", "timeout=30; Path=/test; HttpOnly");
    // 设置https的cookie
    response.addHeader("Set-Cookie", "uid=112; Path=/; Secure; HttpOnly");
    

上文有说到,攻击者可以通过注入恶意脚本获取用户的 Cookie 信息。通常 Cookie 中都包含了用户的登录凭证信息,攻击者在获取到 Cookie 之后,则可以发起 Cookie 劫持攻击。所以,严格来说,HttpOnly 并非阻止 XSS 攻击,而是能阻止 XSS 攻击后的 Cookie 劫持攻击。

输入检查

不要相信用户的任何输入。 对于用户的任何输入要进行检查、过滤和转义

在 XSS 防御中,输入检查一般是检查用户输入的数据中是否包含 <,> 等特殊字符,如果存在,则对特殊字符进行过滤或编码,这种方式也称为 XSS Filter。

而在一些前端框架中,都会有一份 decodingMap, 用于对用户输入所包含的特殊字符或标签进行编码或过滤,如果输入带 script 标签的内容,会直接过滤掉.

输出检查

用户的输入会存在问题,服务端的输出也会存在问题。在变量输出到 HTML 页面时,可以使用编码或转义的方式来防御 XSS 攻击。对输出内容进行有规则的过滤之后再输出到页面中。

参考资料

先看干货

美团

githup

1-csdn

2-csdn