Sanitizer:跟XSS说拜拜

1,267 阅读3分钟

作者:我也想一夜暴富

原文链接# Sanitizer:跟XSS说拜拜

今天的搬砖任务是完成产品提出的富文本需求

等我吭哧吭哧写完后,有一位前端同学提出了一个绝妙的问题:“我们这个……有预防xss攻击吗?”

1634802303(1).png

众所周知,富文本内容的展示,避免不了要在页面上添加一些粗体,斜体,下划线等html内容,而这个操作很容易引发xss的攻击,这也是React提供插入html内容的属性以dangerouslySetInnerHTML命名,提醒我们这是危险的操作

什么是XSS

XSS(Cross Site Script,跨站脚本攻击),一直是最普遍且最危险的 Web 安全漏洞之一。通常是由带有页面可解析内容的数据未经处理直接插入到页面上解析导致的,分为储存型,反射型,DOM型。通俗来说,就是恶意攻击者往Web页面里插入恶意Script代码,当用户浏览该页面时,嵌入其中Web里面的Script代码会被执行,从而达到恶意攻击用户的目的。

传统解决xss攻击的方式有:

  • 转义
  • 消毒
  • 什么是转义

拿我这次的工作内容来说,如果我对输入的内容不做任何处理,并且我在输入框中输入了
<img src="error.jpg" onerror=alert('我就是想要xss攻击一下')>

当我在使用dangerouslySetInnerHTML渲染页面时

<div dangerouslySetInnerHTML={{ __html: inputValue}} />
复制代码

就会出现下面这种情况

image.png

因为输入的内容被完整的通过innerHTML,插入到了DOM中并且因为没有error.jpg这个图片路径而执行了onerror方法

因此,我们会对输入的内容做转义,即

  • <转换为&lt;
  • >转换为&gt;

借此破坏输入内容中的标签,当我们再次输入带攻击的html内容时,就会展示成这样

<p>&lt;img src="error.jpg" onerror=alert('我就是想要xss攻击一下')&gt;</p>
复制代码

从而起到预防xss的效果

  • 什么是消毒

对于转义,有时候我们希望安全插入一段html内容,通过转义后它将失去原本的样式变成一段字符串

<em>我是斜体</em>
//转义后
&lt;em&gt;我是斜体&lt;/em&gt;
复制代码

因此我们重磅引出一个新的API——Sanitizer

Sanitizer API:消毒剂

其作用是直接移除有风险的标签或属性

比如上面的例子,当我们输入
<img src="error.jpg" onerror=alert('我就是想要xss攻击一下')>
通过Sanitizer进行处理

const $div = document.querySelector('div')
const content = `<em>我是斜体</em><img src="error.jpg" onerror=alert('我就是想要xss攻击一下')>`
const sanitizer = new Sanitizer()
$div.setHTML(content, sanitizer)
复制代码

就会得到这样的结果,安全的html内容被保留,而作为onerror等有可能产生恶意攻击的属性被移除

image.png

如果不想直接插入DOM,也可以通过下面的方式生成一个HTMLElement

sanitizer.sanitizeFor("div", content) //HTMLDivElement <div>
复制代码

自定义配置

因为默认情况下,Sanitizer会删除一切可能引发恶意攻击的属性或标签,我们可以通过自定义配置保留我们需要的部分

const config = {
  allowElements: [],
  blockElements: [],
  dropElements: [],
  allowAttributes: {},
  dropAttributes: {},
  allowCustomElements: true,
  allowComments: true
};
// sanitized result is customized by configuration
new Sanitizer(config)
复制代码

allowElements:Sanitizer允许保留的元素数组
blockElements:Sanitizer应该删除的元素数组,并保留他们的子元素
dropElements:Sanitizer应该删除的元素数组,并删除他们的子元素
allowAttributes:允许保留的属性
dropAttributes:应该删除的属性

  const text = 'text1 <b style="font-weight: bold" class="test-class"><i>text 2</i></b><em>text 3</em>';

  // <div>text1 <b style="font-weight: bold" class="test-class">text 2</b>text 3</div>.
  new Sanitizer({allowElements: [ "b" ]}).sanitizeFor('div',text);

  // <div>text1 <i>text 2</i><em>text 3</em></div>.
  new Sanitizer({blockElements: [ "b" ]}).sanitizeFor('div',text);

  // <div>text1 <em>text 3</em></div>.
  new Sanitizer({dropElements: [ "b" ]}).sanitizeFor('div',text);
  
  // <div>text1 <b style="font-weight: bold"><i>text 2</i></b><em>text 3</em></div>.
  new Sanitizer({allowAttributes: {"style": ["b"]}}).sanitizeFor("div", text)
  
  // <div>text1 <b><i>text 2</i></b><em>text 3</em></div>.
  new Sanitizer({allowAttributes: {"style": ["div"]}}).sanitizeFor("div", text)
  
  // <div>text1 <b class="test-class"><i>text 2</i></b><em>text 3</em></div>.
  new Sanitizer({dropAttributes: {"style": ["b"]}}).sanitizeFor("div", text)
复制代码

兼容性

Sanitizer API作为新提案,目前各大浏览器正在努力实现中
当前只有在chrome 93版本中,可以通过打开about://flags/#enable-experimental-web-platform-features对这个API进行试用 在此之前我们可以将DOMPurify作为backup