Solved Aggressive port blocking - does it actually work?

I have a small VPS with FreeBSD and a bunch of jails with various services. Recently I stumbled upon a guide to aggressively block scanners, bots, etc. in order to proactively prevent them from even finding the moved ports for critical services like SSH, IMAP, submission, etc. I have to note that the simple measure to move the ports to non-standard ones almost completely eliminated junk traffic in logs (well, it's a small VPS).

But my patchy and rather superficial knowledge of networking in general, and firewalling in particular, makes me worry if I have really achieved what I have intended to. As I understand, the " synproxy state" directive along with " pass in on $if_ext" basically open the range of ports from the outside. And this is where I am becoming confused. My concerns are that some services I running without access from the outside (like net-mgmt/prometheus2 and some its exporters, for instance) actually listen to on ports in the range. So, I wonder, if I make my defence actually worse than just blocking all incoming traffic and employ blacklistd/fail2ban for buggers on specific ports.

Basically, I would like to learn from the opinion of the experienced fellows if such a setup is a good thing. And if is not, what is a better alternative? Oh, a sideway question: which port does the pkg use to communicate with FreeBSD servers? I mean if I block outgoing traffic, which port should I open?

Here is an excerpt from /etc/pf.conf:

Code:
#
################## 1. Macros
#
if_ext="vtnet0"
minefield = "1024:9999"
#
################## 2. Tables
#
#table <me> const file "/etc/trusted"
table <troublemakers> persist
table <rfc6890-in> { 0.0.0.0/8 127.0.0.0/8 100.64.0.0/10 169.254.0.0/16 172.16.0.0/12 \
                    192.168.0.0/16 192.0.2.0/24 240.0.0.0/4 255.255.255.255 }
table <rfc6890-out> { 0.0.0.0/8 100.64.0.0/10 169.254.0.0/16 172.16.0.0/12 192.168.0.0/16 \
                    192.0.2.0/24 192.0.2.0/24 240.0.0.0/4 255.255.255.255 }
#
################## 6. Translation
#
# Allow outbound connections from within the jails
nat on $if_ext from lo1:network to any -> ($if_ext)
##### 7. Redirection
# nginx as reverse proxy jail
rdr on $if_ext proto tcp from any to $ip_pub port { http, https } -> $ng_pr
# redirect to Mail Server
rdr on $if_ext proto tcp from any to $ip_pub port { sieve2, submission2, imaps2, smtp } -> $mail
#
################## 8. Packet filtering
#
anchor "blacklistd/*" in on $if_ext
anchor "f2b/*" in on $if_ext
########### Block unroutable addresses
block in quick on $if_ext from <rfc6890-in> to any
block return out quick on $if_ext from any to <rfc6890-out>

block quick proto tcp from <troublemakers> to any
block in

pass quick from lo0 to lo0
# Filtering loopback traffic in jails
pass quick from $ng_pr to $ng_pr
pass quick from $mds to $mds
...
pass quick from $dns to $dns
pass quick from $mail to $mail

pass out
#
########### Allow access to external IP
#
# Tagging scanners and bots
# These are just randomly probing port 22, 587, 993 is a dead-giveaway
# because we know we moved our service ports elsewhere
# Also, we have no telnet or SMB so anyone poking there is up to no good
# Tag stuff in the range $minefield as trouble as well
pass in on $if_ext proto tcp to $if_ext port { telnet, ftp-data, ftp, 22, pop3, \
   rpcbind, imap, netbios-ns, netbios-ssn, microsoft-ds, 465, 993, 995, $minefield } \
   synproxy state tag trouble
# Allow ICMP
pass in inet proto icmp to $if_ext icmp-type $icmp_types
# Allow ssh
pass in on $if_ext proto tcp to $if_ext port ssh2 tag good
pass out on $if_ext proto tcp to $if_ext port ssh2 tag good
# Allow access to the nginx proxy
pass in on $if_ext proto tcp to $ng_pr port { http, https }
# Allow access to the mail server
pass in on $if_ext proto tcp to $mail port { smtp, sieve2, submission2, imaps2 } tag good
# Put the troublemakers in the the corresponding table
pass proto tcp from any to $if_ext port { $sshd_port, $mail_tsl, $imap, $sieve, $sth_port, $minefield, \
  telnet, ftp-data, ftp, 22, pop3, rpcbind, imap, netbios-ns, netbios-ssn, microsoft-ds, 465, 993, 995 } \
  tagged trouble synproxy state \
  (max-src-conn 1, max-src-conn-rate 1/1000, overload <troublemakers> flush global)
#
########### Allow access between jails
#
# Allow access local DNS cache server
pass quick proto { tcp, udp } from { lo0, lo1 } to $dns port 53
# Allow  local mail to the mail server
pass quick proto tcp from { lo0, lo1 } to $mail port smtp
# Allow nginx proxy trafic to jails
pass quick proto tcp from $ng_pr to { $ap, $lib, $rss, $cal } port { http, 81, 8080, 8081 }
...
 
Synproxy state delegates the TCP 3 way handshake to the firewall. It is used against floods, so the flooding gets "filtered out" (simplified) and it doesn't reach the user application.

In the example I believe it is used for lower accuracy detection of hostile scanners. You can track a connection attempt on a port even if a port is closed but there is no way of knowing is it a scanner, or something "legit but misconfigured" like a simple typo on the client's part. Scanners will send SYN packets to an entire port range and then wait for SYN-ACK, interpreting that packet as port being open on the server side. But they won't ACK back because they do not want to initiate a connection really. So you rely on PF to do the SYN-ACK response for you, and if the connection is invalid, you ban them from ingress.

Tho I'm not completely sure what is it trying to achieve in PF ruleset you posted, apart from its basic feature of a degree of protection against SYN flooding.

You are on the right track on what you're trying to do, however keep in mind that for DNS and SMTP you will not be able to utilize this pattern because their ports cannot be moved easily, not if you expect them to work with Internet-routed mail transport, since the port is not in the scheme (and even if it was it would be useless since the endpoints must be public by concept).

Edit : Portknocking is my favourite technique of easy concealment of services. It's like a PIN that client needs to have. Its main advantage is that you can use standard ports but still appear to be closed if the client does not do the portknocking dance properly.

 
Synproxy state delegates the TCP 3 way handshake to the firewall
This part I have already read but was not sure that understood the implications. Thank you for the detailed explanation, appreciate that, especially "from a scanner point of view". That's really helping me to actually comprehend (at least a bit) what I do :)

Tho I'm not completely sure what is it trying to achieve
I think you do. The basic idea is to hide the moved ports better (I am not expecting an intelligent attack, that's way above my skills and knowledge anyway) to reduce useless connections and port abuse attempts.

keep in mind that for DNS and SMTP you will not be able to utilize this pattern
Yes, I understand that. DNS server strictly internal, for smtp I have fail2ban rules in place. Alas, I haven't succeed in implementing blacklistd though... It just doesn't block anything.

Portknocking is my favourite technique of easy concealment of services
I will definitely look into that.

Well, thank you for reassuring that I did not open my server to the entire Internet. That was my primary concern.

You can track a connection attempt on a port even if a port is closed
Could you please kindly kick my ignorant behind into that direction? I would rather have blocked ports AND maintain a table of hostile scanners along.
 
synproxy state moves the load from the server behind the firewall to the firewall itself; typically not a big deal, but something to keep in mind because it does use resources on the firewall. But since it's on the firewall you are protecting the services behind it.

My opinions, some may agree, some may disagree, some may do both:
A stance on a firewall is default deny. This is typically the strongest postion because by default nothing gets in, nothing gets out. Of course not very useful.

Then you start drawing pictures:
A block representing the firewall, make sure you denote the WAN (internet facing) and LAN(s) (internal facing).
Blocks representing devices behind the firewall, draw arrows to the LAN interface.

Now starts the fun.
Pretend you are a client on the LAN side and think about what you need out of the client. HTTP, HTTPS, DNS, NTP are good for a base set if you put them on both tcp and udp (and typically covers most traffic to the Internet).
That gives you a rule on the firewall along the lines of:
allow in on LAN from LANNET to any port {port list} keep state
At this point, your LAN clients should be able to talk to things on the Internet. Keep state allows the return traffic to come back in.

Services you want to expose out of the WAN become trickier. But again, start with a list of what you want to allow out and create the appropriate allow in rules (and redirections, etc), with synproxy state.
 
Well, thank you for reassuring that I did not open my server to the entire Internet. That was my primary concern.

I cannot do that since you didn't provide a full PF config, and there is a much better way than config review, which is simply to do a port scan yourself, use an online scanner, it will not trigger any rules you might've set for origin networks like CIDR of your home ISP or something like that.
 
I cannot do that since you didn't provide a full PF config
I think you just did anyway. In the above mentioned pf config I omitted only the internal jail stuff. All stuff related to inbound and outbound traffic is there. I use this config for years and recently added this "aggressive port blocking", which gave me worries. Not anymore. Thanks again!
 
well, two online scanners that I tried both show all ports on the server IP are opened... Don/t know what to think about it now...
 
set block-policy drop

When a block command happens, it can either be dropped silently or can return RST for tcp or ICMP unreachable for UDP.
I don't know what the default is, but if it's an RST, that may give a false "port is open" for a scanner.

man pf.conf

You can also add the following sysctls to forcefully drop packets for ports not open:
net.inet.tcp.blackhole=2
net.inet.udp.blackhole=1
net.inet.icmp.drop_redirect=1
net.inet.icmp.log_redirect=0
 
I don't know what the default is
for pf default is DROP, and I didn't change that.

Well, it seems like the online scanners I tried either cache the results for a given IP or use different IP each time to perform a scan from. I successfully blocked myself out of the server (and unblocked from different IP). As Zare kindly explained, scanners only do TCP 3 way handshake, which right now my firewall is happy to provide, in the same time blocking their IP right away. Which means they do not have second chance. Which means I stop worry.
 
  • Like
Reactions: mer
well, two online scanners that I tried both show all ports on the server IP are opened... Don/t know what to think about it now...

What you're looking as failure is fingerprinting of these services - if scanner can actually connect to the service and trigger the banner or the particular protocol handshake then you're looking at disclosure.
 
Back
Top