Benjamin Sago / ogham / cairnrefinery / etc…

Technical notes Use custom nmap port sets

If you have a server in the cloud, it’s important to set up a firewall to make sure you don’t inadvertently let services listen on the public internet. Maybe you use ufw or iptables, or maybe you have a cloud firewall in front of your machine entirely. No matter what you do, it’s also important to check that the firewall you’ve configured actually works.

nmap can be used for this, as well as any task involving sending packets to servers, waiting for responses, and displaying the results in a nice little table. I recommend that you save a script of your own, so you can easily re-scan when you change something.

Here’s my version of such a script:

nmap-box.sh
#!/bin/sh
exec sudo nmap "$@" \
    -PE \
    -v --reason \
    -p 22,80,443,1234,4646,4647,4648,5353,8300,8301,8302,8500,8600,8080 \
    --datadir=$HOME/path/to/my/custom/nmap/datadir

We use the following arguments:

  • The -PE argument checks the host is up with an ICMP Echo Request packet. (See the “Why the sudo?” section for more.)
  • The -v argument enables verbose output, which is useful if, for you, it takes a while to print any output. If it’s fast, you can remove it.
  • The --reason argument tells nmap to print the reason why each port is in its state.
  • The -p argument specifies the list of TCP ports to scan, as comma-separated numbers. Our port numbers here include SSH, HTTP, HTTPS, the ports used by the Hashicorp suite of services, and one (1234) that I know should never be listening, as a sort of meta-test.
  • The --datadir argument allows us to specify custom service names. (See the “Custom service names” section for more.)

Here’s an (abridged) version of what it outputs:

$ nmap-box.sh some-host
Nmap scan report for some-host
Host is up, received echo-reply ttl 47 (0.025s latency).

PORT     STATE    SERVICE    REASON
22/tcp   open     ssh        syn-ack ttl 47
80/tcp   open     http       syn-ack ttl 47
443/tcp  open     https      syn-ack ttl 47
1234/tcp filtered never-open no-response
4646/tcp filtered nomad      no-response
5353/tcp filtered madns      no-response
8080/tcp filtered http-misc  no-response
8300/tcp filtered consul     no-response
8500/tcp filtered consul     no-response
8600/tcp filtered consul     no-response

Nmap done: 1 IP address (1 host up) scanned in 1.30 seconds

From this, I can see that the rules present in my cloud firewall and the firewall on the machine itself, when put together, allow inbound SSH, HTTP, and HTTPS traffic: each port responds with a syn-ack TCP packet when probed.

I can also see that the internal services are unreachable, as they should be, as nmap reports no-response: they appear identical to port 1234, the port with nothing listening on it.

Why the sudo?

Before the status of each port is fetched, it’s important to first check that the machine is up and online. Otherwise, every port will come back as filtered with the reason no-response, and you’ll think everything’s OK when it’s not.

nmap will automatically perform this check before scanning a server. This is known as host discovery. Our script above passes the -PE argument to nmap, meaning it should first ping the server with an Echo request, and only continue scanning once it’s received a ping response.

However, in order for nmap to do this, it needs to send and receive raw network traffic, rather than relying on the simplified interfaces that the OS provides. On Unix and Unix-like systems, this usually requires you to be the root user. If you’re not root, nmap will ignore the -PE option, and perform host discovery by checking if TCP ports 80 or 443 are open instead:

Warning: You are not root -- using TCP pingscan rather than ICMP

This means that if your server isn’t serving anything on those ports, nmap will assume it’s down and scan no further. Hence, sudo.

A nice side-effect of this is that nmap can use quicker TCP SYN scan as its port scanning technique, rather than the slower TCP connect scan, meaning the script will run slightly faster.

Custom service names

In the SERVICE column of the output, nmap will list the name of the service associated with that port number. This confused me for a long time: I assumed that nmap was somehow parsing the bytes it receives in the response and using that to determine which service was running. Instead, all it’s doing is looking up the number in /etc/services and printing what’s in there.

This means that if you stray from common port numbers, nmap has very strange ideas of what services are running. By default, it calls Nomad “dots-signal” and Consul “fmtp”, despite me never having heard of either of those before, just because they happen to share the same port.

To get around this, we can create our own nmap “datadir” that contains the data nmap uses to look things like this up. Create a directory, and place inside it a file named nmap-services with contents such as the following:

/path/to/datadir/nmap-services
ssh          22/tcp
http         80/tcp
https       443/tcp

never-open 1234/tcp

nomad      4646/tcp
nomad      4647/tcp
nomad      4648/tcp
nomad      4648/udp

Then, if you pass the parent directory to nmap with the --datadir parameter as in the script above, nmap will use your custom name–service mappings.