前端安全之浅谈xss攻击和预防


外圆内方 2020-1-19 xss

XSS

  1. 基本概念
  2. 攻击原理
  3. 攻击类型
  4. 预防措施
  5. 实践

一、基本概念

跨站脚本攻击

二、攻击原理

通过注入可执行的代码且被浏览器执行,这就产生了执行成功的两个必要条件

  1. 注入恶意代码
  2. 恶意代码被成功执行

三、攻击类型

  1. 反射型,发出请求时,XSS代码出现在URL中,作为输入提交到服务器,服务器解析并响应,XSS代码随着响应内容返回浏览器,浏览器执行XSS代码。

使用express搭个服务

var express = require('express')
var app = express()

app.all('*', function (req, res, next) {
  res.header('Access-Control-Allow-Origin', 'http://127.0.0.1:8082');
  res.header('Access-Control-Allow-Headers', 'Content-Type');
  res.header('Access-Control-Allow-Methods', '*');
  res.header('Content-Type', 'application/json;charset=utf-8');
  next();
})

app.get('/test', function (req, res, next) {
  // 2. 服务端解析成JSON后响应
   res.json({
     test: req.query.test
   })
 })

 app.listen(8081, () => console.log('Example app listening on port 8081!'))

使用http-server起一个8082,html内容:

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>Document</title>
</head>
<body>
  <textarea name="txt" id="txt" cols="100" rows="8"></textarea>
  <button type="button" id="test">测试</button>
  <img src="null" onerror='alert(document.cookie)' />
  <script>
    var test = document.querySelector('#test')
    test.addEventListener('click', function () {
    var url = `http://localhost:8081/test?test=${txt.value}`   // 1. 发送一个GET请求
    var xhr = new XMLHttpRequest()
    xhr.onreadystatechange = function () {
        if (xhr.readyState === 4) {
          if ((xhr.status >= 200 && xhr.status < 300) || xhr.status === 304) {
            // 3. 客户端解析JSON,并执行
            var str = JSON.parse(xhr.responseText).test
            var node = `${str}`
            document.body.insertAdjacentHTML('beforeend', node)
          } else {
            console.log('error', xhr.responseText)
          }
        }
      }
      xhr.open('GET', url, true)
      xhr.send(null)
    }, false)
  </script>
</body>
</html>

在textarea中输入<img src="null" onerror='alert(1)' />,将会弹出1。以上模拟了一次反射型的xss攻击.

  1. 储存型,与反射型的区别在于,提交的代码会存储在服务器上(数据库,内存,文件系统)。具有较强的稳定性和持久性。

eg:用户a在留言板留言,里面插入了恶意代码并提交。网站将留言存在数据库中。用户b访问留言板,用户a的恶意代码此时被浏览器执行。此时用户a可能窃取了用户b的信息,或者将用户b的访问重定向到钓鱼网站等等

四、预防措施

  1. 编码,对用户输入的数据进行HTML Entity编码。把 &, <, >, 空格, ', ", 换行符 等改为 &, <, >,  , ', "转义字符或十进制字符
  2. 过滤,移除用户上传的DOM属性(如onerror等)和节点(style、script、iframe等)
  3. 校正,避免直接对HTML Entity解码;使用DOM Parse转换,校正不匹配的DOM标签

五、实践 建议采用目前主流成熟的库xss,不要自己写

  1. 前端使用
<script src="https://rawgit.com/leizongmin/js-xss/master/dist/xss.js"></script>
<script>
// apply function filterXSS in the same way
var html = filterXSS('<script>alert("xss");</scr' + 'ipt>');
alert(html);
</script> 
  1. 后端使用
var express = require('express');
var router = express.Router();

router.get('/test', function(req, res, next) {
  res.send(xss(req.body.userInput))
});

module.exports = router;

注:xss在使用时如果过滤的是一段html,那么它默认会过滤掉标签上的style,这里在富文本使用时不友好。可以按照以下配置

// 第一个参数是需要过滤的字符串
xss(text, {
    // 自定义匹配标签属性的处理程序功能, 参数依次为:标签名(<a></a>为a),属性名称,属性值,是否在白名单中
    onTagAttr: function(tag, name, value, isWhiteAttr) {
        if (name === 'style') {
            return `${name}='${value}'`
        }
    }
})