Jump to content
×
×
  • Create New...

PF Firewall and other AntiDDoS measures


Shogun

Recommended Posts

  • Premium

Update April 2020 I have modified the rules to allow ipv6 connections, and added some comments.

Update: January 2021 New more exhaustive and optimized config, and more explanations

 

Hello,
 

As I had just posted the file without any explanation of it, which is not really useful unless you are already familiar with pf, I have added a little tutorial.

 

Here is a sample pf.conf file that you can use as a base to create your own for your FreeBSD server. It assumes you are not using UDP as the original client does.

 

Preliminary steps

 

Add the following to /etc/rc.conf to enable pf. It will enable the pf firewall on boot and log blocked packets to /var/log/pflog. This is not a text file but a pcap file that can be opened using tcpdump -r /var/log/pflog (followed by any other flags you may want to use). For a good tcpdump cheatsheet check https://danielmiessler.com/study/tcpdump/

 

The second part is ip6addrctl which attempts to use ipv4 addresses instead of ipv6, since the game does not support ipv4.

 

# Packet Filter
pf_enable="YES"
pf_rules="/etc/pf.conf"
pflog_enable="YES"
pflog_logfile="/var/log/pflog"

# Prefer ipv4 when available
ip6addrctl_enable="YES"
ip6addrctl_verbose="NO"
ip6addrctl_policy="ipv4_prefer"


Firewalling

 

The configuration file further below must be save as /etc/pf.conf (be aware the default location for pf rules may not be here anymore; if you used my rc.conf above that should be no issue).

 

Before attempting to use it, make sure to change everything that is between brackets. You can find out the relevant parameters with the ifconfig command on FreeBSD.

 

[email protected]:/home/www $ ifconfig
igb0: flags=8843<UP,BROADCAST,RUNNING,SIMPLEX,MULTICAST> metric 0 mtu 1500
  options=e53fbb<RXCSUM,TXCSUM,VLAN_MTU,VLAN_HWTAGGING,JUMBO_MTU,VLAN_HWCSUM,TSO4,TSO6,LRO,WOL_UCAST,WOL_MCAST,WOL_MAGIC,VLAN_HWFILTER,VLAN_HWTSO,RXCSUM_IPV6,TXCSUM_IPV6>
        ether d0:40:19:d5:e3:69
        inet 57.93.137.82 netmask 0xffffff00 broadcast 57.93.137.255
        inet 57.93.114.128 netmask 0xffffffff broadcast 57.93.114.128
        inet6 fe80::d110:99ff:fec4:e469%igb0 prefixlen 64 scopeid 0x1
        inet6 2001:27d0:119:1872:: prefixlen 64
        media: Ethernet autoselect (10Gbase-T <full-duplex>)
        status: active
        nd6 options=8063<PERFORMNUD,ACCEPT_RTADV,AUTO_LINKLOCAL,NO_RADR,DEFAULTIF>

 

 

[int] is name of your external interface (ix0, igb0, etc...). It's the first thing that appears below ifconfig.

[primary_ip] is the one after the first "inet" (in this case 57.93.137.82)

[game_ip] is the one after the second "inet"  (in this case 57.93.114.128)

[ipv6] is the one you see after the second inet6.

 

Finally, edit the service_ports, game_ports and auth_ports with the ports you want to open for administrative tasks and for the game, separated by commas. At the very least, add your SSH port to service_ports or you won't be able to connect to your machine.

 

Why have 2 IPs? Easy: you cannot change your primary IP. If a DDoS brings down your machine, you can block all traffic to your secondary IP while working on protective measures through your primary IP. If all else fails you could even bind your game, or whatever you are running, to a third IP and hope the attacker doesn't notice (yeah sounds dumb but so is the people doing that stuff!). If you just have one IP, you may not be able to access your server at all.

 

So the idea is that you access SSH and other administrative stuff through the primary IP while any public facing services go through a secondary IP or several of them. The OVH firewall can help you closing unneeded ports. Closing UDP there is also a very good idea if you don't need it - if you get a UDP flood bigger than your bandwidth, PF is not going to help you. When inbound traffic saturates your link, it's already too late to do anything at the OS level - it must be done by a hardware firewall or mitigation system such as the one provided in the OVH Panel (IP menu).

 

If you prefer to use just one IP, you can just enter the same one as both primary and secondary. Or, if you want to do it properly,  remove all references to $game_ip throughout the file. Also, if you don't have ipv6 set up, comment out (#) or remove every line that mentions "inet6".

 

And finally, there is a file named /var/db/trusted_hosts which is a simple text file you can create with ee or vi.

 

This is where you can add your own IP, or the IP address of other servers such as your webserver that you want to give full access without going through the rules. Write one IP per line, and be aware that your own IP could change so don't rely on this to give you access permanently - more like a fallback in case you make a mistake.

 


 

ext_if="[int]"

set skip on lo0
set block-policy drop
set loginterface $ext_if

primary_ip="[x.x.x.x]"
game_ip="[x.x.x.x]"
ipv6="[x:x:x::]"

icmp_types = "{ echorep, unreach, squench, echoreq, timex, paramprob }"
icmp6_types = "{ unreach, toobig, timex, paramprob, echoreq, echorep, neighbradv, neighbrsol, routeradv, routersol }"

table <trusted_hosts> persist file "/var/db/trusted_hosts"
table <bad_hosts> persist

scrub in on $ext_if all fragment reassemble

martians = "{ 127.0.0.0/8, 192.168.0.0/16, 172.16.0.0/12, \
              10.0.0.0/8, 169.254.0.0/16, 192.0.2.0/24, \
              0.0.0.0/8, 240.0.0.0/4  255.255.255.255/32 \
              ::/128 ::1/128 ::ffff:0:0/96 ::/96 100::/64 \
              2001:10::/28 2001:db8::/32 fc00::/7 fe80::/10 \
              fec0::/10 ff00::/8 }"

service_ports="{ 21, 80, 443 }"

# Game
game_ports="{ 24000, 24001, 24002, 24010, 24011, 24099 }"
auth_ports="{ 28000 }"

## Set default policy ##
block return in log all

pass in quick from <trusted_hosts>

# Drop all Non-Routable Addresses
block drop in quick on $ext_if from $martians to any
block drop out quick on $ext_if from any to $martians

block in quick from <bad_hosts>

## Blocking spoofed packets
antispoof quick for $ext_if

pass proto tcp from any to any port $service_ports

pass inet proto icmp icmp-type echoreq

# Allow essential outgoing traffic
pass out on $ext_if inet proto { tcp, udp, icmp }
pass out on $ext_if inet6 proto { tcp, udp, icmp6 }
pass out on $ext_if inet6 proto icmp6 all icmp6-type echoreq keep state

# Game, we do not ratelimit here

pass proto tcp from any to any port $game_ports
pass proto tcp from any to $ipv6 port $game_ports

# Auth

pass in quick proto tcp to $game_ip port $auth_ports keep state (max-src-conn 32, max-src-conn-rate 16/3, overload <bad_hosts> flush global)
pass in quick proto tcp to $ipv6 port $auth_ports keep state (max-src-conn 32, max-src-conn-rate 16/3, overload <bad_hosts> flush global)

## allow icmp6 for getting address using IPv6 autoconfiguration from router
pass inet6 proto ipv6-icmp all icmp6-type routeradv
pass inet6 proto ipv6-icmp all icmp6-type routersol

## allow icmp6 for getting neighbor addresses
pass inet6 proto ipv6-icmp all icmp6-type neighbradv
pass inet6 proto ipv6-icmp all icmp6-type neighbrsol

## allow icmp6 echo, not required, but sometimes nice
pass in inet6 proto ipv6-icmp all icmp6-type echoreq

## pass icmp-types: unreachable, time exceeded, parameter problem
pass in inet6 proto ipv6-icmp all icmp6-type {1 3 4}

 

Using pfctl

 

pfctl as its name implies is a tool to control pf. There are plenty of cheatsheets online, but here's the most basic stuff:

 

pfctl -e Enable pf (WARNING will drop all connections including yours)

 

pfctl -d Disable pf (may have the same effect)

 

pfctl -f /etc/pf.conf Reload rules (may drop connectionds as well but usually it shouldn't)

 

pfctl -sa Show all status

 

pfctl -t bad_hosts -T flush Flush the bad_hosts table (effectively unbanning all IPs banned by the ruleset)

 

pfctl -t bad_hosts -T show Show all the IPs banned so far

 

Using tcpdump with pf

 

You may watch the packets being blocked in realtime by using tcpdump on the pflog0 interface:

 

tcpdump -i pflog0 -ttt -n -e

 

Or instead show the previously logged ones. You may probably want to filter the output with either grep or further tcpdump flags.

 

tcpdump -r /var/log/pflog -n -e

 

Extras

 

Here are some sysctl tunings that may help with certain DDoS attacks. You cann add them at the end of /etc/sysctl.conf and then type service sysctl restart at the command prompt to activate those.

I don't recommend doing so unless you're being attacked though. That's why I have commented them out - remove all the # if you actually need them at some point.

 

#net.inet.tcp.syncookies=1
#net.inet.tcp.syncache.rexmtlimit=0
#net.inet.ip.check_interface=1         # verify packet arrives on correct interface (default 0)
#net.inet.ip.process_options=0         # IP options in the incoming packets will be ignored (default 1)
#net.inet.ip.redirect=0                # do not send IP redirects (default 1)
#net.inet.ip.accept_sourceroute=0      # drop source routed packets since they can not be trusted (default 0)
#net.inet.ip.sourceroute=0             # if source routed packets are accepted the route data is ignored (default 0)
#net.inet.icmp.bmcastecho=0            # do not respond to ICMP packets sent to IP broadcast addresses (default 0)
#net.inet.icmp.maskfake=0              # do not fake reply to ICMP Address Mask Request packets (default 0)
#net.inet.icmp.maskrepl=0              # replies are not sent for ICMP address mask requests (default 0)
#net.inet.icmp.log_redirect=0          # do not log redirected ICMP packet attempts (default 0)
#net.inet.icmp.drop_redirect=1         # no redirected ICMP packets (default 0)
#net.inet.icmp.icmplim_output=1        # show "Limiting open port RST response" messages (default 1)
#net.inet.tcp.always_keepalive=0       # tcp keep alive detection for dead peers, can be spoofed (default 1)
#net.inet.tcp.drop_synfin=1            # SYN/FIN packets get dropped on initial connection (default 0)
#net.inet.tcp.fast_finwait2_recycle=1  # recycle FIN/WAIT states quickly (helps against DoS, but may cause false RST) (default 0)
#net.inet.tcp.icmp_may_rst=0           # icmp may not send RST to avoid spoofed icmp/udp floods (default 1)
#net.inet.tcp.msl=15000                # 15s maximum segment life waiting for an ACK in reply to a SYN-ACK or FIN-ACK (default 30000)
#net.inet.tcp.path_mtu_discovery=0     # disable MTU discovery since most ICMP type 3 packets are dropped by others (default 1)
#net.inet.tcp.rfc3042=0                # disable limited transmit mechanism which can slow burst transmissions (default 1)
#net.inet.tcp.sack.enable=1            # TCP Selective Acknowledgments are needed for high throughput (default 1)
#net.inet.udp.blackhole=1              # drop udp packets destined for closed sockets (default 0)
#net.inet.tcp.blackhole=2              # drop tcp packets destined for closed ports (default 0)

 

Disclaimer

 

I am by no means an expert in the subject, and I have just a vague idea about what many of the settings are actually doing. The above stuff has been helpful in real life situations, having been through literally hundreds of DDoS attacks. 

 

Be aware that pf may lock you out of your own system, and you may need to use IPMI/KVM to gain access again by disabling pf in /etc/rc.conf and then fixing the errors.

 

Use at your own risk.

 

PS: Here is a very good writeup about modern DDoS attacks:

https://www.imperva.com/docs/DS_Incapsula_The_Top_10_DDoS_Attack_Trends_ebook.pdf

Edited by Shogun
  • Metin2 Dev 3
  • Love 19
  • Love 2
  • Good 4

Currently working on my FreeBSD blog:

FreeBSD is Fun, practical tutorials and articles

⚠️                Are you under attack?               ⚠️

Link to comment
Share on other sites

  • 2 weeks later...
  • Premium

I have added a little explanation of the file on the first post. Here's also a

 

PF Quickstart Guide
 
To enable pf on your server, you can follow these steps after you have saved the pf.conf file. Be careful not to lock yourself out with wrong firewall rules, it's a good idea to add your own IP address to trusted_hosts if you are unsure of your changes.

kldload pf
pfctl -f /etc/pf.conf

If everything is correct it should display this:

No ALTQ support in kernel
ALTQ related functions disabled

To load pf on every boot add this line to /etc/rc.conf:

pf_enable="YES"

Managing the IP ban list

 

To show all banned IPs:

pfctl -t abusive_hosts -T show

To ban an IP:

pfctl -t abusive_hosts -T add 8.8.8.8

To unban an IP:

pfctl -t abusive_hosts -T delete 8.8.8.8

To unban all IPs:

pfctl -t abusive_hosts -T flush
  • Love 2

Currently working on my FreeBSD blog:

FreeBSD is Fun, practical tutorials and articles

⚠️                Are you under attack?               ⚠️

Link to comment
Share on other sites

  • 2 weeks later...
  • Premium

And the port 15000 ?

 

Port 15000 should never be publicly open! Only the game cores need to connect to it, not your users. In fact it's a security risk as anybody can create an auth server which connects to your db core and login any account they want on your server.

Currently working on my FreeBSD blog:

FreeBSD is Fun, practical tutorials and articles

⚠️                Are you under attack?               ⚠️

Link to comment
Share on other sites

 

pass in on $ext_if proto {tcp,udp} to any port $game_ports flags S/SA keep state

        (max-src-conn 30, max-src-conn-rate 15/5, overload <abusive_hosts> flush)

i think to know that the rule had to be like this

pass in on $ext_if proto {tcp,udp} to any port $game_ports flags S/SA keep state 
        (source-track rule, max-src-conn 30, max-src-conn-rate 15/5, overload <abusive_hosts> flush)

I'm not completly sure, but i think "max-src-conn" and "conn-rate" can't take affect without sourcetrackrule

Link to comment
Share on other sites

  • Premium

According to this

 

"Both of these options automatically invoke the source-track rule option and are incompatible with source-track global."

  • Love 1

Currently working on my FreeBSD blog:

FreeBSD is Fun, practical tutorials and articles

⚠️                Are you under attack?               ⚠️

Link to comment
Share on other sites

  • Premium

So i have a question. How can i approach this task. I want to reject all conections wich are for more than 3 seconds synchronizing? Or these connections which are lagging for more than 15sec?

 

I don't think that's even possible.

  • Love 1

Currently working on my FreeBSD blog:

FreeBSD is Fun, practical tutorials and articles

⚠️                Are you under attack?               ⚠️

Link to comment
Share on other sites

  • Premium

You have to change to whatever the name of the public interface is:

 

For example here my public interface is called re0

208106ff93.png.f889177c35c4f429049d3ab77

Edited by Metin2 Dev
Core X - External 2 Internal
  • Love 1

Currently working on my FreeBSD blog:

FreeBSD is Fun, practical tutorials and articles

⚠️                Are you under attack?               ⚠️

Link to comment
Share on other sites

You have to change to whatever the name of the public interface is:

 

For example here my public interface is called re0

 

208106ff93.png

 

Juan, You are showing your servers ip address.

Edited by Metin2 Dev
Core X - External 2 Internal
Link to comment
Share on other sites

  • 8 months later...
  • 1 month later...
  • Premium

A new sample config for a FreeBSD server running nginx with cloudflare and teamspeak.

 

If you want to use the bruteforce protection feature, install the sshguard-pf package (/usr/ports/security/sshguard-pf)

ext_if="igb0"
service_ports="{ 22,80,443,10011,30033 }"
udp_ports="{ 9987 }"
table <trusted_hosts> persist file "/var/db/trusted_hosts"
table <abusive_hosts> persist
table <sshguard> persist
icmp_types = "{ echoreq, unreach }"

set block-policy drop
set loginterface $ext_if
set optimization aggressive
set limit { frags 32000, states 64000 }
set skip on lo

scrub on $ext_if all random-id min-ttl 64 max-mss 1440 set-tos reliability reassemble tcp fragment reassemble

antispoof quick for { lo0 $ext_if }

block in

block in quick on $ext_if proto tcp from <sshguard> to any port 22 label "ssh bruteforce"

pass out all keep state
pass out on $ext_if all modulate state

pass in quick from <trusted_hosts>
block in quick from <abusive_hosts>

pass inet proto icmp all icmp-type $icmp_types keep state

pass in on $ext_if proto tcp to any port $service_ports flags S/SA synproxy state 
        (max-src-conn 50, max-src-conn-rate 25/5, overload <abusive_hosts> flush global)

pass in on $ext_if proto udp to any port $udp_ports keep state (max-src-states 5, max-src-conn-rate 5/5, overload <abusive_hosts> flush)

In /var/db/trusted_hosts, Cloudflare:

199.27.128.0/21
173.245.48.0/20
103.21.244.0/22
103.22.200.0/22
103.31.4.0/22
141.101.64.0/18
108.162.192.0/18
190.93.240.0/20
188.114.96.0/20
197.234.240.0/22
198.41.128.0/17
162.158.0.0/15
104.16.0.0/12
2400:cb00::/32
2606:4700::/32
2803:f800::/32
2405:b500::/32
2405:8100::/32
  • Love 2

Currently working on my FreeBSD blog:

FreeBSD is Fun, practical tutorials and articles

⚠️                Are you under attack?               ⚠️

Link to comment
Share on other sites

 

A new sample config for a FreeBSD server running nginx with cloudflare and teamspeak.

 

If you want to use the bruteforce protection feature, install the sshguard-pf package (/usr/ports/security/sshguard-pf)

ext_if="igb0"
service_ports="{ 22,80,443,10011,30033 }"
udp_ports="{ 9987 }"
table <trusted_hosts> persist file "/var/db/trusted_hosts"
table <abusive_hosts> persist
table <sshguard> persist
icmp_types = "{ echoreq, unreach }"

set block-policy drop
set loginterface $ext_if
set optimization aggressive
set limit { frags 32000, states 64000 }
set skip on lo

scrub on $ext_if all random-id min-ttl 64 max-mss 1440 set-tos reliability reassemble tcp fragment reassemble

antispoof quick for { lo0 $ext_if }

block in

block in quick on $ext_if proto tcp from <sshguard> to any port 22 label "ssh bruteforce"

pass out all keep state
pass out on $ext_if all modulate state

pass in quick from <trusted_hosts>
block in quick from <abusive_hosts>

pass inet proto icmp all icmp-type $icmp_types keep state

pass in on $ext_if proto tcp to any port $service_ports flags S/SA synproxy state 
        (max-src-conn 50, max-src-conn-rate 25/5, overload <abusive_hosts> flush global)

pass in on $ext_if proto udp to any port $udp_ports keep state (max-src-states 5, max-src-conn-rate 5/5, overload <abusive_hosts> flush)

In /var/db/trusted_hosts, Cloudflare:

199.27.128.0/21
173.245.48.0/20
103.21.244.0/22
103.22.200.0/22
103.31.4.0/22
141.101.64.0/18
108.162.192.0/18
190.93.240.0/20
188.114.96.0/20
197.234.240.0/22
198.41.128.0/17
162.158.0.0/15
104.16.0.0/12
2400:cb00::/32
2606:4700::/32
2803:f800::/32
2405:b500::/32
2405:8100::/32

Thanks fot this :D

  • Love 1
Link to comment
Share on other sites

  • Premium

You are welcome let me know if you have any question.

 

The configuration is made by MartPwnS and me based on the original one by Tim which you can find in the first post.

Currently working on my FreeBSD blog:

FreeBSD is Fun, practical tutorials and articles

⚠️                Are you under attack?               ⚠️

Link to comment
Share on other sites

  • 1 month later...

For me,don`t work I copy and paste your config and : No ALTQ support in kernel ALTQ related functions disabled /etc/pf.conf:41: syntax error pfctl: Syntax error in config file: pf rules not loaded The problems is at : Rate limits, trial and error pass in on $ext_if proto tcp to any port $service_ports flags S/SA keep state (max-src-conn 30, max-src-conn-rate 15/5, overload flush) pass in on $ext_if proto {tcp,udp} to any port $game_ports flags S/SA keep state (max-src-conn 30, max-src-conn-rate 15/5, overload flush)

Link to comment
Share on other sites

  • 4 weeks later...

The original config (and the modified one too!) uses states to keep track of open connections. The state table however, is by default limited to 10000 entries (FreeBSD 10.1 amd64), which could lead to problems during medium-/large-scale attacks, since new connections will be dropped once the table is full.

 

I usually use the following memory pool limits for PF:

set limit { states 100000, frags 20000, src-nodes 100000, table-entries 200000 }

Raising the table-entries limit is also a good idea if you have dynamically filled tables of "bad hosts", same goes for src-nodes.

 

It's also recommended to have a few whitelisted static IPs, whose traffic is passed unconditionally and stateless.

e.g.:

table <ovh> const { 213.186.33.13, 213.186.50.100 }

pass in quick on $ext_if from <ovh> to any no state
  • Love 4
Link to comment
Share on other sites

  • 2 years later...
  • Recently Browsing   0 members

    • No registered users viewing this page.


Important Information

Terms of Use / Privacy Policy / Guidelines / We have placed cookies on your device to help make this website better. You can adjust your cookie settings, otherwise we'll assume you're okay to continue.