$npx -y skills add killvxk/cybersecurity-skills-zh --skill analyzing-docker-container-forensics通过分析镜像、层、卷、日志和运行时痕迹,调查受损的 Docker 容器,识别恶意活动和证据。
| 1 | # 分析 Docker 容器取证 |
| 2 | |
| 3 | ## 适用场景 |
| 4 | - 调查受损的 Docker 容器或容器主机时 |
| 5 | - 分析从注册表拉取的恶意 Docker 镜像时 |
| 6 | - 涉及容器化应用程序漏洞的事件响应(Incident Response)期间 |
| 7 | - 检查容器逃逸尝试或权限提升(Privilege Escalation)时 |
| 8 | - 审计容器配置并识别错误配置时 |
| 9 | |
| 10 | ## 前置条件 |
| 11 | - 取证工作站上的 Docker CLI 访问权限 |
| 12 | - 访问 Docker 主机文件系统(取证镜像或实时系统) |
| 13 | - 了解 Docker 分层文件系统(overlay2、aufs) |
| 14 | - 用于镜像分析的 dive、docker-explorer 或 container-diff |
| 15 | - 了解 Docker 守护进程配置和 socket 安全 |
| 16 | - 用于容器镜像漏洞扫描的 Trivy 或 Grype |
| 17 | |
| 18 | ## 工作流程 |
| 19 | |
| 20 | ### 步骤 1:保全容器状态和证据 |
| 21 | |
| 22 | ```bash |
| 23 | # 列出所有容器(包括已停止的) |
| 24 | docker ps -a --no-trunc > /cases/case-2024-001/docker/container_list.txt |
| 25 | |
| 26 | # 检查受损容器 |
| 27 | CONTAINER_ID="abc123def456" |
| 28 | docker inspect $CONTAINER_ID > /cases/case-2024-001/docker/container_inspect.json |
| 29 | |
| 30 | # 将容器文件系统导出为 tarball(保留当前状态) |
| 31 | docker export $CONTAINER_ID > /cases/case-2024-001/docker/container_export.tar |
| 32 | |
| 33 | # 从容器当前状态创建镜像 |
| 34 | docker commit $CONTAINER_ID forensic-evidence:case-2024-001 |
| 35 | docker save forensic-evidence:case-2024-001 > /cases/case-2024-001/docker/container_image.tar |
| 36 | |
| 37 | # 捕获容器日志 |
| 38 | docker logs $CONTAINER_ID --timestamps > /cases/case-2024-001/docker/container_logs.txt 2>&1 |
| 39 | |
| 40 | # 捕获运行中的进程(如果容器仍在运行) |
| 41 | docker top $CONTAINER_ID > /cases/case-2024-001/docker/container_processes.txt |
| 42 | |
| 43 | # 捕获网络连接 |
| 44 | docker exec $CONTAINER_ID netstat -tlnp 2>/dev/null > /cases/case-2024-001/docker/container_network.txt |
| 45 | |
| 46 | # 从容器复制特定文件 |
| 47 | docker cp $CONTAINER_ID:/var/log/ /cases/case-2024-001/docker/container_var_log/ |
| 48 | docker cp $CONTAINER_ID:/tmp/ /cases/case-2024-001/docker/container_tmp/ |
| 49 | docker cp $CONTAINER_ID:/etc/passwd /cases/case-2024-001/docker/container_passwd |
| 50 | |
| 51 | # 对所有导出证据进行哈希计算 |
| 52 | sha256sum /cases/case-2024-001/docker/*.tar > /cases/case-2024-001/docker/evidence_hashes.txt |
| 53 | ``` |
| 54 | |
| 55 | ### 步骤 2:分析容器镜像层 |
| 56 | |
| 57 | ```bash |
| 58 | # 安装 dive 用于镜像层分析 |
| 59 | wget https://github.com/wagoodman/dive/releases/latest/download/dive_linux_amd64.deb |
| 60 | sudo dpkg -i dive_linux_amd64.deb |
| 61 | |
| 62 | # 交互式分析镜像层 |
| 63 | dive forensic-evidence:case-2024-001 |
| 64 | |
| 65 | # 非交互式层分析 |
| 66 | dive forensic-evidence:case-2024-001 --ci --json /cases/case-2024-001/docker/dive_analysis.json |
| 67 | |
| 68 | # 提取并检查单个层 |
| 69 | mkdir -p /cases/case-2024-001/docker/layers/ |
| 70 | tar -xf /cases/case-2024-001/docker/container_image.tar -C /cases/case-2024-001/docker/layers/ |
| 71 | |
| 72 | # 列出镜像清单和层顺序 |
| 73 | cat /cases/case-2024-001/docker/layers/manifest.json | python3 -m json.tool |
| 74 | |
| 75 | # 检查每一层的变更 |
| 76 | for layer in /cases/case-2024-001/docker/layers/*/layer.tar; do |
| 77 | echo "=== 层: $(dirname $layer | xargs basename) ===" |
| 78 | tar -tf "$layer" | head -20 |
| 79 | echo "..." |
| 80 | done |
| 81 | |
| 82 | # 使用 container-diff 与原始基础镜像进行比较 |
| 83 | # 安装 container-diff |
| 84 | curl -LO https://storage.googleapis.com/container-diff/latest/container-diff-linux-amd64 |
| 85 | chmod +x container-diff-linux-amd64 |
| 86 | |
| 87 | # 将提交的镜像与原始镜像进行比较 |
| 88 | ./container-diff-linux-amd64 diff daemon://nginx:latest daemon://forensic-evidence:case-2024-001 \ |
| 89 | --type=file --type=apt --type=history --json \ |
| 90 | > /cases/case-2024-001/docker/container_diff.json |
| 91 | ``` |
| 92 | |
| 93 | ### 步骤 3:检查 Docker 主机痕迹 |
| 94 | |
| 95 | ```bash |
| 96 | # Docker 数据目录(默认: /var/lib/docker/) |
| 97 | DOCKER_ROOT="/mnt/evidence/var/lib/docker" |
| 98 | |
| 99 | # 检查 overlay2 文件系统层 |
| 100 | ls -la $DOCKER_ROOT/overlay2/ |
| 101 | |
| 102 | # 查找容器的合并文件系统 |
| 103 | CONTAINER_HASH=$(docker inspect $CONTAINER_ID --format '{{.GraphDriver.Data.MergedDir}}' 2>/dev/null) |
| 104 | # 或从取证镜像手动查找: |
| 105 | # 查看 /var/lib/docker/containers/<container_id>/config.v2.json |
| 106 | |
| 107 | # 分析容器配置文件 |
| 108 | cat $DOCKER_ROOT/containers/$CONTAINER_ID/config.v2.json | python3 -m json.tool \ |
| 109 | > /cases/case-2024-001/docker/container_config.json |
| 110 | |
| 111 | # 检查 Docker 守护进程配置 |
| 112 | cat /mnt/evidence/etc/docker/daemon.json 2>/dev/null > /cases/case-2024-001/docker/daemon_config.json |
| 113 | |
| 114 | # 检查 Docker 事件日志 |
| 115 | cat $DOCKER_ROOT/containers/$CONTAINER_ID/*.log > /cases/case-2024-001/docker/container_json_logs.txt |
| 116 | |
| 117 | # 检查卷挂载(可能的主机文件系统访问) |
| 118 | python3 << 'PYEOF' |
| 119 | import json |
| 120 | |
| 121 | with open('/cases/case-2024-001/docker/container_inspect.json') as f: |
| 122 | data = json.load(f) |
| 123 | |
| 124 | inspect = data[0] if isinstance(data, list) else data |
| 125 | |
| 126 | print("=== 容器安全分析 ===\n") |
| 127 | |
| 128 | # 检查挂载点 |
| 129 | print("卷挂载:") |
| 130 | for mount in inspect.get('Mounts', []): |
| 131 | rw = "读写" if mount.get('RW') else "只读" |
| 132 | print(f" {mount.get('Source', 'N/A')} -> {mount.get('Destination', 'N/A')} ({rw})") |
| 133 | if mount.get('Source') in ('/', '/etc', '/var', '/root') and mount.get('RW'): |
| 134 | print(f" 警告: 敏感主机路径以读写方式挂载!") |
| 135 | |
| 136 | # 检查特权模式 |
| 137 | host_config = inspect.get('HostConfig', {}) |
| 138 | if host_config.get('Privileged'): |
| 139 | print("\n警告: 容器以特权(PRIVILEGED)模式运行!") |
| 140 | |
| 141 | # 检查能力 |
| 142 | cap_add = host_config.get('CapAdd', []) |
| 143 | if cap_add: |
| 144 | print(f"\n已添加能力: {cap_add}") |
| 145 | dangerous_caps = ['SYS_ADMIN', 'SYS_PTRACE', 'NET_ADMIN', 'SYS_MODULE'] |
| 146 | for cap in cap_add: |
| 147 | if cap in dangerous_caps: |
| 148 | print(f" 警告: 危险能力: {cap}") |
| 149 | |
| 150 | # 检查 PID 命名空间 |
| 151 | if host_config.get('PidMode') == 'host': |
| 152 | print("\n警告: 容器共享主机 PID 命名空间!") |
| 153 | |
| 154 | # 检查网络模式 |
| 155 | if host_config.get('NetworkMode') == 'host': |
| 156 | print("\n警告: 容器共享主机网络命名空间!") |
| 157 | |
| 158 | # 检查用户 |
| 159 | user = inspect.get('Config', {}).get('User', 'root(默认)') |
| 160 | print(f"\ |