Table of contents
- Introduction
- Learning objectives
- Logistics
- A stateful firewall
- Exploring
conntrack
- Motivating thought experiment
Introduction
So far, in creating our firewalls, we were only looking at single packets coming into our system, we do not retain any state about the packets or the connections that are established throughout (outside of simply couting packets). This is referred to as stateless firewall.
In this concept lab, we would like to augment our firewalls to retain state about the packets that are sent through it, and to make decisions based on the state of certain connections and some packet manipulations done.
Learning objectives
At the end of this concept lab, you should be able to:
- Define how a stateful firewall works in the context of a Linux box.
- Experiment with different stateful rules that are based on network connections, rather than individual packets.
Logistics
Getting the configuration
To start with this concept lab, login to the class server, and navigate to your netsec-labs-username
directory. Grab the latest updates using:
(class-server) $ git fetch upstream
(class-server) $ git pull upstream main
A folder called statefulfw
should show up in your directory, that is where you will do most of your lab.
Patching the docker file
Before starting here, please make sure that your experiments from all other labs are down. To do so, navigate back to the latest lab directory and do docker compose down
.
I have updated the patch script to no longer ask you for your username and subnet, it will try to extract those on its own and print out your subnet (it is the same on as the one announced on the Moodle page). Also, it now generates scripts for you to connect to your hosts quickly.
To do so, in the statefulfw
directory, run the patch script:
(class-server) $ ./patch_docker_compose.sh
Attempting to fetch subnet automatically...
Found your subnet, it is 10.10
Done...
If you had already patched your script, you will see something like this:
(class-server) $ ./patch_docker_compose.sh
Attempting to fetch subnet automatically...
Found your subnet, it is 10.10
[ERROR] ########################################################################
[ERROR] # It looks like your docker-compose.yml file has already been patched. #
[ERROR] # #
[ERROR] # If you are having issues bringing up the environment, it means it is #
[ERROR] # still in use. #
[ERROR] # #
[ERROR] # Try to take down the experiment first, then bring it up again. #
[ERROR] # To bring it down: docker compose down #
[ERROR] # To bring it up: docker compose up -d #
[ERROR] ########################################################################
If for some reason, the script fails to find your subnet, you can override its behavior by providing your subnet on the command line:
(class-server) $ ./patch_docker_compose.sh SUBNET
If all goes well, you should also see two new files in your directory: connct_client.sh
and connect_server.sh
. You can use these scripts to directly connect to the desired host, without having to type the whole docker container exec -it
command. Finally, I have also adjust the container’s hostnames to make it easier for you to identify which is which.
For example, to connect to client
, you can use:
$ ./connect_client.sh
┌──(root㉿client)-[/]
└─#
Hopefully, that would make things a bit easier for you.
If you are unable to execute a script due to a permissions issue, then try the following $ chmod +x <script name.sh>
to make it executable and try again.
In the remainder of this document, I will not be using your specific prefixes and subnets. For example, when I refer to client
, you should replace that with user-client
where user
is your RHIT username. Similarly, I will be using 10.10.0
as the default subnet, you should replace that in all IP addresses with your own subnet. For example, if your subnet is 10.11.0
, then replace the IP address 10.10.0.1
with 10.11.0.1
.
A stateful firewall
We will start by looking at the possible connection tracking techniques that we can employ to make routing decisions based on the states of connections, rather than individual packets.
At this point, all machines on either subnet can access and reach each other. To confirm that, grab a terminal window on clien1
and try to telnet
into the server (username is root
and password is netsec
). You should be able to establish the connection and obtain a shell on the server.
Next, we would like to perform actions on a connection-basis rather than on a packet basis. First, using the techniques from the previous two concept labs, create a nftables
table (I called mine fw_tbl
) and in it, create a chain on the forward
hook with its default action being drop
.
Check if your table and chain are created using nft list table fw_tbl
. We don’t have any rules yet, we have only set up the chain. Before you install the table, please grab a terminal on either of the clients, and start a telnet
session to the server.
(client1) $ telnet server
Trying 10.10.1.4...
Connected to server.
Escape character is '^]'.
Linux 6.1.0-10-amd64 (server) (pts/1)
server login: root
Password:
Linux server 6.1.0-10-amd64 #1 SMP PREEMPT_DYNAMIC Debian 6.1.37-1 (2023-07-03) x86_64
The programs included with the Kali GNU/Linux system are free software;
the exact distribution terms for each program are described in the
individual files in /usr/share/doc/*/copyright.
Kali GNU/Linux comes with ABSOLUTELY NO WARRANTY, to the extent
permitted by applicable law.
Last login: Thu Feb 1 16:07:13 UTC 2024 from 10.10.0.4 on pts/1
┏━(Message from Kali developers)
┃
┃ This is a minimal installation of Kali Linux, you likely
┃ want to install supplementary tools. Learn how:
┃ ⇒ https://www.kali.org/docs/troubleshooting/common-minimum-setup/
┃
┗━(Run: “touch ~/.hushlogin” to hide this message)
┌──(root㉿server)-[~]
└─# ls
┌──(root㉿server)-[~]
└─#
Please keep this session alive before you move on to the next step.
Next, let’s add the following rule to out table (please adjust the table name and chain name to the ones you have chosen). In my script, I added the following (my table is called fw_tbl
and my chain is called fwd_chain
):
add rule fw_tbl fwd_chain ct state established,related counter accept
Question sheet
Now, let’s find out what this rule has done. Go back to the telnet session you had already started on client1
. Try to use that telnet
session and check to see if you can still access the server.
From client2
, grab a terminal and attempt to start a new telnet
session on the server using telnet server
and see if you can reach the server. Also try to reach the server using ssh
and ping
.
Is the
telnet
fromclient1
still active?Can you access the
server
fromclient2
?
Next, get another terminal on client1
(do not kill the telnet session) and attempt to access the server again. First, attempt to ping the server using ping -c1 server
.
Are you able to
ping
the server fromclient1
?Also, try to start a new
telnet
session form theclient1
, are you able to do so?
Next, kill the telnet
session on client1
and then attempt to restart it immediately.
- Are you able to reestablish the
telnet
connection fromclient1
to theserver?
Based on all of your observations from above, answer the following question:
- What do you think the rule
ct state established,related counter accept
is doing?
You might find listing the table to view the counter values very useful.
Finally, before you kill the session and delete the table rules, on the firewall, run a tcpdump
capture on the interface eth0
and capture TCP traffic (something like tcpdump -i eth0 tcp -w /volumes/step1.pcap
) and then attempt the telnet
session from client1
to the server again. Please keep this packet capture handy for the next steps.
Exploring conntrack
What allowed us to do the above tracking and maintenance of state is a Linux kernel utility called conntrack
. As its name suggests, it allows us to track connection seen by the kernel as they are passing through, making some heuristics in order to classify packets as being part of a connection or not.
In our rule above, we used the ct state
match option to look at the connection. ct state
can have one of the possible values:
new
: packets on a new connection have only be flowing in one direction, so the connection is not yet established but might be in the process of doing so.established
: the connection has valid packets traveling in both directions between a pair of hosts. For TCP, this means we have already established the three-way handshake we discussed in a previous concept lab.related
: this is useful in the context of some protocols (like HTTP and FTP) that might open several connections as part of the same session.invalid
: packets from an invalid session do not follow a typical expected behavior of a connection.
Experiment 1
Let’s experiment a bit with our previous rule by switching the policies. We would like our firewall to accept
packets by default but drop established or related connections (this is a bit idiotic by let’s try it).
Modify your firewall rules from the previous section to now accept all packets except those part of an established connection. To test things out, first clear our your firewall by deleting the table, then create an active telnet
session between client1
and the server
prior to installing the rules.
Question sheet
Before you run your experiment, please answer the following question:
- What do you expect the behavior of this firewall rule to be?
Next, while the telnet session is still active, install the firewall rules on the firewall container, then answer the following questions:
- What happens to the active
telnet
session (you can try to input anything or run any command to test it)? Why do you think that happened?
Next, let’s try to establish a new connection on from either clients to the server. From client2
, try to telnet server
to attempt to establish the connection.
- Was the
telnet
connection setup successful?
Let’s examine what happened even further. On the firewall container, run a packet capture on the eth1
interface (i.e., the one connected to the server subnet) and examine the packets that are flowing through. Then, please answer the following question about the telnet
connection.
Does the SYN packets sent from the client to the server reach the server?
Does the server reply to the packet? And does that packet ever make it back to the client?
Based on your answers to the above two questions, explain the difference between this experiment (where we accept everything except established connections) and the one from the first section (where we drop everything except established connections). Specifically, we are interested in the answer to the question of when are packets dropped by the firewall?
You will find it useful to compare the packet capture from the previous step with this one to gain better clarity of where things are dropped.
Hint: The firewall is connected to the clients subnet on
eth0
and to the server and workstation subnet oneth1
.
Experiment 2
In this experiment, we would like to further monitor and view the connection directions. conntrack
allows us to do so using the ct direction
match rule, which matches one of two possible values:
original
: this will match packets origination from the host who initiates the connection.reply
: this will match packets originating from the host who replies back to the initiator.
Let’s examine this through an experiment. First, delete the table you created in experiment 1 and let’s create a new one, with the same chain, but we will change the rule. Make sure your default action in this chain to accept
.
For the rules in this case, use the following in your script:
add rule fw_tbl fwd_chain ct state established counter
add rule fw_tbl fwd_chain ct state established ct direction original counter
Next, install the firewall rules and then start a netcat
session from any of the clients to the server. We will use netcat
since it’s a bit simpler and not as verbose as telnet
. So start a netcat
listening server on the server
container using nc -n -v -l 9090
. Before you connect from the client, grab another terminal on the server and start a packet capture (tcpdump -i eth0 tcp
), no need to write it to a file unless you prefer it this way.
Question sheet
With your firewall rules installed and your packet capture running on the server, connect the netcat
server from either of the client containers, do not send any packets after the connection is established.
On the firewall, observe the content of the table (nft list table fw_tbl
). Answer the following questions.
How many packets show up in the
established
rule?How many packets show up in the
ct direction original
rule?How many packets show up in the packet capture on the
server
?Do the number of those packets add up? If not, why do you think so?
Task: Count all packets
In this task, you will need to modify the firewall rules above to capture all of the following:
The packet first sent by the client to establish the connection, i.e., the first SYN packet in the case of TCP.
The reply packet sent back by the server in response to the
SYN
packet.Any packets exchanged between the client and the server in the
netcat
session.The number of packets capture by the firewall should match the number of packets capture on
tcpdump
on the server side.
Hint: To write the correct rules about this, you will need to ask yourself the following question: what is the state and direction of each packet sent by the client and the server in the case of a netcat
connection.
Here’s a sample output from my case when establishing a netcat
connection (and not sending anything else). I added four rules to do this (you might need less or more depending on how you do it).
table ip fw_tbl {
chain fwd_chain {
type filter hook forward priority filter; policy accept;
<Rule 1> packets 2 bytes 112
<Rule 2> packets 1 bytes 52
<Rule 3> packets 1 bytes 60
<Rule 4> packets 1 bytes 60
}
}
In total, there were three packets exchanged (SYN
, SYN/ACK
, and then ACK
) but one of my rules (the first one) double counts one of those. So in total, I had three packets that all showed up in the firewall and in the packet capture.
Motivating thought experiment
Say now you are designing your network and you have a webserver running on TCP port 80. Since port 80 is a standard port, it is easy for attacker to find out about it by doing a simple network scan, for example using nmap
. But we don’t want that to happen, we would like to block port scans from seeing the presence of port 80 on our protected network.
The first thing you can do is install a firewall at the perimeter of the subnetwork hosting the webserver, but that is not enough since port 80 is still exposed to the outside and still be reached by attackers attempting to perform a port scan. We need something more.
In the space below, describe a way to hide port 80 away from everyone except those that really know about it. Please note that we cannot filter based on IPv4
addresses since we cannot really tell from where our clients might be coming from, so that is off the table.
Here’s an analogy. I am hiding from CSSE332
students in my office and I do not want to open the door except for students from the network security class, can you suggest a way for me to only open the door is I know that the student at the door is not a CSSE332
student and rather one from this class?