So everytime I connect to a wifi hotspot (either from a friend, institution, or street), I wonder what kind of environment is my laptop “seeing”.
In fact, once in a while I execute a tcpdump to my network interface, and I see all kind of traffic even when all browsers, or network services are completely off in my computer.

So I was wondering how to catch some “events” of interest that my interface is catching, and I thought of service scans.
I would not like anyone to scan me without my conscent, unless I am in some sort of tournament or defcon-like event, where it’s implicit that this kind of things happen just “for fun”.

So after investigating previous works on hardening a computer against network scans (e.g. using NMap), I found that a simplistic/minimalistic setup for that is using iptables.
I very much liked that approach because it gives me an excuse to learn about iptables :D :D

So in this post, and others following I want to summarize and share what I got to learn about iptables-vs-nmap stuff, and then how to use it in a virtual lab (using Docker).

Since we are dealing with incoming packets (i.e. from a remote host to the local one that we are trying to defend), all iptables rules below are inserted into the INPUT chain.


Block scan attempts:

Scans like:

nmap -sN <ip>

are known as NULL scans sends TCP packets with no flags to the host with IP address <ip>, and is usually executed to find out which ports are closed.
One way to block packets of this kind of scans, is setting the rules:

iptables -I INPUT -p tcp --tcp-flags ALL NONE -m recent --name banned__scan_null --set -j DROP

which basically uses the iptables-module recent to create a temporary blacklist (lasting 60 seconds by default) of source addresses that matches this rule.


Scans like:

nmap -sS <ip>

are called SYN scans, and sends TCP packets with only the SYN flag.
This flag is usually used in the TCP protocol to start a “conversation” with another host.
It’s like saying “hey, let’s talk”, and then wait for the other to either say “hey, yeah sure” or “sorry, i’m not available” or just silence.
Trying to block this type of scans is not easy since it’s its the standard way to communicate between hosts; that is, trying to block this type of scans to a given <port> is like blocking any connection attempts to such <port>.
So well adopt a criteria here, where basically one assumes that the scan is being performed against a lot of ports, including those that are rarely used or which belong to “deprecated” services (e.g. telnet).

So main idea is to block such rare (or not-likely used) ports, so that we can detect these “massive” scans against a lot of ports.
The rule to block ports 23 (usually the telnet service) and 79 for instance is:

iptables -I INPUT -p tcp -m multiport --dports 23,79 --tcp-flags ALL SYN -m recent --name banned__scan_syn --set -j DROP

For rules that block other type of scans, see here, where they also explain how NMap can sometimes give misleading results since it interprets the data.

To also log scans attempts, you can:

iptables -I INPUT -p tcp --tcp-flags ALL NONE -m limit --limit 3/m --limit-burst 5 -j LOG --log-prefix "[fw::null-scan] "

for NULL scans, and

iptables -I INPUT -p tcp -m multiport --dports 23,79 --tcp-flags ALL SYN -m limit --limit 3/m --limit-burst 5 -j LOG --log-prefix "[fw::syn-scan] "

for SYN scans.


IpTables Logs:

So far we just blocked the scans , but we can also log data about the “attacker” too (if scanning is considered an attack :P).
So let’s get a bit familiar with the logs that are generated by the iptables rules above.
If you are using systemd, you can query with journalctl -b and grep that with the strings used in the rules (see --log-prefix arguments).

For example, if we scan our local host from a remote host using:

sudo nmap -sS <victim_host> -p23

to browse logs of SYN scan attempts (in the host <victim_host>), go like so:

$ journalctl -b | grep "\[fw::syn-scan]"
Nov 19 20:52:49 victim_host kernel: [fw::syn-scan] IN=br-72d5c02de577 OUT= PHYSIN=veth257969b MAC=02:42:a0:a1:a2:a3:02:42:13:b0:b1:b2:c0:c1 SRC=172.19.0.2 DST=172.19.0.254 LEN=44 TOS=0x00 PREC=0x00 TTL=50 ID=8039 PROTO=TCP SPT=45757 DPT=23 WINDOW=1024 RES=0x00 SYN URGP=0

telling us that:

  • the packet was received by the network interface 72d5c02de577 (which is a bridge virtual interface because I scanned from a Docker container)
  • the MAC address of the interface that received the packet is 02:42:a0:a1:a2:a3, and the one that sent the packet is
  • the IP of the source is 172.19.0.2 (sender), and the destination is 02:42:13:b0:b1:b2 (receiver)
  • the TOS (Type of Service; which is a 8-bit number, so ranges from 0 to 255) code is 0x00. This can be used to set a given value and make the packet tracable across routers; which is nice for debugging.
  • the destination port is DPT=23; the source port SPT is different maybe because of how Docker handles port mapping.

This info is very useful if later you want to fine-tune the iptables rules with other speifiec (e.g. input/output interface, IP address, or MAC).
Of course, this info is useful to trace the host that is scanning you too!!


Practice with Docker containers:

Now shit gets interesting, since we can apply this knowledge to a “Docker lab” (see my other post).
This way we can practice different hardening setups using iptables in spare containers, testing with different services.

The only problem with using the above, is that LOG support is disabled in containers (see here) at kernel level.
The good news is that we can set iptables rules for the container from outside the container!
I know, that sounds a little dirty but works! and can be coordinated in a script to give more consistency to this approach.
Basically, what I mean is that, given a running container CNAME1, we can set the rules in the host using the bridged network interface br-a1a2a3 that the container CNAME1 is using.
For instance, to dynamically block incoming SYN scans in the interface br-a1a2a3, with destination ports 22, 23 or 80:

iptables -I FORWARD -i br-a1a2a3 -m physdev --physdev-is-bridged \
    -p tcp -m multiport --dports 22,23,80 --tcp-flags ALL SYN \
    -m recent --name banned__scan_syn --set \
    -j DROP

which is very similar to the SYN-rule above but except for the first line that specifies the interface and makes sure to match a bridged interface.
Now you can see that logs are generated in host if you scan CNAME1 from another container.

Now the natural question is: how in the hell I am supposed to know which interface is associated to a specific container??
I think that’s not a trivial question at all; at least not there’s no direct answer when googling or duckducking! :P
So to save some time, it’s a combination of this commands:

$ docker container inspect CNAME1 | grep NetworkID 
$ docker network ls
$ ifconfig -a

I wrote a bash function (see the docker_network_iface_from_container() function here) to get this association right away.

Knowing this association is useful when we can’t (or don’t want to) install iptables on the containers; or also when we want to analyze the traffic (with tcpdump for ex.) going to/from that container!