前端安全之防范XSS实战小结

因为前段时间业务有接触到富文本编辑器,且编辑器由用户直接使用,因此不可避免须要对其涉及到的XSS防御有所了解,所以对XSS防御作一个实战小结。

前言

XSS大部分前端coder都不会陌生,全称:跨站脚本漏洞(Cross Site Scripting,简写做XSS)是Web应用程序在将数据输出或者展现到网页的时候存在问题,致使攻击者能够将对网站的正常功能形成影响甚至窃取或篡改用户我的信息,其诱发的主要缘由是没有针对用户输入来源的数据以及不可信数据源的过滤。本文将针对XSS进行简单的归类总结,而且提供相关的实战处理手段。javascript

1、XSS 类型划分

对于常见的XSS攻击方式,大部分人再熟悉不过了,这里再帮你们复习一下。css

一、反射性XSS:常见状况是攻击者经过构造一个恶意连接的形式,诱导用户传播和打开,因为连接内所携带的参数会回显于页面中或做为页面的处理数据源,最终形成XSS攻击。html

二、存储型XSS:与前者不一样,存储型XSS是持久化的XSS攻击方式,经过用户输入我的信息或者发表文章的方式将恶意代码存储于服务器端,当其余用户再次访问页面时触发,形成XSS攻击。前端

三、DOM based型XSS:一样也是利用对数据源不可靠,缺少过滤,因为开发过程当中,不可避免部分数据须要回填到DOM中,例如href、src、data-id等标签属性,而经过构造特殊的字符串闭合原有的DOM标签,在其中注入script标签的方式形成攻击。java

2、XSS防范手段

针对XSS的防范,须要开发者养成意识,针对用户输入源的数据进行过滤,针对页面不可信任的数据源也要作好过滤,相似于响应头中的字段、url中的参数、refer等字段都是不可信的,而且结合其余手段和方法,让页面更加安全可控。node

一、Htmlencode转义特殊字符

大部分的论坛或者博客平台,注册帐号都会容许用户填写我的信息,包括昵称、邮箱和个性签名等,此类文本信息属于非富文本类型,最多见的方法就是对尖括号标签转义成实体字符存储,因为是非富文本信息,而且以标签内容的形式展现,若是不使用框架渲染,直接原生JS经过docoment.getElementById('xxx').innerText = 'your name'展现回页面中便可。git

常见的处理方式以下:浏览器

const htmlEncode = function (handleString){
    return handleString
    .replace(/&/g,"&")
    .replace(/</g,"&lt;")
    .replace(/>/g,"&gt;")
    .replace(/ /g,"&nbsp;")
    .replace(/\'/g,"&#39;")
    .replace(/\"/g,"&quot;");
}

二、引入XSS库针对用户输入源过滤,设置标签白名单

前面说明了在非富文本的状况下较为快捷方便的处理方法,然而在大部分论坛中发帖发文章都是富文本编辑的形式,即最后回显于页面中的是一段HTML内容,不能单纯以文本形式处理。安全

大部分的富文本编辑器原理,都是提供一个具有contenteditable属性的dom元素,让用户对一段富文本进行编辑,其本质是对一段html进行处理,新增或删除样式等,最后经过回传富文本框中html的方式提供给开发者,意味着咱们要容许用户填充一段html于咱们的页面中。而获取到的html字符串咱们不能直接进行简单的标签替换,不然会致使原有的样式丢失,最终展现在页面中的也再也不是一篇排版精致的文章,所以咱们要另寻他路。服务器

首先须要明确的是,不管是这份来自于富文本编辑器的html,仍是来自于最终用户发起请求所获取到的html,都是不可信的,意味着在前端进行过滤是没有任何实际意义和价值的,由于攻击者能够轻易的伪造请求绕过限制,因此咱们须要在咱们的服务器端针对这段html进行过滤处理。针对html的标签白名单过滤,不一样的语言有不一样的库实现,这里主要介绍nodejs中经常使用的标签过滤库,nodejs中经常使用的库主要是xss和xss-filter,下面以xss库的使用为例:

import * as xss from 'xss';
function handleXss(content: Content) {
  // 设置HTML过滤器的白名单
  const options = {
    whiteList: {
      p: ['class', 'style'],
      em: ['class', 'style'],
      strong: ['class', 'style'],
      br: ['class', 'style'],
      u: ['class', 'style'],
      s: ['class', 'style'],
      blockquote: ['class', 'style'],
      li: ['class', 'style'],
      ol: ['class', 'style'],
      ul: ['class', 'style'],
      h1: ['class', 'style'],
      h2: ['class', 'style'],
      h3: ['class', 'style'],
      h4: ['class', 'style'],
      h5: ['class', 'style'],
      h6: ['class', 'style'],
      span: ['class', 'style'],
      div: ['class', 'style'],
      img: ['src', 'class', 'style', 'width'],
    },
  }; 
  // 自定义规则
  const myxss = new xss.FilterXSS(options);
  // 直接调用 myxss.process() 便可
  content.content = myxss.process(content.content);
  return content;
}

经过限定白名单,仅容许常见的文本展现标签以及图片img标签进入白名单,这部分会再过滤后被保留,而且标签内的class和style属性也会被保留;其余属性和诸如script、iframe等标签都会被直接过滤掉。 

这里为什么要严格限定标签的以及属性的缘由,例如一个a标签能够经过href属性注入一段相似这样的js代码:

<a href\="javascript:alert('xss')"\>click me</a\>

更有甚者会对注入的字符进行大小写字符转换,或转义成等效字符,或插入空格或tab,但脚本仍然能正常运行,形成攻击。

<p onclick="alert('xss')">this is a text</p>
<img src="xxx.com/test.jpg" onerror="alert('xss')"/>

其次,普通的标签能够直接经过绑定onclick的方式攻击,即使是img、video等资源加载标签,也能够经过onload、onerror等事件注入脚本,可见针对标签内属性的过滤也是不可或缺的。

然而本觉得这样已足够,可是即便是只开放了class和style属性开放了,也是不安全的,关键在于style属性,若是任由用户自定义的话,能够经过style属性实现:点击劫持(将元素铺满整个界面)、加载外域图片、脚本注入甚至能够给文章设置一些花里胡哨的动画:

<div style="position:absolute;top:0;left:0;width:2000px;height:2000px;z-index:9999;">
点击劫持的元素,阻止页面其余操做</div>

<div style="background:url(javacript:alert('xss'))">
借助style标签注入脚本,大部分xss过滤库会帮咱们过滤这部分脚本</div>

<div style="background:url(//xxx.com/H图.jpg)">
偷偷加载其余网站的小H图,绕过过滤和审核</div>

可见style属性也不容忽视,所以咱们须要在option参数中额外为style属性设置白名单,确保style属性安全可控:

...
   css: {
      whiteList: {
        color: true,
        'background-color': true,
      },
    },
...

其次为了确保不加载非本域名下的图片资源,咱们也能够再这一层作一些针对img标签的过滤:

...
    stripIgnoreTag: true,
    onTagAttr: (tag:string, name:string, value:string, isWhiteAttr:boolean) => {
      // 判断img下的src属性 若是非本域名下 返回空
      if (isWhiteAttr && tag === 'img' && name === 'src' && !checkLegal(value)) 
      {
        return '#';
      }
    },
    ...

三、cookie设置HttpOnly,配合token或验证码防范

针对信息源的过滤,针对不可信数据源的过滤,已经能达到初步的效果,但这远远不够,毕竟没有绝对的安全。

因为大部分攻击者会想要获取到用户的cookie去作别的坏事,因此咱们须要在http的响应头set-cookie时设置httpOnly,让浏览器知道不能经过document.cookie的方式获取到cookie内容。

app.get('/', (req, res) => {
    if(req.cookies.isVisit) {
        console.log(req.cookies)
        res.send('欢迎再次光临')
    } else {
        res.cookie('isVisit', 1, {maxAge: 3600 * 1000, httpOnly: true}) 
        res.send('欢迎初次光临')
    }
})

虽然避免了攻击者直接获取到cookie,可是攻击者仍然能够页面内发起别的请求,直接篡改用户的信息,所以须要咱们配合token或者验证码的形式,防止攻击者直接经过脚本的方式篡改用户我的信息。而这个token相似于CSRF中咱们所须要的token,不过若是攻击者仔细研究了代码,而且知道的token在页面中的来源,也是能够直接获取到token的,所以,相比之下验证码安全性更高。

四、设置CSP安全策略

除了针对数据源的严格过滤之外,CSP安全策略的限制也是主要的XSS防范手段之一,经过在页面中设置容许加载的资源的来源,来严格限制页面可加载的脚本以及图片等资源,防止外部的脚本攻击后注入其余脚本以及内容。

CSP,内容安全策略,是一种基于内容的声明式网络应用程序机制,对缓解内容注入漏洞的危害很是有效。经过一系列指令告诉客户端(如浏览器)被保护资源(如页面)内只容许加载和执行指令集中限定的内容,相似白名单机制,不知足限定条件的资源和内容将被客户端阻断或不被执行。能够经过两种方式设置CSP,一种是meta标签,一种是HTTP响应头Content-Security-Policy:

指令及其说明:
default-src 定义资源默认加载策略
connect-src 定义Ajax、 WebSocket等加载策略
font-src 定义Font 加载策略
frame- src 定义Frame加载策略
img-src 定义图片加载策略
media- src 定义<audio>、 <video> 等引用资源加载策略
object-src 定义 <applet>、 <embed>、 <object> 等引用资源加载策略
script-src 定义JS加载策略
style- src 定义CSS加载策略

对应的指令value能够设置为:(图片引自:https://www.jianshu.com/p/4bad03d89c04
image.png
相信聪明的你看了这个规则一看就懂了,接下来,以meta标签设置CSP为例,咱们能够以下设置,以此来限制对白名单外资源加载:

<meta http-equiv="Content-Security-Policy" content=
    "script-src 'self' *.qq.com *.cdn-go.cn; 
    img-src 'self' *.cdn-go.cn *.gtimg.cn data:;
    style-src 'unsafe-inline' *.cdn-go.cn; 
    media-src 'none'; 
    child-src *.qq.com">

五、其余HTTP安全响应头设置

除了CSP这个最经常使用最强势的限制规则外,还有其余的HTTP安全响应头,若是心情好的话,能够通通整上(滑稽):

1)、X-Frame-Options:X-Frame-Options HTTP 响应头是用来给浏览器指示容许一个页面能否在 <frame>, <iframe>或者 <object> 中展示的标记。网站可使用此功能,来确保本身网站的内容没有被嵌到别人的网站中去,也从而避免了点击劫持 (clickjacking) 的攻击。
X-Frame-Options 有三个值:

DENY
表示该页面不容许在 frame 中展现,即使是在相同域名的页面中嵌套也不容许。
SAMEORIGIN
表示该页面能够在相同域名页面的 frame 中展现。
ALLOW-FROM uri
表示该页面能够在指定来源的 frame 中展现。

2)、X-Content-Type-Options:X-Content-Type-Options 响应首部至关于一个提示标志,被服务器用来提示客户端必定要遵循在 Content-Type 首部中对 MIME 类型 的设定,而不能对其进行修改。这就禁用了客户端的 MIME 类型嗅探行为,(类型嗅探指的是浏览器在加载资源时,自动嗅探资源类型过程当中,致使执行了注入脚本),换句话说,也就是意味着网站管理员肯定本身的设置没有问题。

3)、X-XSS-Protection:当检测到跨站脚本攻击 (XSS)时,浏览器将中止加载页面。

然而这个响应头因为只有IE、谷歌等少部分浏览器支持,且不一样浏览器的实现不一致,存在错误过滤行为,甚至可能致使带额外的漏洞,因此在实际状况中较少使用。(为避免错误过滤,部分门户网站会将其值设置为0)

X-XSS-Protection: 0
X-XSS-Protection: 1
X-XSS-Protection: 1; mode=block
X-XSS-Protection: 1; report=<reporting-uri>
0禁止XSS过滤。
1启用XSS过滤(一般浏览器是默认的)。 若是检测到跨站脚本攻击,浏览器将清除页面(删除不安全的部分)。
1;mode=block启用XSS过滤。 若是检测到攻击,浏览器将不会清除页面,而是阻止页面加载。
1; report=<reporting-URI> (Chromium only)启用XSS过滤。 若是检测到跨站脚本攻击,浏览器将清除页面并使用CSP report-uri指令的功能发送违规报告。

以推特网站为例,其设置的响应头以下:
x-content-type-options: nosniff
x-frame-options: DENY
x-xss-protection: 0

3、总结

本文主要总结概括了实践过程当中针对XSS漏洞的防御措施,首先针对用户输入源和不可信数据源须要咱们作好必要的过滤和校验,其次,经过CSP安全策略和其余HTTP响应头的设置等进一步确保安全性。

只有综合多种手段和方法才能确保本身的网站更加安全,若是不安全,那么就删git仓库跑路。

谢谢观看~

相关文章
相关标签/搜索