Skip to content

Latest commit

 

History

History
867 lines (663 loc) · 41.9 KB

File metadata and controls

867 lines (663 loc) · 41.9 KB

六、使用 Python 实现网络安全

在我看来,网络安全是一个棘手的话题。原因不是技术上的,而是与设置正确的范围有关。网络安全的边界如此之宽,以至于它们触及 OSI 模型的所有七层。从窃听的第 1 层到传输协议漏洞的第 4 层,再到中间人欺骗的第 7 层,网络安全无处不在。所有新发现的漏洞都加剧了这一问题,这些漏洞有时似乎每天都有。这甚至不包括网络安全的人类社会工程方面。

因此,在本章中,我想为我们将要讨论的内容确定范围。正如我们到目前为止所做的那样,我们将主要关注在 OSI 第 3 层和第 4 层使用 Python 实现网络设备安全。我们将研究 Python 工具,这些工具可用于出于安全目的管理单个网络设备,以及使用 Python 作为连接不同组件的粘合剂。希望通过在不同的 OSI 层中使用 Python,我们能够从整体的角度看待网络安全。

在本章中,我们将了解以下主题:

  • 实验室设置
  • 用于安全测试的 Python Scapy
  • 访问列表
  • 使用 Python 使用 Syslog 和 UFW 进行法医学分析
  • 其他工具,如 MAC 地址筛选器列表、专用 VLAN 和 Python IP 表绑定

实验室设置

本章中使用的设备与前几章略有不同。在前面的章节中,我们通过关注手头的主题来隔离特定的设备。在本章中,我们将在实验室中使用更多的设备,以说明我们将使用的工具的功能。连接性和操作系统信息非常重要,因为它们与我们将在本章后面介绍的安全工具有关。例如,如果我们想应用访问列表来保护服务器,我们需要知道拓扑结构是什么样子,以及客户端从哪个方向进行连接。Ubuntu 主机连接与我们目前看到的有点不同,因此如果需要,请在稍后看到示例时参考本实验部分

我们将使用相同的 Cisco VIRL 工具和四个节点:两个主机和两个网络设备。如果您需要更新 Cisco VIRL,请随时返回第 2 章低层网络设备交互中我们首次介绍该工具的地方:

Lab topology The IP addresses listed will be different in your own lab. They are listed here for an easy reference for the rest of the chapter.

如图所示,我们将顶部的主机重命名为客户机,底部的主机重命名为服务器。这类似于 internet 客户端试图访问我们网络中的公司服务器。我们将再次使用管理网络的共享平面网络选项来访问用于带外管理的设备:

对于这两个交换机,我将选择先开放最短路径OSPF作为IGP,并将两个设备放在0区域。默认情况下,BGP处于打开状态,两个设备都作为 1 使用。从配置自动生成开始,连接到 Ubuntu 主机的接口被放入 OSPF 区域1,因此它们将显示为区域间路由。此处显示了 NX OSv 配置,IOSv 配置和输出类似:

 interface Ethernet2/1
 description to iosv-1
 no switchport
 mac-address fa16.3e00.0001
 ip address 10.0.0.6/30
 ip router ospf 1 area 0.0.0.0
 no shutdown

 interface Ethernet2/2
 description to Client
 no switchport
 mac-address fa16.3e00.0002
 ip address 10.0.0.9/30
 ip router ospf 1 area 0.0.0.0
 no shutdown

 nx-osv-1# sh ip route
 <skip>
 10.0.0.12/30, ubest/mbest: 1/0
 *via 10.0.0.5, Eth2/1, [110/41], 04:53:02, ospf-1, intra
 192.168.0.2/32, ubest/mbest: 1/0
 *via 10.0.0.5, Eth2/1, [110/41], 04:53:02, ospf-1, intra
 <skip>

此处显示了 NX OSv 的 OSPF 邻居和 BGP 输出,IOSv 输出类似:

nx-osv-1# sh ip ospf neighbors
 OSPF Process ID 1 VRF default
 Total number of neighbors: 1
 Neighbor ID Pri State Up Time Address Interface
 192.168.0.2 1 FULL/DR 04:53:00 10.0.0.5 Eth2/1

nx-osv-1# sh ip bgp summary
BGP summary information for VRF default, address family IPv4 Unicast
BGP router identifier 192.168.0.1, local AS number 1
BGP table version is 5, IPv4 Unicast config peers 1, capable peers 1
2 network entries and 2 paths using 288 bytes of memory
BGP attribute entries [2/288], BGP AS path entries [0/0]
BGP community entries [0/0], BGP clusterlist entries [0/0]

Neighbor V AS MsgRcvd MsgSent TblVer InQ OutQ Up/Down State/PfxRcd
192.168.0.2 4 1 321 297 5 0 0 04:52:56 1

我们网络中的主机运行的是 Ubuntu 14.04,类似于我们目前使用的 Ubuntu VM 16.04:

cisco@Server:~$ lsb_release -a
No LSB modules are available.
Distributor ID: Ubuntu
Description: Ubuntu 14.04.2 LTS
Release: 14.04
Codename: trusty

在两台 Ubuntu 主机上都有两个网络接口,eth0eth1eth0连接到管理网络172.16.1.0/24,而eth1连接到网络设备10.0.0.x/30。到设备环回的路由直接连接到网络块,远程主机网络静态路由到eth1,默认路由到管理网络:

cisco@Client:~$ route -n
Kernel IP routing table
Destination Gateway Genmask Flags Metric Ref Use Iface
0.0.0.0 172.16.1.2 0.0.0.0 UG 0 0 0 eth0
10.0.0.4 10.0.0.9 255.255.255.252 UG 0 0 0 eth1
10.0.0.8 0.0.0.0 255.255.255.252 U 0 0 0 eth1
10.0.0.8 10.0.0.9 255.255.255.248 UG 0 0 0 eth1
172.16.1.0 0.0.0.0 255.255.255.0 U 0 0 0 eth0
192.168.0.1 10.0.0.9 255.255.255.255 UGH 0 0 0 eth1
192.168.0.2 10.0.0.9 255.255.255.255 UGH 0 0 0 eth1

要验证客户端到服务器的路径,让我们 ping 并跟踪路由,以确保主机之间的通信通过网络设备,而不是默认路由:

## Our server IP is 10.0.0.14 cisco@Server:~$ ifconfig
<skip>
eth1 Link encap:Ethernet HWaddr fa:16:3e:d6:83:02
 inet addr:10.0.0.14 Bcast:10.0.0.15 Mask:255.255.255.252

## From the client ping toward server
cisco@Client:~$ ping -c 1 10.0.0.14
PING 10.0.0.14 (10.0.0.14) 56(84) bytes of data.
64 bytes from 10.0.0.14: icmp_seq=1 ttl=62 time=6.22 ms

--- 10.0.0.14 ping statistics ---
1 packets transmitted, 1 received, 0% packet loss, time 0ms
rtt min/avg/max/mdev = 6.223/6.223/6.223/0.000 ms

## Traceroute from client to server
cisco@Client:~$ traceroute 10.0.0.14
traceroute to 10.0.0.14 (10.0.0.14), 30 hops max, 60 byte packets
 1 10.0.0.9 (10.0.0.9) 11.335 ms 11.745 ms 12.113 ms
 2 10.0.0.5 (10.0.0.5) 24.221 ms 41.635 ms 41.638 ms
 3 10.0.0.14 (10.0.0.14) 37.916 ms 38.275 ms 38.588 ms
cisco@Client:~$

伟大的我们有我们的实验室;现在,我们已经准备好使用 Python 查看一些安全工具和措施。

蟒蛇

Scapy(https://scapy.net 是一个功能强大的基于 Python 的交互式数据包制作程序。据我所知,除了一些昂贵的商业程序外,几乎没有什么工具能像 Scapy 那样做。它是 Python 中我最喜欢的工具之一

Scapy 的主要优点是,它允许您从最基本的层面上设计自己的数据包。用斯卡皮的创造者的话说:

"Scapy is a powerful interactive packet manipulation program. It is able to forge or decode packets of a wide number of protocols, send them on the wire, capture them, match requests and replies, and much more.... with most other tools, you won't build something the author did not imagine. These tools have been built for a specific goal and can't deviate much from it." 

让我们看看这个工具。

安装 Scapy

在撰写本文时,scapy2.3.1 支持 python2.7。不幸的是,有一些关于 Python 3 对 Scapy 的支持的错误,对于 Scapy 2.3.3 来说,它仍然是相对较新的。对于您的环境,请随时试用版本为 2.3.3 及更高版本的 Python 3。在本章中,我们将在 Python 2.7 中使用 Scapy 2.3.1。如果您想了解更多有关选择背后的原因,请参阅信息侧栏

The long story for Python 3 support in Scapy is that there was an independent fork of Scapy from version 2.2.0 in 2015, aimed at supporting only Python 3. The project was named Scapy3k. The fork diverged from the main Scapy code base. If you read the first edition of this book, that was the information provided at the time of writing. There were confusions surrounding python3-scapy on PyPI and the official support of the Scapy code base. Our main purpose was to learn about Scapy in this chapter, and so therefore I made the choice to use an older, Python 2-based Scapy version. 

在我们的实验室中,由于我们正在制作从客户端到目标服务器的数据包源,因此需要在客户端上安装 Scapy:

cisco@Client:~$ sudo apt-get update
cisco@Client:~$ sudo apt-get install git
cisco@Client:~$ git clone https://github.com/secdev/scapy
cisco@Client:~$ cd scapy/
cisco@Client:~/scapy$ sudo python setup.py install

下面是一个快速测试,以确保软件包已正确安装:

cisco@Client:~/scapy$ python
Python 2.7.6 (default, Mar 22 2014, 22:59:56)
[GCC 4.8.2] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> from scapy.all import *

交互式示例

在我们的第一个示例中,我们将在客户端上创建一个互联网控制消息协议ICMP)数据包,并将其发送到服务器。在服务器端,我们将使用tcpdump和主机过滤器来查看数据包的传入:

## Client Side
cisco@Client:~/scapy$ sudo scapy
<skip>
Welcome to Scapy (2.3.3.dev274)
>>> send(IP(dst="10.0.0.14")/ICMP())
.
Sent 1 packets.
>>>

## Server Side
cisco@Server:~$ sudo tcpdump -i eth1 host 10.0.0.10
tcpdump: verbose output suppressed, use -v or -vv for full protocol decode
listening on eth1, link-type EN10MB (Ethernet), capture size 65535 bytes
02:45:16.400162 IP 10.0.0.10 > 10.0.0.14: ICMP echo request, id 0, seq 0, length 8
02:45:16.400192 IP 10.0.0.14 > 10.0.0.10: ICMP echo reply, id 0, seq 0, length 8

正如您所见,从 Scapy 制作一个包非常简单。Scapy 允许您使用斜杠(/作为分隔符逐层构建数据包。send功能在第三层运行,为您处理路由和第二层。还有一个在第 2 层运行的sendp()替代方案,这意味着您需要指定接口和链路层协议。

让我们看看使用发送请求(sr函数捕获返回的数据包。我们使用的是一种特殊的sr变体,称为sr1,它只返回一个对发送的数据包进行应答的数据包:

>>> p = sr1(IP(dst="10.0.0.14")/ICMP())
>>> p
<IP version=4L ihl=5L tos=0x0 len=28 id=26713 flags= frag=0L ttl=62 proto=icmp chksum=0x71 src=10.0.0.14 dst=10.0.0.10 options=[] |<ICMP type=echo-reply code=0 chksum=0xffff id=0x0 seq=0x0 |>>

需要注意的是,sr()函数本身返回一个元组,其中包含已应答和未应答列表:

>>> p = sr(IP(dst="10.0.0.14")/ICMP()) 
>>> type(p)
<type 'tuple'>

## unpacking
>>> ans,unans = sr(IP(dst="10.0.0.14")/ICMP())
>>> type(ans)
<class 'scapy.plist.SndRcvList'>
>>> type(unans)
<class 'scapy.plist.PacketList'>

如果我们只查看已应答的数据包列表,我们可以看到它是另一个元组,包含我们已发送的数据包以及返回的数据包:

>>> for i in ans:
...     print(type(i))
...
<type 'tuple'>
>>> for i in ans:
...     print i
...
(<IP frag=0 proto=icmp dst=10.0.0.14 |<ICMP |>>, <IP version=4L ihl=5L tos=0x0 len=28 id=27062 flags= frag=0L ttl=62 proto=icmp chksum=0xff13 src=10.0.0.14 dst=10.0.0.10 options=[] |<ICMP type=echo-reply code=0 chksum=0xffff id=0x0 seq=0x0 |>>)

Scapy 还提供了第 7 层构造,比如一个DNS查询。在下面的示例中,我们正在查询一个开放的 DNS 服务器以获取www.google.com的解析:

>>> p = sr1(IP(dst="8.8.8.8")/UDP()/DNS(rd=1,qd=DNSQR(qname="www.google.com")))
>>> p
<IP version=4L ihl=5L tos=0x0 len=76 id=21743 flags= frag=0L ttl=128 proto=udp chksum=0x27fa src=8.8.8.8 dst=172.16.1.152 options=[] |<UDP sport=domain dport=domain len=56 chksum=0xc077 |<DNS id=0 qr=1L opcode=QUERY aa=0L tc=0L rd=1L ra=1L z=0L ad=0L cd=0L rcode=ok qdcount=1 ancount=1 nscount=0 arcount=0 qd=<DNSQR qname='www.google.com.' qtype=A qclass=IN |> an=<DNSRR rrname='www.google.com.' type=A rclass=IN ttl=299 rdata='172.217.3.164' |> ns=None ar=None |>>>
>>>

Scapy 还可用于轻松捕获线路上的数据包:

>>> a = sniff(filter="icmp and host 172.217.3.164", count=5)
>>> a.show()
0000 Ether / IP / TCP 192.168.225.146:ssh > 192.168.225.1:50862 PA / Raw
0001 Ether / IP / ICMP 192.168.225.146 > 172.217.3.164 echo-request 0 / Raw
0002 Ether / IP / ICMP 172.217.3.164 > 192.168.225.146 echo-reply 0 / Raw
0003 Ether / IP / ICMP 192.168.225.146 > 172.217.3.164 echo-request 0 / Raw
0004 Ether / IP / ICMP 172.217.3.164 > 192.168.225.146 echo-reply 0 / Raw
>>>

我们可以更详细地查看数据包,包括原始格式:

>>> for i in a:
...     print i.show()
...
<skip>
###[ Ethernet ]###
 dst= <>
 src= <>
 type= 0x800
###[ IP ]###
 version= 4L
 ihl= 5L
 tos= 0x0
 len= 84
 id= 15714
 flags= DF
 frag= 0L
 ttl= 64
 proto= icmp
 chksum= 0xaa8e
 src= 192.168.225.146
 dst= 172.217.3.164
 options
###[ ICMP ]###
 type= echo-request
 code= 0
 chksum= 0xe1cf
 id= 0xaa67
 seq= 0x1
###[ Raw ]###
 load= 'xd6xbfxb1Xx00x00x00x00x1axdcnx00x00x00x00x00x10x11x12x13x14x15x16x17x18x19x1ax1bx1cx1dx1ex1f !"#$%&'()*+,-./01234567'
None

我们已经看到了 Scapy 的基本工作原理。让我们继续,看看如何使用 Scapy 进行一些常见的安全测试。

TCP 端口扫描

对于任何潜在的黑客来说,第一步几乎总是试图了解网络上哪些服务是开放的,这样他们就可以集中精力进行攻击。当然,为了服务我们的客户,我们需要打开某些端口;这是我们需要接受的风险的一部分。但我们也应该关闭任何其他不必要地暴露更大攻击面的开放端口。我们可以使用 Scapy 做一个简单的 TCP 开放端口扫描来扫描我们自己的主机

我们可以发送一个SYN包,看看服务器是否会返回SYN-ACK

>>> p = sr1(IP(dst="10.0.0.14")/TCP(sport=666,dport=23,flags="S"))
>>> p.show()
###[ IP ]###
 version= 4L
 ihl= 5L
 tos= 0x0
 len= 40
 id= 25373
 flags= DF
 frag= 0L
 ttl= 62
 proto= tcp
 chksum= 0xc59b
 src= 10.0.0.14
 dst= 10.0.0.10
 options
###[ TCP ]###
 sport= telnet
 dport= 666
 seq= 0
 ack= 1
 dataofs= 5L
 reserved= 0L
 flags= RA
 window= 0
 chksum= 0x9907
 urgptr= 0
 options= {}

注意,在这里的输出中,服务器对 TCP 端口23响应为RESET+ACK。但是,TCP 端口22(SSH)是开放的;因此,返回一个SYN-ACK

>>> p = sr1(IP(dst="10.0.0.14")/TCP(sport=666,dport=22,flags="S"))
>>> p.show()
###[ IP ]###
 version= 4L
<skip>
 proto= tcp
 chksum= 0x28b5
 src= 10.0.0.14
 dst= 10.0.0.10
 options
###[ TCP ]###
 sport= ssh
 dport= 666
<skip>
 flags= SA
<skip>

我们还可以扫描从2022的一系列目标端口;请注意,我们使用sr()进行发送-接收,而不是sr1()发送-接收一个数据包变体:

>>> ans,unans = sr(IP(dst="10.0.0.14")/TCP(sport=666,dport=(20,22),flags="S"))
>>> for i in ans:
...     print i
...
(<IP frag=0 proto=tcp dst=10.0.0.14 |<TCP sport=666 dport=ftp_data flags=S |>>, <IP version=4L ihl=5L tos=0x0 len=40 id=4126 flags=DF frag=0L ttl=62 proto=tcp chksum=0x189b src=10.0.0.14 dst=10.0.0.10 options=[] |<TCP sport=ftp_data dport=666 seq=0 ack=1 dataofs=5L reserved=0L flags=RA window=0 chksum=0x990a urgptr=0 |>>)
(<IP frag=0 proto=tcp dst=10.0.0.14 |<TCP sport=666 dport=ftp flags=S |>>, <IP version=4L ihl=5L tos=0x0 len=40 id=4127 flags=DF frag=0L ttl=62 proto=tcp chksum=0x189a src=10.0.0.14 dst=10.0.0.10 options=[] |<TCP sport=ftp dport=666 seq=0 ack=1 dataofs=5L reserved=0L flags=RA window=0 chksum=0x9909 urgptr=0 |>>)
(<IP frag=0 proto=tcp dst=10.0.0.14 |<TCP sport=666 dport=ssh flags=S |>>, <IP version=4L ihl=5L tos=0x0 len=44 id=0 flags=DF frag=0L ttl=62 proto=tcp chksum=0x28b5 src=10.0.0.14 dst=10.0.0.10 options=[] |<TCP sport=ssh dport=666 seq=4187384571 ack=1 dataofs=6L reserved=0L flags=SA window=29200 chksum=0xaaab urgptr=0 options=[('MSS', 1460)] |>>)
>>>

我们还可以指定目标网络,而不是单个主机。从10.0.0.8/29块可以看到,主机10.0.0.910.0.0.1310.0.0.14返回了SA,对应于两个网络设备和主机:

>>> ans,unans = sr(IP(dst="10.0.0.8/29")/TCP(sport=666,dport=(22),flags="S"))
>>> for i in ans:
...     print(i)
...
(<IP frag=0 proto=tcp dst=10.0.0.9 |<TCP sport=666 dport=ssh flags=S |>>, <IP version=4L ihl=5L tos=0x0 len=44 id=7304 flags= frag=0L ttl=64 proto=tcp chksum=0x4a32 src=10.0.0.9 dst=10.0.0.10 options=[] |<TCP sport=ssh dport=666 seq=541401209 ack=1 dataofs=6L reserved=0L flags=SA window=17292 chksum=0xfd18 urgptr=0 options=[('MSS', 1444)] |>>)
(<IP frag=0 proto=tcp dst=10.0.0.14 |<TCP sport=666 dport=ssh flags=S |>>, <IP version=4L ihl=5L tos=0x0 len=44 id=0 flags=DF frag=0L ttl=62 proto=tcp chksum=0x28b5 src=10.0.0.14 dst=10.0.0.10 options=[] |<TCP sport=ssh dport=666 seq=4222593330 ack=1 dataofs=6L reserved=0L flags=SA window=29200 chksum=0x6a5b urgptr=0 options=[('MSS', 1460)] |>>)
(<IP frag=0 proto=tcp dst=10.0.0.13 |<TCP sport=666 dport=ssh flags=S |>>, <IP version=4L ihl=5L tos=0x0 len=44 id=41992 flags= frag=0L ttl=254 proto=tcp chksum=0x4ad src=10.0.0.13 dst=10.0.0.10 options=[] |<TCP sport=ssh dport=666 seq=2167267659 ack=1 dataofs=6L reserved=0L flags=SA window=4128 chksum=0x1252 urgptr=0 options=[('MSS', 536)] |>>)

根据我们到目前为止学到的知识,我们可以制作一个简单的可重用脚本,scapy_tcp_scan_1.py。我们从建议导入的scapysys模块开始,以接受参数:

  #!/usr/bin/env python2

  from scapy.all import *
  import sys

tcp_scan()功能与我们目前看到的类似:

  def tcp_scan(destination, dport):
      ans, unans = sr(IP(dst=destination)/TCP(sport=666,dport=dport,flags="S"))
      for sending, returned in ans:
          if 'SA' in str(returned[TCP].flags):
              return destination + " port " + str(sending[TCP].dport) + " is open"
          else:
              return destination + " port " + str(sending[TCP].dport) + " is not open"

然后我们可以从参数中获取输入,然后调用main()中的tcp_scan()函数:

  def main():
      destination = sys.argv[1]
      port = int(sys.argv[2])
      scan_result = tcp_scan(destination, port)
      print(scan_result)

  if __name__ == "__main__":
      main()

记住,访问低级网络需要根访问;因此,我们的脚本需要按sudo执行:

cisco@Client:~$ sudo python scapy_tcp_scan_1.py "10.0.0.14" 23
<skip>
10.0.0.14 port 23 is not open
cisco@Client:~$ sudo python scapy_tcp_scan_1.py "10.0.0.14" 22
<skip>
10.0.0.14 port 22 is open

这是一个相对较长的 TCP 扫描脚本示例,它演示了使用 Scapy 制作自己的数据包的能力。我们在交互式 shell 中测试了这些步骤,并用一个简单的脚本完成了使用。让我们再看一些 Scapy 用于安全测试的示例。

平集

假设我们的网络混合了 Windows、Unix 和 Linux 机器,用户添加了自己的自带设备BYOD);它们可能支持也可能不支持 ICMP ping。我们现在可以在scapy_ping_collection.py中为我们的网络构建一个包含三种常见 ping 的文件,即 ICMP、TCP 和 UDP ping

#!/usr/bin/env python2

from scapy.all import *

def icmp_ping(destination):
    # regular ICMP ping
    ans, unans = sr(IP(dst=destination)/ICMP())
    return ans

def tcp_ping(destination, dport):
    # TCP SYN Scan
    ans, unans = sr(IP(dst=destination)/TCP(dport=dport,flags="S"))
    return ans

def udp_ping(destination):
    # ICMP Port unreachable error from closed port
    ans, unans = sr(IP(dst=destination)/UDP(dport=0))
    return ans

在本例中,我们还将使用summary()sprintf()作为输出:

def answer_summary(answer_list):
 # example of lambda with pretty print
    answer_list.summary(lambda(s, r): r.sprintf("%IP.src% is alive"))

If you were wondering why there is a lambda in the preceding answer_summary() function, it is a way to create a small anonymous function. Basically, it is a function without a name. More information on it can be found at https://docs.python.org/3.5/tutorial/controlflow.html#lambda-expressions.

然后,我们可以在一个脚本中在网络上执行所有三种类型的 ping:

def main():
    print("** ICMP Ping **")
    ans = icmp_ping("10.0.0.13-14")
    answer_summary(ans)
    print("** TCP Ping **")
    ans = tcp_ping("10.0.0.13", 22)
    answer_summary(ans)
    print("** UDP Ping **")
    ans = udp_ping("10.0.0.13-14")
    answer_summary(ans)

if __name__ == "__main__":
    main()

在这一点上,希望您会同意我的观点,通过构建自己的数据包,您可以负责您想要运行的操作和测试类型。

常见攻击

在本例中,让我们看看如何构造数据包来执行一些经典攻击,例如死亡 Ping)https://en.wikipedia.org/wiki/Ping_of_death对地攻击https://en.wikipedia.org/wiki/Denial-of-service_attack 。这可能是您以前必须使用类似的商业软件支付的网络渗透测试。使用 Scapy,您可以在保持完全控制的同时进行测试,并在将来添加更多测试。

第一次攻击基本上是向目标主机发送假 IP 头,例如长度为 2 的 IP 版本 3:

def malformed_packet_attack(host):
    send(IP(dst=host, ihl=2, version=3)/ICMP()) 

ping_of_death_attack由负载大于 65535 字节的常规 ICMP 数据包组成:

def ping_of_death_attack(host):
    # https://en.wikipedia.org/wiki/Ping_of_death
    send(fragment(IP(dst=host)/ICMP()/("X"*60000)))

land_attack想要将客户端响应重定向回客户端本身,并耗尽主机的资源:

  def land_attack(host):
      # https://en.wikipedia.org/wiki/Denial-of-service_attack
      send(IP(src=host, dst=host)/TCP(sport=135,dport=135))

这些都是相当古老的漏洞或经典的攻击,现代操作系统不再容易受到它们的影响。对于我们的 Ubuntu14.04 主机,前面的任何一次攻击都不会使它崩溃。然而,随着越来越多的安全问题被发现,Scapy 是一个很好的工具,可以针对我们自己的网络和主机启动测试,而无需等待受影响的供应商为您提供验证工具。这对于 zero day(在未事先通知的情况下发布)攻击尤其如此,这种攻击在互联网上似乎越来越常见。

肩胛骨资源

在本章中,我们花了相当多的精力与 Scapy 合作。这部分是因为我个人对该工具的评价很高。我希望您同意我的观点,作为一名网络工程师,Scapy 是一个很好的工具,可以保存在您的工具集中。Scapy 最棒的一点是,它一直在与一个积极参与的用户社区一起开发。

I would highly recommend at least going through the Scapy tutorial at http://scapy.readthedocs.io/en/latest/usage.html#interactive-tutorial, as well as any of the documentation that is of interest to you.

访问列表

网络访问列表通常是抵御外部入侵和攻击的第一道防线。一般来说,路由器和交换机处理数据包的速度比服务器快得多,因为它们使用硬件,如三元内容寻址存储器TCAM)。他们不需要查看应用层信息,而只需检查第 3 层和第 4 层信息,并决定是否可以转发数据包。因此,我们通常使用网络设备访问列表作为保护网络资源的第一步。

根据经验,我们希望访问列表尽可能靠近源(客户机)。从本质上讲,我们也信任内部主机,而不信任网络边界之外的客户机。因此,访问列表通常位于面向外部的网络接口的入站方向。在我们的实验室场景中,这意味着我们将在 Ethernet2/2 上放置一个直接连接到客户端主机的入站访问列表。

如果您不确定访问列表的方向和位置,以下几点可能会有所帮助:

  • 从网络设备的角度考虑访问列表
  • 仅根据源 IP 和目标 IP 简化数据包,并以一台主机为例:
    • 在我们的实验室中,来自我们服务器的流量的源 IP 为10.0.0.14,目的 IP 为10.0.0.10
    • 来自客户端的流量的源 IP 为10.10.10.10,目的 IP 为10.0.0.14

显然,每个网络都是不同的,如何构建访问列表取决于服务器提供的服务。但作为入站边界访问列表,您应该执行以下操作:

  • 拒绝 RFC 3030 特殊用途地址源,如127.0.0.0/8
  • 拒绝 RFC1918 空间,如10.0.0.0/8
  • 拒绝我们自己的空间作为源 IP;在本例中,10.0.0.12/30
  • 允许将入站 TCP 端口22(SSH)和80(HTTP)连接到主机10.0.0.14
  • 否认一切

用 Ansible 实现访问列表

实现此访问列表的最简单方法是使用 Ansible。在最后两章中,我们已经介绍了 Ansible,但值得重复在本场景中使用 Ansible 的优点:

  • 更容易管理:对于长访问列表,我们可以利用include语句将其分解为更易于管理的部分。较小的部分可以由其他团队或服务所有者管理。
  • 幂等性:我们可以定期安排剧本,只做必要的修改。
  • 每个任务都是明确的:我们可以分离条目的构造,并将访问列表应用到适当的接口。
  • 可重用性:未来如果增加额外的外部接口,只需将设备添加到访问列表的设备列表中即可。
  • 可扩展:您会注意到,我们可以使用相同的剧本来构建访问列表,并将其应用到正确的界面。我们可以从小处着手,将来根据需要扩展到单独的剧本。

主机文件是相当标准的。为简单起见,我们将主机变量直接放在清单文件中:

[nxosv-devices]
nx-osv-1 ansible_host=172.16.1.155 ansible_username=cisco ansible_password=cisco

我们将暂时在剧本中声明变量:

---
- name: Configure Access List
  hosts: "nxosv-devices"
  gather_facts: false
  connection: local

  vars:
    cli:
      host: "{{ ansible_host }}"
      username: "{{ ansible_username }}"
      password: "{{ ansible_password }}"
      transport: cli

为了节省空间,我们将演示仅拒绝 RFC1918 空间。实现对 RFC 3030 和我们自己的空间的拒绝将与 RFC 1918 空间使用的步骤相同。请注意,我们没有在我们的剧本中否认10.0.0.0/8,因为我们的配置目前使用10.0.0.0网络进行寻址。当然,我们可以先执行单主机许可,然后在后面的条目中拒绝10.0.0.0/8,但在本例中,我们只是选择省略它:

tasks:
  - nxos_acl:
      name: border_inbound
      seq: 20
      action: deny
      proto: tcp
      src: 172.16.0.0/12
      dest: any
      log: enable
      state: present
      provider: "{{ cli }}"
  - nxos_acl:
      name: border_inbound
      seq: 40
      action: permit
      proto: tcp
      src: any
      dest: 10.0.0.14/32
      dest_port_op: eq
      dest_port1: 22
      state: present
      log: enable
      provider: "{{ cli }}"
  - nxos_acl:
      name: border_inbound
      seq: 50
      action: permit
      proto: tcp
      src: any
      dest: 10.0.0.14/32
      dest_port_op: eq
      dest_port1: 80
      state: present
      log: enable
      provider: "{{ cli }}"
  - nxos_acl:
      name: border_inbound
      seq: 60
      action: permit
      proto: tcp
      src: any
      dest: any
      state: present
      log: enable
      established: enable
      provider: "{{ cli }}"
  - nxos_acl:
      name: border_inbound
      seq: 1000
      action: deny
      proto: ip
      src: any
      dest: any
      state: present
      log: enable
      provider: "{{ cli }}"

请注意,我们允许从内部服务器建立的连接源返回。我们使用最后一个显式的deny ip any any语句作为高序列号(1000,因此我们可以在以后插入任何新条目。

然后,我们可以将访问列表应用到正确的界面:

- name: apply ingress acl to Ethernet 2/2
  nxos_acl_interface:
    name: border_inbound
    interface: Ethernet2/2
    direction: ingress
    state: present
    provider: "{{ cli }}"

The access list on VIRL NX-OSv is only supported on the management interface. You will see this warning: Warning: ACL may not behave as expected since only management interface is supported if you configure this ACL via the CLI. This warning is okay, as our purpose is only to demonstrate the configuration automation of the access list.

对于单个访问列表来说,这似乎是一个很大的工作。对于经验丰富的工程师来说,使用 Ansible 完成此任务所需的时间比登录设备和配置访问列表所需的时间更长。但是,请记住,该手册将来可以多次重复使用,因此从长远来看,它将为您节省时间。

根据我的经验,对于一个长的访问列表,一些条目将用于一个服务,一些条目将用于另一个服务,依此类推。访问列表往往会随着时间的推移而有机地增长,并且很难跟踪每个条目的来源和目的。事实上,我们可以将它们分开,这使得长访问列表的管理更加简单。

MAC 访问列表

如果您有 L2 环境或在以太网接口上使用非 IP 协议,您仍然可以使用 MAC 地址访问列表基于 MAC 地址允许或拒绝主机。这些步骤与 IP 访问列表类似,但匹配将基于 MAC 地址。回想一下,对于 MAC 地址或物理地址,前六个十六进制符号属于组织唯一标识符OUI)。因此,我们可以使用相同的访问列表匹配模式来拒绝某一组主机。

We are testing this on IOSv with the ios_config module. For older Ansible versions, the change will be pushed out every single time the playbook is executed. For newer Ansible versions, the control node will check for change first and only make changes when needed.

主机文件和 playbook 的顶部类似于 IP 访问列表;tasks部分使用不同的模块和参数:

<skip>
  tasks:
    - name: Deny Hosts with vendor id fa16.3e00.0000
      ios_config:
        lines:
          - access-list 700 deny fa16.3e00.0000 0000.00FF.FFFF
          - access-list 700 permit 0000.0000.0000 FFFF.FFFF.FFFF
        provider: "{{ cli }}"
    - name: Apply filter on bridge group 1
      ios_config:
        lines:
          - bridge-group 1
          - bridge-group 1 input-address-list 700
        parents:
          - interface GigabitEthernet0/1
        provider: "{{ cli }}"   

随着越来越多的虚拟网络变得流行,L3 信息有时对底层虚拟链路变得透明。在这些场景中,如果需要限制对这些链接的访问,MAC 访问列表将成为一个不错的选择。

系统日志搜索

有大量记录在案的网络安全漏洞发生在很长一段时间内。在这些缓慢的违规行为中,我们经常在日志中看到迹象和痕迹,表明存在可疑活动。这些可以在服务器和网络设备日志中找到。没有发现这些活动,不是因为缺乏信息,而是因为信息太多。我们所寻找的关键信息通常深藏在难以整理的信息堆中。

Besides Syslog, Uncomplicated Firewall (UFW) is another great source of log information for servers. It is a frontend to iptables, which is a server firewall. UFW makes managing firewall rules very simple and logs a good amount of information. See the Other tools section for more information on UFW.

在本节中,我们将尝试使用 Python 搜索 Syslog 文本,以检测我们正在寻找的活动。当然,我们将搜索的确切术语取决于我们使用的设备。例如,Cisco 提供了一个消息列表,以便在 Syslog 中查找任何访问列表冲突日志记录。可在获取 http://www.cisco.com/c/en/us/about/security-center/identify-incidents-via-syslog.html

For more understanding of access control list logging, go to http://www.cisco.com/c/en/us/about/security-center/access-control-list-logging.html.

在我们的练习中,我们将使用一个 Nexus switch 匿名系统日志文件,其中包含约 65000 行日志消息。该文件包含在为您提供的附带书籍 GitHub 存储库中:

$ wc -l sample_log_anonymized.log
65102 sample_log_anonymized.log

我们已经从 Cisco 文档(中插入了一些系统日志消息 http://www.cisco.com/c/en/us/support/docs/switches/nexus-7000-series-switches/118907-configure-nx7k-00.html )作为我们应该寻找的日志消息:

2014 Jun 29 19:20:57 Nexus-7000 %VSHD-5-VSHD_SYSLOG_CONFIG_I: Configured from vty by admin on console0
2014 Jun 29 19:21:18 Nexus-7000 %ACLLOG-5-ACLLOG_FLOW_INTERVAL: Src IP: 10.1 0.10.1,
 Dst IP: 172.16.10.10, Src Port: 0, Dst Port: 0, Src Intf: Ethernet4/1, Pro tocol: "ICMP"(1), Hit-count = 2589
2014 Jun 29 19:26:18 Nexus-7000 %ACLLOG-5-ACLLOG_FLOW_INTERVAL: Src IP: 10.1 0.10.1, Dst IP: 172.16.10.10, Src Port: 0, Dst Port: 0, Src Intf: Ethernet4/1, Pro tocol: "ICMP"(1), Hit-count = 4561

我们将使用正则表达式的简单示例。如果您已经熟悉 Python 中的正则表达式,可以跳过本节的其余部分。

使用 RE 模块进行搜索

对于我们的第一次搜索,我们将简单地使用正则表达式模块来查找我们正在查找的术语。我们将使用一个简单的循环来执行以下操作:

#!/usr/bin/env python3

import re, datetime

startTime = datetime.datetime.now()

with open('sample_log_anonymized.log', 'r') as f:
   for line in f.readlines():
       if re.search('ACLLOG-5-ACLLOG_FLOW_INTERVAL', line):
           print(line)

endTime = datetime.datetime.now()
elapsedTime = endTime - startTime
print("Time Elapsed: " + str(elapsedTime))

搜索日志文件大约需要 6/100 秒的时间:

$ python3 python_re_search_1.py
2014 Jun 29 19:21:18 Nexus-7000 %ACLLOG-5-ACLLOG_FLOW_INTERVAL: Src IP: 10.1 0.10.1,

2014 Jun 29 19:26:18 Nexus-7000 %ACLLOG-5-ACLLOG_FLOW_INTERVAL: Src IP: 10.1 0.10.1,

Time Elapsed: 0:00:00.065436

建议编译搜索词,以便进行更有效的搜索。它不会对我们产生太大影响,因为脚本已经相当快了。事实上,Python 的解释性质实际上可能会使它变得更慢。但是,当我们在更大的文本体中搜索时,它将产生不同,因此让我们进行更改:

searchTerm = re.compile('ACLLOG-5-ACLLOG_FLOW_INTERVAL')

with open('sample_log_anonymized.log', 'r') as f:
   for line in f.readlines():
       if re.search(searchTerm, line):
           print(line)

计时结果实际上较慢:

Time Elapsed: 0:00:00.081541

让我们稍微扩展一下这个例子。假设我们要搜索多个文件和多个术语,我们会将原始文件复制到新文件:

$ cp sample_log_anonymized.log sample_log_anonymized_1.log

我们还将搜索PAM: Authentication failure术语。我们将添加另一个循环来搜索这两个文件:

term1 = re.compile('ACLLOG-5-ACLLOG_FLOW_INTERVAL')
term2 = re.compile('PAM: Authentication failure')

fileList = ['sample_log_anonymized.log', 'sample_log_anonymized_1.log']

for log in fileList:
    with open(log, 'r') as f:
       for line in f.readlines():
           if re.search(term1, line) or re.search(term2, line):
               print(line) 

通过扩展搜索词和邮件数量,我们现在可以看到性能上的差异:

$ python3 python_re_search_2.py
2016 Jun 5 16:49:33 NEXUS-A %DAEMON-3-SYSTEM_MSG: error: PAM: Authentication failure for illegal user AAA from 172.16.20.170 - sshd[4425]

2016 Sep 14 22:52:26.210 NEXUS-A %DAEMON-3-SYSTEM_MSG: error: PAM: Authentication failure for illegal user AAA from 172.16.20.170 - sshd[2811]

<skip>

2014 Jun 29 19:21:18 Nexus-7000 %ACLLOG-5-ACLLOG_FLOW_INTERVAL: Src IP: 10.1 0.10.1,

2014 Jun 29 19:26:18 Nexus-7000 %ACLLOG-5-ACLLOG_FLOW_INTERVAL: Src IP: 10.1 0.10.1,

<skip>

Time Elapsed: 0:00:00.330697

当然,在性能调整方面,这是一场永无止境的、不可能的零竞争,性能有时取决于您使用的硬件。但重要的一点是使用 Python 定期对日志文件执行审计,这样您就可以捕获任何潜在违规的早期信号。

其他工具

我们还可以使用其他网络安全工具,并通过 Python 实现自动化。让我们来看看其中的一些。

专用 VLAN

虚拟局域网VLAN已经存在很长时间了。它们本质上是一个广播域,其中所有主机都可以连接到单个交换机,但被划分到不同的域,因此我们可以根据哪个主机可以通过广播看到其他主机来将主机分离出来。让我们看一看基于 IP 子网的映射。例如,在企业大楼中,我可能会看到每个物理层有一个 IP 子网:192.168.1.0/24用于第一层,192.168.2.0/24用于第二层,依此类推。在这种模式中,我们为每层使用 1/24 块。这清楚地描述了我的物理网络和逻辑网络。想要在自己的子网之外进行通信的主机将需要穿越其第 3 层网关,在那里我可以使用访问列表来加强安全性。

当不同部门位于同一楼层时会发生什么情况?也许财务和销售团队都在二楼,我不希望销售团队的主持人和财务团队的主持人在同一个广播域中。我可以进一步分解子网,但这可能会变得单调乏味,并破坏以前设置的标准子网方案。这是专用 VLAN 可以提供帮助的地方。

私有 VLAN 实质上将现有 VLAN 分解为子 VLAN。专用 VLAN 中有三个类别:

  • 混杂(P)端口:该端口允许从 VLAN 上的任何其他端口发送和接收第二层帧;这通常属于连接到第 3 层路由器的端口
  • 隔离(I)端口:该端口仅允许与 P 端口通信,当您不希望它与同一 VLAN 中的其他主机通信时,它们通常连接到主机
  • 社区(C)端口:允许该端口与同一社区中的其他 C 端口和 P 端口通信

我们可以再次使用 Ansible 或迄今为止介绍的任何其他 Python 脚本来完成此任务。到目前为止,我们应该有足够的实践和信心通过自动化实现此功能,因此我不会在这里重复这些步骤。当您需要在 L2 VLAN 中进一步隔离端口时,了解专用 VLAN 功能将非常有用。

UFW 与 Python

我们简要地提到 UFW 是 Ubuntu 主机上 iptables 的前端。下面是一个快速概述:

$ sudo apt-get install ufw
$ sudo ufw status
$ sudo ufw default outgoing
$ sudo ufw allow 22/tcp
$ sudo ufw allow www
$ sudo ufw default deny incoming

我们可以看到 UFW 的状态:

$ sudo ufw status verbose
Status: active
Logging: on (low)
Default: deny (incoming), allow (outgoing), disabled (routed)
New profiles: skip

To Action From
-- ------ ----
22/tcp ALLOW IN Anywhere
80/tcp ALLOW IN Anywhere
22/tcp (v6) ALLOW IN Anywhere (v6)
80/tcp (v6) ALLOW IN Anywhere (v6)

正如您所看到的,UFW 的优点是它提供了一个简单的接口来构造复杂的 IP 表规则。有几种与 Python 相关的工具可以与 UFW 一起使用,使事情变得更简单:

UFW 被证明是保护网络服务器的好工具。

进一步阅读

Python 是许多安全相关领域中使用的一种非常通用的语言。以下是我推荐的几本书:

  • 暴力巨蟒:T.J.O'Connor 为黑客、法医分析师、渗透测试人员和安全工程师编写的食谱(ISBN-10:1597499579)
  • 黑帽 Python:贾斯汀·塞茨(Justin Seitz)的《黑客和 Pentester 的 Python 编程》(ISBN-10:1593275900)

我个人在研究 A10 网络上的分布式拒绝服务DDoS)时广泛使用了 Python。如果您有兴趣了解更多信息,可在免费下载本指南 https://www.a10networks.com/resources/ebooks/distributed-denial-service-ddos

总结

在本章中,我们使用 Python 研究了网络安全。我们使用 Cisco VIRL 工具来建立实验室,包括主机和网络设备,包括 NX OSv 和 IOSv 类型。我们参观了斯卡皮,这使我们能够从头开始构建数据包。Scapy 可以在交互模式下用于快速测试。在交互模式下完成后,我们可以将步骤放入文件中,以进行更具可伸缩性的测试。它可用于对已知漏洞执行各种网络渗透测试。

我们还研究了如何使用 IP 访问列表和 MAC 访问列表来保护我们的网络。它们通常是我们网络保护的第一道防线。使用 Ansible,我们能够一致且快速地将访问列表部署到多个设备。

Syslog 和其他日志文件包含有用的信息,我们应该定期梳理这些信息,以检测任何早期违规迹象。使用 Python 正则表达式,我们可以系统地搜索已知的日志条目,这些日志条目可以指向需要我们注意的安全事件。除了我们已经讨论过的工具之外,私有 VLAN 和 UFW 是我们可以用于更多安全保护的其他有用工具之一。

第 7 章使用 Python 进行网络监控–第 1 部分中,我们将了解如何使用 Python 进行网络监控。通过监控,我们可以了解网络中正在发生的事情以及网络的状态。*