网络安全第一次实验


Lab1 数据包嗅探与欺骗

实验原理

img

Task1

使用scapy

image-20220929110057393

ifconfig可知本机ip地址为10.9.0.1

Task1.A 嗅探数据包

image-20220929114149399

执行ping:

image-20220929120618127

sniff到了icmp报文:

image-20220929120715827

sniffer.py

  1. filter:过滤规则,这里过滤的是icmp包
  2. prn:为每个数据包定义一个回调函数
#!/usr/bin/python3
from scapy.all import *

def print_pkt(pkt):
    pkt.show()

pkt = sniff(filter="icmp",prn=print_pkt)

ping百度官网:

image-20221005201950441

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)

image-20220929122025101

使用WireShark抓包:

image-20220929122220354

添加ls(ip)语句:

image-20220929122424924

image-20220929122501553

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函数返回两个参数,分别是得到响应的数据包和未响应的数据包。

image-20221013222144939

image-20220929122915332

Task1.D 先Sniffing后Spoofing

我们抓取icmp协议的数据包,然后对数据包进行改造后发回给源地址实现数据包嗅探和欺骗。

ttl=64为我们伪造的回复包,ttl=128为正常的相应包。

image-20221005170949415

image-20221005171529451

我们在另一台虚拟机终端上ping一个不存在的ip地址1.2.3.4,就会发现竟然得到了回显,即我们运行程序的终端在进行嗅探和欺骗。

image-20221005170902195

Task2

Libpcap库主要函数说明

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()函数获取错误消息。

image-20221005205058296

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

image-20221005213231037

伪造ICMP请求

gcc -o spoof2 spoof.c task2.c checksum.c -lpcap

image-20221005215915441

image-20221005215929948

Task2.C 嗅探&欺骗

准备两个在同一个局域网的虚拟机,任务中一台虚拟机将同时嗅探和伪造包,实现一个机器ping任意IP x,另一个机器伪造ICMP回复请求,使得其有回复,而IP x所对应的机器可能根本不存在。

image-20221005221640676

image-20221005221612075

Lab2 公钥基础设施PKI

实验原理

实验场景:虚拟机seedlab ubuntu20.04,及内置docker容器。

实验原理:

在计算机网络上,OpenSSL是一个开放源代码的软件库包,应用程序可以使用这个包来进行安全通信,避免窃听,同时确认另一端连接者的身份。这个包广泛被应用在互联网的网页服务器上。

PKI基本结构由证书认证机构(certificate authority, CA)、证书持有者(certificate holder)、依赖方(relying party)三方构成。实验主要包含三大部分,示意图如下所示:

(1)证书的生成

img

(2)证书的验证

img

(3)中间人攻击

假设Alice希望通过HTTPS协议访问example.com。她需要从example.com服务器获取公钥;Alice将生成一个秘密,并使用服务器的公钥加密该秘密,然后将其发送到服务器。如果攻击者能够拦截Alice和服务器之间的通信,则攻击者可以用自己的公钥替换服务器的公钥。因此,Alice的秘密实际上是用攻击者的公钥加密的,因此攻击者将能够读取该秘密。攻击者可以使用服务器的公钥将机密转发给服务器。该秘密用于加密Alice和服务器之间的通信,因此攻击者可以解密加密的通信。

img

实验步骤

配置实验环境

image-20221006101307005

Task1 生成CA

将/usr/lib/ssl目录下的openssl.cnf文件复制到当前实验文件夹lab-pki中,并对其进行修改,将” #unique_subject = no”注释取消。然后在lab-pki中创建demoCA文件夹并在其中创建以下目录和文件:certs、crl、newcerts、index.txt(空文档)、serial(文档,内写有字符“1000”)。

image-20221006103459603

openssl req -x509 -newkey rsa:4096 -sha256 -days 3650 -keyout ca.key -out ca.crt

image-20221006103805755

存储在两个文件中: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

image-20221006104150515

image-20221006104331494

image-20221006104348337

我们可以在证书签名请求中添加两个备用名称:

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

image-20221006105014276

证书签名后,使用以下命令打印证书的解码内容,并检查是否包含替代名称。

openssl x509 -in server.crt -text -noout

在我们的容器中:

image-20221006110318157

通过docker容器和虚拟机之间的共享文件夹volumes,我们可以在SSLCertificateFile和SSLCertificateKeyFile后面写上的目录中放入我们之前生成的证书和密钥。即:server.crt,server.key

image-20221006174207655

a2enmod ssl // 启用ssl模块
a2ensite bank32_apache_ssl // 启用此文件中描述的站点
service apache2 restart // 重启服务器

image-20221006174124010

共享文件夹使用:

虚拟机

image-20221006113338312

docker容器

image-20221006113357164

server.crtserver.key 复制进 certs 中,并修改 Dockerfile

image-20221006113715270

123

image-20221006174226363

Task4 发起中间人攻击

在/etc/hosts文件中加入:10.9.0.80 www.csujwc.its.csu.edu.cn

image-20221006204100866

将网站的html放至/var/www目录中:

image-20221006205550794

由于无法得到csujwc.its.csu.edu.cn的有效证书,所以我们将使用与我们服务器相同的证书。

image-20221006225909010

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 缓存定位攻击的结果。

image-20221006225831025

Task5 使用受损CA发起中间人攻击

在此任务中,我们假设在前面创建的根 CA 被攻击者破坏,并且其私钥被盗。因此,攻击者可以使用该CA 的私钥生成任意证书。在这项任务中,我们将看到这种妥协的后果。

请设计一个实验,证明攻击者可以成功地对任何 HTTPS 网站发起 MITM 攻击。您可以使用在任务5 中创建的相同设置,但这一次,您需要证明 MITM 攻击成功,即当受害者尝试访问网站时,浏览器不会引起任何怀疑,但登陆 MITM 攻击者的假冒网站。

以同样的步骤为www.csujwc.its.csu.edu.cn生成证书csu.crt:

image-20221006233908522

配置apache文件:

image-20221006233800236

image-20221006233041547

可以看到:重启docker容器的Apache服务器,在虚拟机浏览器中访问网址https://www.csujwc.its.csu.edu.cn/,即可正常浏览。

image-20221006234011517

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()

image-20221007162832371

ECDHE-RSA-AES128-GCM-SHA256,每个参数的意思为:

img

1.2 CA证书

观察到GlobalSign有关的证书,在/etc/ssl/certs中查找到相关的证书,并将其拷贝到client-certs文件夹中:

image-20221007163042218

image-20221007163049397

获得证书的哈希值:

openssl x509 -in GlobalSign_Root_CA.pem -noout -subject_hash

得到5ad8a5d6,并进行链接:

ln -s GlobalSign_Root_CA.pem 5ad8a5d6.0

image-20221007163244458

我们修改handshake.py文件:

image-20221007163443912

可以成功进行TLS握手:

image-20221007163517209

1.3 主机名检查

handshake.py文件中的cadir 更换回 ’/etc/ssl/certs’。在任意位置打开终端,输入命令:

dig www.example.com

我们获得www.example.com的ip地址93.184.216.34:

ip地址93.184.216.34

然后我们在/etc文件夹中打开终端,输入命令:

vim hosts

修改hosts文件,在其末尾加入93.184.216.34 www.example2020.com这一条数据

image-20221007164254294

然后esc,输入:wq!保存退出hosts文件。将handshake.py文件中的context.check_hostname = True一句True更改为False,即取消域名检查。再在lab3文件夹中执行命令:

sudo ./handshake.py www.example2020.com

发现连接成功:

image-20221007164737724

如果我们将handshake.py文件中的域名检查命令更改为真,再次执行命令,将发现连接失败,原因是域名不匹配

image-20221007164818169

修改handshake.py文件,在关闭连接的代码前添加http发送和接受的代码:

image-20221007165108547

保存后执行,连接任意网站,即可获得服务器的response报文。

image-20221007165217410

Task2 TLS服务器

2.1 实现一个简单的TLS服务器

在docker容器中运行server.py文件即可模拟TLS服务器,需要注意的是,我们还需要生成服务器对应的证书和密钥,使用PKI实验中的命令,我们得到了qzs.crt和qzs.key,本实验中使用的域名为:

www.qzs2022.com

image-20221007172959533

将这两个文件复制,放入lab3/labsetup/volumes/server_certs中,将其对应的CA证书ca.crt放入lab-tls/labsetup/volumes/client_certs中。

image-20221007174223539

image-20221007174212323

使用openssl命令获取hash之后在client-certs中建立软链接

image-20221007193312800

然后我们修改/etc/hosts文件,向其中添加docker容器ip和域名的映射关系。

我们将容器作为server,宿主机作为client,在client中添加下列地址映射:

image-20221007180609364

在server端运行.server.pyserver.py定义了html页面、服务器证书、服务器私钥,TLS协议目录,利用socket通信机制,打开443端口并进行侦听,进入等待TLS连接请求状态;当有客户端发送连接请求时,包装现有套接字与SSL层协议,建立TLS连接。若连接失败,则报TLS连接异常。

image-20221007200011472

在lab3/labsetup/volumes中打开终端输入命令:

sudo ./handshake.py www.qzs2022.com

即可获得连接成功的回显信息。

image-20221007193355489

image-20221007193414910

注:服务器重启需要:

netstat -tunlp

并kill进程:kill -9 19(19为PID)

2.2 使用浏览器测试服务器程序

在浏览器的about:preferences#privacy页面中找到证书选项,添加我们的CA证书ca.crt,然后访问我们的域名https://www.qzs2022.com/即可看到我们的页面信息:

image-20221007200118872

2.3 具有多个名称的证书

首先创建server_openssl.cnf文件,内容如下:

image-20221007200613862

输入以下命令创建服务器签名申请和私钥:

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

image-20221007211451122

client端加入地址映射:

image-20221007210135874

将ca.crt放入lab3文件夹下的volume/client-certs中,mycert.crt和mycert.key放入volume/server-certs中然后服务器端重新运行server.py即可。

image-20221007211351865

Task3 一个简单的HTTPS代理

TLS可以抵御中间人攻击,但前提是基础公钥基础设施是安全的。在本任务中,我们将演示在PKI基础设施受损(即某些受信任的CA受损或服务器的私钥被盗)的情况下,针对TLS服务器的中间人攻击。

我们将实现一个名为mHTTPSproxy(m代表mini)的简单HTTPS代理。代理程序只是将任务1和任务2中的客户端和服务器程序集成在一起。其工作原理如图所示。我们将在代理容器上运行代理。

image-20221007212444268

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以使其不受主虚机的域名解析影响。

image-20221008113750395

在Proxy容器的/etc/hosts加入地址映射,使得Proxy容器可以将www.qzs2022.com域名定位到10.9.0.5地址:

10.9.0.5   www.qzs2022.com

image-20221008130821448

在proxy.py文件中配置服务器与客户端的证书、hostname,并在Proxy容器中运行:

image-20221008130932053

在docker容器(ip:10.9.0.5)中运行server.py,然后在主虚机的浏览器中访问域名www.qzs2022.com,得到以下页面:

image-20221008131144012

server端打印出以下信息:

image-20221008131300995

我们仍然能正常和服务端交流,但是所有信息都会经由代理进行转发并且显示出来:

image-20221008131315648

3.2 对真实网站进行攻击

本任务中,使用docker容器(ip:10.9.0.143)作为Proxy代理,主虚机作为客户端。首先在主虚机的/etc/hosts中将10.9.0.143 csujwc.its.csu.edu.cn加入地址映射。

image-20221008113521555

在Proxy容器中修改/etc/resolv.conf,将代理的nameserver修改为8.8.8.8以使其不受主虚机的域名解析影响。

image-20221008113750395

我们利用任务二的方法为教务系统网址csujwc.its.csu.edu.cn生成证书。

在Proxy容器中运行./proxy.py,然后在主虚机的浏览器访问教务系统网址:

可以看到,所有HTTP请求都将经过代理进行转发,Proxy容器中可以打印出所有的请求信息。

image-20221008114156471


文章作者: ShiQuLiZhi
版权声明: 本博客所有文章除特别声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 ShiQuLiZhi !
评论
  目录