xss 简介

xss 跨站脚本攻击(Cross Site Scripting),为了不和层叠样式表(Cascading Style Sheets,CSS)缩写混淆,所以将跨站脚本攻击缩写为xss。

什么是XSS

xss是一种经常出现在web应用中的计算机安全漏洞,它指的是攻击者在web页面插入恶意的Script代码,当用户浏览该页之时,嵌入其中web里面的Script代码会被执行,从而达到恶意攻击用户的特殊目的。

  • 不同于大多数攻击(一般只涉及攻击者和受害者),XSS涉及到三方,即攻击者、客户端与Web应用。

  • 这些恶意网页程序通常是JavaScript,但实际上也可以包括Java,VBScript,ActiveX,Flash或者甚至是普通的HTML。

  • 攻击成功后,攻击者可能得到更高的权限(如执行一些操作)、盗取存储在客户端的cookie或者其他网站用于识别客户端身份的敏感信息、破坏页面结构、重定向到其它网站等。

XSS攻击基本原理

XSS攻击基本原理 —— 代码注入

在web的世界里有各种各样的语言,于是乎对于语句的解析大家各不相同,有一些语句在一种语言里是合法的,但是在另外一种语言里是非法的。这种二义性使得黑客可以用代码注入的方式进行攻击——将恶意代码注入合法代码里隐藏起来,再诱发恶意代码,从而进行各种各样的非法活动。只要破坏跨层协议的数据/指令的构造,我们就能攻击。

历史悠久的SQL注入和XSS注入都是这种攻击方式的典范。现如今,随着参数化查询的普及,我们已经离SQL注入很远了。但是,历史同样悠久的XSS却没有远离我们。

XSS的基本实现思路很简单 —— 比如持久型XSS:Web应用未对用户提交请求的数据做充分的检查过滤,允许用户在提交的数据中掺入HTML代码(最主要的是“>”、“<”),并将未经转义的恶意代码输出到第三方用户的浏览器解释执行,从而被攻击。

理论上,所有可输入的地方没有对输入数据进行处理的话,都会存在XSS漏洞,漏洞的危害取决于攻击代码的威力。

xss攻击的危害

  • 盗取各类用户帐号,如机器登录帐号、用户网银帐号、各类管理员帐号
  • 控制企业数据,包括读取、篡改、添加、删除企业敏感数据的能力
  • 盗窃企业重要的具有商业价值的资料
  • 非法转账
  • 强制发送电子邮件
  • 网站挂马
  • 控制受害者机器向其它网站发起攻击

xss分类

XSS分三类,存储型XSS、反射型XSS、DOM-based XSS。

反射型XSS

反射型XSS,是最常用的,使用最广的一种方式。

诱骗用户点击URL带攻击代码的链接,XSS代码作为输入提交到服务器端,服务器端解析后响应,XSS代码随响应内容一起传回给浏览器,被浏览器执行,从而攻击用户。这个过程像一次反射,故叫反射型XSS。

它的特点:是非持久化,必须用户点击带有特定参数的链接才能引起。

反射型攻击的危害会小一点,因为用户能看见,攻击者通常会通过短网址转换成用户不易察觉的URL。

反射型XSS例子

构建Node应用,演示反射型XSS攻击

$ mkdir xss
$ cd xss
$ express -e ./
$ npm install

express -e表示使用ejs作为模板
./表示当前目录中

启动服务

$ npm start

浏览器打开 http://localhost:3000/

index.js

var express = require('express');
var router = express.Router();

/* GET home page. */
router.get('/', function(req, res, next) {
  res.set('X-XSS-Protection', 0);
  res.render('index', { title: 'Express' , xss: req.query.xss});
});

module.exports = router;

index.ejs

<!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 class="">
      <%- xss %>
      <!-- <img src="null" onerror="alert(1)" /> -->
      <!-- <p onclick="alert('你点了我')">点我</p> -->
    </div>
  </body>
</html>

在浏览器中输入

http://localhost:3000/?xss=<img src="null" onerror="alert(1)" />
http://localhost:3000/?xss=<p onclick="alert('你点了我')">点我</p>
http://localhost:3000/?xss=<iframe src="//www.baidu.com/t.html"></iframe>

通过img方式自动触发
通过onclick方式改变页面结构内容,引诱触发
通过iframe方式可以植入恶意广告

储存型XSS

存储型XSS和反射型XSS的差别仅在于,提交的代码会存储在服务端(数据库、内存、文件系统等),下次请求目标页面时不用再提交XSS代码。

储存型XSS主要出现在让用户输入数据,供其他浏览此页的用户进行查看的地方,包括留言、评论、博客日志和各类表单等。

攻击者主动提交恶意数据到服务器,在数据中嵌入代码,这样当其他用户请求后,服务器从数据库中查询数据并发给用户,用户浏览此类页面时就可能受到攻击。

储存型XSS流程:恶意用户的HTML或JS输入服务器 -> 恶意数据进入数据库 -> 服务器响应时查询数据库 -> 用户浏览器。

储存型XSS, 由于攻击代码已经存储到服务器上或者数据库中,所以受害者是很多人。

而且存储型攻击的危害可能会大点,因为接收攻击的用户不易察觉。

DOM-based XSS

基于DOM的XSS,通过对具体DOM代码进行分析,根据实际情况构造dom节点进行XSS跨站脚本攻击。

一般是提供一个免费的wifi,但是提供免费wifi的网关会往你访问的任何页面插入一段脚本或者是直接返回一个钓鱼页面,从而植入恶意脚本。这种直接存在于页面,无须经过服务器返回就是基于本地的XSS攻击。

DOM-based XSS 是纯粹发生在客户端的XSS攻击

DOM-based XSS 的特点是中招的人是少数人。

注:dom xss取决于输出位置,并不取决于输出环境,因此dom xss既有可能是反射型的,也有可能是存储型的。dom-based与非dom-based,反射和存储是两个不同的分类标准。

XSS攻击注入点

  • HTML节点内容
  • HTML属性
  • js代码
  • 富文本

HTML节点内容(包含脚本)

<div>
  #{content}
</div>

<div>
  <script>
  </script>
</div>

HTML属性

<img src="#{image}" />
<img src="1" onerror="alert(1)" />

用户输入 " onerror="alert(1) ,使img产生新的属性onError

js代码

<script>
  var data = "#{data}";
  var data = "hello";alert(1);"";
</script>

用户输入 hello";alert(1);" ,提前将属性值关闭

富文本

富文本得保留HTML
HTML有XSS攻击风险

XSS防御

“所有用户输入都是不可信的”,对每个用户的输入都做严格检查,过滤,在输出的时候,对某些特殊字符进行转义,替换等。

掌握XSS的防御措施:

  • 编码
  • 过滤
  • 校正

编码-HTML Entity

对用户输入的数据进行 HTML Entity 编码

字符 十进制 转义字符
&#34; &quot;
& &#38; &amp;
< &#62; &lt;
> &#62; &gt;
空格 &#160; &nbsp;

过滤-filter

  • 移除用户上传的DOM属性,如onError等

  • 移除用户上传的 Style、link节点、Script节点、Iframe、frame节点等

  • 其中style可以控制整个页面的显示和隐藏,比如:body{display: none !important}

校正-Correcting

避免直接对HTML Entity解码

使用DOM Parse转换,校正不配对的DOM标签

掌握XSS的防范措施(反转义、DOM Parse)

  • encode.js:可以使用 https://github.com/mathiasbynens/he 中的he.js

  • domParse: https://github.com/blowsie/Pure-JavaScript-HTML5-Parser

浏览器自带防御-Browser defense

浏览器能防御:参数出现在HTML内容或属性的反射型攻击

routes/site.js

router.all('/*', async function(ctx, next){
    console.log('enter site.js');	
    // ctx.set('X-XSS-Production', 0);
    await next();
});

浏览器 set('X-XSS-Production', 0) 就不会启动防御功能了。

浏览器自带防御不能防御js代码,如下

比如,浏览器输入

localhost:1521/?from=";alert(document.cookie);"

页面js代码

<script>
  var from="";alert(document.cookie);""
</script>

HTML内容和属性转义

HTML内容转义

可以在渲染输出之前对内容进行转义

<div>
  #{content}
</div>

转义 < &lt; 和 > &gt;

var escapeHtml = function(str) {
  if (!str) return '';
  str = str.replace(/</g, '&lt;');
  str = str.replace(/>/g, '&gt;');
  return str;
}

HTML属性转义

<img src="#{image}" />
<img src="1" onerror="alert(1)" />

转义 “ &quto;、’&apos;、空格&#32;

var escapeHtmlProperty = function(str) {
  if (!str) return '';
  str = str.replace(/"/g, '&quto;');
  str = str.replace(/'/g, '&#39;');
  str = str.replace(/ /g, '&#32;');
  return str;
}

html 实体

合并成一个函数

var escapeHtml = function(str) {
  if (!str) return '';
  str = str.replace(/&/g, '&amp;');
  str = str.replace(/</g, '&lt;');
  str = str.replace(/>/g, '&gt;');
  str = str.replace(/"/g, '&quto;');
  str = str.replace(/'/g, '&#39;');
  <!--str = str.replace(/ /g, '&#32;');-->
  return str;
}

js转义

<script>
  var data = "#{data}";
  var data = "hello";alert(1);"";
</script>

转义”" 或者转换成JSON

var escapeForJs = function(str) {
  if (!str) return '';
  str = str.replace(/\\/g, '\\\\');
  str = str.replace(/"/g, '\\"');
  return str;
}

不过比较好的办法是JSON.stringify(query.form)

富文本-Rich text

  • 黑名单过滤
  • 按白名单保留部分标签和属性

黑名单方案:

var xssFilter = function(html){
  if (!html) return '';
  html = html.replace(/<\s*\/?script\s*>/g, '');
  html = html.replace(/javascript:[^'"]*/g, '');
  html = html.replace(/onerror\s*=['"]?[^'"]*['"]?/g, '');
  return html;
}

白名单方案:

cheerio

npm install cheerio --save
var xssFilter = function(html){
  if (!html) return '';
  var cheerio = require('cheerio');
  var $ = cheerio.load(html);
  
  //白名单
  var whiteList = {
    'img': ['src']
  };
  $('*').each(function(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);
      }
    }
  });
  return $.html();
}

js-xss

https://github.com/leizongmin/js-xss

$ npm install xss
var xssFilter = function(html){
  if (!html) return '';
  
  var xss = require('xss');
  var ret = xss(html, {
    whiteList: {
      img: ['src'],
      a: ['href']
    },
    onIgnoreTag(){
      return '';
    }
  });
 
  return ret;
}

CSP

content security policy 内容安全策略

用于指定哪些内容可执行

https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP

routes/site.js

router.all('/*', async function(ctx, next){	
    console.log('enter site.js');
    // ctx.set('X-XSS-Production', 0);
    ctx.set(`Content-Security-Policy`, ` script-src 'self'`);
    await next();
});

PHP-XSS

PHP中防御XSS

  • 内置函数转义
  • DOM解析白名单
  • 第三方库
  • csp

困难和幸运

真正麻烦的是,在一些场合我们要允许用户输入HTML,又要过滤其中的脚本。这就要求我们对代码小心地进行转义。否则,我们可能既获取不了用户的正确输入,又被XSS攻击。

幸好,由于XSS臭名昭著历史悠久又极其危险,现代web开发框架如vue.js、react.js等,在设计的时候就考虑了XSS攻击对html插值进行了更进一步的抽象、过滤和转义,我们只要熟练正确地使用他们,就可以在大部分情况下避免XSS攻击。

同时,许多基于MVVM框架的SPA(单页应用)不需要刷新URL来控制view,这样大大防止了XSS隐患。另外,我们还可以用一些防火墙来阻止XSS的运行。

更多-more