我們在Move IR編譯器中發(fā)現(xiàn)了一個漏洞,其內聯(lián)注釋可以偽裝成可執(zhí)行代碼。這是因為move-IR解析器無法識別內聯(lián)注釋末尾的一些unicode換行符
我們在Move IR編譯器中發(fā)現(xiàn)了一個漏洞,其內聯(lián)注釋可以偽裝成可執(zhí)行代碼。這是因為move-IR解析器無法識別內聯(lián)注釋末尾的一些unicode換行符。特別是代碼在解析公共的\r\n和\r\n時,無法正確解析。另外,其他有效的unicode換行符完全被解析器忽略。
該漏洞的潛在影響可能有很大的不同:
1)每個特定模塊的業(yè)務邏輯及其用例;
2)Move IR語言的當前和將來功能;
3)用于向libra網(wǎng)絡提交bytecode的開發(fā)平臺;
我們可能想到的一些潛在利用場景是:
· 制造資產(chǎn)(Libra Coins或Libra網(wǎng)絡上的任何其他資產(chǎn))以換取費用的水龍頭可以部署一個惡意模塊,該模塊收取費用,但實際上不會向用戶提供制造此類資產(chǎn)的可能性。
· 宣稱凍結存款并在一段時間后釋放的錢包實際上可能永遠不會釋放這些資金。
· 一個支付拆分器模塊拆分部分資產(chǎn)并將資產(chǎn)轉發(fā)給多方用戶,但實際上可能永遠不會將拆分的部分資產(chǎn)發(fā)送給相應的用戶。
· 獲取敏感數(shù)據(jù)并應用某種加密操作來隱藏它的模塊(例如哈?;蚣用懿僮?實際上可能永遠不會應用這種操作。
惡意模塊發(fā)布者通過在內聯(lián)注釋結尾處創(chuàng)建具有不同于\n的特定換行符的模塊,可以欺騙信任其明顯源代碼的用戶。這在區(qū)塊鏈環(huán)境中尤為重要,因為在區(qū)塊鏈環(huán)境中,信任被可審核性所取代。我們的團隊之前在2018年11月1日的Solidity編譯器審核中發(fā)現(xiàn)了一個相關漏洞。
目前該漏洞已被修復,為了讓大家更好理解,下面給出一個詳細的講解。
漏洞細節(jié)展示
我們首先解釋漏洞的位置,然后介紹兩個關鍵方案。在該方案下,可以利用它來將嵌入式注釋偽裝為可執(zhí)行代碼。 該漏洞利用依賴于以下事實:不同的文本編輯器以不同的方式解釋和渲染換行符。有權在Libra網(wǎng)絡中發(fā)布Move模塊以欺騙用戶的惡意行為者可以利用這一點來欺騙用戶,然后用戶將與行為異常的模塊進行交互。
雖然此功能目前僅限于一組受信任的用戶,但將來它可能是開放的、公開的和無需權限的。在漏洞利用部分,我們展示了一個簡單的概念驗證Move模塊,其中包含單個資源聲明。 在處理有價值資產(chǎn)的模塊中,可以想到更危險的情況。
漏洞
在strip_comments函數(shù)中,提交8ea3329處的IR編譯器代碼,解析器模塊嘗試使用簡單的正則表達式來標識內聯(lián)注釋,該正則表達式旨在匹配以\ r,\ n或\ r \ n結尾的注釋:
fn strip_comments(string: &str) -> String
{ // Remove line comments let line_comments = Regex::new(
r"//.*(\r\n|\n|\r)").unwrap(); line_comments.replace_all(
string, "$1").into_owned()}
我們確定了解析器失敗的兩種情況:
1. 上面的函數(shù)在檢測用\r字符標記的內聯(lián)注釋的結尾時失敗。
2. 上面的函數(shù)將忽略其他幾個合法換行符的Unicode字符。
案例1
move IR解析器使用regex rust crate中的正則表達式來檢測move模塊代碼中的內聯(lián)注釋。 它嘗試使用以下命令匹配以\r \n,\ n或\r結尾的內聯(lián)注釋:
Regex::new(r"//.*(\r\n|\n|\r)")
但是正則表達式實際上沒有按預期方式運行,因為它實際上沒有檢測到\ r換行符所標記的內聯(lián)注釋的結尾。
為了理解錯誤正則表達式的正確行為,我們分別解釋其每個組件:
1. 雙引號前面的r是一個用于標識“原始字符串文本”的rust特性,它被用作編寫regex字符串的方便方法,而不必轉義特殊字符。
2. //:匹配“Move IR內聯(lián)注釋”語法的開頭。
3. .*:匹配換行符\n以外的零個或多個字符。這是由于歷史原因,因為正則表達式最初是在基于行的工具中使用的。 該行為記錄在正則表達式Rust箱子中,如下所示:“ [The character]。 將匹配\n“以外的任何有效UTF-8編碼的Unicode標量值。
4. (\r \n | \n | \r):是一個與字符\r \n,\n或\r匹配的匹配組,嘗試標識嵌入式注釋的結尾。
通常正則表達式引擎會遍歷整個正則表達式,嘗試將正則表達式中的下一個標記與字符串中的下一個字符進行匹配。 如果找到匹配項,引擎將通過正則表達式和主題字符串前進。 在我們的案例中,Move IR內聯(lián)注釋中的每個字符都將首先由.*表達式匹配,但\n除外,如第3點中所述。最重要的是,\r字符將位于與.*匹配的字符之中。當引擎最終在字符串中找到\n時,它將停止使用其他字符。
這意味著解析器將僅將\n標識為內聯(lián)注釋的末尾,而沒有將\r標識為行終止符。 換句話說,與其他常規(guī)字符一樣,\r被視為注釋的一部分,而不是其結尾。
在“案例1”部分中,我們展示了一個惡意模塊的概念驗證案例,該模塊利用了主要編輯器呈現(xiàn)\r字符的方式。
案例2
Move IR解析器處理最常見的換行符CR(\r),LF(\n)和CRLF(\r \n)。除了未能正確解析\r之外,如漏洞–情況1中所述,還有其他幾個Unicode字符表示解析器沒有檢測到的換行符。完整的Unicode換行符集是:
LF(\n或十六進制的0x0A)
VT(\v或十六進制的0x0B)
FF(十六進制的0x0C)
CR(\r或十六進制的0x0D)
CRLF(\r \n或十六進制的0x0D0A)
NEL(十六進位0xC285)
LS(十六進制的0xE280A8)
PS(十六進制的0xE280A9)
字符LF,CR和CRLF是最廣泛使用的換行符。但是其中一些流行的Visual Studio IDE等代碼編輯器仍可以正確識別一些不太常見的換行符,例如NEL,LS或PS。與案例1一樣,這可能導致難以檢測到惡意move模塊。在案例2中展示了此類示例。
漏洞利用
作為概念證明,我們現(xiàn)在介紹兩種惡意模塊案例,分別利用“漏洞”部分中描述的兩種案例。此操作中引用的所有測試文件都可以在專用存儲庫中找到:https://github.com/openzeppelin/move-compiler-vulnerability。
案例1: \r
幾個代碼編輯器和渲染器(例如Visual Studio Code,Sublime Text,Gedit,GitHub)認為\r字符(十六進制為0x0d)是有效的換行符。這意味著當用a\r分隔時,這些編輯器將在兩個不同的行中顯示內聯(lián)注釋和以下指令。 例如GitHub提供的以下Move模塊在聲明資源。
但是內嵌注釋的末尾有一個隱藏的\r換行符,可以通過檢查文件的hexdump來檢測到:
$ xxd Module_CR.mvir00000000: 6d6f 6475 6c65 204d 207b 0a20 2020 202f
module M {. /00000010: 2f20 536f 6d65 2063 6f6d 6d65 6e74 0d20 /
Some comment. 00000020: 2020 2072 6573 6f75 7263 6520 417b 783a
resource A{x:00000030: 2075 3634 7d0a 7d0a u64}.}.
如漏洞-案例1部分所述,上述模塊實際上將被編譯為空模塊。
在使用MOVE IR編譯器輸出進行驗證部分,我們將通過更深入地比較為常規(guī)和惡意move模塊生成的編譯器字節(jié)碼來演示該場景。
案例2:其他換行符
最常用的換行符是\n,\r和\r \n。 但是正如我們之前說過的,文本編輯器呈現(xiàn)換行符的方式因編輯器而異。這是Visual Studio IDE編輯器(由軟件開發(fā)人員廣泛使用)如何呈現(xiàn)一個非常簡單的Move模塊的方法,其中內聯(lián)注釋和資源聲明之間使用常見的\n換行符:
但是此編輯器不僅將LF解釋為有效的換行符,還將CRLF,NEL,LS和PS解釋為有效的換行符。
接下來,我們演示Visual Studio IDE如何顯示相同的簡單模塊,但分別具有PS,NEL和LS換行符:
可能會誘使用戶使用Visual Studio IDE從最后三個文件中的任何一個讀取move模塊的代碼,以為m模塊正在聲明一個資源,而Move IR編譯器會將該指令視為內聯(lián)注釋的一部分。
正如圖片文件夾中包含的屏幕截圖所示,unix命令行cat和gnome的gedit也顯示了類似的行為,與其他主要編輯器(如visual studio代碼、atom、sublime文本或github在線編輯器)不同。
驗證Move IR編譯器的輸出
為了簡單起見,我們使用兩個簡單的概念驗證模塊來展示編譯器的輸出,每個模塊都對應于前面描述的案例1和2。 但是對于Move IR解析器無法檢測到的所有其他換行符,可以重現(xiàn)以下內容。 我們使用的編譯器是從最新提交8ea33298678117748f1c75112f35a9fbc05b2172生成的。
首先,使用內嵌注釋和資源聲明(其中所有換行符均為\n)編譯一個簡單的非惡意Move模塊(請參見Module_LF.mvir):
$ ./compiler Module_LF.mvir --module --output lf-output
這種情況下的輸出是:
$ xxd -ps lf-output4c49425241564d0a01000801530000000200000002550000000
40000000b59000000020000000d5b00000002000000055d00000006000000046300000
02000000008830000000400000009870000000300000000000001010001020300014d0
1410178000000000000000000000000000000000000000000000000000000000000000
000020100000200
現(xiàn)在編譯一個沒有資源聲明的非惡意Move模塊(請參閱Module_Commented.mvir):
$ ./compiler Module_Commented.mvir --module --output commented-output
$ xxd -ps commented-output4c49425241564d0a010004012f000000020000000d31
0000000200000005330000000200000004350000002000000000000300014d00000000
00000000000000000000000000000000000000000000000000000000
現(xiàn)在讓我們編譯兩個不同的惡意Move模塊,它們將嵌入式注釋偽裝為可執(zhí)行代碼。 首先與案例1對應,內聯(lián)注釋和資源聲明之間的換行符將是CR字符(\r或十六進制的0x0D;請參見Module_CR.mvir):
$ ./compiler Module_CR.mvir --module --output cr-output
輸出
$ xxd -ps ps-output4c49425241564d0a010004012f000000020000000d310000000
200000005330000000200000004350000002000000000000300014d000000000000000
0000000000000000000000000000000000000000000000000
請注意,在兩種情況下生成的bytecode與在沒有資源聲明的模塊情況下生成的bytecode相同。這表明編譯器確實遺漏了模塊中的資源聲明,其中注釋以CR和PS換行符結尾。
代碼更改
在提交5fb715e中,Libra團隊對受漏洞影響的代碼行進行了更改,并修改了用于識別strip_comments_function中的內聯(lián)注釋的正則表達式。 改進的代碼以更簡潔的方式匹配以換行符(\n)結尾的注釋,但是仍然容易將內聯(lián)注釋偽裝為可執(zhí)行代碼。 讓我們仔細看一下代碼:
fn strip_comments(string: &str) -> String { // Remove line comments
let line_comments = Regex::new(r"(?m)//.*$").unwrap();
line_comments.replace_all(string, "$1").into_owned()}
當使用\n作為換行符時,用于匹配內聯(lián)注釋的正則表達式可以很好地工作,但是,與原始表達式一樣,它無法檢測\r或其他Unicode標記的內聯(lián)注釋結尾的能力。
為了理解新的錯誤正則表達式的確切行為,我們分別解釋其每個組件:
1. 雙引號前面的r是一個用于標識“原始字符串文本”的rust特性,它被用作編寫regex字符串的方便方法,而不必轉義特殊字符。
2. (?m)標志表示多行模式,這意味著^和$不再僅匹配輸入的開頭/結尾,而是行的開頭/結尾。
3. //:匹配“Move IR內聯(lián)注釋”語法的開頭。
4. .*:匹配換行符\n以外的零個或多個字符。
5. $是與換行符(\n)匹配的特殊字符。是否在正則表達式的文檔中明確說明此字符與其他Unicode換行符(包括\ r換行符)不匹配。
總而言之,此正則表達式與上一個正則表達式完全一樣,除了\n字符以外,不匹配任何其他換行符,從而基于相同的Move IR模塊,產(chǎn)生了與原始報告相同的攻擊向量和利用用于說明漏洞。
漏洞修復
在提交7efb022中,Libra團隊通過引入strip_comments_and_verify函數(shù)修復了該漏洞,該函數(shù)依次在實際剝離注釋之前調用verify_string函數(shù)。此函數(shù)通過is_permitted_printable_char和is_permitted_newline_char函數(shù)確保字符串中的所有字符均為允許的字符。這些功能通過確保字符位于整個字符集的有限范圍內來驗證字符。特別是明確允許的唯一換行符是\n(0x0A),而\r和其他更深奧的換行符不在is_permitted_printable_char函數(shù)定義的范圍內。
我們已經(jīng)使用所有新的行字符測試用例驗證了此修復程序。除了\n情況可以正常工作之外,所有其他測試模塊現(xiàn)在都引起編譯時解析器錯誤。
結論
我們證明,由于語言解析器中的漏洞,Move模塊可以將內聯(lián)注釋偽裝為可執(zhí)行代碼。有權在Libra網(wǎng)絡中發(fā)布Move模塊的惡意行為者可能會欺騙與模塊進行交互的行為與預期不同的用戶。我們得出的結論是,只要不修復漏洞,沒有深入分析文件的實際內容或編譯后產(chǎn)生的實際bytecode,就不能信任帶有內嵌注釋的Move IR模塊文件來達到預期的效果。
研究了兩種情況,最關鍵的一種是move-ir解析器的源代碼如何欺騙開發(fā)人員和審計人員,使他們相信廣泛使用的換行符被認為是有效的內聯(lián)注釋結尾。為了修復此漏洞,我們建議Move的開發(fā)團隊修改strip_comments函數(shù)中的regex,以便正確解析該字符以及表示換行符的所有其他Unicode字符。為了提高軟件的透明度,還建議考慮使用帶有所有不同換行符的Move模塊進行徹底和廣泛的單元測試。(鏈三豐)