聊一聊這個總下載量3603w的xss庫,是如何工做的?

上篇文章這一次,完全理解XSS攻擊講解了XSS攻擊的類型和預防方式,本篇文章咱們來看這個36039K的XSS-NPM庫(你沒有看錯就是3603W次, 36039K次,36,039,651次,數據來自https://npm-stat.com),相信挺多小夥伴在項目中,也用到了這個庫。javascript

話很少說,咱們來看~css

js-xss簡介

js-xss是一個用於對用戶輸入的內容進行過濾,以免遭受 XSS 攻擊的模塊(什麼是 XSS 攻擊?)。主要用於論壇、博客、網上商店等等一些可容許用戶錄入頁面排版、格式控制相關的 HTML 的場景。html

特性:前端

  • 可配置白名單控制容許的HTML標籤及各標籤的屬性;java

  • 經過自定義處理函數,可對任意標籤及其屬性進行處理;node

js-xss有多受歡迎?

讓咱們來看看下面的數據:git

🥇 GitHub 3.8K Star; (數據日期:2020-12-30,數據來源:js-xss-githubangularjs

🥇 周下載量575,790次; (數據日期:2020-12-24 ~ 2020-12-30,數據來源:xss-npmgithub

🥇 總下載量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方法就是加上火藥進口的意大利炮!💥

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()	   				 // 若是被雙引號或者單引號包裹的去除引號,不然返回原值

結尾

好了,以上就是所有的內容啦.

若有疑問,可在下方留言,會第一時間進行回覆!

碼字不易。若是以爲本篇文章對你有幫助的話,但願能能夠留言點贊支持,很是感謝~

2021你那已經來啦,祝你們新年快樂,2021代碼無bug~

我曾踏足山巔,也曾跌落谷底,二者都讓我受益良多。我的網站:zhaohongcheng.com

相關文章
相關標籤/搜索