最新公告
  • 欢迎您光临起源地模板网,本站秉承服务宗旨 履行“站长”责任,销售只是起点 服务永无止境!立即加入钻石VIP
  • 聊一聊这个总下载量36039K的XSS-NPM库,是如何工作的?

    正文概述 掘金(Tz)   2021-01-03   840

    上篇文章这一次,彻底理解XSS攻击讲解了XSS攻击的类型和预防方式,本篇文章我们来看这个36039K的XSS-NPM库(你没有看错就是3603W次, 36039K次,36,039,651次,数据来自npm-stat.com),相信挺多小伙伴在项目中,也用到了这个库。

    话不多说,我们来看~

    js-xss简介

    js-xss是一个用于对用户输入的内容进行过滤,以避免遭受 XSS 攻击的模块(什么是 XSS 攻击?)。主要用于论坛、博客、网上商店等等一些可允许用户录入页面排版、格式控制相关的 HTML 的场景。

    特性:

    • 可配置白名单控制允许的HTML标签及各标签的属性;

    • 通过自定义处理函数,可对任意标签及其属性进行处理;

    js-xss有多受欢迎?

    让我们来看看下面的数据:

    ? GitHub 3.8K Star; (数据日期:2020-12-30,数据来源:js-xss-github)

    ? 周下载量575,790次; (数据日期:2020-12-24 ~ 2020-12-30,数据来源:xss-npm)

    ? 总下载量36,039,651次;(数据日期:2013-01-31 ~ 2020-12-30,数据来源:npm-stat.com)

    哪些网站在使用它?

    ? ​Teambition

    ? cnpmjs.org

    ? AngularJS中文社区

    ? CNode中文社区

    ? 前端乱炖

    ? ​为知笔记

    使用方法

    在 Node.js 中使用

    
    // 安装xss依赖
    
    npm install xss
    
    // 引入xss模块
    
    const xss = require("xss");
    
    
    
    // 使用 xss()方法处理内容
    
    const html = xss('<script>alert("xss");</script>');
    
    console.log(html);
    
    

    CDN引入使用

    
    // 注意请勿将URL地址用于生产环境,可以保存在本地引入使用。
    
    <script src="https://rawgit.com/leizongmin/js-xss/master/dist/xss.js"></script>
    
    
    
    // 使用 filterXSS()方法处理内容
    
    <script>
    
    var html = filterXSS('<script>alert("xss");</scr' + 'ipt>');
    
    console(html);
    
    </script>
    
    

    自定义配置过滤规则

    在调用 xss()或者filterXSS() 函数进行过滤时,可通过第二个参数来设置自定义规则:

    
    options = {}; // 自定义规则
    
    // 第二个形参填入自定义规则
    
    html = xss('<script>alert("xss");</script>', options);
    
    

    如果多处使用,但不想每次都传入一个 options 参数,可以创建一个 FilterXSS 实例;

    
    options = {};  // 自定义规则
    
    myxss = new xss.FilterXSS(options);
    
    // 以后直接调用 myxss.process() 来处理即可
    
    html = myxss.process('<script>alert("xss");</script>');
    
    

    配置白名单标签和属性

    通过options对象中的 whiteList 来指定,格式为:{'标签名': ['属性1', '属性2']}。不在白名单上的标签将被过滤,不在白名单上的属性也会被过滤。以下是示例:

    
    // 只允许a标签,该标签只允许href, title, target这三个属性
    
    var options = {
    
      whiteList: {
    
        a: ["href", "title", "target"]
    
      }
    
    };
    
    // 使用以上配置后,下面的HTML
    
    // <a href="#" onclick="hello()"><i>大家好</i></a>
    
    // 将被过滤为
    
    // <a href="#">大家好</a>
    
    

    自定义匹配到标签时的处理方法

    通过 onTag 来指定相应的处理函数。以下是详细说明:

    
    function onTag(tag, html, options) {
    
      // tag是当前的标签名称,比如<a>标签,则tag的值是'a'
    
      // html是该标签的HTML,比如<a>标签,则html的值是'<a>'
    
      // options是一些附加的信息,具体如下:
    
      //   isWhite    boolean类型,表示该标签是否在白名单上
    
      //   isClosing  boolean类型,表示该标签是否为闭合标签,比如</a>时为true
    
      //   position        integer类型,表示当前标签在输出的结果中的起始位置
    
      //   sourcePosition  integer类型,表示当前标签在原HTML中的起始位置
    
      // 如果返回一个字符串,则当前标签将被替换为该字符串
    
      // 如果不返回任何值,则使用默认的处理方法:
    
      //   在白名单上:  通过onTagAttr来过滤属性,详见下文
    
      //   不在白名单上:通过onIgnoreTag指定,详见下文
    
    }
    
    

    自定义匹配到标签的属性时的处理方法

    通过 onTagAttr 方法来指定相应的处理函数。以下是详细说明:

    
    function onTagAttr(tag, name, value, isWhiteAttr) {
    
      // tag是当前的标签名称,比如<a>标签,则tag的值是'a'
    
      // name是当前属性的名称,比如href="#",则name的值是'href'
    
      // value是当前属性的值,比如href="#",则value的值是'#'
    
      // isWhiteAttr是否为白名单上的属性
    
      // 如果返回一个字符串,则当前属性值将被替换为该字符串
    
      // 如果不返回任何值,则使用默认的处理方法
    
    }
    
    
    
    

    更多详细的options参数与配置建议查看官方文档:js-xss-README

    js-xss 源码阅读

    下面让我们来一起看看,js-xss的库是怎么防止xss攻击的吧~

    对应源码地址:dist/xss.js

    下面的源码分析从上到下,大家可以打开上述地址,两个窗口对比查看效果

    getDefaultWhiteList()

    首先打开上面的源码地址我们首先看到时getDefaultWhiteList()方法:

    
    function getDefaultWhiteList() {
    
      return {
    
        a: ["target", "href", "title"],
    
        abbr: ["title"],
    
        address: [],
    
     	···
    
        ···
    
        ···
    
        tt: [],
    
        u: [],
    
        ul: [],
    
        video: ["autoplay", "controls", "loop", "preload", "src", "height", "width"]
    
      };
    
    }
    
    
    
    

    getDefaultWhiteList()方法return出默认的所有标签名,如果用户没有自定义options参数与配置,那xss()将默认处理所有的标签属性;

    接下来的方法:

    
    // 以下为函数方法的作用,FN:后面为函数方法名称
    
    FN: onTag()                          // 自定义匹配到标签时的处理方法,默认不做处理;
    
    FN: onIgnoreTag()                    // 自定义匹配到不在白名单上的标签时的处理方法,默认不做处理;
    
    FN: onTagAttr()                      // 自定义匹配到标签的属性时的处理方法,默认不做处理;
    
    FN: onIgnoreTagAttr()                // 自定义匹配到不在白名单上的标签时的处理方法,默认不做处理;
    
    FN: escapeHtml()                     // 把所有‘< >’ 处理为 “&lt; "&gt;”
    
    FN: safeAttrValue()	   				 // 处理 href、src、style、url等属性,如不规范则返回空
    
    
    
    
    
    

    核心的正则表达式

    接下来就是js-xss最核心的正则部分了,xss()过滤规则主要是靠下面13个正则表达式匹配之后进行处理。

    话不多说,我们就看看大名鼎鼎的xss库到底用了哪些正则吧~

    
    // 匹配 尖括号
    
    var REGEXP_LT = /</g;
    
    var REGEXP_GT = />/g;
    
    // 匹配 双引号
    
    var REGEXP_QUOTE = /"/g;
    
    var REGEXP_QUOTE_2 = /&quot;/g;
    
    
    
    // 匹配 大小写&#数字 全局换行忽略大小写搜索
    
    var REGEXP_ATTR_VALUE_1 = /&#([a-zA-Z0-9]*);?/gim;
    
    
    
    // 匹配 &colon; &newline; 
    
    var REGEXP_ATTR_VALUE_COLON = /&colon;?/gim;
    
    var REGEXP_ATTR_VALUE_NEWLINE = /&newline;?/gim;
    
    
    
    // 匹配 ‘/*’、‘*\’ 全局换行搜索
    
    var REGEXP_DEFAULT_ON_TAG_ATTR_3 = /\/\*|\*\//gm;
    
    
    
    // 匹配javascript和vscript和livescript
    
    var REGEXP_DEFAULT_ON_TAG_ATTR_4 = /((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;
    
    
    
    // 匹配 data
    
    var REGEXP_DEFAULT_ON_TAG_ATTR_5 = /^[\s"'`]*(d\s*a\s*t\s*a\s*)\:/gi;
    
    
    
    //  匹配 "'` data  imge 
    
    var REGEXP_DEFAULT_ON_TAG_ATTR_6 = /^[\s"'`]*(d\s*a\s*t\s*a\s*)\:\s*image\//gi;
    
    
    
    // 匹配 expression( 
    
    var REGEXP_DEFAULT_ON_TAG_ATTR_7 = /e\s*x\s*p\s*r\s*e\s*s\s*s\s*i\s*o\s*n\s*\(.*/gi;
    
    
    
    // 匹配 url( 
    
    var REGEXP_DEFAULT_ON_TAG_ATTR_8 = /u\s*r\s*l\s*\(.*/gi;
    
    

    如果你把上面的正则一个个去理解,相信你就会知道这个总下载量3000W的xss库到底针对哪些属性做了处理。

    封装的处理方法

    我们继续往下看,是对相关内容特殊符号及各种特殊字符方法:

    
    // 以下为函数方法的作用,FN:后面为函数方法名称
    
    FN: escapeQuote()                    // 所有的 " 替换成 &quot;
    
    FN: unescapeQuote()                  // 所有的 &quot; 替换成 "
    
    FN: escapeHtmlEntities()             // 处理Unicode编码  
    
    FN: escapeDangerHtml5Entities()      // 处理&colon; &newline;转换为 : 空
    
    FN: clearNonPrintableCharacter()     // 清除无法使用的字符
    
    FN: friendlyAttrValue()              // 处理特殊的字符,将它们变成可展示的字符
    
    FN: escapeAttrValue()                // 将尖括号<>和引号" 进行转义
    
    FN: onIgnoreTagStripAll()            // 删除所有不在白名单的标签
    
    FN: StripTagBody()            	     // 指定一个标签列表,如果标签不在标签列表中,则通过指定函数处理
    
    FN: stripCommentTag()         	     // 删除html注释
    
    FN: stripBlankChar()        	     // 删除不可见字符
    
    

    紧接着通过exports.将所有方法暴露至全局:

    
    exports.whiteList = getDefaultWhiteList();
    
    exports.getDefaultWhiteList = getDefaultWhiteList;
    
    exports.onTag = onTag
    
    ···
    
    ···
    
    ···
    
    exports.cssFilter = defaultCSSFilter;
    
    exports.getDefaultCSSWhiteList = getDefaultCSSWhiteList;
    
    

    这里是将filterXSS()方法创建并暴露至全局,filterXSS看起来很简洁,new 了 FilterXSS对象,具体FilterXSS对象是什么从哪里,我们在后面再做介绍。

    
    /**
    
    
    
     * @param {String} html
    
     * @param {Object} 配置对象{ whiteList, onTag, onTagAttr... }
    
     * @return {String}
    
     */
    
    function filterXSS(html, options) {
    
      var xss = new FilterXSS(options); 
    
      return xss.process(html);
    
    }
    
    

    接下来针对不同环境将filterXSS方法暴露至全局:

    
    exports = module.exports = filterXSS;
    
    exports.filterXSS = filterXSS;
    
    exports.FilterXSS = FilterXSS;
    
    for (var i in DEFAULT) exports[i] = DEFAULT[i];
    
    for (var i in parser) exports[i] = parser[i];
    
    
    
    // 在浏览器上使用xss,输出filterxss'到全局变量
    
    if (typeof window !== "undefined") {
    
      window.filterXSS = module.exports;
    
    }
    
    
    
    // 在WebWorker上使用xss,输出filterxss'到全局变量
    
    function isWorkerEnv() {
    
      return typeof self !== 'undefined' && typeof DedicatedWorkerGlobalScope !== 'undefined' && self instanceof DedicatedWorkerGlobalScope;
    
    }
    
    if (isWorkerEnv()) {
    
      self.filterXSS = module.exports;
    
    }
    
    
    
    },{"./default":1,"./parser":3,"./xss":5}],3:[function(require,module,exports){
    
    /**
    
    

    接下来依旧是封装了很多处理的方法:

    
    FN: getTagName()             	     // 获取标签的属性
    
    FN: isClosing()           	     	 // 是否有结束标记
    
    FN: parseTag()						 // 解析输入html并返回已处理的html
    
    FN: parseAttr()						 // 解析输入属性并返回已处理的属性
    
    FN: findNextEqual()					 // 查找下一个空格,用于寻找标签内属性
    
    FN: findBeforeEqual()				 // 向前寻找空格
    
    FN: isQuoteWrapString() 			 // 判断是否是被双引号或者单引号包裹的
    
    FN: stripQuoteWrap()				 // 如果被双引号或者单引号包裹的去除引号,否则返回原值
    
    
    
    FN: isNull()             	  	     // 判断输入的是否为 `undefined` or `null`
    
    FN: getAttrs()						 // 获取去除标签名后的内容
    
    FN: shallowCopyObject()				 // 浅拷贝方法
    
    

    重头戏:FilterXSS()方法

    如果说上面的正则和各种封装的方法是炮弹的话,这个FilterXSS方法就是加上火药进口的意大利炮!:boom:

    
    function FilterXSS(options) {
    
      options = shallowCopyObject(options || {});
    
    
    
       // 判断用户是否传入配置如未传入则使用默认配置
    
      if (options.stripIgnoreTag) {
    
        if (options.onIgnoreTag) {
    
          console.error(
    
            'Notes: cannot use these two options "stripIgnoreTag" and "onIgnoreTag" at the same time'
    
          );
    
        }
    
        options.onIgnoreTag = DEFAULT.onIgnoreTagStripAll;
    
      }
    
      options.whiteList = options.whiteList || DEFAULT.whiteList;
    
      options.onTag = options.onTag || DEFAULT.onTag;
    
      options.onTagAttr = options.onTagAttr || DEFAULT.onTagAttr;
    
      options.onIgnoreTag = options.onIgnoreTag || DEFAULT.onIgnoreTag;
    
      options.onIgnoreTagAttr = options.onIgnoreTagAttr || DEFAULT.onIgnoreTagAttr;
    
      options.safeAttrValue = options.safeAttrValue || DEFAULT.safeAttrValue;
    
      options.escapeHtml = options.escapeHtml || DEFAULT.escapeHtml;
    
      this.options = options;
    
    
    
      if (options.css === false) {
    
        this.cssFilter = false;
    
      } else {
    
        options.css = options.css || {};
    
        this.cssFilter = new FilterCSS(options.css);
    
      }
    
    }
    
    
    
    /**
    
     * 启动进程,在FilterXSS.prototype注入方法
    
     *
    
     * @param {String} html
    
     * @return {String}
    
     */
    
    FilterXSS.prototype.process = function(html) {
    
      // 兼容html内容
    
      html = html || "";
    
      html = html.toString();
    
      if (!html) return "";
    
      ···
    
      ···
    
      ···
    
      // 移除不可见字符
    
      if (options.stripBlankChar) {
    
        html = DEFAULT.stripBlankChar(html);
    
      }
    
      // 移除html注释
    
      if (!options.allowCommentTag) {
    
        html = DEFAULT.stripCommentTag(html);
    
      }
    
    
    
      // 是否过滤掉不在白名单中的标签
    
      var stripIgnoreTagBody = false;
    
      if (options.stripIgnoreTagBody) {
    
        var stripIgnoreTagBody = DEFAULT.StripTagBody(
    
          options.stripIgnoreTagBody,
    
          onIgnoreTag
    
        );
    
        onIgnoreTag = stripIgnoreTagBody.onIgnoreTag;
    
      }
    
    
    
      // 处理html内容
    
      var retHtml = parseTag(
    
        html,
    
        function(sourcePosition, position, tag, html, isClosing) {
    
         	···
    
         	···
    
         	···
    
            var attrs = getAttrs(html); // 获取去除标签名后的内容
    
            var whiteAttrList = whiteList[tag];
    
            // 解析输入属性并返回已处理的属性
    
            var attrsHtml = parseAttr(attrs.html, function(name, value) {
    
    		  ···
    
              ···
    
              ···
    
            });
    
    
    
            // 把处理过的标签+属性重新组合起来创建新的html标签
    
            var html = "<" + tag;
    
            if (attrsHtml) html += " " + attrsHtml;
    
            if (attrs.closing) html += " /";
    
            html += ">";
    
            return html;
    
          } else {
    
            // call `onIgnoreTag()`
    
            var ret = onIgnoreTag(tag, html, info);
    
            if (!isNull(ret)) return ret;
    
            return escapeHtml(html);
    
          }
    
        },
    
        escapeHtml
    
      );
    
    
    
      // if enable stripIgnoreTagBody
    
      if (stripIgnoreTagBody) {
    
        retHtml = stripIgnoreTagBody.remove(retHtml);
    
      }
    
    
    
      return retHtml;
    
    };
    
    
    
    

    继续往下看,CSS过滤器

    
    function FilterCSS (options) {
    
      // 判断用户是否传入配置如未传入则使用默认配置
    
      options = shallowCopyObject(options || {});
    
      options.whiteList = options.whiteList || DEFAULT.whiteList;
    
      options.onAttr = options.onAttr || DEFAULT.onAttr;
    
      options.onIgnoreAttr = options.onIgnoreAttr || DEFAULT.onIgnoreAttr;
    
      options.safeAttrValue = options.safeAttrValue || DEFAULT.safeAttrValue;
    
      this.options = options;
    
    }
    
    // FilterCSS.prototype注入方法
    
    FilterCSS.prototype.process = function (css) {
    
      // 兼容各种奇葩输入
    
      css = css || '';
    
      css = css.toString();
    
      if (!css) return '';
    
      ···
    
      ···
    
      ···
    
      // 解析style并处理style样式
    
      var retCSS = parseStyle(css, function (sourcePosition, position, name, value, source) {
    
    
    
        var check = whiteList[name];
    
        var isWhite = false;
    
        if (check === true) isWhite = check;
    
        else if (typeof check === 'function') isWhite = check(value);
    
        else if (check instanceof RegExp) isWhite = check.test(value);
    
        if (isWhite !== true) isWhite = false;
    
    
    
        // 如果过滤后 value 为空则直接忽略
    
        value = safeAttrValue(name, value);
    
        if (!value) return;
    
        ···
    
        ···
    
        ···
    
      });
    
      return retCSS;
    
    };
    
    
    
    // 以下为函数方法的作用,FN:后面为函数方法名称
    
    FN: getDefaultWhiteList()			 // 获取白名单值,返回true表示允许该属性,其他值均表示不允许
    
    FN: safeAttrValue()	   				 // 如果被双引号或者单引号包裹的去除引号,否则返回原值
    
    

    结尾

    好了,以上就是全部的内容啦.

    如有疑问,可在下方留言,会第一时间进行回复!

    码字不易。如果觉得本篇文章对你有帮助的话,希望能可以留言点赞支持,非常感谢~


    起源地下载网 » 聊一聊这个总下载量36039K的XSS-NPM库,是如何工作的?

    常见问题FAQ

    免费下载或者VIP会员专享资源能否直接商用?
    本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
    提示下载完但解压或打开不了?
    最常见的情况是下载不完整: 可对比下载完压缩包的与网盘上的容量,若小于网盘提示的容量则是这个原因。这是浏览器下载的bug,建议用百度网盘软件或迅雷下载。若排除这种情况,可在对应资源底部留言,或 联络我们.。
    找不到素材资源介绍文章里的示例图片?
    对于PPT,KEY,Mockups,APP,网页模版等类型的素材,文章内用于介绍的图片通常并不包含在对应可供下载素材包内。这些相关商业图片需另外购买,且本站不负责(也没有办法)找到出处。 同样地一些字体文件也是这种情况,但部分素材会在素材包内有一份字体下载链接清单。
    模板不会安装或需要功能定制以及二次开发?
    请QQ联系我们

    发表评论

    还没有评论,快来抢沙发吧!

    如需帝国cms功能定制以及二次开发请联系我们

    联系作者

    请选择支付方式

    ×
    迅虎支付宝
    迅虎微信
    支付宝当面付
    余额支付
    ×
    微信扫码支付 0 元