前年我們在 Synology 的 NAS 中,發現了,Pre-auth RCE 的漏洞,並在 Pwn2Own Tokyo 中,取得了 Synology DS418 play 的控制權,而成功獲得 Pwn2Own 的點數,後續也發現這個漏洞不只存在 Synology 的 NAS,也同時存在多數廠牌的 NAS 中,這篇研究將講述這漏洞的細節及我們的利用方式。
此份研究亦發表於 HITCON 2021,你可以從這裡取得投影片!
Network Attached Storage
早期 NAS 一般用途為讓伺服器本身與資料分開也為了做異地備援而使用的設備,功能上主要單純讓使用者可以直接在網路上存取資料及分享檔案,現今的 NAS 更是提供多種服務,不止檔案分享更加方便,也與 IoT 的環境更加密切,例如 SMB/AFP 等服務,可輕易的讓不同系統的電腦分享檔案,普及率也遠比以前高很多。
現今的 NAS,也可裝上許多套件,更是有不少人拿來架設 Server,在這智慧家庭的年代中,更是會有不少人與 home assistant 結合,使得生活更加便利。
Motivation
為何我們要去研究 NAS 呢 ?
紅隊需求
過去在我們團隊在執行紅隊過程中,NAS 普遍會出現在企業的內網中,有時更會暴露在外網,有時更會存放不少企業的機密資料在 NAS 上,因此 NAS 漸漸被我們關注,戰略價值也比以往高很多。
勒索病毒
近年來因為 NAS 日益普及,常被拿來放個人的重要資料,使 NAS 成為了勒索病毒的目標,通常駭客組織都會利用漏洞入侵 NAS 後,將存放在 NAS 中的檔案都加密後勒索,而今年年初才又爆發一波 locker 系列的事件,我們希望可以減少類似的事情再次發生,因而提高 NAS 研究的優先程度,來增加 NAS 安全性。也為了我們實現讓世界更安全的理想。
Pwn2Own Mobile 2020
最後一點是 NAS 從 2020 開始,成為了 Pwn2Own Mobile 的主要目標之一,又剛好前年我們也想嘗試挑戰看看 Pwn2Own 的舞台,所以決定以 NAS 作為當時研究的首要目標,前年 Pwn2Own 的目標為 Synology 及 WD ,由於 Synology 為台灣企業常見設備,所以我們最後選擇了 Synology 開始研究。
Recon
Environment
- DS918+
- DSM 6.2.3-25426
我們的測試環境是 DS918+ 與 Pwn2own 目標極為類似的型號,我們為了更佳符合平常會遇到的環境以及 Pwn2Own 中要求,會是全部 default setting 的狀態。
Attack surface
首先可先用 netstat 看 tcp 和 udp 中有哪些 port 是對外開放,可以看到 tcp 及 udp 中 在 default 環境下,就開了不少服務,像是 tcp 的 smb/nginx/afpd 等
而 udp 中則有 minissdpd/findhost/snmpd 等,多數都是一些用來幫助尋找設備的協定。
我們這邊挑了幾個 Service 做初步的分析
DSM Web interface
首先是 DSM Web 介面,最直覺也最直接的一部分,這部分大概也會是最多人去分析的一塊,有明顯的入口點,在古老時期常有 command injection 漏洞,但後來有 Synology 有嚴格規範,後徹底改善這問題,程式也採用相對保守的方式開發,相對安全不少。
SMB
Synology 中的 SMB 協定,使用的是 Open Source 的 Samba,因使用的人眾多,進行 code review 及漏洞挖掘的人也不少,使得每年會有不少小洞,近期最嚴重的就是 SambaCry,但由於較多人在 review 安全性相對也比其他服務安全。
iSCSI Manager
主要協助使用者管理與監控 iSCSI 服務,由 Synology 自行開發,近期算比較常出現漏洞的地方,但需要花不少時間 Reverse ,不過是個不錯的目標,如果沒有其他攻擊面,可能會優先分析。
Netatalk
最後一個要提的是 Netatalk 也就是 afp 協定,基本上沒什麼改,大部分沿用 open source 的 Netatalk,近期最嚴重的漏洞為 2018 的 Pre-auth RCE (CVE-2018-1160),關於這漏洞可參考 Exploiting an 18 Year Old Bug,Netatalk 相對其他 Service 過去的漏洞少非常多,是比較少被注意到的一塊,並且已經長時間沒在更新維護。
我們經過整體分析後, 認為 Netatalk 也會是 Synology 中最軟的一塊,且有 Source code可以看,所以我們最後決定先分析他。當然也還有其他 service 跟攻擊面,不過這邊由於篇幅因素及並沒有花太多時間去研究就不一一分析介紹了。我們這次的重點就在於 Netatalk。
Netatalk
Apple Filing Protocol (AFP) 是個類似 SMB 的檔案傳輸協定,提供 Mac 來傳輸及分享檔案,因 Apple 本身並沒有開源,為了讓 Unlx like 的系統也可以使用,於是誕生了 Netatalk,Netatalk 是個實作 Mac 的 AFP 協定的 OpenSource 專案,為了讓 Mac 可以更方便的用 NAS 來分享檔案,幾乎每一廠牌的 NAS 都會使用。
Netatalk in Synology
Synology 中的 netatalk 是預設開啟,版本是改自 3.1.8 的 netatalk,並且有在定期追蹤安全性更新,只要剛裝好就可以用 afp 協定來與 Synology NAS 分享檔案,而 binary 本身保護有 ASLR/NX/StackGuard。
DSI
講漏洞之前,先帶大家來看一下 netatalk 中,部分重要結構,首先是 DSI,Netatalk 在連線時是使用的 DSI (Data Stream interface) 來傳遞資訊,Server 跟 Client 都是通過 DSI 這個協定來溝通,每個 connectation 的 packet 都會有 DSI 的 header 在 packet 前面
DSI Packet Header :
DSI 封包中內容大致上會如上圖所示,會有 Flag/Command 等等 metadata 以及 payload 通常就會是一個 DSI Header + payload 的結構
AFP over DSI :
afp 協定的通訊過程大概如上圖所示,使用 AFP 時,client 會先去拿 server 資訊,來確定有哪些認證的方式還有使用的版本等等資訊,這個部分可以不做,然後會去 Open Session 來,開啟新的 Session,接著就可以執行 AFP 的 command ,但在未認證之前,只可以做登入跟登出等相關操作,我們必須用 login 去驗證使用者身份,只要權限沒問題接下來就可像 SMB 一樣做檔案操作
在 Netatalk 實作中,會用 dsi_block 作為封包的結構
dsi_block :
- dsi_flag 就是指該 packet 是 request or reply
- dsi_command 表示我們的 request 要做的事情
- DSICloseSession
- DSICommand
- DSIGetStatus
- DSIOpenSession
- dsi_code
- Error code
- For reply
- dsi_doff
- DSI data offset
- Using in DSIWrite
- dsi_len
- The Length of Payload
DSI : A descriptor of dsi stream
在 netatalk 中,除了原始封包結構外,也會將封包及設定檔 parse 完後,將大部分的資訊,存放到另外一個名為 DSI 結構中,例如 server_quantum 及 payload 內容等,以便後續的操作。
而封包中的 Payload 會存放在 DSI 中 command 的 buffer 中,該 buffer 大小,取自於 server_quantum,該數值則是取自於 afp 的設定檔 afp.conf 中。
如果沒特別設定,則會取用 default 大小 0x100000。
有了初步了解後,我們可以講講漏洞。
Vulnerability
我們發現的漏洞就發生在,執行 dsi command 時,讀取 payload 內容發生了 overflow,此時並不需登入就可以觸發。問題函式是在 dsi_stream_receive
這是一個將接收到封包的資訊 parse 後放到 DSI 結構的 function,這個 function 接收封包資料時,會先根據 header 中的 dsi_len
來決定要讀多少資料到 command buffer 中,而一開始有驗證dsi_cmdlen
不可超過 server quantum 也就是 command buffer 大小。
然而如上圖黃匡處,如果有給 dsi_doff
,則會將 dsi_doff
作為 cmdlen 大小,但這邊卻沒去檢查是否有超過 command buffer。
使得 dsi_strem_read
以這個大小來讀取 paylaod 到 command buffer 中,此時 command buffer 大小為 0x100000,如果 dsi_doff
大小超過 0x100000 就會發生 heap overflow。
Exploitation
由於是 heap overflow,所以我們這邊必須先理解 heap 上有什麼東西可以利用,在 DSM 中的 Netatalk 所使用的 Memory Allocator 是 glibc 2.20,而在 glibc 中,當 malloc 大小超過 0x20000 時,就會使用 mmap 來分配記憶體空間,而我們在 netatalk 所使用的大小則是 0x100000 超過 0x20000 因此會用 mmap 來分配我們的 command buffer。
因為是以 mmap 分配的關係,最後分配出來的空間則會在 Thread Local Storage 區段上面,而不是在正常的 heap segment 上,如上圖的紅框處。
afpd 的 memory layout 如上圖所示,上述紅框那塊就是,紅色+橘色這區段,在 command buffer 下方的是 Thread-local Storage。
Thread-local Storage
Thread-local Storage(TLS) 是用來存放 thread 的區域變數,每個 thread 都會有自己的 TLS,在 Thread 建立時就會分配,當 Thread 結束的時候就會釋放,而 main thread 的 TLS 則會在 Process 建立時就會分配,如前面圖片中的橘色區段,因此我們可利用 heap overflow 的漏洞來覆蓋掉大部分存放在 TLS 上的變數。
Target in TLS
事實上來說 TLS 可控制 RIP 的變數有不少,這邊提出幾個比較常見的
- 第一個是 main arena,主要是 glibc 記憶體管理個結構,改 main arena 可以讓記憶體分配到任意記憶體位置,做任意寫入,但構造上比較麻煩。
- 第二個是 pointer guard 可藉由修改 pointer guard 來改變原本呼叫的 function pointer ,但這邊需要先有 leak 跟知道原本 pointer guard 的值才能達成
- 第三個則是改
tls_dtor_list
,不須 leak 比較符合我們現在的狀況
Overwrite tls_dtor_list
這技巧是由 project zero 在 2014 所提出的方法,覆蓋 TLS 上的 tls_dtor_list 來做利用,藉由覆蓋該變數可在程式結束時控制程式流程。
structdtor_list{dtor_funcfunc;void*obj;structlink_map*map;structdtor_list*next;}
這邊就稍微提一下這個方法,tls_dtor_list
是個 dtor_list
object 的 singly linked list 主要是存放 thread local storage 的 destructor,在 thread 結束時會去看這個 linked list 並去呼叫 destructor function,我們可藉由覆蓋 tls_dtor_list
指向我們所構造的 dtor_list。
而當程式結束呼叫 exit() 時,會去呼叫 call_tls_dtors()
,該 function 會去取 tls_dtor_list
中的 object 並去呼叫每個 destructor,此時如果我們可以控制 tls_dtor_list
就會去使用我們所構造的 dtor_list
來呼叫我們指定的函式。
但在新版本和 synology 的 libc 中,dtor_list 的 function pointer 有被 pointer guard 保護,導致正常情況下,我們並不好利用,一樣需要先 leak 出 pointer guard 才能好好控制 rip 到我們想要的位置上。
但有趣的是 pointer guard 也會在 TLS 上,他會存在 TLS 中的 tcbhead_t 結構中,如果我們 overflow 夠多,也可以在 overflow tls_dtor_list
的同時,也將 pointer guard 也一併清掉,這樣就可以讓我們不用處理 pointer guard 問題。
先來講講 tcbhead_t 這結構,這個結構主要是 Thread Control Block (TCB),有點類似 Windows 中的 TEB 結構 是 thread 的 descriptor,主要會用來存放 thread 的各種資訊,而在 x86_64 的 Linux 架構的 usermode 下,fs 暫存器會指向這位置,每當我們要存取 thread local variable 時,都會透過 fs 暫存器去 存取,我們可以看到 TCB 結構會有 stack guard 及 pointer guard 等資訊,也就是說當我們再拿 pointer guard 時,也適用 fs 暫存器從這個結構取出的。
我們回頭看一下 TLS 上的結構分佈,可以看到 tls_dtor_list
後方就是這個,tcbhead_t
結構。只要我們 overflow 夠多就可以蓋掉 pointer guard,然而此時會出現另外一個問題。
因為 stack guard 在 pointer guard 前,當我們蓋掉 pointer guard 的同時,也會蓋掉 stack guard。那麼蓋掉 stack guard 會有什麼影響呢?
在我們呼叫 dsi_stream_receive()
時,因為有開啟 stack guard 保護的關係,會先從 TLS 上,取得 stack guard 放在 stack 上,等到我們呼叫 dsi_stream_read 去 trigger overflow 且蓋掉 pointer guard 及 stack guard 後,在 dsi_stream_receive()
返回時,會去檢查 stack guard 是否與 TLS 中的相同,但因為這時候的 TLS 的 stack guard 已經被我們蓋掉了,導致檢查不通過而中止程式,就會造成我們無法利用這個技巧來達成 RCE。
Bypass stack guard
在 netatalk(afpd) 的架構中,事實上每次連線都會 fork 一個新的 process 來 handle 使用者的 request,而 Linux 中的 process 有個特性是 fork 出來的 process,memory address 及 stack gurad 等都會與原先的 parent process 相同,因此我們可以利用 CTF 常見的招式,一個 byte 一個 bytes brute-force 的方式來獲得 stack guard 。
Brute-force stack guard
基本概念是 在 overflow 之後,我們可以只蓋 TLS 中的 stack guard 最尾端一個 byte ,每次連線都蓋不同的 byte,一旦與 stack guard 不同,就會因為 abort 而中斷連線,我們可依據連線的中斷與否,判斷我們所覆蓋的數值是否與 stack guard 相同。
以上圖來說,我們假設 stack guard 是 0xdeadbeeffacebc00
,由於 stack guard 特性,最低一個 byte 一定會是 0 ,這邊從第二個 byte 蓋起,這邊可以先蓋 00 試看看連線是否被中斷,如果被中斷代表蓋的數值是錯的,接下來我們就測其他數值看看有沒有中斷,依此類推,測到 0xbc 發現沒有中斷,代表第二個 byte 是 0xbc,接下來就繼續蓋第三 byte ,一樣從 0x00 蓋到沒中斷,直到蓋滿 8 bytes 的 stack guard 都沒中斷連線後,我們就可以知道 stack guard 的值是什麼,接下來我們就可以解決 stack guard 問題。
Construct the _dtor_list
to control RIP
在解決 stack guard 問題後,netatalk 已可正常運作,接下來我們需要構造 _dtor_list
結構並結束程式來控制 RIP,在當時的 synology 的 afpd 中並沒有開啟 PIE,我們可以在 afpd 的 data 段中,構造 _dtor_list
。
剛好在使用 dhx2 method 的 login 功能中,會將我們要登入的 username 複製到 global 的 buffer 中,所以我們可以將這結構跟著 username 一起寫入固定的已知位置。
在一切都構造完成後,我們這邊可以觸發正常功能的 DSICloseSession
即可觸發 exit()
tls_dtor_list
in Synology
在 reverse 後,發現 synology 的 glibc 中,會使用 __tls_get_addr()
來取得 tls_dtor_list
,並非直接存取 tls_dtor_list
這個全域變數,而這函式的取得方式則會從前述 tcbhead_t
中先取 div 欄位後,再取得其中的 tls_dtor_list
,因此我們需要連同 tcb->div
一起構造在固定位置,另外一點是 Synology 的 afpd 中並沒有 system 可用,但事實上有 execl 可以使用,只是參數稍微複雜一點而已。
最後我們構造的結構如上圖所示,我們將 tcb 及 dtor_list 結構都構造在 username buffer 中,觸發 exit() 後,就會去執行 execl 並取得反連 shell。
Remark
在一般的 Netatalk 中,是會啟用 PIE ,不太容易在已知位置構造 _dtor_list
,實際上也可以用類似方法 leak 出 libc 位置,依舊是 exploitable,該漏洞不只影響 Synology 也會影響到大部分有使用 Netatalk 的設備。
Other vendor
我們測試了許多家有使用到 Netatalk 的廠商,發現不少家有存在類似的問題,部分是 unexploitable 但也有部分是 exploitable。我們這邊實測了 QNAP 及 Asustor,皆有成功獲得 shell。
QNAP
- We tested on TS451
- QTS 4.5.4.1741
- Not enable by default
- Protection
- No Stack Guard
- No PIE
- 內建 system
Asustor
- We tested on AS5202T
- ADM 3.5.7.RJR1
- Not enable by default
- Protection
- No Stack Guard
- No PIE
- 內建 system
QNAP 及 Asustor 兩家 NAS 都沒有開啟 Stack guard,不需 brute-force 即可獲得反連 shell。
這個漏洞在 Synology 尚未修補時,只要 default 裝好就可以利用,不需任何認證,而 QNAP 及 Asustor 雖然不是預設開啟,但不少有使用 Mac 的用戶,還是會為了方便把它打開,基本上只要是 NAS 幾乎都會用到 Netatalk,絕大多數的 NAS 都有影響,只要有開啟 Netatalk,攻擊者可以利用這個漏洞打下大部分的 NAS。你的 NAS 就再也不會是你的 NAS。
我們後來也從 shodan 上發現,其實也有非常多人將 netatalk 開在外網,光在 shodan 上就有 13 萬台機器,其中大部分是 Synology。
Mitigation
Update
目前上述三台皆已修補,請尚未更新的用戶更新到最新
- Synology
- https://www.synology.com/zh-hk/security/advisory/Synology_SA_20_26
- QNAP
- https://www.qnap.com/en/security-advisory/qsa-21-50
- Asustor
- https://www.asustor.com/service/release_notes#ADM%203.5.7.RKU2
Disable AFP
- 沒使用時,最好直接關掉或是關在內網,該 project 幾乎已經很少維護,繼續使用風險極高。
- 改用 SMB 相對安全
- 如果想要用類似功能,建議可使用 SMB 相對安全不少,但只能說相對安全,不能說絕對沒問題,建議還是將相關服務都開在內網就好,沒用到的能關就關
Summary
我們已成功在 NAS 中找到一個嚴重漏洞,並且成功寫出概念證明程式,證實可以利用在 Synology、QNAP 及 Asustor 等主流 NAS 上利用。我們也認為 Netatalk 是在 NAS 中新一代的後門!
未來希望有使用到第三方套件的 NAS 廠商,可以多重新審視一下第三方套件所帶來的安全性問題,強烈建議可以自行 Review 一次,並且注意其他廠商是否也有修復同樣套件上的漏洞,很有可能自己也會受到影響,也希望使用 NAS 的用戶,也能多多重視不要把 NAS 開在外網,能關的服務就盡可能關閉,以減少攻擊面,讓攻擊者有機可趁。
To be continue
事實上,我們並不只有找到一個漏洞,我們也發現還有不少問題,也運用在去年的 Pwn2Own Austin 上,這部分我們在大部分廠商修復後會在公開其他的研究,就盡請期待 Part II。