13

Is it possible to prevent nmap from observing my machine entirely? After dropping all incoming connections with iptables port scans return as "filtered". It'd prefer if nmap couldn't see what ports existed at all. Is this possible?

The below solutions don't seem to work:

http://sharadchhetri.com/2013/06/15/how-to-protect-from-port-scanning-and-smurf-attack-in-linux-server-by-iptables/

https://dangertux.wordpress.com/2011/09/18/defeating-port-scans-using-iptables/

http://prithak.blogspot.de/2011/12/blocking-nmap-scans-with-pf-and.html

If it's not possible to keep nmap from seeing my device, would it be possible to rate-limit so that nmap takes a REALLY long time to fully scan my IP?

5
  • What OS are you using? The first link you provided should work if your OS is actually using iptables. If it's a RHEL 7.x derivative, then it would need to be edited to use firewalld or iptables would need enabling and firewalld disabling Feb 15, 2017 at 12:46
  • 1
    What do you think 'filtered' means? Add --reason to see why, and I bet they all say "no response." You can't get more silent than no response. Feb 15, 2017 at 13:52
  • @bonsaiviking, my goal (for example) is to keep nmap from learning I have SSH configured on port 43245. By dropping all INPUT, an attacker can still identify an SSH server on my machine. I'm trying to prevent that identification from happening. Feb 15, 2017 at 14:35
  • @RobotJohnny, I'm using Ubuntu 16 :) Feb 15, 2017 at 14:36
  • You seem to have a fundamental misunderstanding of TCP. If a host with a given address responds in any way to any remote request, the requester knows that a host at that address exists. If the host exists, that host will have a port 22, will have a port 43245 -- will have all valid TCP ports. If no service is listening on a port, TCP will, by design, send a rejection response. You can configure a firewall to suppress that response, but again, if any other service on any other port responds, the requester knows the host exists. Feb 15, 2017 at 16:21

5 Answers 5

24

Simple rate limit is not enough because nmap increases scan delay when it hits rate limit. Here is what you can do best with iptables.

First create ipset lists

ipset create port_scanners hash:ip family inet hashsize 32768 maxelem 65536 timeout 600
ipset create scanned_ports hash:ip,port family inet hashsize 32768 maxelem 65536 timeout 60

And iptables rules

iptables -A INPUT -m state --state INVALID -j DROP
iptables -A INPUT -m state --state NEW -m set ! --match-set scanned_ports src,dst -m hashlimit --hashlimit-above 1/hour --hashlimit-burst 5 --hashlimit-mode srcip --hashlimit-name portscan --hashlimit-htable-expire 10000 -j SET --add-set port_scanners src --exist
iptables -A INPUT -m state --state NEW -m set --match-set port_scanners src -j DROP
iptables -A INPUT -m state --state NEW -j SET --add-set scanned_ports src,dst

How this works:

Here we store scanned ports in scanned_ports set and we only count newly scanned ports on our hashlimit rule. If a scanner send packets to 5 different port(see --hashlimit-burst 5) that means it is a probably scanner so we will add it to port_scanners set.

Timeout of port_scanners is the block time of scanners(10 minutes in that example). It will start counting from beginning (see --exist) till attacker stop scan for 10 seconds (see --hashlimit-htable-expire 10000)

You can set these parameters to most proper values for you.

Be aware of that someone can make any IP blocked by just make scan as spoofing. I suggest you don't set block timeout too long.

Addition:

If you want to add a whitelist, create a whitelisted list

ipset create whitelisted hash:net

and change drop rule with that

iptables -A INPUT -m state --state NEW -m set --match-set port_scanners src -m set ! --match-set whitelisted src -j DROP
6
  • 7
    This should be the accepted answer.
    – pa4080
    Oct 14, 2018 at 12:28
  • How can I add an IP to ignore/do not block by these rules?
    – Feriman
    Feb 1, 2021 at 13:17
  • I updated answer
    – ibrahim
    Feb 5, 2021 at 8:57
  • Thank you, your sample contains a mistake --match-set scanned_ports src,dst is matching against source and destination IP but should be matching against source IP and destination port, Also I converted your code to nftables (see my answer) and have accounted for that mistake. Apr 12 at 10:03
  • 1
    @metablaster It matches both port and ip addresses since ipset holds ip-port pair
    – ibrahim
    Apr 26 at 11:19
9

If you want a service (such as SSH) to be available and able to be used, then Nmap will be able to find it. Generally speaking, port scans are not a threat; your security should not depend on an attacker not knowing what services are running. Using a non-standard port for SSH is mainly useful for log noise reduction, since there is so much automated brute-forcing done on the default port 22.

Your primary goals from a security standpoint should be: Know/Predict, Prevent, Detect, Respond, and Recover. Here's how port scanning relates to these:

Know/Predict: Know what assets you have and what attackers will go for. Port scan yourself to see your exposure. Understand what port scans can and cannot do; they are not magical hacking fairy dust.

Prevent: Use a firewall to prevent access to ports/services that should not be public. Restrict access to known IP addresses. Move sensitive data and servers behind the network perimeter and control access with a VPN or other access control. Rate limiting is not prevention, only delay.

Detect: Monitor logs for port scan, brute force, and other indications of attack. Understand what is normal background noise and what actually constitutes a threat. Set up alerts for indications of compromise.

Respond: Have a plan for dealing with a security breach. Set up automated defenses like fail2ban to respond to threats. Rate limiting can be a response here, but does it really prevent anything?

Recover: Have a recovery plan. Make regular backups and test restoring from backup.

5

Here is what nmap's documentation says about the "filtered" state:

The state is either open, filtered, closed, or unfiltered. Open means that an application on the target machine is listening for connections/packets on that port. Filtered means that a firewall, filter, or other network obstacle is blocking the port so that Nmap cannot tell whether it is open or closed. Closed ports have no application listening on them, though they could open up at any time. Ports are classified as unfiltered when they are responsive to Nmap's probes, but Nmap cannot determine whether they are open or closed. Nmap reports the state combinations open|filtered and closed|filtered when it cannot determine which of the two states describe a port.

It seems that the normal behavior of a "closed" (i.e., a reachable port, but where no server is listening), is closer to the iptables REJECT action than to DROP, and that nmap's "filtered" diagnostic recognizes DROP (where the connection eventually times out, because of total silence from the server being examined, instead of being closed immediately, as would happen with REJECT.)

So I would suggest trying with REJECT instead of DROP, and seeing if the scan results are more to your liking.

1
  • Thank you for your answer! I forgot to mention I also tried REJECT instead of DROP. Rejecting input also returns "filtered" in nmap scans. Feb 15, 2017 at 15:51
1

Based on ibrahim answer, I created a script to configure and keep these firewall rules always in iptable. By default these iptable rules lost after system restart. This script will run these iptable commands immediately after reboot.

You can reach this one on GitHub, the script name is Portscan Protection

1

The accepted answer provided by @ibrahim is very good but iptables is deprecated and modern systems use nftables instead.

So if you guys wonder how to convert his iptables code to nftables then below is a complete and tested solution including comments.

NOTE: This sample works only for IPv4 and TCP but potential attacker might as well use other protocols such as UDP or do the attack over IPv6, therefore you need to do a bit of additional work here to account for that as well.

File: attacker.nft
To run the script: sudo nft -f ./attacker.nft

#!/usr/sbin/nft -f

#
# IPv4 port scanning and information gathering detection
#
add table filter_4

# Sees all incoming packets, before any routing decision has been made
# Packets may be addressed to the local or remote systems
add chain filter_4 prerouting_4 {
    # -175 = After conntrack and before mangle
    type filter hook prerouting priority -175; policy accept;
}

# Port scanning detection chain
add chain filter_4 attacker_4 {
    comment "IPv4 port scanning detection chain"
}

# Prerouting filter
add rule filter_4 prerouting_4 jump attacker_4

# IPv4 attack count
add counter filter_4 attacker_count_4 {
    comment "Count of attacks over IPv4"
}

# A record of current attackers on cooldown
# NOTE: Do not set too high timeout because the attacker might as well spoof it's IP
add set filter_4 port_scanners_4 {
    flags dynamic
    type ipv4_addr
    timeout 10m
    size 256
    comment "IP of the attacker performing port scan"
}

# A record of source IP and local ports being accessed
add set filter_4 scanned_ports_4 {
    type ipv4_addr . inet_service
    flags dynamic
    timeout 1m
    size 256
    comment "Potential attacker IP and local port accessed"
}

# Detect port scanning and record attackers IP
# meter: iptables equivalent is hashlimit
# portscan: meter name
# timeout: After how many seconds do meter entries expire
# limit rate over: Match if the rate is above amount
# burst: Maximum initial number of packets to match: this number gets recharged by one every time the limit specified above is not reached, up to this number
# the default is 5, if the entry expires, the burst value is reset too
add rule filter_4 attacker_4 ct state new ip saddr . tcp dport != @scanned_ports_4 meter portscan { ip saddr timeout 10s limit rate over 1/hour burst 5 packets } update @port_scanners_4 { ip saddr }

# Drop packets originating from attacker's IP
add rule filter_4 attacker_4 ct state new ip saddr == @port_scanners_4 log flags all prefix "drop attacker_4 TCP port scanner: " counter name attacker_count_4 drop

# Keep a record of potential attacker IP and local port being probed
add rule filter_4 attacker_4 ct state new update @scanned_ports_4 { ip saddr . tcp dport }

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .