从头到脚实现一个 XSS-Filter

4,343 阅读5分钟

上文中,我们已经熟悉了 XSS 攻击的定义、分类以及防护手段了,我们知道 XSS 防护最重要和有效的手段就是进行 XSS-Filter 处理。既然 XSS-Filter 如此重要,那我们怎么开发一个 XSS-Filter 库呢?这篇文章会告诉你答案,现在就跟随我,一步步开发一个 XSS-Filter 吧?

其实对于一个 XSS-Filter 来说,无非就是做字符串替换的操作,即:

replace(/a/, /b/);

也就是说,我们要做的就是将一些字符串替换成另外一些字符串。这样我们可以找到那些可能引发安全问题的标签或者属性替换成(转义成)安全的字符,或者将除了某些安全标签或属性之外的其他字符都进行转义或过滤。这样一来就对应两种思路,即:

  • 黑名单的技术
  • 白名单的技术 我们先简单介绍一下这两种技术。

黑名单技术

在计算机安全中,黑名单只是一种防止已知恶意程序运行的简单有效的方法,更新黑名单可以快速通过更新服务器来实现,大多数防病毒程序使用的是黑名单技术来阻止已知威胁,垃圾邮件过滤器往往需要依赖于黑名单技术。 黑名单技术只在某些应用中能够发挥良好作用,当然前提是黑名单内容准确性和完整性。 黑名单最重要的问题就是,它只能抵御已知的有害的程序和发送者,不能够抵御新威胁(零日攻击等),对进入网络的流量进行扫描并将其与黑名单对比还可能浪费相当多的资源以及降低网络流量。

白名单技术

白名单技术的宗旨是不阻止某些特定的事物,它采取了与黑名单相反的做法,利用一份“已知为良好”的实体(程序、电子邮件地址、域名、网址)名单,以下是白名单技术的优点:

  • 没有必要运行必须不断更新的防病毒软件,任何不在名单上的事物将被阻止运行;
  • 系统能够免受零日攻击。
  • 用户不能够运行不在名单上的未经授权的程序

白名单技术的优点是,除了名单上的实体外都不能运行或者通过,缺点则是,不在名单上的实体都不能运行和通过。可见,优点也是缺点。

技术选择

我们已经了解了黑名单和白名单两种技术了。在实现 XSS-Filter 时,对比了黑名单和白名单的特点,我们应该选择白名单来实现,原因如下:

  • 使用黑名单,无法应对未来出现的威胁,未来可能新增一些存在安全威胁的标签或属性;
  • 黑名单内容准确性和完整性很难做到。 而使用白名单,不存在未知的威胁,所有能够通过的,都是安全的,这样可以极大增加系统的安全性。接下来我们一起看看一个可用白名单。
/*
 * {
 *    key: value
 * }
 * key 标签名  value 支持的属性列表
 */
var whiteList = {
  a: ['target', 'href', 'title'],
  abbr: ['title'],
  address: [],
  area: ['shape', 'coords', 'href', 'alt'],
  article: [],
  aside: [],
  audio: ['autoplay', 'controls', 'loop', 'preload', 'src'],
  b: [],
  bdi: ['dir'],
  bdo: ['dir'],
  big: [],
  blockquote: ['cite'],
  br: [],
  caption: [],
  center: [],
  cite: [],
  code: [],
  col: ['align', 'valign', 'span', 'width'],
  colgroup: ['align', 'valign', 'span', 'width'],
  dd: [],
  del: ['datetime'],
  details: ['open'],
  div: [],
  dl: [],
  dt: [],
  em: [],
  font: ['color', 'size', 'face'],
  footer: [],
  h1: [],
  h2: [],
  h3: [],
  h4: [],
  h5: [],
  h6: [],
  header: [],
  hr: [],
  i: [],
  img: ['src', 'alt', 'title', 'width', 'height'],
  ins: ['datetime'],
  li: [],
  mark: [],
  nav: [],
  ol: [],
  p: [],
  pre: [],
  s: [],
  section: [],
  small: [],
  span: [],
  sub: [],
  sup: [],
  strong: [],
  table: ['width', 'border', 'align', 'valign'],
  tbody: ['align', 'valign'],
  td: ['width', 'rowspan', 'colspan', 'align', 'valign'],
  tfoot: ['align', 'valign'],
  th: ['width', 'rowspan', 'colspan', 'align', 'valign'],
  thead: ['align', 'valign'],
  tr: ['rowspan', 'align', 'valign'],
  tt: [],
  u: [],
  ul: [],
  video: ['autoplay', 'controls', 'loop', 'preload', 'src', 'height', 'width']
};

如何通过白名单过滤标签

我们首先将输入的 HTML Dom String 转化为 HTML 标签以及 HTML 属性,然后再通过白名单将 HTML 标签和属性转换。 比如我们可以这么做:

// 输入HTML Dom String
var domString = '<div><a></a><script></script></div>';
// 找到第一个标签 div, 在白名单里,不做转义操作

...
// 找到第*个标签 script,不在白名单里,做转义操作
<script> ===>  &lt;script&gt;

对属性的处理也类似,我们尤其注意对 src 和 href 属性的处理。

function safeAttrValue(name, value) {

  var Reg = /((j\s*a\s*v\s*a|v\s*b|l\s*i\s*v\s*e)\s*s\s*c\s*r\s*i\s*p\s*t\s*|m\s*o\s*c\s*h\s*a)\:/gi;

  if (name === "href" || name === "src") {

    value = value.trim();
    if (value === "#") return "#";
    if (
      !(
        value.substr(0, 7) === "http://" ||
        value.substr(0, 8) === "https://" ||
        value.substr(0, 7) === "mailto:" ||
        value.substr(0, 4) === "tel:" ||
        value[0] === "#" ||
        value[0] === "/"
      )
    ) {
      // 如果href和src属性不是链接则清空,防止href="javascript:alert(/xss/)"这种情形
      return "";
    }
  } else if (name === "background") {

    if (reg.test(value)) {
      // 如果包含javascript脚本则清空
      return "";
    }
  }

  // escape `<>"` before returns
  var REGEXP_QUOTE = /"/g;
  var REGEXP_LT = /</g;
  var REGEXP_GT = />/g;
  value =value.replace(, "&quot;").replace(REGEXP_LT, "&lt;").replace(REGEXP_GT, "&gt;");
  return value;
}

整体的流程如下图所示:

当然,实现一个XSS-Filter要考虑的东西远远要多很多,这里主要是给大家提供一个思路,具体的源码大家可以参见网上一个比较流行的XSS库的实现,仓库,这里的源码都是用es5语法写的,整体看起来比较凌乱,大家有兴趣可以用最新的ES6语法写个类似的仓库。

结语

又到了结尾了,很开心大家能坚持看到最后。XSS防御还是很值得大家去深入研究的,总之一句话,希望我们大家能从我的文章学到知识。最后,祝大家工作愉快!

@Author: WaterMan