xss攻击

106 阅读8分钟

背景

同源策略可以隔离各个站点之间的 DOM 交互、页面数据和网络通信,虽然严格的同源策略会带来更多的安全,但是也束缚了 Web。这就需要在安全和自由之间找到一个平衡点,所以我们默认页面中可以引用任意第三方资源,然后又引入 CSP 策略来加以限制,默认 XMLHttpRequest 和 Fetch 不能跨站请求资源,然后又通过 CORS 策略来支持其跨域。

不过支持页面中的第三方资源引用和 CORS 也带来了很多安全问题,其中最典型的就是 XSS 攻击。

什么是xss攻击

XSS 全称是 Cross Site Scripting,为了与“CSS”区分开来,故简称 XSS,翻译过来就是“跨站脚本”。XSS 攻击是指黑客往 HTML 文件中或者 DOM 中注入恶意脚本,从而在用户浏览页面时利用注入的恶意脚本对用户实施攻击的一种手段。
发展到现在,往 HTML 文件中注入恶意代码的方式越来越多了,所以是否跨域注入脚本已经不是唯一的注入手段了,但是 XSS 这个名字却一直保留至今。
当页面被注入了恶意 JavaScript 脚本时,浏览器无法区分这些脚本是被恶意注入的还是正常的页面内容,如果页面被注入了恶意 JavaScript 脚本,恶意脚本都能做哪些事情。

  • 可以窃取 Cookie 信息。恶意 JavaScript 可以通过“document.cookie”获取 Cookie 信息,然后通过 XMLHttpRequest 或者 Fetch 加上 CORS 功能将数据发送给恶意服务器;恶意服务器拿到用户的 Cookie 信息之后,就可以在其他电脑上模拟用户的登录,然后进行转账等操作。
  • 可以监听用户行为。恶意 JavaScript 可以使用“addEventListener”接口来监听键盘事件,比如可以获取用户输入的信用卡等信息,将其发送到恶意服务器。
  • 可以通过修改 DOM伪造假的登录窗口,用来欺骗用户输入用户名和密码等信息。
  • 还可以在页面内生成浮窗广告,这些广告会严重地影响用户体验。

除了以上几种情况外,恶意脚本还能做很多其他的事情,这里就不一一介绍了。总之,如果让页面插入了恶意脚本,那么就相当于把我们页面的隐私数据和行为完全暴露给黑客了。

常见的的注入方式

通常情况下,主要有存储型 XSS 攻击、反射型 XSS 攻击和基于 DOM 的 XSS 攻击三种方式来注入恶意脚本。

1.存储型xss攻击

image.png
通过上图,我们可以看出存储型 XSS 攻击大致需要经过如下步骤:

  • 首先黑客利用站点漏洞将一段恶意 JavaScript 代码提交到网站的数据库中
  • 然后用户向网站请求包含了恶意 JavaScript 脚本的页面
  • 当用户浏览该页面的时候,恶意脚本就会将用户的 Cookie 信息等数据上传到服务器
  • 黑客拿到了用户 Cookie 信息之后,就可以利用 Cookie 信息在其他机器上登录该用户的账户,并利用用户账号进行一些恶意操作

2.反射型xss攻击

首先恶意 JavaScript 脚本属于用户发送给网站请求中的一部分,随后网站又把恶意 JavaScript 脚本返回给用户。当恶意 JavaScript 脚本在用户页面中被执行时,黑客就可以利用该脚本做一些恶意操作。

下面我们结合一个简单的 Node 服务程序来看看什么是反射型 XSS

var express = require('express');
var router = express.Router();
router.get('/', function(req, res, next) {
  res.render('index', { title: 
 'Express',xss:req.query.xss });
})
module.exports = router;
<!DOCTYPE html>
<html>
<head>
  <title><%= title %></title>
  <link rel='stylesheet' href='/stylesheets/style.css' />
</head>
<body>
  <h1><%= title %></h1>
  <p>Welcome to <%= title %></p>
  <div>
      <%- xss %>
  </div>
</body>
</html>

第一段是路由,第二段是视图,作用是将 URL 中 xss 参数的内容显示在页面。我们可以在本地演示下,比如打开http://localhost:3000/?xss=123这个链接,这样在页面中展示就是“123”了(如下图),是正常的,没有问题的。\ image.png
但当打开http://localhost:3000/?xss=这段 URL 时,其结果如下图所示:
image.png
通过这个操作,我们会发现用户将一段含有恶意代码的请求提交给 Web 服务器,Web 服务器接收到请求时,又将恶意代码反射给了浏览器端,这就是反射型 XSS 攻击。现实生活中常常通过QQ、邮件发送包含恶意的链接。

另外需要注意的是,Web 服务器不会存储反射型 XSS 攻击的恶意脚本,这是和存储型 XSS 攻击不同的地方

3.基于DOM的xss攻击

基于 DOM 的 XSS 攻击是不牵涉到页面 Web 服务器的。具体来讲,黑客通过各种手段将恶意脚本注入用户的页面中,比如通过网络劫持在页面传输过程中修改 HTML 页面的内容,这种劫持类型很多,有通过 WiFi 路由器劫持的,有通过本地恶意软件来劫持的,它们的共同点是在 Web 资源传输过程或者在用户使用页面的过程中修改 Web 页面的数据。

如何阻止xss攻击

存储型 XSS 攻击和反射型 XSS 攻击都是需要经过 Web 服务器来处理的,因此可以认为这两种类型的漏洞是服务端的安全漏洞。而基于 DOM 的 XSS 攻击全部都是在浏览器端完成的,因此基于 DOM 的 XSS 攻击是属于前端的安全漏洞。

但无论是何种类型的 XSS 攻击,它们都有一个共同点,那就是首先往浏览器中注入恶意脚本,然后再通过恶意脚本将用户信息发送至黑客部署的恶意服务器上。
所以要阻止 XSS 攻击,我们可以通过阻止恶意 JavaScript 脚本的注入和恶意消息的发送来实现。
接下来我们就来看看一些常用的阻止 XSS 攻击的策略。

1. 服务器对输入脚本进行过滤或转码

不管是反射型还是存储型 XSS 攻击,我们都可以在服务器端将一些关键的字符进行转码,比如:

code:<script>alert('你被xss攻击了')</script>

过滤后,只留下了:

code:

这样,当用户再次请求该页面时,由于

code:&lt;script&gt;alert(&#39;你被xss攻击了&#39;)&lt;/script&gt;

经过转码之后的内容,如

2. 充分利用 CSP

虽然在服务器端执行过滤或者转码可以阻止 XSS 攻击的发生,但完全依靠服务器端依然是不够的,我们还需要把 CSP 等策略充分地利用起来,以降低 XSS 攻击带来的风险和后果。实施严格的 CSP 可以有效地防范 XSS 攻击。
csp功能:

  • 限制加载其他域下的资源文件,这样即使黑客插入了一个 JavaScript 文件,这个 JavaScript 文件也是无法被加载的
  • 禁止向第三方域提交数据,这样用户数据也不会外泄
  • 禁止执行内联脚本和未授权的脚本
  • 还提供了上报机制,这样可以帮助我们尽快发现有哪些 XSS 攻击,以便尽快修复问题
    利用好 CSP 能够有效降低 XSS 攻击的概率。

3.使用HttpOnly属性

由于很多 XSS 攻击都是来盗用 Cookie 的,因此还可以通过使用 HttpOnly 属性来保护我们 Cookie 的安全。

通常服务器可以将某些 Cookie 设置为 HttpOnly 标志,HttpOnly 是服务器通过 HTTP 响应头来设置的。

set-cookie 属性值最后使用了 HttpOnly 来标记该 Cookie。顾名思义,使用 HttpOnly 标记的 Cookie 只能使用在 HTTP 请求过程中,所以无法通过 JavaScript 来读取这段 Cookie。我们还可以通过 Chrome 开发者工具来查看哪些 Cookie 被标记了 HttpOnly,如下图:
image.png

这个 Cookie 的 HttpOlny 属性是被勾选上的,所以内容是无法通过 document.cookie 是来读取的。
由于 JavaScript 无法读取设置了 HttpOnly 的 Cookie 数据,所以即使页面被注入了恶意 JavaScript 脚本,也是无法获取到设置了 HttpOnly 的数据。因此一些比较重要的数据我们建议设置 HttpOnly 标志。

总结

XSS 攻击就是黑客往页面中注入恶意脚本,然后将页面的一些重要数据上传到恶意服务器。常见的三种 XSS 攻击模式是存储型 XSS 攻击、反射型 XSS 攻击和基于 DOM 的 XSS 攻击。

共同点是都需要往用户的页面中注入恶意脚本,然后再通过恶意脚本将用户数据上传到黑客的恶意服务器上。而三者的不同点在于注入的方式不一样,有通过服务器漏洞来进行注入的,还有在客户端直接注入的。

针对这些 XSS 攻击,主要有三种防范策略,第一种是通过服务器对输入的内容进行过滤或者转码,第二种是充分利用好 CSP,第三种是使用 HttpOnly 来保护重要的 Cookie 信息。

除了以上策略之外,我们还可以通过添加验证码防止脚本冒充用户提交危险操作。而对于一些不受信任的输入,还可以限制其输入长度,这样可以增大 XSS 攻击的难度。