分析 Docker 容器鑑識 是做什麼的?
使用時機
- 調查受入侵的 Docker 容器或容器主機時
- 分析從 registry 拉取的惡意 Docker 映像檔時
- 應對容器化應用程式入侵事件時
- 檢查容器逃脫嘗試或權限提升時
- 稽核容器設定並找出錯誤配置時
先決條件
- 在鑑識工作站上具備 Docker CLI 存取權限
- 可存取 Docker 主機檔案系統(鑑識映像檔或即時系統)
- 了解 Docker 分層檔案系統(overlay2、aufs)
- 安裝 dive、docker-explorer 或 container-diff 進行映像檔分析
- 具備 Docker daemon 設定與 socket 安全知識
- 安裝 Trivy 或 Grype 進行容器映像檔弱點掃描
工作流程
步驟 1:保全容器狀態與證據
# 列出所有容器(包括已停止的)
docker ps -a --no-trunc > /cases/case-2024-001/docker/container_list.txt
# 檢查受入侵容器
CONTAINER_ID="abc123def456"
docker inspect $CONTAINER_ID > /cases/case-2024-001/docker/container_inspect.json
# 匯出容器檔案系統為 tar 檔(保全當前狀態)
docker export $CONTAINER_ID > /cases/case-2024-001/docker/container_export.tar
# 從容器當前狀態建立映像檔
docker commit $CONTAINER_ID forensic-evidence:case-2024-001
docker save forensic-evidence:case-2024-001 > /cases/case-2024-001/docker/container_image.tar
# 擷取容器日誌
docker logs $CONTAINER_ID --timestamps > /cases/case-2024-001/docker/container_logs.txt 2>&1
# 擷取執行中的程序(如果容器仍在執行)
docker top $CONTAINER_ID > /cases/case-2024-001/docker/container_processes.txt
# 擷取網路連線
docker exec $CONTAINER_ID netstat -tlnp 2>/dev/null > /cases/case-2024-001/docker/container_network.txt
# 從容器複製特定檔案
docker cp $CONTAINER_ID:/var/log/ /cases/case-2024-001/docker/container_var_log/
docker cp $CONTAINER_ID:/tmp/ /cases/case-2024-001/docker/container_tmp/
docker cp $CONTAINER_ID:/etc/passwd /cases/case-2024-001/docker/container_passwd
# 雜湊所有匯出的證據
sha256sum /cases/case-2024-001/docker/*.tar > /cases/case-2024-001/docker/evidence_hashes.txt
步驟 2:分析容器映像檔層
# 安裝 dive 進行映像檔層分析
wget https://github.com/wagoodman/dive/releases/latest/download/dive_linux_amd64.deb
sudo dpkg -i dive_linux_amd64.deb
# 互動式分析映像檔層
dive forensic-evidence:case-2024-001
# 非互動式層分析
dive forensic-evidence:case-2024-001 --ci --json /cases/case-2024-001/docker/dive_analysis.json
# 解壓縮並檢查個別層
mkdir -p /cases/case-2024-001/docker/layers/
tar -xf /cases/case-2024-001/docker/container_image.tar -C /cases/case-2024-001/docker/layers/
# 列出映像檔 manifest 與層順序
cat /cases/case-2024-001/docker/layers/manifest.json | python3 -m json.tool
# 檢查每一層的變更
for layer in /cases/case-2024-001/docker/layers/*/layer.tar; do
echo "=== 層:$(dirname $layer | xargs basename) ==="
tar -tf "$layer" | head -20
echo "..."
done
# 使用 container-diff 與原始基礎映像檔比較
# 安裝 container-diff
curl -LO https://storage.googleapis.com/container-diff/latest/container-diff-linux-amd64
chmod +x container-diff-linux-amd64
# 比較提交的映像檔與原始版本
./container-diff-linux-amd64 diff daemon://nginx:latest daemon://forensic-evidence:case-2024-001 \
--type=file --type=apt --type=history --json \
> /cases/case-2024-001/docker/container_diff.json
步驟 3:檢查 Docker 主機端產物
# Docker 資料目錄(預設:/var/lib/docker/)
DOCKER_ROOT="/mnt/evidence/var/lib/docker"
# 檢查 overlay2 檔案系統層
ls -la $DOCKER_ROOT/overlay2/
# 尋找容器的合併檔案系統
CONTAINER_HASH=$(docker inspect $CONTAINER_ID --format '{{.GraphDriver.Data.MergedDir}}' 2>/dev/null)
# 或從鑑識映像檔手動尋找:
# 查看 /var/lib/docker/containers/<container_id>/config.v2.json
# 分析容器設定檔
cat $DOCKER_ROOT/containers/$CONTAINER_ID/config.v2.json | python3 -m json.tool \
> /cases/case-2024-001/docker/container_config.json
# 檢查 Docker daemon 設定
cat /mnt/evidence/etc/docker/daemon.json 2>/dev/null > /cases/case-2024-001/docker/daemon_config.json
# 檢查 Docker 事件日誌
cat $DOCKER_ROOT/containers/$CONTAINER_ID/*.log > /cases/case-2024-001/docker/container_json_logs.txt
# 檢查磁碟區掛載(可能的主機檔案系統存取)
python3 << 'PYEOF'
import json
with open('/cases/case-2024-001/docker/container_inspect.json') as f:
data = json.load(f)
inspect = data[0] if isinstance(data, list) else data
print("=== 容器安全性分析 ===\n")
# 檢查掛載
print("磁碟區掛載:")
for mount in inspect.get('Mounts', []):
rw = "讀寫" if mount.get('RW') else "唯讀"
print(f" {mount.get('Source', 'N/A')} -> {mount.get('Destination', 'N/A')} ({rw})")
if mount.get('Source') in ('/', '/etc', '/var', '/root') and mount.get('RW'):
print(f" 警告:敏感的主機路徑以讀寫方式掛載!")
# 檢查特權模式
host_config = inspect.get('HostConfig', {})
if host_config.get('Privileged'):
print("\n警告:容器以「特權」模式執行!")
# 檢查能力
cap_add = host_config.get('CapAdd', [])
if cap_add:
print(f"\n新增的能力:{cap_add}")
dangerous_caps = ['SYS_ADMIN', 'SYS_PTRACE', 'NET_ADMIN', 'SYS_MODULE']
for cap in cap_add:
if cap in dangerous_caps:
print(f" 警告:危險能力:{cap}")
# 檢查 PID 命名空間
if host_config.get('PidMode') == 'host':
print("\n警告:容器共用主機 PID 命名空間!")
# 檢查網路模式
if host_config.get('NetworkMode') == 'host':
print("\n警告:容器共用主機網路命名空間!")
# 檢查使用者
user = inspect.get('Config', {}).get('User', 'root (預設)')
print(f"\n執行使用者:{user}")
# 檢查環境變數中的秘密
env_vars = inspect.get('Config', {}).get('Env', [])
print(f"\n環境變數:{len(env_vars)} 個")
for env in env_vars:
key = env.split('=')[0]
if any(s in key.upper() for s in ['PASSWORD', 'SECRET', 'KEY', 'TOKEN', 'CREDENTIAL']):
print(f" 敏感性資訊:{key}=***已遮罩***")
PYEOF
步驟 4:分析容器檔案系統變更
# 比較容器檔案系統與原始映像檔
docker diff $CONTAINER_ID > /cases/case-2024-001/docker/filesystem_changes.txt
# A = 新增,C = 變更,D = 刪除
# 分析變更
python3 << 'PYEOF'
added = []
changed = []
deleted = []
with open('/cases/case-2024-001/docker/filesystem_changes.txt') as f:
for line in f:
line = line.strip()
if line.startswith('A '):
added.append(line[2:])
elif line.startswith('C '):
changed.append(line[2:])
elif line.startswith('D '):
deleted.append(line[2:])
print(f"新增檔案:{len(added)} 個")
print(f"變更檔案:{len(changed)} 個")
print(f"刪除檔案:{len(deleted)} 個")
# 標記可疑新增
suspicious = [f for f in added if any(s in f for s in
['/tmp/', '/dev/shm/', '/root/', '.sh', '.py', '.elf', 'reverse', 'shell', 'backdoor'])]
if suspicious:
print(f"\n可疑新增檔案:")
for f in suspicious:
print(f" {f}")
# 標記可疑變更
sus_changed = [f for f in changed if any(s in f for s in
['/etc/passwd', '/etc/shadow', '/etc/crontab', '/etc/ssh', '.bashrc'])]
if sus_changed:
print(f"\n可疑變更檔案:")
for f in sus_changed:
print(f" {f}")
PYEOF
# 解壓縮並檢查容器匯出內容
mkdir -p /cases/case-2024-001/docker/container_fs/
tar -xf /cases/case-2024-001/docker/container_export.tar -C /cases/case-2024-001/docker/container_fs/
# 掃描網頁後門與惡意檔案
find /cases/case-2024-001/docker/container_fs/tmp/ -type f -exec file {} \;
find /cases/case-2024-001/docker/container_fs/ -name "*.php" -newer /cases/case-2024-001/docker/container_fs/etc/hostname
步驟 5:掃描弱點並產生報告
# 掃描映像檔的已知弱點
trivy image forensic-evidence:case-2024-001 \
--format json \
--output /cases/case-2024-001/docker/vulnerability_scan.json
# 掃描匯出的檔案系統
trivy fs /cases/case-2024-001/docker/container_fs/ \
--format table \
--output /cases/case-2024-001/docker/fs_vulnerabilities.txt
# 檢查映像檔中的秘密
trivy image forensic-evidence:case-2024-001 \
--scanners secret \
--format json \
--output /cases/case-2024-001/docker/secrets_scan.json
關鍵概念
| 概念 | 說明 |
|---|---|
| 映像檔層 | 唯讀檔案系統層堆疊形成容器映像檔 |
| overlay2 | 預設 Docker 儲存驅動程式,使用聯合檔案系統處理層 |
| 容器差異 | 比較執行時期檔案系統變更與原始映像檔 |
| 特權模式 | 容器具備完整主機能力(繞過大部分隔離) |
| Docker socket | 控制 Docker daemon 的 Unix socket(/var/run/docker.sock) |
| 容器逃脫 | 突破容器隔離到主機的技術 |
| 磁碟區掛載 | 可在容器內存取的主機檔案系統路徑 |
| 映像檔歷史 | 用於建立每一層的 Dockerfile 指令記錄 |
工具與系統
| 工具 | 目的 |
|---|---|
| docker inspect | 詳細容器設定與狀態資訊 |
| docker diff | 顯示執行中/已停止容器所做的檔案系統變更 |
| dive | 互動式 Docker 映像檔層分析工具 |
| container-diff | Google 工具,用於比較容器映像檔內容 |
| Trivy | 容器映像檔與檔案系統的弱點掃描器 |
| docker-explorer | 離線 Docker 產物分析的鑑識工具 |
| Sysdig | 容器執行時期安全性監控與鑑識 |
| Falco | 容器與 Kubernetes 的執行時期威脅偵測 |
常見情境
情境 1:網頁應用程式容器入侵 匯出容器檔案系統,找出網頁根目錄中的網頁後門,分析存取日誌以探查利用嘗試,檢查新增的檔案與修改的設定,檢查網路連線以找出 C2 通訊,檢視容器能力以尋找權限提升路徑。
情境 2:透過惡意映像檔的供應鏈攻擊 使用 dive 分析映像檔層以找出哪一層新增惡意內容,使用 container-diff 與官方基礎映像檔比較,檢查映像檔歷史尋找可疑的 RUN 指令,掃描內建的後門與加密貨幣挖礦程式,從 registry 日誌追蹤映像檔拉取記錄。
情境 3:容器逃脫調查 檢查容器是否以特權或危險能力執行,檢查主機檔案系統掛載點以探查未授權存取,檢視 Docker socket 掛載(可能啟用 Docker-in-Docker 濫用),分析主機系統日誌以找出容器逃脫指標,檢查核心漏洞利用產物。
情境 4:容器環境中的挖礦劫持 找出高 CPU 使用率的容器,匯出並分析容器映像檔中的挖礦二進位檔,檢查 registry 中是否有未授權映像檔,檢視容器建立事件以找出惡意部署,檢查網路連線以找出礦池通訊。
輸出格式
Docker 容器鑑識摘要:
Container:abc123def456 (nginx-app)
Image:company/web-app:v2.1
Status:執行中 (啟動於 2024-01-10 09:00 UTC)
Host:docker-host-01.corp.local
安全性設定:
特權模式:否
新增的能力:NET_ADMIN (警告)
磁碟區掛載:/var/log -> /host-logs (讀寫)
網路模式:bridge
使用者:root (警告)
檔案系統變更:
新增:23 個檔案 (5 個可疑)
變更:12 個檔案 (2 個可疑)
刪除:0 個檔案
可疑發現:
/tmp/reverse.sh - 反向 shell 腳本 (新增)
/var/www/html/.hidden/shell.php - PHP 網頁後門 (新增)
/etc/crontab - 已修改 (已新增持續性 cron 項目)
/root/.ssh/authorized_keys - 已修改 (已新增未授權金鑰)
弱點掃描:
重大:3 (CVE-2024-xxxx 於基礎映像檔中)
高:12
中:34
證據位置:/cases/case-2024-001/docker/