Web 安全 | 青训营

132 阅读8分钟

Web 安全

XSS 跨站脚本攻击

XSS 攻击指的是跨站脚本攻击,是一种代码注入攻击。攻击者通过在网站注入恶意脚本,使之在用户的浏览器上运行,从而盗取用户的信息如 cookie 等。

攻击者可以通过 XSS 攻击实现以下操作:

  • 获取页面的数据,如 DOM、cookie、localStorage
  • DOS 攻击,发送合理请求,占用服务器资源,从而使用户无法访问服务器
  • 破坏页面结构
  • 流量劫持(将链接指向某网站)

XSS:

  • 盲目信任用户提交的内容

  • string -> DOM

    • document.write
    • element.innerHTML
    • SSR(user_data)

XSS 的特点:

  • 通常难以从 UI 上感知(暗地执行脚本)
  • 窃取用户信息(cookie / token)
  • 绘制 UI(例如弹窗),诱骗用户点击,填写表单

反射型 XSS

不涉及数据库,仅在 URL 上攻击

  1. 攻击者构造出特殊的 URL,其中包含恶意代码

  2. 用户打开带有恶意代码的 URL,网站服务端将恶意代码从 URL 中取出,拼接在 HTML 中返回给浏览器

  3. 用户浏览器接收到响应后解析执行,混在其中的恶意代码也被执行

  4. 恶意代码窃取用户数据并发送到攻击者的网站,或者冒充用户的行为,调用目标网站接口执行攻击者指定的操作。

常见于通过 URL 传递参数的功能,如网站搜索、跳转等。

由于需要用户主动打开恶意的 URL 才能生效,攻击者往往会结合多种手段诱导用户点击。

举例:

// 某个 query 参数为
`host/path/?param=<script>alert('xss')</script>`

// 服务端
public async render(ctx) {
  const { param } = ctx.query;
  ctx.status = 200;
  ctx.body = `<div>${param}</div>`
}

存储型 XSS

最严重、最厉害的(对所有用户可见)

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

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

举例:

// 服务端:没有过滤
public async submit(ctx) {
  const { content, id } = ctx.request;
  // 没有对 content 过滤
  await db.save({
    content,
    id
  });
}


public async render(ctx) {
  const { content } = await db.query({
    id: ctx.query.id
})
  // 可能 content 直接执行脚本
  ctx.body = `<div>${content}</div>`
}

// 浏览器端:用户恶意提交
fetch("/submit", {
  body: JSON.stringify({
    id: "1",
    content: `<script>alert("xss")</script>`
  })
});

DOM 型 XSS

不需要服务器参与,恶意攻击的发起和执行都在浏览器完成

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

DOM 型 XSS 跟前两种 XSS 的区别:

  • DOM 型:取出和执行的代码由浏览器端完成,属于前端 JavaScript 自身的安全漏洞
  • 而另外两种:属于服务端的安全漏洞

举例:

// 某个 query 参数为
`host/path/?param=<script>alert('xss')</script>`

// 浏览器端
const content = new URL(location.href).searchParams.get("param");
const divEle = document.createElement("div");
// 恶意脚本被注入
divEle.innerHTML = content;
document.body.append(divEle);

Mutation-based XSS

  • 利用了浏览器渲染 DOM 的特性(独特优化)
  • 不同浏览器,会有区别(按浏览器进行攻击)

举例:

<noscript><p title="</noscript><img src=x onerror=alert(1)>">

<!-- 实际的渲染 -->
<div>
  <noscript><p title=" </noscript>
  
  <img src="x" onerror="alert(1)">
  "">"
</div>

由于 img 的 src 错误,触发了 onerror。

预防 XSS 攻击

永远不信任用户的提交内容,不要将用户提交内容直接转换成 DOM

现成的防御工具:

  • 前端

    • 主流框架默认防御 XSS
    • google-closure-libarary
  • 服务端(Node)

    • DOMPurify(npm 包)

  1. 用纯前端的方式,不适用服务器端拼接后返回(不使用服务端渲染)

  1. 字符串转义

替换字符 <​​​ >​​​ 为对应的 html 字符实体

value.replace(/</g, '&lt;').replace(/>/g, '&gt;')

  1. 使用 CSP

CSP 的本质是建立一个白名单,告诉浏览器哪些外部资源可以加载和执行,从而防止恶意代码的注入攻击

对 eval + inline script 直接拒绝

image.png

其他需要防范的情况:

image.png

image.png

image.png

CSRF 跨站请求伪造攻击

Cross-site request forgery

攻击者诱导用户进入一个第三方网站,然后该网站向被攻击网站发送跨站请求。如果用户在被攻击网站中保存了登录状态,那么攻击者就可以利用这个登录状态,绕过后台的用户验证,冒充用户向服务器执行一些操作。也就是:

  • 在用户不知情的前提下
  • 利用用户权限(cookie)
  • 构造指定 HTTP 请求,窃取或修改用户敏感信息

本质:利用 cookie 会在同源请求中携带发送给服务器 的特点,冒充用户。

image.png

GET 类型的 CSRF

比如一个网站的 img 标签里构建一个请求,当用户打开这个网站的时候就会自动发起请求

举例:

<img style="display:none;" src="https://bank.com/transfer?to=hacker&amount=100">

POST 类型的 CSRF

比如构建一个表单,然后隐藏它,当用户进入页面时,自动提交这个表单

链接类型的 CSRF

比如在 a 标签的 href 属性里构建要给请求,然后诱导用户去点击

举例:

<a href="https://bank.com/transfer?to=hacker&amount=100">点我抽奖</a>

预防 CSRF 攻击

  1. 进行同源检测

服务器根据 http 请求头中的 orgin​​ 和 referer​​ 信息来判断请求是否为允许访问的站点

缺点是:referer 也可以伪造;搜索引擎的请求会被屏蔽(通常会设置为允许,但这也容易被利用)

比如 iframe 请求是同源的:

image.png

应该设置 X-Frame-Options 响应头部。

  1. 使用 CSRF Token 进行验证

image.png

服务器向用户返回一个随机数 Token,当网站再次发起请求时,在请求参数或者表单中加入这个 token,然后服务器对这个 token 进行验证。(必须保证令牌是由服务器动态生成的,而不是客户端生成)

这种方法解决了使用 cookie 单一验证方式时,可能会被冒用的问题。

缺点:

  • 网站中的所有请求都添加上这个 token,操作比较繁琐
  • 一般不会只有一台网站服务器,如果请求经过负载平衡转移到了其他的服务器,但是这个服务器的 session 中没有保留这个 token 的话,就没有办法验证了。(这种情况可以通过改变 token 的构建方式来解决)
  • 应该进行用户绑定,以免一个合法用户作为攻击者试探其他的越权功能
  • 需要设置过期时间,否则被窃取则会一直被攻击

  1. 对 Cookie 进行双重验证

服务器在用户访问页面时,向请求域名注入一个 Cookie,内容为随机字符串,然后当用户再次向服务器发送请求的时候,从 cookie 中取出这个字符串,添加到 URL 参数中,然后服务器通过对 cookie 中的数据和参数中的数据进行比较来验证。

使用这种方式是利用了攻击者只能利用 cookie,但是不能访问获取 cookie 的特点。

并且这种方法比 CSRF Token 的方法更加方便,并且不涉及到分布式访问的问题。

缺点:如果网站存在 XSS 漏洞的,那么这种方式会失效。同时这种方式不能做到子域名的隔离。

  1. cookie 属性设置 Samesite

image.png

限制 cookie 不能作为被第三方使用,从而可以避免被攻击者利用。

Samesite 一共有两种模式:

  • 严格模式:cookie 在任何情况下都不能作为第三方 Cookie 使用
  • 宽松模式:cookie 可以被 类型是 GET 且 会发生页面跳转 的请求使用

image.png

SameSite vs CORS

image.png

应该在 node 层用中间件防御各种 CSRF 攻击。

Injection 注入攻击

SQL Injection

image.png

  1. 读取请求字段
  2. 直接以字符串的形式拼接 SQL 语句
// 服务端
public async renderForm(ctx) {
  const { username, form_id } = ctx;
  const result = await sql.query(`
    SELECT a, b, c FROM table
    WHERE username = ${username}
    AND form_id = ${form_id}
`);
  ctx.body = renderForm(result);
}

// 浏览器端
fetch("/api", {
  mothod: "POST",
  headers: {
    "Content-Type": "application/json"
  },
  body: JSON.stringify({
    username: "any; DROP TABLE table;"
  })
})

// 执行的结果(删库)
SELECT xxx FROM xxx; DROP TABLE table;

还有其他注入:

  • CLI

  • OS command(命令行)

    • 举例

      // 服务器端
      public async convertVideo(ctx) {
        const { video, options } = ctx.request.body;
        exec(`convert-cli ${video}-o ${options}`);
        ctx.body = "ok";
      }
      
      // 浏览器端
      fetch("/api", {
        method: "POST",
        body: JSON.stringify({
          options: `' && rm -rf xxx`
        })
      });
      
      // 执行的结果
      const command = `convert-cli video -o && rm -rf xxx`
                                            // ~~~~~~~~~~
      
  • SSRF(服务器伪造请求,不是注入,但原理类似)

    • 举例:
    • image.png

读取和修改 举例:

image.png

防御

  • 最小权限原则:不要轻易给 sudo 或 root 权限
  • 建立允许名单 + 过滤:不要轻易允许使用 rm 命令
  • 对 URL 类型参数进行协议、域名、ip等限制:拒绝访问内网

Dos

Denial of Service,服务器拒绝

通过某种方式(构造特定请求),导致服务器资源被显著消耗,来不及响应更多请求,导致请求挤压,进而雪崩效应。

基于贪婪正则表达式的 Dos

image.png

image.png

防御:

  • Code Review:避免贪婪正则表达式
  • 代码扫描 + 正则性能测试
  • 禁止用户提供的正则表达式

DDoS

Distributed DoS,分布式的 DoS

短时间内,来自大量僵尸设备的请求流量,服务器不能及时完成全部请求,导致请求堆积,进而雪崩效应。

特点:不限制在域名,而是 ip

  • 耗时的同步操作
  • 数据库写入
  • SQL join
  • 文件备份
  • 循环执行逻辑

SYN 洪水攻击 不返回 TCP 握手的第三次:

image.png

防御:

  • 流量治理

    • 负载均衡(过滤)
    • API 网关(过滤)
    • CDN(抗量)
  • 快速扩容(抗量)

  • 非核心服务降级(抗量)

传输层攻击

中间人攻击

详见 HTTP 的笔记中 HTTPS 部分

image.png

防御:HTTPS