Lab1 数据包嗅探与欺骗
实验原理
Task1
使用scapy
ifconfig
可知本机ip地址为10.9.0.1
Task1.A 嗅探数据包
执行ping:
sniff到了icmp报文:
sniffer.py
- filter:过滤规则,这里过滤的是icmp包
- prn:为每个数据包定义一个回调函数
#!/usr/bin/python3
from scapy.all import *
def print_pkt(pkt):
pkt.show()
pkt = sniff(filter="icmp",prn=print_pkt)
ping百度官网:
Task1.B 欺骗ICMP数据包
我们的目标是任意源IP地址欺骗IP数据包。我们将欺骗ICMP回显请求数据包,并将其发送到同一网络上的另一个VM。我们将使用Wireshark观察我们的请求是否会被接收方接受。如果接受,将向伪造的IP地址发送回显回复数据包。sendFakePackage.py:
#!/usr/bin/python3
from scapy.all import *
ip = IP()
ip.src ="10.0.2.4"
ip.dst ="192.169.0.1"
icmp=ICMP()
send(ip/icmp)
ls(ip)
使用WireShark抓包:
添加ls(ip)
语句:
Task1.C 路由跟踪
模拟一个traceroute,循环每次TTL+1,中间节点都发回ICMP TTL字段过期的错误信息,目的节点发回ICMP reply就结束
trace.py
from scapy.all import *
import sys
def traceroute(target, minttl=1, maxttl=30, dport=80):
print("target: %s(port=%s)" % (target, dport))
ans, unans = sr(IP(dst=target, ttl=(minttl,maxttl),id=RandShort())/TCP(flags=0x2, dport=dport), timeout=10)
for snd,rcv in ans:
print(snd.ttl, rcv.src)
if __name__ == '__main__':
if len(sys.argv) <= 1:
traceroute("baidu.com")
else:
traceroute(sys.argv[1])
sr函数返回两个参数,分别是得到响应的数据包和未响应的数据包。
Task1.D 先Sniffing后Spoofing
我们抓取icmp协议的数据包,然后对数据包进行改造后发回给源地址实现数据包嗅探和欺骗。
ttl=64为我们伪造的回复包,ttl=128为正常的相应包。
我们在另一台虚拟机终端上ping一个不存在的ip地址1.2.3.4,就会发现竟然得到了回显,即我们运行程序的终端在进行嗅探和欺骗。
Task2
Task2.A 嗅探数据包
#include <pcap.h>
#include <stdio.h>
#include <stdlib.h>
#include <arpa/inet.h>
/* Ethernet header */
struct ethheader {
u_char ether_dhost[6]; /* destination host address */
u_char ether_shost[6]; /* source host address */
u_short ether_type; /* protocol type (IP, ARP, RARP, etc) */
};
/* IP Header */
struct ipheader {
unsigned char iph_ihl:4, //IP header length
iph_ver:4; //IP version
unsigned char iph_tos; //Type of service
unsigned short int iph_len; //IP Packet length (data + header)
unsigned short int iph_ident; //Identification
unsigned short int iph_flag:3, //Fragmentation flags(分片标记)
iph_offset:13; //Flags offset
unsigned char iph_ttl; //Time to Live
unsigned char iph_protocol; //Protocol type
unsigned short int iph_chksum; //IP datagram checksum
struct in_addr iph_sourceip; //Source IP address
struct in_addr iph_destip; //Destination IP address
};
void got_packet(u_char *args, const struct pcap_pkthdr *header,const u_char *packet)
{
struct ethheader *eth = (struct ethheader *)packet;
if (ntohs(eth->ether_type) == 0x0800) { // 0x0800 is IP type
struct ipheader * ip = (struct ipheader *)
(packet + sizeof(struct ethheader));
printf(" From: %s\n", inet_ntoa(ip->iph_sourceip));
printf(" To: %s\n", inet_ntoa(ip->iph_destip));
/* determine protocol */
switch(ip->iph_protocol) {
case IPPROTO_TCP:
printf(" Protocol: TCP\n\n");
return;
case IPPROTO_UDP:
printf(" Protocol: UDP\n\n");
return;
case IPPROTO_ICMP:
printf(" Protocol: ICMP\n\n");
return;
default:
printf(" Protocol: others\n\n");
return;
}
}
printf("Got a packet\n");
}
int main(){
pcap_t *handle;
char errbuf[PCAP_ERRBUF_SIZE];
struct bpf_program fp;
char filter_exp[] = "icmp";
bpf_u_int32 net;
handle = pcap_open_live("ens33", BUFSIZ, 1, 1000, errbuf);
pcap_compile(handle, &fp, filter_exp, 0, net);
if (pcap_setfilter(handle, &fp) !=0) {
pcap_perror(handle, "Error:");
exit(EXIT_FAILURE);
}
pcap_loop(handle, -1, got_packet, NULL);
pcap_close(handle); //Close the handle
return 0;
}
代码中运用到的函数说明如下:
pcap_open_live函数获得用于捕获网络数据包的数据包捕获描述字,pcap_compile函数将str参数指定的字符串编译到过滤程序中,pcap_setfilter函数指定一个过滤程序,pcap_loop函数捕获并处理数据包。
详细介绍:
函数名称:pcap_t *pcap_open_live(char *device, int snaplen, int promisc, int to_ms, char *ebuf)
函数功能:获得用于捕获网络数据包的数据包捕获描述字。
参数说明:device 参数为指定打开的网络设备名。snaplen参数定义捕获数据的最大字节数。promisc指定是否将网络接口置于混杂模式。to_ms参数指定超时时间(毫秒)。ebuf参数则仅在pcap_open_live()函数出错返回NULL时用于传递错误消息
函数名称:int pcap_compile(pcap_t *p, struct bpf_program *fp,char *str, int optimize, bpf_u_int32 netmask)
函数功能:将str参数指定的字符串编译到过滤程序中。
参数说明:fp是一个bpf_program结构的指针,在pcap_compile()函数中被赋值。optimize参数控制结果代码的优化。netmask参数指定本地网络的网络掩码。
函数名称:int pcap_setfilter(pcap_t *p, struct bpf_program *fp)
函数功能:指定一个过滤程序。
参数说明:fp参数是bpf_program结构指针,通常取自pcap_compile()函数调用。出错时返回-1;成功时返回0。
函数名称:int pcap_loop(pcap_t *p, int cnt,pcap_handler callback, u_char *user)
函数功能:捕获并处理数据包。
参数说明:cnt 参数指定函数返回前所处理数据包的最大值。cnt=-1表示在一个缓冲区中处理所有的数据包。cnt=0表示处理所有数据包,直到产生以下错误之一:读取到EOF;超时读取。callback参数指定一个带有三个参数的回调函数,这三个参数为:一个从pcap_dispatch()函数传递过来的 u_char指针,一个pcap_pkthdr结构的指针,和一个数据包大小的u_char指针。如果成功则返回读取到的字节数。读取到EOF时则返回零值。出错时则返回-1,此时可调用pcap_perror()或pcap_geterr()函数获取错误消息。
Task2.B 数据包欺骗
我们在这里伪造UDP和ICMP请求。我们在myheader.h头文件中定义Ethernet头、IP头、ICMP头、UDP头、TCP头、Psuedo TCP头。spoof.c文件中定义send_raw_ip_packet发送数据包函数,需要执行以下步骤:创建一个原始套接字、设置套接字规则、提供目的地所需的信息,并发出数据包。
这里以UDP请求的伪造为例(ICMP伪造请求类似,也编写了相应代码并成功运行),task.c中的执行步骤分别为:填充UDP数据域、UDP头、IP头,并发送欺骗的数据包。
伪造UDP请求:
gcc -o spoof spoof.c task.c -lpcap
伪造ICMP请求:
gcc -o spoof2 spoof.c task2.c checksum.c -lpcap
Task2.C 嗅探&欺骗
准备两个在同一个局域网的虚拟机,任务中一台虚拟机将同时嗅探和伪造包,实现一个机器ping任意IP x,另一个机器伪造ICMP回复请求,使得其有回复,而IP x所对应的机器可能根本不存在。
Lab2 公钥基础设施PKI
实验原理
实验场景:虚拟机seedlab ubuntu20.04,及内置docker容器。
实验原理:
在计算机网络上,OpenSSL是一个开放源代码的软件库包,应用程序可以使用这个包来进行安全通信,避免窃听,同时确认另一端连接者的身份。这个包广泛被应用在互联网的网页服务器上。
PKI基本结构由证书认证机构(certificate authority, CA)、证书持有者(certificate holder)、依赖方(relying party)三方构成。实验主要包含三大部分,示意图如下所示:
(1)证书的生成:
(2)证书的验证:
(3)中间人攻击:
假设Alice希望通过HTTPS协议访问example.com。她需要从example.com服务器获取公钥;Alice将生成一个秘密,并使用服务器的公钥加密该秘密,然后将其发送到服务器。如果攻击者能够拦截Alice和服务器之间的通信,则攻击者可以用自己的公钥替换服务器的公钥。因此,Alice的秘密实际上是用攻击者的公钥加密的,因此攻击者将能够读取该秘密。攻击者可以使用服务器的公钥将机密转发给服务器。该秘密用于加密Alice和服务器之间的通信,因此攻击者可以解密加密的通信。
实验步骤
配置实验环境
Task1 生成CA
将/usr/lib/ssl目录下的openssl.cnf文件复制到当前实验文件夹lab-pki中,并对其进行修改,将” #unique_subject = no”注释取消。然后在lab-pki中创建demoCA文件夹并在其中创建以下目录和文件:certs、crl、newcerts、index.txt(空文档)、serial(文档,内写有字符“1000”)。
openssl req -x509 -newkey rsa:4096 -sha256 -days 3650 -keyout ca.key -out ca.crt
存储在两个文件中:ca.key 和 ca.crt。文件ca.key 包含 CA 的私钥,而 ca.crt 包含公钥证书。
Task2 为Web服务器生成证书请求(CSR)
openssl req -newkey rsa:2048 -sha256 -keyout server.key -out server.csr -subj "/CN=www.bank32.com/O=Bank32 Inc./C=US" -passout pass:dees
该命令将生成一对公钥和私钥,然后从公钥创建证书签名请求。
我们查看CSR与私钥文件的内容:
openssl req -in server.csr -text -noout
openssl rsa -in server.key -text -noout
我们可以在证书签名请求中添加两个备用名称:
openssl req -newkey rsa:2048 -sha256 -keyout server.key -out server.csr -subj "/CN=www.bank32.com/O=Bank32 Inc./C=US" -passout pass:dees -addext "subjectAltName = DNS:www.bank32.com,DNS:www.bank32A.com,DNS:www.bank32B.com"
Task3 为服务器生成证书
openssl ca -config openssl.cnf -policy policy_anything -md sha256 -days 3650 -in server.csr -out server.crt -batch -cert ca.crt -keyfile ca.key
证书签名后,使用以下命令打印证书的解码内容,并检查是否包含替代名称。
openssl x509 -in server.crt -text -noout
在我们的容器中:
通过docker容器和虚拟机之间的共享文件夹volumes,我们可以在SSLCertificateFile和SSLCertificateKeyFile后面写上的目录中放入我们之前生成的证书和密钥。即:server.crt,server.key
a2enmod ssl // 启用ssl模块
a2ensite bank32_apache_ssl // 启用此文件中描述的站点
service apache2 restart // 重启服务器
共享文件夹使用:
虚拟机
docker容器
将 server.crt
和 server.key
复制进 certs
中,并修改 Dockerfile
Task4 发起中间人攻击
在/etc/hosts文件中加入:10.9.0.80 www.csujwc.its.csu.edu.cn
将网站的html放至/var/www目录中:
由于无法得到csujwc.its.csu.edu.cn的有效证书,所以我们将使用与我们服务器相同的证书。
service apache2 reload //重新配置
a2enmod ssl // 启用ssl模块
a2ensite bank32_apache_ssl // 启用此文件中描述的站点
service apache2 restart // 重启服务器
我们没有发起实际的 DNS 缓存中毒攻击,而是简单地修改受害者机器的 /etc/hosts 文件,通过将主机名映射到https://www.csujwc.its.csu.edu.cn/我们的恶意Web 服务器来模拟DNS 缓存定位攻击的结果。
Task5 使用受损CA发起中间人攻击
在此任务中,我们假设在前面创建的根 CA 被攻击者破坏,并且其私钥被盗。因此,攻击者可以使用该CA 的私钥生成任意证书。在这项任务中,我们将看到这种妥协的后果。
请设计一个实验,证明攻击者可以成功地对任何 HTTPS 网站发起 MITM 攻击。您可以使用在任务5 中创建的相同设置,但这一次,您需要证明 MITM 攻击成功,即当受害者尝试访问网站时,浏览器不会引起任何怀疑,但登陆 MITM 攻击者的假冒网站。
以同样的步骤为www.csujwc.its.csu.edu.cn生成证书csu.crt:
配置apache文件:
可以看到:重启docker容器的Apache服务器,在虚拟机浏览器中访问网址https://www.csujwc.its.csu.edu.cn/,即可正常浏览。
TLS为套接字提供安全通信需要四个步骤。
Lab3 安全传输层协议TLS
实验原理
TLS为套接字提供安全通信需要四个步骤。
- 第一步是创建TLS上下文对象。对象中保存了我们对证书的认证与加密算法选择的偏好设置。代码中context部分.
- 第二步是建立TCP握手协议,进行TCP连接,代码sock部分.
- 第三步是调用第一步创建的上下文对象,对其使用wrap_socket()方法,表示让OpenSLL库负责控制我们的TCP链接。然后与通信对方交换必要的握手信息,并建立加密链接。代码中ssock =context.wrap_socket…部分
- 最后一步是使用wrap_socket()调用返回的ssl_sock对象,进行所有的后续通信。后续进行通信需要使用类似 ssock.方法名 格式进行使用.
Task1 配置TLS客户端
1.1 TLS握手协议
在lab3/Labsetup文件夹中打开终端,将handshake.py
设置为可执行,并执行命令:./handshake.py www.baidu.com
,终端打印出TLS握手协议过程。
handshake.py
中定义了要握手的域名主机地址、端口(https为443端口)、证书地址cadir、TLS协议目录等,并调用sock.connect函数进行TCP连接,ssl.wrap_socket函数接受一个已存在的socket作为参数并使用SSL层来包装它,并执行TLS握手,并打印相关主机信息。代码如下:
#!/usr/bin/env python3
import socket
import ssl
import sys
import pprint
hostname = sys.argv[1]
port = 443
cadir = '/etc/ssl/certs'
#cadir = './client-certs'
# Set up the TLS context
context = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT) # For Ubuntu 20.04 VM
# context = ssl.SSLContext(ssl.PROTOCOL_TLSv1_2) # For Ubuntu 16.04 VM
context.load_verify_locations(capath=cadir)
context.verify_mode = ssl.CERT_REQUIRED
context.check_hostname = True
# Create TCP connection
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.connect((hostname, port))
input("After making TCP connection. Press any key to continue ...")
# Add the TLS
ssock = context.wrap_socket(sock, server_hostname=hostname,
do_handshake_on_connect=False)
ssock.do_handshake() # Start the handshake
print("=== Cipher used: {}".format(ssock.cipher()))
print("=== Server hostname: {}".format(ssock.server_hostname))
print("=== Server certificate:")
pprint.pprint(ssock.getpeercert())
pprint.pprint(context.get_ca_certs())
input("After TLS handshake. Press any key to continue ...")
# Send HTTP Request to Server
#request = b"GET /it/u=1101547997,212706247&fm=26&fmt=auto HTTP/1.0\r\nHost: " + \
# hostname.encode('utf-8') + b"\r\n\r\n"
#ssock.sendall(request)
# Read HTTP Response from Server
#response = ssock.recv(2048)
#while response:
# pprint.pprint(response.split(b"\r\n"))
# response = ssock.recv(2048)
# Close the TLS Connection
ssock.shutdown(socket.SHUT_RDWR)
ssock.close()
ECDHE-RSA-AES128-GCM-SHA256,每个参数的意思为:
1.2 CA证书
观察到GlobalSign有关的证书,在/etc/ssl/certs
中查找到相关的证书,并将其拷贝到client-certs
文件夹中:
获得证书的哈希值:
openssl x509 -in GlobalSign_Root_CA.pem -noout -subject_hash
得到5ad8a5d6,并进行链接:
ln -s GlobalSign_Root_CA.pem 5ad8a5d6.0
我们修改handshake.py
文件:
可以成功进行TLS握手:
1.3 主机名检查
handshake.py文件中的cadir 更换回 ’/etc/ssl/certs’。在任意位置打开终端,输入命令:
dig www.example.com
我们获得www.example.com的ip地址93.184.216.34:
然后我们在/etc文件夹中打开终端,输入命令:
vim hosts
修改hosts文件,在其末尾加入93.184.216.34 www.example2020.com这一条数据
然后esc,输入:wq!保存退出hosts文件。将handshake.py文件中的context.check_hostname = True一句True更改为False,即取消域名检查。再在lab3文件夹中执行命令:
sudo ./handshake.py www.example2020.com
发现连接成功:
如果我们将handshake.py文件中的域名检查命令更改为真,再次执行命令,将发现连接失败,原因是域名不匹配。
修改handshake.py文件,在关闭连接的代码前添加http发送和接受的代码:
保存后执行,连接任意网站,即可获得服务器的response报文。
Task2 TLS服务器
2.1 实现一个简单的TLS服务器
在docker容器中运行server.py文件即可模拟TLS服务器,需要注意的是,我们还需要生成服务器对应的证书和密钥,使用PKI实验中的命令,我们得到了qzs.crt和qzs.key,本实验中使用的域名为:
将这两个文件复制,放入lab3/labsetup/volumes/server_certs中,将其对应的CA证书ca.crt放入lab-tls/labsetup/volumes/client_certs中。
使用openssl命令获取hash之后在client-certs中建立软链接:
然后我们修改/etc/hosts文件,向其中添加docker容器ip和域名的映射关系。
我们将容器作为server,宿主机作为client,在client中添加下列地址映射:
在server端运行.server.py
。server.py
中定义了html页面、服务器证书、服务器私钥,TLS协议目录,利用socket通信机制,打开443端口并进行侦听,进入等待TLS连接请求状态;当有客户端发送连接请求时,包装现有套接字与SSL层协议,建立TLS连接。若连接失败,则报TLS连接异常。
在lab3/labsetup/volumes中打开终端输入命令:
sudo ./handshake.py www.qzs2022.com
即可获得连接成功的回显信息。
注:服务器重启需要:
netstat -tunlp
并kill进程:
kill -9 19
(19为PID)
2.2 使用浏览器测试服务器程序
在浏览器的about:preferences#privacy页面中找到证书选项,添加我们的CA证书ca.crt,然后访问我们的域名https://www.qzs2022.com/即可看到我们的页面信息:
2.3 具有多个名称的证书
首先创建server_openssl.cnf文件,内容如下:
输入以下命令创建服务器签名申请和私钥:
openssl req -newkey rsa:2048 -config ./server_openssl.cnf -batch -sha256 -keyout qzs1.key -out qzs1.csr
我们通过实验二生成的ca.crt创建证书:
openssl ca -config openssl.cnf -policy policy_anything -md sha256 -days 3650 -in qzs1.csr -out qzs1.crt -batch -cert ca.crt -keyfile ca.key
client端加入地址映射:
将ca.crt放入lab3文件夹下的volume/client-certs中,mycert.crt和mycert.key放入volume/server-certs中然后服务器端重新运行server.py即可。
Task3 一个简单的HTTPS代理
TLS可以抵御中间人攻击,但前提是基础公钥基础设施是安全的。在本任务中,我们将演示在PKI基础设施受损(即某些受信任的CA受损或服务器的私钥被盗)的情况下,针对TLS服务器的中间人攻击。
我们将实现一个名为mHTTPSproxy
(m代表mini)的简单HTTPS代理。代理程序只是将任务1和任务2中的客户端和服务器程序集成在一起。其工作原理如图所示。我们将在代理容器上运行代理。
proxy只是一个服务器程序,它从浏览器(客户端)接收HTTP请求,并向其返回HTTP响应。代理不生成任何HTTP响应;相反,它将HTTP请求转发到实际的web服务器,然后从web服务器获取HTTP响应。对于实际的web服务器,TLS代理只是一个客户端程序。得到响应后,代理将响应转发给浏览器,即真正的客户端。
此任务的目的是使用此简单代理来了解当PKI基础设施受到破坏时,中间人攻击是如何工作的。
3.1 对自己的服务器进行中间人攻击
本任务中,使用docker容器(ip:10.9.0.143)作为Proxy代理,docker容器(ip:10.9.0.5)作为server端,主虚机作为客户端。
在主虚机的/etc/hosts加入地址映射:
10.9.0.143 www.qzs2022.com
在Proxy容器中修改/etc/resolv.conf,将代理的nameserver修改为8.8.8.8以使其不受主虚机的域名解析影响。
在Proxy容器的/etc/hosts加入地址映射,使得Proxy容器可以将www.qzs2022.com域名定位到10.9.0.5地址:
10.9.0.5 www.qzs2022.com
在proxy.py文件中配置服务器与客户端的证书、hostname,并在Proxy容器中运行:
在docker容器(ip:10.9.0.5)中运行server.py,然后在主虚机的浏览器中访问域名www.qzs2022.com,得到以下页面:
server端打印出以下信息:
我们仍然能正常和服务端交流,但是所有信息都会经由代理进行转发并且显示出来:
3.2 对真实网站进行攻击
本任务中,使用docker容器(ip:10.9.0.143)作为Proxy代理,主虚机作为客户端。首先在主虚机的/etc/hosts中将10.9.0.143 csujwc.its.csu.edu.cn
加入地址映射。
在Proxy容器中修改/etc/resolv.conf,将代理的nameserver修改为8.8.8.8以使其不受主虚机的域名解析影响。
我们利用任务二的方法为教务系统网址csujwc.its.csu.edu.cn生成证书。
在Proxy容器中运行./proxy.py
,然后在主虚机的浏览器访问教务系统网址:
可以看到,所有HTTP请求都将经过代理进行转发,Proxy容器中可以打印出所有的请求信息。