浅谈前端安全-xss

2,229 阅读5分钟

引言

当代社会网络的快速普及,人们越来越依赖网络,广大网络用户通过互联网了解更多信息的同时,也面临着信息泄露的风险。网络安全成为了社会发展的焦点话题,展现在用户面前的前端界面越来越多元化,这就需要更多地去了解信息安全,这篇文章主要谈xss攻击 github

xss(Cross Site Scripting)跨站脚本攻击

观察以下代码,它们如果作为html的节点内容,就会引起xss攻击

<p><svg onload=prompt(/xss/)></p>
<iframe src="http://www.baidu.com" height="250" width="300"></iframe>
<a href="javascript:alert('xss')">你好</a>
<img src=1 onerror=alert('xss')>

因此,XSS 攻击,一般是指攻击者通过在网页中注入恶意脚本,当用户浏览网页时,恶意脚本执行,控制用户浏览器行为的一种攻击方式。

然后谈谈跨站这个词: 我们一般希望网站运行的所有逻辑均来自本站,当本站运行了其他网站的东西,例如常见的js脚本,就可能产生跨站脚本攻击,当然在这里我们可以理解为不局限于远程脚本,因为通常探测是否页面是否存在潜在的xss攻击,都会使用一个alert框,也被称做xss探测器。举个简单的例子:

例子1
// server.js
const express = require('express');
const app = express();
app.get('/', function(req, res){
  res.send('hello world')
})

app.get('/index', function(req, res){
  const id = req.query.id
  res.send(`<textarea>${id}</textarea>`)
})
app.listen(3001)

在浏览器输入网址 localhost:3001/index?id=</textarea><script>alert(1)</script>

在谷歌浏览器的运行结果

按下回车的那一瞬,并没看到预期的弹框,内心一阵失落,但同时也注意到了textarea输入框并没有东西,于是想莫非是谷歌浏览器已经做了什么事情,来预防此种简易的攻击,打开console面板

果然,好吧,输给了chrome,我还是心服口服的,在这里值得提一下,谷歌的安全机制还是很完善的,这种简单的反射性跨站攻击,谷歌直接就帮忙处理拦截了,safari也会对这种简易的攻击做基本的拦截。

换个浏览器,firefox

好吧,好大一个框!

xss攻击的原理

本质: 插值造成的注入。

脚本程序 + 数据源 = 渲染结果

脚本程序: <textarea>${id}</textarea>

数据源 渲染结果
id: 1 <textarea>1</textarea>
id: </textarea><script>alert(1)</script> <textarea></textarea><script>alert(1)</script></textarea>

在这个例子中,攻击者首先把盛放数据的容器提前闭合,然后通过script标签为数据赋予行为,让数据源不再单纯,改变整个程序的逻辑,从而实现攻击的目的

只是弹个框吗?

说到这里,我们已经用了个简单的例子引出了今天的主题xss,也许有人心里会有疑问,不就是弹个框吗?果真如此吗?我们可以换个角度考虑一下,我们平时写的脚本能干什么?

  • 获取页面数据

  • 获取cookie

  • 劫持前端的逻辑

  • 发送请求

  • ...

    因此,xss也可以干这些事,只有你想不到,没有黑客做不到的,攻击者通过注入脚本,可以通过dom抓取页面中的数据,也可以通过document.cookie获取用户的cookie,一般cookie保存有用户登陆态,攻击者只需要将这个cookie种在自己的浏览器中,就可以获取用户的各种信息,模拟用户进行各种操作等。

业界被攻击案例

上网搜了下之前被xss恶意攻击的案例,发现了一个典型,作为前端工程师,或多或少都知道站酷这个网站,这是国内非常有名的设计师交流平台,在官网首页有个搜索功能,当输入搜索关键词后点击搜索,关键词会在搜索结果页二次显示 站酷

值得说明的是:现在站酷已经修复了,目前的效果是对标签进行了处理

xss攻击分类

  • 反射型xss攻击
  • 存储型xss攻击
  • dom型xss攻击

我理解的三者的区别:

反射型xss攻击:

通过url链接点击触发,是一次性行为,实质是服务器端没有对用户的恶意输入做安全处理,直接反射相应内容。文章开始的例子1就是一个典型的反射型xss攻击,这类攻击可以通过url辨别,谷歌这类安全性高的浏览器,基本可以自动处理这类攻击

存储型xss攻击:

顾名思义,存储,一般涉及后端数据存储,常见的场景就是APP的意见反馈模块,前端通过接口把用户输入的信息传给后端,当有些后台管理系统需要展示反馈意见时,就会取数据库中的这些数据,当这些数据中含有攻击的脚本,那就造成了存储型xss攻击。这种攻击是持久性的。

dom型攻击:

客户端的脚本程序可以动态地检查和修改页面内容,而不依赖于服务器端的数据。可能引起dom型xss的:使用innerHTML, documen.write属性...

xss的注入点

  • html节点内容动态生成
  • html属性,属性是由用户输入的信息组成的
<img src="null" onerror='alert(document.cookie)' />
  • js代码-js代码存在后台注入的变量或者用户的信息
// test.html
  <script>
    const info = '';alert(1);''
    const data = `hello${info}`
  </script>
  • 富文本标签

xss防御

1 浏览器自带的防御:

X-XSS-Protection: Chrome 和 Safari 的一项功能,可在检测到反射的跨站点脚本(XSS)攻击时阻止页面加载 可选的值:

  • X-XSS-Protection: 0
  • X-XSS-Protection: 1
  • X-XSS-Protection: 1; mode=block 启用 XSS
  • X-XSS-Protection: 1; report=reporting-uri
  • 0 禁用 XSS 过滤.
  • 1 启用 XSS 过滤(通常在浏览器中默认)。如果检测到跨站点脚本攻击,浏览器将清理页面(删除不安全的部分)。
  • mode = block 如果检测到攻击,浏览器将阻止页面的呈现,而不是消毒该页面
  • report = (仅限 Chromium)启用 XSS
  • 筛选。如果检测到跨站点脚本攻击,浏览器将清理页面并报告违规行为。这使用 CSP report-uri指令的功能发送报告。
  • 拦截结果:
    防御范围: 反射型xss攻击

2. html标签转义

将标签符号'<', '>'全局转义

...
const escapeHtml = (str) => {
  str = str.replace(/&/g, '&amp;')
            .replace(/</g, '&lt;')
            .replace(/>/g, '&gt;')
            .replace(/"/g, '&quto;')
            .replace(/'/g, '&#39;')
            .replace(/ /g, '&#32;')
  return str
}
...
app.get('/index', (req, res) => {
  const id = req.query.id || ''
  res.send(`<textarea>${escapeHtml(id)}</textarea>`)
})

输出:

用户输入什么,显示什么,并且也没造成xss攻击。

3.白名单过滤

高度定制要展示的内容,这里我使用了cheerio来将html字符串解析成html实体, cheerio

// <img src="123" onerror="alert(1)"></img>使用cheerio的输出
{ type: 'tag',
  name: 'img',
  attribs: { src: '123', onerror: 'alert(1)' },
  children: [],
  next: null,
  prev: null,
  parent: null,
  root:
   { type: 'root',
     name: 'root',
     attribs: {},
     children: [ [Circular] ],
     next: null,
     prev: null,
     parent: null 
   } 
}


// server.js 无关代码已省略
...
const whiteList = {
  'img': ['src']
}
const xssFilter = (html) => {
  if (!html) return ''
  const $ = cheerio.load(html, {
    normalizeWhitespace: true,
    xmlMode: true
  })
  $('*').each((index, elem) => {
    if (!whiteList[elem.name]) {
      $(elem).remove()
      return
    }
    for (var attr in elem.attribs) {
      if (whiteList[elem.name].indexOf(attr) === -1) {
        $(elem).attr(attr, null)
      }
    }
  })
  onsole.log(html, $.html())
  return $.html()
}
app.get('/api', (req, res) => {
  const cb = (result) => {
    result = result.map(item => {
      const { comment, name, time } = item
      return {
        name,
        time,
        comment: xssFilter(comment)
      }
    })
    res.send({
      data: result,
      message: 'success',
      status: 1
    })
  }
  model.find({}, cb) // 自定义的查询方法
})
...

过滤结果: 因为白名单只配置了:'img': ['src'], 因此img的onerror属性被过滤,其他不在白名单的标签整体被过滤

4.csp(Content Security Policy)

csp文档

csp是个http的头,实质是用于规定内容是否可执行,因此只要将用户的内容标记为不可执行,就ok了~~~

// server.js
...
app.use((req, res, next) => {
  res.set({
    'Content-Security-Policy': "default-src 'self'" // 与文档同源
  })
  next()
})
...

没有攻击成功