6
Network Security
using Python
Writing on the topic of network security is very difficult. The problem
is not technical complexity, but the right choice of the area covered.
The scope of network security is extremely broad and permeates all
seven layers of the OSI model. We can talk about information
interception at the physical level, vulnerabilities in transport protocols,
and man-in-the-middle attacks at the application level. This problem is
further complicated by the discovery of new vulnerabilities, which, as
it may sometimes seem, are becoming something common . And this is
not to mention such an aspect of network security as social
engineering. For this reason, I would like to immediately define the scope
of the discussion in this chapter. As before, the main focus will be
on using Python to protect network devices at the network and
transport layers of the OSI model. We 'll look at Python tools that are
suitable for securing individual network devices, as well as using
Python to link different components. I hope that using Python at
different levels of the OSI model will allow us to develop a common
approach to network security. This chapter covers topics such as:
z laboratory preparation;
z security testing with
z Python Scapy; access lists;
212 Chapter 6. Network Security using Python
z
retrospective analysis based on Syslog and UFW
(Uncomplicated Firewall) using Python; other tools:
z MAC address filtering lists, private VLAN interfaces,
and a Python package for working with Iptables.
Laboratory preparation
Here we will need devices of a slightly different kind than those
with which we dealt earlier. In the previous chapters, we
isolated the device to focus on the topic. In this chapter, our lab
will contain a little more devices, which will allow us to
illustrate the capabilities of the tools. Information about the
connection and operating system will be important, as it is the
basis for choosing security tools. For example, if we want to use
an access list to protect a server, we need to have an idea of the
network topology and where the client connection is being
established from. Connections to hosts running Ubuntu are
slightly different from the ones we've seen so far, so you can
check this section later if necessary. We use the same Cisco VIRL
tool with four nodes: two hosts and two network devices. If you
need to refresh your skills with Cisco VIRL, please re-read Chapter
2, where we introduced you to the system (Figure 6.1).
Figure 6.1. Laboratory topology
Laboratory preparation 213
The IP addresses in your lab may differ from those listed
above. We 've included them to make it easier for you to
navigate the code examples presented in this chapter.
Following this illustration, we will rename the upper and lower hosts to Client
and Server, respectively. This is similar to an Internet client trying to
access a corporate server inside our network. We will select the item again
Shared flat network (Shared flat network) in the list Management
Network (Control network) to be able to control devices
via an additional channel (Figure 6.2).
Figure 6.2. Control network option in the laboratory
In this example, the client host must be externally
accessible via VMnet2. In my lab, I use an external
USB interface connected to my ESXi host providing
such a connection. You may also need to add external
DNS servers to /etc/resolveconf/resolv. conf.d/base:
cisco@Client:~$ cat
/etc/resolvconf/[Link].d/ base nameserver
[Link] nameserver [Link]
The two switches use a routing algorithm as the Internal Gateway
Protocol (IGP ). OSPF (Open Shortest Path
First), and both devices are in the zone . BGP is enabled by0default-
In both cases, AS 1 is used.
According to the generated configuration, interfaces
connected to hosts running Ubuntu are located in the 1
OSPF zone, so they are represented as inter-area
routes. The configuration for NX-OSv is shown below (IOSv
devices have similar configuration and output):
214 Chapter 6. Network Security using Python
interface Ethernet2/1
description to Client
no switchport
mac-address fa16.3e00.0001
ip address [Link]/30
ip router ospf 1 area [Link]
no shutdown
!
interface Ethernet2/2
description to iosv-1
no switchport
mac-address fa16.3e00.0002
ip address [Link]/30
ip router ospf 1 area [Link]
no shutdown
!
nx-osv-1# sh ip route
<omitted> [Link]/30, ubest/mbest:
1/0 *via [Link], Eth2/2,
[110/41], [Link], ospf-1, intra
[Link]/32, ubest/mbest: 1/0
*via [Link], Eth2/2, [110/41],
[Link], ospf-1, intra <omitted>
Below you can see the OSPF neighbor and
BGP output for NX-OSv (IOSv has a similar output):
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 [Link] 1 FULL/DR [Link] [Link]
Eth2/2 ! nx-osv-1# sh ip bgp summary BGP
summary information for VRF default, address
family IPv4 Unicast BGP router identifier
[Link], 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 [Link] 4 1 936 857 5 0 0 [Link] 1
The hosts on our network are running Ubuntu 16.04 OS; it is similar
to the Ubuntu VM 18.04 that we have been using up to this point:
cisco@Server:~$
lsb_release -a No LSB modules are
available. Distributor ID:
Ubuntu Description:
Ubuntu 16.04.3 LTS Release:
16.04 Codename: xenial
Laboratory preparation 215
Both hosts with Ubuntu are equipped with two network interfaces, eth0 andeth1.
The first one is connected to the control network ([Link]/24), and
the second one is connected to network devices (10.0.0.x/30). Routes
leading to the loopback address of the device are directly connected to the
network block, and remote host networks are
sostatically
that the default
routed route
to eth1
leads to:-
connecting network:
cisco@Client:~$ route -n
Kernel IP routing table
Destination Gateway
Genmask Flags Metric Ref
Use Iface [Link]
[Link] [Link] UG 0 0 0
eth0 [Link] [Link]
[Link] UG 0 0 0 eth1
[Link] [Link]
[Link] U 0 0 0 eth1
[Link] [Link]
[Link] U 0 0 0 eth0
[Link] [Link]
[Link] UG 0 0 0 eth1
and after that, to check the pa
dim the route. This way, we will make sure that traffic between our hosts
passes through network devices, and not along the standard route.:
# Server interface IP address Eth1 [Link] / 30
cisco@Server:~$ ifconfig eth1 eth1
Link encap:Ethernet HWaddr [Link]
inet addr:[Link] Bcast:[Link]
Mask:[Link] <omitted> # echo request
from the IP address of the client interface
Eth1 ([Link] / 30) to the cisco@Client server:~$
ifconfig eth1 eth1 Link encap:Ethernet
HWaddr [Link] inet addr:[Link]
Bcast:[Link] Mask:[Link] <omitted>
cisco@Client:~$ ping -c 1 [Link] PING [Link]
([Link]) 56(84) bytes of data. 64 bytes from
[Link]: icmp_seq=1 ttl=62 time=7.00 ms ---
[Link] ping statistics --- 1 packets transmitted, 1
received, 0% packet loss, time 0ms rtt min/avg/max/mdev
= 7.007/7.007/7.007/0.000 ms # Tracing
the route from the client to the cisco@Client
server:~$ traceroute [Link] traceroute
to [Link] ([Link]), 30 hops max, 60 byte packets
1 [Link] ([Link]) 5.694 ms 10.632 ms 10.599 ms 2
[Link] ([Link]) 13.078 ms 19.132 ms 19.101 ms
3 [Link] ([Link]) 14.929 ms 19.026 ms 19.004 ms
216 Chapter 6. Network Security using Python
Great! We have set up the lab; now we will introduce you
to some Python-based security tools and measures.
Python Scapy
Scapy ( [Link] ) is a powerful interactive program in the following language:
Python for generating network packets. Apart from some
expensive commercial counterparts, there are not many
tools with similar capabilities, as far as I know. This is one of my
favorite projects in the Python world. The main advantage
of Scapy is that it allows you to create your own network
packet at a very low level. According to the creator of Scapy:
"Scapy is a powerful interactive package management
program. It can construct or decode packets of
various protocols, send them over the network, intercept
them, compare requests and responses, and much
more... most other tools do not allow you to create
something that is not provided by their authors. These
tools were created for a specific purpose and cannot
deviate from it." Let's get acquainted with this tool.
Installing Scapy
In an attempt to introduce Python 3 support, the Scapy project
took an interesting path. In 2015, an independent version
called Scapy3k was spun off from Scapy 2.2.0 to implement Python 3
support . In this book, we use the core code base of the original
Scapy project. If you have read previous editions and used a
version of Scapy that is only compatible with Python 2, then see
how different versions of this tool support Python 3 (Figure
6.3). Since we want to generate packets on the client and send
them to the server, Scapy should be installed on the client side:
cisco@Client:~$ git clone [Link]/secdev/[Link]
cisco@Client:~$ cd scapy/
cisco@Client:~/scapy$ sudo python3 [Link] install
Python Scapy 217
After installation, we will launch the interactive
Scapy shell by typing in the command line scapy
(Figure 6.4).
Figure 6.3. Python version support (source: [Link]
Learn more about how Python 3 support was introduced in Scapy. It
all started with a spinoff of the independent version from Scapy
2.2.0 in 2015. The project was named Scapy3k. This branch has
divergeda cd with the main Scapy code base. In the first edition of
this book, the story ended there. Later, there was a
misunderstanding about the python3-scapy package in PyPI and the official Scapy
support. The main purpose of this chapter is to learn this tool. So I
decided to use an older version of it, based on Python 2.
Figure 6.4. Python Scapy Testing
218 Chapter 6. Network Security using Python
Easy check for Scapy library availability in Python 3:
cisco@Client:~$ python3 Python
3.5.2 (default, Jul 10 2019, [Link])
[GCC 5.4.0 20160609] on linux Type
"help", "copyright", "credits" or
"license" for more information. >>>
from [Link] import * >>> exit()
Great! The Scapy package is installed and available
for use in the Python interpreter. In the next section,
you will see how you can use the interactive shell.
Interactive examples
In the first example, we will generate an ICMP packet on the client
and send it to the server. On the server side, we will use the utility tcpdump with a filter
hosts to see this package:
## Client side
cisco@Client:~/scapy$ sudo
scapy >>> send (IP(dst="[Link]")/ICMP
()) .
Sent 1 packets.
# Сторона сервера cisco@Server:~$
sudo tcpdump -i eth1 tcpdump: verbose
output suppressed, use -v or -vv for full
protocol decode listening on eth1, link-type
EN10MB (Ethernet), capture size 262144
bytes [Link].812184 IP [Link] > [Link]:
ICMP echo request, id 0, seq 0, length
8 [Link].812205 IP [Link] > [Link]:
ICMP echo reply, id 0, seq 0, length 8
As you can see, this is a very simple process. Scapy
/
allows you to generate a packet by adding protocol
headers separated by a slash (). Function send
works at the network layer, so you don't have to worry
about routing and physical addressing. It has an alternative, sendp() ,
which works at the link layer; this means that you will need to
specify the interface and connection protocol. Let's
try to capture the response packet using the function send-request (sr).
Let's use the special versionher
sr named ,sr1which returns tol-
co one package:
Python Scapy 219
>>> p = sr1(IP(dst="[Link]")/ICMP())
Begin emission: .Finished
sending 1 packets. * Received 2 packets,
got 1 answers, remaining 0 packets
>>> p <IP version=4 ihl=5 tos=0x0 len=28
id=44710 flags= frag=0 ttl=62 proto=icmp
chksum=0xba2d src=[Link] dst=[Link]
|<ICMP type=echoreply code=0
chksum=0xffff id=0x0 seq=0x0 |>>
returns a tuple with lists of packages that were referenced and a non-function sr()
a response was received:
>>> p =
sr(IP(dst="[Link]")/ICMP()) .Begin emission:
.....Finished sending 1 packets.
* Received 7
packets, got 1 answers, remaining 0
packets >>> type(p)
<class 'tuple'>
Let's look inside this tuple:
>>> ans, unans =
sr(IP(dst="[Link]")/ICMP())
.Begin emission: ...Finished
sending 1 packets. ..* Received 7
packets, got 1 answers,
remaining 0 packets >>>
type(ans) <class '[Link]'>
>>> type(unans)
<class '[Link]'>
If we display a list of packets that received a response, we will
see that this is another tuple containing sent and received packets:
>>> for i in ans:
... print(type(i))
... <class 'tuple'> >>>
>>> >>> for i in ans: ... print(i)
... (<IP frag=0 proto=icmp
dst=[Link] |<ICMP |>>, <IP
version=4 ihl=5 tos=0x0 len=28
id=19027 flags= frag=0 ttl=62
proto=icmp chksum=0x1e81
src=[Link] dst=[Link]
|<ICMP type=echo-reply
code=0 chksum=0xffff
id=0x0 seq=0x0 |>>)
Scapy also supports packet construction for back-end protocols
, such as DNS queries. In the following example, we apply
220 Chapter 6. Network Security using Python
connect to a public DNS server to get the www.
IP address of the domain name [Link]:
>>> p =
sr1(IP(dst="[Link]")/UDP()/DNS(rd=1,qd=DNSQR(qname="[Link]. com"))) Begin emission: ......Finished sending 1
packets. ..........*
Received 17 packets, got 1 answers, remaining 0 packets
>>> p <IP version=4 ihl=5 tos=0x20 len=76 id=17713 flags=
frag=0 ttl=121 proto=udp chksum=0x28c5 src=[Link] dst=[Link]
|<UDP sport=domain dport=domain len=56 chksum=0xa9db
|<DNS id=0 qr=1 opcode=QUERY aa=0 tc=0 rd=1 ra=1 z=0 ad=0
cd=0 rcode=ok qdcount=1 ancount=1 nscount=0 arcount=0 qd=<DNSQR
qname='[Link].' qtype=A qclass=IN |> an=<DNSRR
rrname='[Link].' type=A rclass=IN ttl=274 rdlen=None
rdata=[Link] |> ns=None ar=None |>>>
Let's look at other Scapy features. Let's start with packet capture.
Capture packets with Scapy
In the process of troubleshooting problems, we network engineers
constantly have to capture packets passing through
the connection. Usually Wireshark or similar tools are used
for this, but Scapy also makes it easy to capture packets.:
>>> a = sniff(filter="icmp", count=5) >>> [Link]() 0000
Ether / IP / ICMP [Link] > [Link] echo-request
0 / Raw 0001 Ether / IP / ICMP [Link] > [Link]
echo-reply 0 / Raw 0002 Ether / IP / ICMP
[Link] > [Link] echo-request 0 / Raw 0003 Ether / IP / ICMP
[Link] > [Link] echo-reply 0 / Raw 0004 Ether
/ IP / ICMP [Link] > [Link] echo-request 0 / Raw
You can view packages in more detail, even in the original format:
>>> for packet in a:
... print([Link]())
...
###[ Ethernet ]###
dst= [Link]
src= [Link]
type= IPv4
###[ IP ]###
version= 4
ihl= 5
tos= 0x0
len= 84
Python Scapy 221
id= 1856
flags= DF
frag= 0
ttl= 64
proto= icmp
chksum= 0x5fde
src= [Link]
dst= [Link] \options\ ###[ ICMP ]### type= echo-request code= 0 chksum= 0x4616 id= 0x4a84
seq= 0x1 ###[ Raw ]### load= 'k\x9a\x8f]\x00\x00\x00\x00\xac\x99\x01\x00\x00\x00\
x00\x00\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\
x1d\x1e\x1f!"#$%&\'()*+,-./01234567'
<omitted>
So, we got acquainted with the basic principles of Scapy.
Now let's see how this tool is used to check network security.
Scanning TCP ports
Any hacking attempt almost always starts by searching the
web for open passwords; this allows you to attack a specific
target. Of course, in order to serve our customers, we
need to open some ports; this is a risk we must take. At the
same time,we must close all other ports that could serve
as additional targets for attacks. We can use Scapy to scan
our own host for open TCP ports. Sending the package
SYN and see if the server returns a response SYN-ACK . Let's start
with TCP port 23, which belongs to Telnet:
>>> p = sr1(IP(dst="[Link]")/TCP(sport=666,dport=23,flags="S"))
Begin emission:
Finished sending 1 packets. .* Received 2 packets, got 1
answers, remaining 0 packets >>> [Link]() ###[ IP ]###
version= 4 ihl= 5
tos= 0x0
len= 40
222 Chapter 6. Network Security using Python
id= 14089
flags= DF
frag= 0
ttl= 62 proto=
tcp chksum=
0xf1b9
src= [Link]
dst= [Link]
\options\
###[ TCP ]###
sport= telnet
dport= 666
seq= 0 ack=
1 dataofs=
5 reserved=
0 flags=
RA window=
0 chksum=
0x9911
urgptr= 0
options= []
Note that in this case, the server responded to an attempt to
establish a connection to TCP port 23 with a packet RESET+ACK , because the host doesn't have
Telnet. However, TCP port 22 (SSH) is open, so a packet is
returned for it :
SYN-ACK
>>> p = sr1(IP(dst="[Link]")/TCP(sport=666,dport=22,flags="S"))
>>> p = sr1(IP([Link]()
###[ IP ]###
version= 4
<omitted>
proto= tcp
chksum= 0x28bf
src= [Link]
dst= [Link]
\options\
###[ TCP ]###
sport= ssh
dport= 666
seq= 1671401418
ack= 1
dataofs= 6
reserved= 0
flags= SA
<omitted>
We can also scan a range of ports from 20 ;22 note that before
we send/receive multiple packets at once, so here
we use the function (which sends / receives single packets). sr(), not sr1()
packages):
Python Scapy 223
>>> ans,unans =
sr(IP(dst="[Link]")/TCP(sport=666,dport=(20,22),flags= "S"))
>>> for i in ans:
... print(i)
... (<IP frag=0 proto=tcp
dst=[Link] |<TCP sport=666 dport=ftp_data flags=S |>>, <IP version=4
ihl=5 tos=0x0 len=40 id=59720 flags=DF frag=0 ttl=62 proto=tcp
chksum=0x3f7a src=[Link] dst=[Link] |<TCP sport=ftp_data
dport=666 seq=0 ack=1 dataofs=5 reserved=0 flags=RA
window=0 chksum=0x9914 urgptr=0 |>>) (<IP frag=0 proto=tcp
dst=[Link] |<TCP sport=666 dport=ftp flags=S |>>, <IP version=4
ihl=5 tos=0x0 len=40 id=59721 flags=DF frag=0 ttl=62 proto=tcp
chksum=0x3f79 src=[Link] dst=[Link] |<TCP sport=ftp
dport=666 seq=0 ack=1 dataofs=5 reserved=0 flags=RA window=0
chksum=0x9913 urgptr=0 |>>) (<IP frag=0 proto=tcp dst=[Link]
|<TCP sport=666 dport=ssh flags=S |>>, <IP version=4
ihl=5 tos=0x0 len=44 id=0 flags=DF frag=0 ttl=62 proto=tcp
chksum=0x28bf src=[Link] dst=[Link] |<TCP sport=ssh
dport=666 seq=3932520059 ack=1 dataofs=6 reserved=0 flags=SA
window=29200 chksum=0xa666 urgptr=0 options=[('MSS',
1460)] |>>) >>>
Instead of a single host, you can scan an entire network. As you can see,
the hosts were returned in the range [Link]/29 [Link], [Link] and [Link];
answer SA
these are two network devices and a server:
>>> ans,unans =
sr(IP(dst="[Link]/29")/TCP(sport=666,dport=(22),flags= "S"))
>>> for i in ans:
... print(i)
... (<IP frag=0 proto=tcp dst=[Link] |<TCP sport=666 dport=ssh
flags=S |>>, <IP version=4 ihl=5 tos=0x0 len=44 id=7289 flags=
frag=0 ttl=64 proto=tcp chksum=0x4a41 src=[Link] dst=[Link] |<TCP
sport=ssh dport=666 seq=1652640556 ack=1 dataofs=6 reserved=0
flags=SA window=17292 chksum=0x9029 urgptr=0 options=[('MSS',
1444)] |>>) (<IP frag=0 proto=tcp dst=[Link] |<TCP
sport=666 dport=ssh flags=S |>>, <IP version=4 ihl=5 tos=0x0
len=44 id=0 flags=DF frag=0 ttl=62 proto=tcp chksum=0x28bf
src=[Link] dst=[Link] |<TCP sport=ssh dport=666 seq=898054835
ack=1 dataofs=6 reserved=0 flags=SA window=29200
chksum=0x9f0d urgptr=0 options=[('MSS', 1460)] |>>) (<IP
frag=0 proto=tcp dst=[Link] |<TCP sport=666 dport=ssh flags=S
|>>, <IP version=4 ihl=5 tos=0x0 len=44 id=38021 flags= frag=0
ttl=254 proto=tcp chksum=0x1438 src=[Link] dst=[Link] |<TCP
sport=ssh dport=666 seq=371720489 ack=1 dataofs=6 reserved=0
flags=SA window=4128 chksum=0x5d82 urgptr=0 options=[('MSS',
536)] |>>) >>>
Based on these results, we will write a scapy_tcp_scan_1.
simple script, ,pysuitable for multiple use:
224 Chapter 6. Network Security using Python
#!/usr/bin/env python3
from [Link] import *
import sys 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." def main():
destination = [Link][1]
port = int([Link][2])
scan_result = tcp_scan(destination, port)
print(scan_result)
if __name__ == "__main__":
main()
for receiving arguments. Function First we import scapy and the m
similar to the code we've used so far; the only difference is
tcp_scan()
in that we can get input from the command
line arguments and theninside
tcp_scan() call main().
Please note that access to a low-level network requires
superuser privileges , so you need to run the .sudoLet's scan it with
script using using ports 22(SSH) and 80(HTTP):
cisco@Client:~$ sudo python3
scapy_tcp_scan_1.py "[Link]" 22
Begin emission: ......Finished sending
1 packets. * Received 7 packets,
got 1 answers, remaining 0 packets
[Link] port 22 is open.
cisco@Client:~$ sudo python3
scapy_tcp_scan_1.py "[Link]" 80 Begin
emission: ...Finished sending 1
packets. * Received 4 packets,
got 1 answers, remaining 0 packets
[Link] port 80 is not open.
This was a relatively long example of a TCP port scanning script
that showed what can be achieved by generating custom packets
using Scapy. We tested individual steps in an interactive
shell and ended up writing them as a simple script. Now let's
look at some examples of using Scapy for security checks.
Python Scapy 225
Collection of packets for checking communication
Imagine that our network consists of computers running
Windows, Unix, and Linux, and users can connect their own
devices to it as part of a policy BYOD (Bring Your Own
Device); we don't know if a particular host supports ICMP
echo request packets (ping). We can write a script that
uses three distributed packet types to verify
communication: ICMP, TCP, and :UDP. Let's call it
scapy_ping_collection.py
#!/usr/bin/env python3 from
[Link] import * def
icmp_ping(destination):
# normal ICMP echo request
ans, unans =
sr(IP(dst=destination)/ICMP ()) return ans
def tcp_ping(destination, dport):
ans, unans =
sr(IP(dst=destination)/TCP(dport=dport,flags="S")) return ans def
udp_ping(destination): ans, unans =
sr(IP(dst=destination)/UDP(dport=0)) return ans
def answer_summary(ans):
for send, recv in ans:
print([Link]("%[Link]% is alive"))
Then we can send all three types of network
packets using a single scenario:
def main():
print("** ICMP Ping **")
ans = icmp_ping("[Link]-14")
answer_summary(ans)
print("** TCP Ping ***")
ans = tcp_ping("[Link]", 22)
answer_summary(ans)
print("** UDP Ping ***")
ans = udp_ping("[Link]-14")
answer_summary(ans)
if __name__ == "__main__":
main()
The ability to create your own packages allows you
toperform exactly the operations and checks that you want. You can
also create packets using Scapy to check network security.
226 Chapter 6. Network Security using Python
Common attacks
In this example, you will learn how to create a batch for classic attacks Ping
[Link] ) and LAND (
of Death (
[Link]
). These checks allow you to identify the possibility of network penetration,wiki/LAND
Previously, they could only be
performed using paid commercial software. Scapy allows you to perform similar checks while maintaining full control over what i
3:
def malformed_packet_attack(host):
send(IP(dst=host, ihl=2, version=3)/ICMP())
Function ping_of_death_attack sends a regular ICMP packet with the volume of
payload greater than 65,535 bytes:
def ping_of_death_attack(host):
# [Link]
send(fragment(IP(dst=host)/ICMP()/("X"*60000)))
Function land_attack tries to forward the client's own response so that the client's response is saved.
the host has run out of resources:
def land_attack(host):
# [Link]
send(IP(src=host, dst=host)/TCP(sport=135,dport=135))
These are fairly old vulnerabilities that modern operating systems
are no longer susceptible to. None of these attacks will disrupt our
Ubuntu 16.04 host. However, new security holes are always
discovered, and Scapy is a great tool for testing your own networks
and hosts, so you no longer have to wait for your vendor to provide
you with the right tool to check. This is especially true for so-called
zero-day attacks (published without warning), which seem to
be becoming increasingly common on the internet. Extensive features
of Scapy nelIt can't fit into a single chapter, but fortunately,
there are plenty of open resources that we can recommend.
Resources about Scapy
In this chapter, we've focused quite a bit on working with Scapy. This
is partly because I think highly of this tool. I hope you agree with me
Access Lists 227
I think that Scapy will be useful for any network engineer. The best part is that
this project is constantly being developed by an active community of users.
I highly recommend reading the Scapy hands-on guide ([Link]
as well as with any documentation you are interested in.
Of course, network security is not limited to packet
generation and vulnerability testing. In the next section,
we'll talk about automating access lists, which are
widely used to protect internal confidential resources.
Access Lists
Network access lists are usually the first line of defense against
intruders and attacks from outside. In general, routers and switches process
packets much faster than servers, using equipment with high-speed
memory, such as TCAM (Ternary Content-Addressable Memory-Ternary
Associative Memory). They don't need application-level information.
They only check the network and transport layer headers to determine
whether a packet should be passed through further. Therefore, the
protection of network resources usually begins with access lists in
network devices. As a rule, they try to place access lists as close as
possible to the source (client). It is also logical that we trust hosts inside
our network and are suspicious of clients outside of it. Therefore, the
access list is usually applied to network interfaces that receive
external traffic. Go to conditionsin our lab, this means that the access list
should be applied to incoming traffic on the host's Ethernet2/1 interface
nx-osv-1 , which is directly connected to the client.
If you are not sure about the traffic direction
and placement of the access list, here are some tips.
Think of an access list in terms of a network device.
z
z
Reduce packets to source and destination IP
addresses by using a single host as an example. In our
z lab, traffic from the server to the client will have an
outgoing IP address [Link] and the final one .[Link]
228 Chapter 6. Network Security using Python
z The outgoing and destination IP addresses in the traffic
accordingly.
from the client to the server will be [Link] and [Link]
Obviously, all networks are different, and making an access list
depends on the services provided by your server. But if we are
talking about an access list for external incoming traffic, you should:
z prohibit receiving packets originating from special addresses, such as
(RFC 3030);
[Link]/8
z prohibit receiving packets originating
from local network address ranges, such as 1918);[Link]/8
(RFC
z
prevent receiving packets originating
);
from your address space [Link]/30
(in this case ,
z allow incoming traffic to TCP ports 22
(SSH) and 80 (HTTP) of the host [Link];
prohibit everything else.
z
Here is a good list of non-routable networks
that should be blocked : [Link]
But understanding what the access list should consist of is only half the battle.
In the next section, you'll learn how to implement it using Ansible.
Implementing access lists using Ansible
The easiest way to implement an access list is to use Ansible. We
have already been introduced to this tool in the previous two chapters,
but once again , let's review its advantages in this situation.
z
Easy administration. Long access lists
can be divided into parts for convenience using the instructions include .
Then other teams or service owners can work with these individual lists
.
z Idempotence. We can schedule
the Ansible script to run regularly, and only the necessary changes will
be made .
z
Explicit description of each task. We can separate the construction
of lists, and also apply access lists to
the corresponding interfaces.
Access Lists 229
z
Re-use. In the future, if we have other outward - facing
interfaces, all we need to do is add them to the list of
devices, and the access rules will apply to them automatically.
z Extensibility. You will see for yourself that the same Ansible
script can be used both to create an access list and to
apply it to the desired interface. We can start small and then
break the code down into separate scripts as we expand, if
necessary.
File host it has a familiar appearance. For simplicity, we will place variables in it
hosts:
[nxosv-devices] nx-osv-1 ansible_host=[Link]
ansible_username=cisco
ansible_ password=cisco
[iosv-devices] iosv-1 ansible_host=[Link]
ansible_username=cisco
ansible_ password=cisco
Let's declare these variables in the script:
--- - 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
To save space, we will only illustrate the prohibition of receiving
packets from local area network address spaces (RFC 1918). Banning
the RFC 3030 range and our own address space is configured
in the same way. Please note that our script does not prohibit the [Link]/8,
range because in our configures currently used by the network .
[Link]
Of course, we could first allow access for an individual host, and
then deny it in one of the following entries, but in this example, we will
[Link]/8
just ignore this range:
tasks:
- nxos_acl:
name: border_inbound
230 Chapter 6. Network Security using Python
seq: 20
action: deny
proto: tcp
src: [Link]/12
dest: any
log: enable
state: present
provider: "{{ cli }}"
- nxos_acl:
name: border_inbound
seq: 30
action: deny
proto: tcp
src: [Link]/16
dest: any
state: present
log: enable
provider: "{{ cli }}"
<omitted>
We allow traffic to be received through connections initiated by
with more software-
the server itself. At the end, we use an explicit statement deny
ip any row number ( )1000, so that you can insert new records
later. Then apply the access list to the appropriate interface:
- name: apply ingress acl to
Ethernet 2/1 nxos_acl_interface:
name: border_inbound
interface: Ethernet2/1
direction: ingress
state: present
provider: "{{ cli }}"
The access list for VIRL NX-OSv is supported only by the
management interface. You will see a warning: Warning: ACL may not behave
as expected since only one management interface is supported
if you configure this ACL via the CLI (Note: if you configure
this access list from the command line, it may have a different
effect than expected, because only one management interface
is supported). This is normal, as I was only trying to show you
how you can automate the configuration of access lists.
Quite a lot of work for a single access list, isn't it? It will be
easier for an experienced engineer to log in to the device and
configure everything manually. But keep in mind that this
script can be reused, so it will save you time in the long run.
Access Lists 231
As my experience shows, in long access lists, the entries
corresponding to individual services are blackthey sulk with each other. Such
lists tend to grow, and over time it can be very difficult to determine
the origin and purpose of a particular record. However, they
can be broken down into parts, which greatly simplifies the
administration of access lists. Now run the script on the host nx-osv-1
and check the result:
$ ansible-playbook -i hosts access_list_nxosv.yml
PLAY [Configure Access List]
******************************************** ******************************** TASK
[nxos_acl] *********************************************************
******************* ok: [nx-osv-1] <omitted>
TASK [apply ingress acl to Ethernet 2/1]
********************************
******************************************** changed:
[nx-osv-1] We should log in to the nx-osv-1 devices to
verify the changes: nx-osv-1# sh ip access-lists
border_inbound IP access list border_inbound
20 deny tcp [Link]/12 any log
30 deny tcp [Link]/16 any log
40 permit tcp any [Link]/32 eq 22 log
50 permit tcp any [Link]/32 eq www log
60 permit tcp any any established log
1000 deny ip any any log
nx-osv-1# sh run int eth 2/1
!
interface Ethernet2/1
description to Client
no switchport
mac-address fa16.3e00.0001
ip access-group border_inbound in
ip address [Link]/30
ip router ospf 1 area [Link]
no shutdown
This is an implementation of an access list with IP addresses that
verifies information at the network layer of the OSI model. In the next section,
you will learn how to restrict access to the device at the data link layer.
Access lists by MAC address
If you work at the data link layer or use protocols other than IP
in your Ethernet interfaces, then limit network availability
232 Chapter 6. Network Security using Python
for external hosts, you can use their MAC addresses. This is similar to
our previous example, only MAC addresses will be checked instead of
IP addresses in the access list . As you may remember, in MAC
addresses (or physical addresses) , the first six hexadecimal characters are
an organization's Unique Identifier (OUI). Therefore
, the same mapping mechanism allows you to deny access to certain
host groups.
We are running this example on a host with IOSv and the
ios_config module. In older versions of Ansible, changes are
applied every time the script is executed. In new versions, the
control node first checks for changes and then applies them.
and the beginning of the script is similar to the example with the IP file access list host
addresses, but in the section tasks other modules and arguments are used:
<omitted>
tasks:
- name: Deny Hosts
with vendor id
fa16.3e00.0000 ios_config: lines:
- access-list 700 deny
fa16.3e00.0000 [Link]
- access-list 700 permit
0000.0000.0000 [Link]
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 }}"
Let's run this playbook on an IOSv-1 device and see the result:
$ ansible-playbook -i hosts access_list_mac_iosv.yml
TASK [Deny Hosts with vendor id fa16.3e00.0000]
*************************
*************************************************** changed: [iosv-1]
TASK [Apply filter on bridge group 1]
***********************************
**************************************************** changed: [iosv-1]
Let's go to the device as we did before,
and make sure that the changes are made:
Search in Syslog 233
iosv-1#sh run int gig 0/1
! interface
GigabitEthernet0/1
description to nx-osv-1
<omitted>
bridge-group 1
bridge-group 1 input-address-list
700 end
With the popularization of virtual networks, network-level
information sometimes becomes transparent to the underlying virtual channels.
In such situations, if you need to restrict access to these interfaces,
a MAC address access list is a good solution. In this section, we
used Ansible to implement such lists at the link and network levels.
Now let's talk about another aspect of network security: how
to extract the necessary information from the Syslog using Python.
Search in Syslog
We know about a lot of holes in the cesecurity
doors that remained open for a long period of time. At that time, we often found
signs of suspicious activity. They could be found in
the logs of servers and network devices. This activity was not detected
, not because of a lack of information, but rather because of a lack of information. redundancies.
Important information that we are looking for is usually lost in the midst of a large amount of
data that is difficult to understand.
Another good source of information for servers, in addition to
Syslog, is UFW. This is the client interface for Iptables, the server firewall.
UFW greatly simplifies the management of firewall rules and records
a lot of data. Read more about UFW in the "Other Tools"section.
In this section, we will try to use Python to search Syslog
for any activity that interests us. Of course, the text you are
looking for depends on your device. For example, Cisco provides
a list of messages that can be used in Syslog to find any violations
of the rules specified in the access list. It is available at
[Link] .
For a description of the features of logging the use of rules in
the access list, see here: [Link]
.
234 Chapter 6. Network Security using Python
For this exercise, we will need a Nexus switch with
an anonymized Syslog file containing about 65,000 lines.
You can get it from the GitHub repo of this book.:
$ wc -l sample_log_anonymized.log
65102 sample_log_anonymized.log
We have inserted several messages from the Cisco documentation (
[Link] [Link]/c/en/us/support
[Link] ) - these are the ones we will be looking for:
2014 Jun 29 [Link] Nexus-7000
%VSHD-5-VSHD_SYSLOG_CONFIG_I: Configured from vty by admin on console0
2014 Jun 29 [Link] Nexus-7000
%ACLLOG-5-ACLLOG_FLOW_INTERVAL: Src IP: 10.1 0.10.1,
Dst IP: [Link], Src Port: 0, Dst Port: 0, Src Intf: Ethernet4/1,
Pro tocol: "ICMP"(1), Hit-count = 2589 2014 Jun 29
[Link] Nexus-7000 %ACLLOG-5-ACLLOG_FLOW_INTERVAL: Src IP:
10.1 0.10.1, Dst IP: [Link], Src Port: 0, Dst Port: 0, Src Intf:
Ethernet4/1, Protocol: "ICMP"(1), Hit-count = 4561
We'll look at simple examples with regular
from thewith
expressions. If you are already familiar Python
the library,
module you
re can safely skip the trace-
the corresponding section.
Search using the regular
expression module
In our first example, we use the regular
expression support module to search for text. Let's
write a simple loop that does the following::
#!/usr/bin/env python3
import re, datetime
startTime = [Link]() with open('sample_log_anonymized.log',
'r') as f:
for line in [Link]():
if
[Link]('ACLLOG-5-ACLLOG_FLOW_INTERVAL', line): print(line)
endTime = [Link]()
elapsedTime = endTime - startTime
print("Time Elapsed: " + str(elapsedTime))
Search in Syslog 235
The search in this log file took about four hundredths of a second:
$ python3 python_re_search_1.py
2014 Jun 29 [Link] Nexus-7000
%ACLLOG-5-ACLLOG_FLOW_INTERVAL: Src IP: 10.1
0.10.1, 2014 Jun 29 [Link] Nexus-7000
%ACLLOG-5-ACLLOG_FLOW_INTERVAL: Src IP: 10.1
0.10.1, Time Elapsed: [Link].047249
We recommend compiling the search expression to make it more
efficient . But this will not have a significant impact, since our
scenario is already fast. And since Python is an interpreted
language, it can even slow down the script. However, when searching
for larger files, compiling can help, so let's see how to do it:
searchTerm = [Link]('ACLLOG-5-ACLLOG_FLOW_INTERVAL')
with open('sample_log_anonymized.log', 'r') as f:
for line in [Link](): if [Link](searchTerm,
line): print(line)
Execution time increased:
Time Elapsed: [Link].081541
Let's expand the example a bit. Imagine that we have several files
and search expressions. Making a copy of the original file:
$ cp sample_log_anonymized.log sample_log_anonymized_1.log
Also add the required string PAM: Authentication failure . To perform
search for both files, we will need another loop:
term1 =
[Link]('ACLLOG-5-ACLLOG_FLOW_INTERVAL') term2 = [Link]('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 [Link]():
if [Link](term1, line) or [Link](term2, line):
print(line)
As there are more search expressions and
messages, we see a difference in performance:
236 Chapter 6. Network Security using Python
$ python3 python_re_search_2.py
2016 Jun 5 [Link] NEXUS-A
%DAEMON-3-SYSTEM_MSG: error: PAM: Authentication
failure for illegal user AAA from [Link]
- sshd[4425] 2016 Sep 14 [Link].210
NEXUS-A %DAEMON-3-SYSTEM_MSG: error: PAM:
Authentication failure for illegal user AAA from
[Link] - sshd[2811] <omitted> 2014
Jun 29 [Link] Nexus-7000
%ACLLOG-5-ACLLOG_FLOW_INTERVAL: Src IP: 10.1
0.10.1, 2014 Jun 29 [Link] Nexus-7000
%ACLLOG-5-ACLLOG_FLOW_INTERVAL: Src IP: 10.1
0.10.1, <omitted>
Time Elapsed: [Link].330697
Of course, any performance optimization is a pursuit of an
unattainable ideal, and the speed of the script sometimes
depends on your hardware. But the bottom line is that you
should regularly audit your log files using Python to identify
signs of a potential security breach as early as possible.
We've covered some of the key ways to improve network
security using Python, but there are other powerful tools
that can make this process easier and more efficient. In the
last section of this chapter, we'll look at some of them.
Other tools
There are other network security support
tools that can be used and automated using Python.
Let's look at the two most common ones.
Private VLANs
Virtual Local Area Networks (VLANs)
they have existed for a long time. This is essentially a broadcast
domain, where all hosts can be connected to a single switch, but are divided
into different domains; this allows you to group them according to
Other tools 237
depends on which of them see each other through the broadcast
channel. Let's look at an example of a network divided into subnets.
For example, if you take a corporate building, each floor is likely to
have a separate subnet: on the second floor [Link]/24
On the first floor,
[Link]/24
In this case, each floor receives a block of addresses ./24This creates
clear boundaries in the physical and logical networks. A host that wants
to go outside its own subnet must first pass through a network
-level gateway, which may have an access list for security reasons.
But what if different departments are on the same floor? For
example, the second floor is reserved for financiers and sales managers,
and you don't want to host their hosts in the same broadcast domain.
You can split a subnet, but this can be a tedious process and break
the standard structure that you already have. A private VLAN will
help in this situation. Private VLANs actually allow you to split an
existing local network into subnets. They fall into three categories.
z
P-порт (Promiscuous port). This port is allowed to exchange
link-level frames with any other VLAN port; this port is usually
connected to a network-level router.
z I-порт (Isolated port). A port is only allowed to communicate with P
ports, and they usually belong to hosts that are not supposed to
communicate with other hosts on the same LAN.
z
C-порт (Community port). This port is allowed to communicate
with other C ports in the same "community", as well as with P ports.
We can again turn to Ansible or any other Python tools
that are suitable for this task. You should already have
enough experience and confidence in your abilitiesx to
automate this capability, so I won't give instructions here.
Getting familiar with VLANs is useful in situations where you
need to additionally isolate LAN ports at the link layer.
UFW and Python
As already mentioned, UFW is a client interface for Iptables on
Ubuntu hosts. Quick instructions on how to install and use it:
$ sudo apt-get install ufw
$ sudo ufw status
238 Chapter 6. Network Security using Python
$ sudo ufw default outgoing
$ sudo ufw allow 22/tcp $ sudo
ufw allow www $ sudo
ufw default deny incoming
You can check the UFW status like this:
$ 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)
The advantage of UFW: It provides a simple interface for creating
Iptables rules that would be difficult to describe manually. To make things
even easier, you can use additional Python tools along with UFW.
z The Ansible UFW module will help you optimize your operations. More
information is available at [Link]
. Since the Ansible framework is written in Python, we can [Link]
even look at the source code of the module. For more information
, go to
[Link]
blob/main/plugins/modules/system/[Link]
z. There are UFW wrapper modules that provide an API ( [Link]
com/dhj/easyufw ). They can make integration easier if you need to.-
we need to change the UFW rules depending on
certain events. The UFW module itself is written
z in Python. Therefore, if you need to expand your
existing command sets, you can apply your knowledge
of this language. More information here: [Link] .
UFW is a good security tool for your network servers.
Additional material
Python is used in many areas related to
bezopasnost'. I would like to recommend two books to you:
Resume 239
O'Connor T. J. Violent Python: A cookbook for hackers, forensic analysts, z
penetration testers and security engineers;
Justin Z., Tim A. Black Hat Python. Programming for hackers z
St. Petersburg: St. Petersburg, 2022 (Black Hat Python: Python
programming for hackers and pentesters; Justin Seitz).
I myself actively use Python in our research work on Distributed
Denial of Service (
DDoS)attacks in A10 Networks. If you want to learn more, download
the free guide:
[Link]
al_of_Service_DDoS_Practical_Detection_and_Defense.pdf .
Resume
This chapter focused on network security with Python. We
used the Cisco VIRL tool to create a lab with servers and
network devices like NX-OSv and IOSv. We also gave a brief
overview of the Scapy project, which allows you to create
packages from scratch. You can use Scapy interactively for
quick testing. You can save interactive steps to a file to make
testing more scalable. This tool is suitable for checking
networks for penetration and searching for known
vulnerabilities. We also discussed network protection with
IP and MAC address-based access lists. This is usually the
first line of defense in networks. With Ansible , you can
quickly and consistently deploy access lists to different
devices. Syslog and other log files contain useful information
that we need to check regularly in order to detect signs of
intrusion in a timely manner. Regular expressions in Python
can be used to systematically search for known log entries
that signal security events that require our attention. For
additional protection, you can use virtual LANs and UFWS. In
Chapter 7, we'll look at how to use Python to monitor your
network. Monitoring allows you to find out what is going
on in the network and understand what state it is in.