Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

action.d/pf.conf actionban doesn't kill state after adding ban. #1924

Open
1 task done
ghost opened this issue Oct 17, 2017 · 40 comments
Open
1 task done

action.d/pf.conf actionban doesn't kill state after adding ban. #1924

ghost opened this issue Oct 17, 2017 · 40 comments

Comments

@ghost
Copy link

ghost commented Oct 17, 2017

Environment:

  • Fail2Ban version (including any possible distribution suffixes): 0.10.1
  • OS, including release name/version: FReebsd 11.1
  • Fail2Ban installed via OS/distribution mechanisms

The issue:

actionban = <pfctl> -t <tablename>-<name> -T add <ip>

actionban should invoke an additional pfctl -k <ip> to kill the state entries, since rules are only applied to new connections. Without clearing the state an attacker is able to keep the connection open and to continue the attack

Expected behavior

actionban = <pfctl> -t <tablename>-<name> -T add <ip> && <pfctl> -k <ip>

@sebres
Copy link
Contributor

sebres commented Oct 17, 2017

Without clearing the state an attacker is able to keep the connection open and to continue the attack

I doubt it, because the ban occurs after failure, recognized from corresponding application. Normally it closes the connection hereafter.

But possibly it would be really a nice feature.

@ghost
Copy link
Author

ghost commented Oct 17, 2017

Hi @sebres,
I really don't want to bother you, but I assume, that you are not familiar with the FreeBSD packet filter.
Please feel free to double-check, for example read the introduction chapter of "A no nonsene guide to PF" or simply reproduce it.

For any rule that has a "keep state" part, which is the default option, the firewall keeps information of packets that already have matched a rule in the "state table". Any new packets are checked first against the state table and will only be processed by "the rule set evaluation" if there is no match in the "state table". This will lead to a prior granted "pass .." until the state table TTL expires.

Default TTL vary from 10s to 1h, depending on current connection state.
See output of pfctl -st

@ghost
Copy link
Author

ghost commented Oct 17, 2017

@sebres
Copy link
Contributor

sebres commented Oct 17, 2017

You have not understood what I wanted to say you... BTW, I'm not against this as "enhancement". Even possibly to enable it per default...

But I'll try to explain what I meant with "Normally the application closes connection after failures":

N. sshd fail2ban
1 sshd[25038]: Invalid user root from 192.0.2.25 port 35588 [sshd] Found 192.0.2.25
2 sshd[25038]: Invalid user root from 192.0.2.25 port 35588 [sshd] Found 192.0.2.25
3 sshd[25038]: Invalid user root from 192.0.2.25 port 35588 [sshd] Found 192.0.2.25
[sshd] Ban 192.0.2.25
4 sshd[25038]: Disconnecting: Too many authentication failures for root [preauth] --

As you see, sshd will reject connection on step 4 regardless the ban, etc...
Kill a connection may be additionally done in fail2ban, but this is rather a feature (note, you may get "zomby" connections, lot a connections with TIME_WAIT state, etc).

P.S. I should not be familiar with the FreeBSD packet filter, to understand what pfctl -k <ip> will do. Note that also other actions like iptables have different modes to ban the IP, e. g. we use REJECT per default (sends a reject responce to peer) instead of DROP.

P.P.S. This is already too many issues with pf here (thus I think that also the people introducing resp. contributing to this action, are not really "familiar" with it). I tend to extract all not common actions (sorry pf is for me not the common case) into separate repository, where the maintainers / contributors can do further development and support of such actions.
We've neither the time nor the desire to do it, really.

@ghost
Copy link
Author

ghost commented Oct 17, 2017

The "state table tcp.closed timeout" is set to 90s and starts to count down after sshd disconnecting.
Any subsequent packet whithin 90s from the meanwhile banned host will be allowed by the entry in the state table. This will result in ongoing attack and, depending on your loglevel, you'll get a "IP already banned warning" in fail2ban.log.

There's no need to be offensive.
I'm wondering why you'd developed FreeBSD support in the first place, if you aren't interested in fixing or improving. That's odd, is it not? I simply wanted to support. Fail2ban has this bug for a very long time.

Since you have neither the time nor the desire, please don't bother me with an answer.

Issue closed due to lack of commitment

@ghost ghost closed this as completed Oct 17, 2017
@sebres
Copy link
Contributor

sebres commented Oct 17, 2017

You still not understood me...

There's no need to be offensive.

Hmmm... where?

if you aren't interested in fixing or improving.

Sure we're interested, but for real fixing.
You've suggested rather a workaround. Why? Because <pfctl> -k <ip> kills any connections, regardless the port, protocol, etc... This may be too aggressive for some.
Many people (not VPS and co) want to protect the services jail-based, this meant if you've specified a port for the jail, everything you do inside fail2ban should affect only connections on this port(s).
Exceptions are jails with allports like pam-generic and recidive...

Fail2ban has this bug for a very long time.

Maybe... But it is IMHO not a bug, rather matter of opinion resp. missing or insufficient functionality of FreeBSD's pf-firewall.
If you are "familiar with the FreeBSD packet filter", can you provide a syntax for pfctl to reject connection anchored to the table specified in the jail? And I meant here definitelly not the killing of all connections regardless of the port/protocol/etc...

@sebres sebres reopened this Oct 17, 2017
@sebres
Copy link
Contributor

sebres commented Oct 17, 2017

Corresponding man of pfctl:

-F states - Flush the state table (NAT and filter).

Together with:

-a anchor - Apply flags -f, -F, and -s only to the rules in the specified anchor.

Would be this not a solution (as replacement for pfctl -k <ip>)?
I mean both anchored via <pfctl> ... add <ip> && <pfctl> -F states

@sebres
Copy link
Contributor

sebres commented Oct 17, 2017

Currently I've tried the old pf-syntax (without modifications) on a freebsd-vm with generic kernel (default settings)...
Conclusion: The kill pfctl -k <ip> seems to be not necessary at all, because after execution of ... add <ip> the active connections are rejected almost immediately (in fewer milliseconds).

Simplified PoC:

# ---- SERVER/1st session: on target FreeBSD machine (with pf/fail2ban):
$ tclsh8.6
% set s [socket -server srv 8080]; proc srv {s args} {
  fconfigure $s -buffering line;  while 1 {
    puts $s test; after 1000; puts [gets $s]}}; vwait infinite

# ++++ CLIENT/1st session on source machine (test client should be banned):
$ tclsh8.6
% set s [socket test-server 8080]; fconfigure $s -buffering line; while 1 {
     puts $s [clock seconds]; after 1000; puts [clock seconds]-[gets $s]}

1508257243-test
1508257244-test
1508257245-test
...

# **** SERVER/2nd session: on target FreeBSD machine (with pf/fail2ban):
$ echo "block return quick proto tcp from <f2b-test> to any port {8080}" | pfctl -a f2b/test -f-
$ pfctl -a f2b/test -t f2b-test -T add test-client
1 table created.
1/1 addresses added.

# ++++ CLIENT/1st session:
1508257274-
error writing "sock480": software caused connection abort
    while executing
"puts $s [clock seconds]"

# **** SERVER/2nd session:
$ pfctl -a f2b/test -t f2b-test -T flush
1 addresses deleted.
$ pfctl -a f2b/test -t f2b-test -T kill
1 table deleted.

I concretes again - connection rejected almost immediately... The same affects the active ssh-sessions (just to uncomfortable to debug it).
WAIDW?

Thus closed, until more info provided.

@catsem
Copy link

catsem commented Dec 12, 2017

Strumbled over the same issue as @ghost. In my case it is an nginx webserver, where I use the nginx-auth filter. The session is not terminated with the default action at pf.conf. So the attacker can continue brute forcing quite undisturbed. @sebres I think your PoC might not be portraying this completely. For me the -k parameter is suitable for the moment. Tested with Fail2Ban 0.10.1 on FreeBSD LTS-Version (10.3-RELEASE-p25). Just my 2 cents.

@sebres
Copy link
Contributor

sebres commented Dec 12, 2017

can continue brute forcing quite undisturbed

For how long? I mean, is it sure not just the rest of the submitted buffer resp. fail2ban latency withing filter-backend?

@sebres sebres reopened this Dec 12, 2017
@catsem
Copy link

catsem commented Dec 12, 2017

For how long? I mean, is it sure not just the rest of the submitted buffer resp. fail2ban latency withing filter-backend?

Don't think this is the delay from the action execution... With -k parameter it's applied in 2-3 seconds.

2017-12-12 16:29:28,384 fail2ban.actions        [30627]: NOTICE  [web_nginx-auth-pf] Unban 10.20.30.40
2017-12-12 16:29:34,231 fail2ban.filter         [30627]: INFO    [web_nginx-auth-pf] Found 10.20.30.40 - 2017-12-12 16:29:34
2017-12-12 16:29:36,334 fail2ban.filter         [30627]: INFO    [web_nginx-auth-pf] Found 10.20.30.40 - 2017-12-12 16:29:36
2017-12-12 16:29:37,605 fail2ban.filter         [30627]: INFO    [web_nginx-auth-pf] Found 10.20.30.40 - 2017-12-12 16:29:37
2017-12-12 16:29:38,842 fail2ban.filter         [30627]: INFO    [web_nginx-auth-pf] Found 10.20.30.40 - 2017-12-12 16:29:38
2017-12-12 16:29:40,083 fail2ban.filter         [30627]: INFO    [web_nginx-auth-pf] Found 10.20.30.40 - 2017-12-12 16:29:39
2017-12-12 16:29:41,344 fail2ban.filter         [30627]: INFO    [web_nginx-auth-pf] Found 10.20.30.40 - 2017-12-12 16:29:40
2017-12-12 16:29:42,567 fail2ban.filter         [30627]: INFO    [web_nginx-auth-pf] Found 10.20.30.40 - 2017-12-12 16:29:42
2017-12-12 16:29:42,772 fail2ban.actions        [30627]: NOTICE  [web_nginx-auth-pf] Ban 10.20.30.40
2017-12-12 16:29:43,190 fail2ban.filter         [30627]: INFO    [web_nginx-auth-pf] Found 10.20.30.40 - 2017-12-12 16:29:43
2017-12-12 16:29:44,660 fail2ban.filter         [30627]: INFO    [web_nginx-auth-pf] Found 10.20.30.40 - 2017-12-12 16:29:44
2017-12-12 16:29:45,300 fail2ban.filter         [30627]: INFO    [web_nginx-auth-pf] Found 10.20.30.40 - 2017-12-12 16:29:45
2017-12-12 16:29:46,541 fail2ban.filter         [30627]: INFO    [web_nginx-auth-pf] Found 10.20.30.40 - 2017-12-12 16:29:46
2017-12-12 16:29:47,804 fail2ban.filter         [30627]: INFO    [web_nginx-auth-pf] Found 10.20.30.40 - 2017-12-12 16:29:47
2017-12-12 16:29:48,439 fail2ban.filter         [30627]: INFO    [web_nginx-auth-pf] Found 10.20.30.40 - 2017-12-12 16:29:48
2017-12-12 16:29:52,579 fail2ban.filter         [30627]: INFO    [web_nginx-auth-pf] Found 10.20.30.40 - 2017-12-12 16:29:52
2017-12-12 16:29:57,370 fail2ban.filter         [30627]: INFO    [web_nginx-auth-pf] Found 10.20.30.40 - 2017-12-12 16:29:57
2017-12-12 16:30:01,554 fail2ban.filter         [30627]: INFO    [web_nginx-auth-pf] Found 10.20.30.40 - 2017-12-12 16:30:00
2017-12-12 16:30:03,636 fail2ban.filter         [30627]: INFO    [web_nginx-auth-pf] Found 10.20.30.40 - 2017-12-12 16:30:03
2017-12-12 16:30:04,202 fail2ban.actions        [30627]: NOTICE  [web_nginx-auth-pf] 10.20.30.40 already banned
2017-12-12 16:30:10,825 fail2ban.filter         [30627]: INFO    [web_nginx-auth-pf] Found 10.20.30.40 - 2017-12-12 16:30:10
2017-12-12 16:30:15,435 fail2ban.filter         [30627]: INFO    [web_nginx-auth-pf] Found 10.20.30.40 - 2017-12-12 16:30:15
2017-12-12 16:30:19,146 fail2ban.filter         [30627]: INFO    [web_nginx-auth-pf] Found 10.20.30.40 - 2017-12-12 16:30:18
2017-12-12 16:30:24,649 fail2ban.filter         [30627]: INFO    [web_nginx-auth-pf] Found 10.20.30.40 - 2017-12-12 16:30:24
2017-12-12 16:30:27,814 fail2ban.filter         [30627]: INFO    [web_nginx-auth-pf] Found 10.20.30.40 - 2017-12-12 16:30:27
2017-12-12 16:30:32,171 fail2ban.filter         [30627]: INFO    [web_nginx-auth-pf] Found 10.20.30.40 - 2017-12-12 16:30:31
2017-12-12 16:30:36,319 fail2ban.filter         [30627]: INFO    [web_nginx-auth-pf] Found 10.20.30.40 - 2017-12-12 16:30:36
2017-12-12 16:30:42,557 fail2ban.filter         [30627]: INFO    [web_nginx-auth-pf] Found 10.20.30.40 - 2017-12-12 16:30:42
2017-12-12 16:30:54,328 fail2ban.filter         [30627]: INFO    [web_nginx-auth-pf] Found 10.20.30.40 - 2017-12-12 16:30:54
2017-12-12 16:31:06,372 fail2ban.filter         [30627]: INFO    [web_nginx-auth-pf] Found 10.20.30.40 - 2017-12-12 16:31:05
2017-12-12 16:31:06,771 fail2ban.actions        [30627]: WARNING [web_nginx-auth-pf] 10.20.30.40 already banned
2017-12-12 16:31:12,557 fail2ban.filter         [30627]: INFO    [web_nginx-auth-pf] Found 10.20.30.40 - 2017-12-12 16:31:11
2017-12-12 16:31:17,929 fail2ban.filter         [30627]: INFO    [web_nginx-auth-pf] Found 10.20.30.40 - 2017-12-12 16:31:17
2017-12-12 16:31:39,377 fail2ban.filter         [30627]: INFO    [web_nginx-auth-pf] Found 10.20.30.40 - 2017-12-12 16:31:38
2017-12-12 16:31:41,468 fail2ban.filter         [30627]: INFO    [web_nginx-auth-pf] Found 10.20.30.40 - 2017-12-12 16:31:41

@sebres
Copy link
Contributor

sebres commented Dec 12, 2017

I would like to understand why I cannot reproduce it... The statement "until TTL expires" does not really explained it.

But again, I'm not against "kill states", but if properly...
Just as already said, <pfctl> -k <ip> will kill any connections from IP, regardless the port, protocol, etc... This may be unexpected (too "aggressive").
If the jail is configured in fail2ban for the port 80 only, thus it should normally affect only this port (protocol, etc).

If you know how it could be made using same anchor/table as implemented in current banaction, please provide an example.
Otherwise it should be something like pfctl -k key -k 'tcp host:<port> <- <ip>:<srcport>', which is not so trivial (multi-port, etc)...

@catsem
Copy link

catsem commented Dec 13, 2017

I was using pf[actiontype=<allports>] as default banaction, so I guessed that your port specific approach was not usable.
I asumed, that this would only work if you use something like action = pf[port="{%(port)s}", name=%(__name__)s. But this also does not work for me. Sessions are still not droped in case of ban, though I can see in pfctl that the rules seems to be correct: block drop quick proto tcp from <f2b-web_nginx-auth-pf> to any port = https. The only effect is, that the attacker can not connect for new sessions.
My test setup is still the session between nginx webserver and a browser.

@sebres
Copy link
Contributor

sebres commented Dec 28, 2017

@catsem, @IdahoPL, @distler, @koeppea, @fail2ban/contributors
I want really close this issue, but I can't as long as I can't get it reproduced on my reference-systems (having pf).
Thus a proposal: can someone (that can reproduce it) test whether the following (already suggested) enhancement solves the issue:
Extend the ban command with anchored flushing states ... && <pfctl> -F states:

-actionban = <pfctl> -t <tablename>-<name> -T add <ip>
+actionban = <pfctl> -t <tablename>-<name> -T add <ip> && <pfctl> -F states

Additionally, does someone know any another syntax, how the states can be killed/flushed without kill of ALL connections for the IP (to all ports), e. g. using labels, anchor, table, etc?

@IdahoPL
Copy link
Contributor

IdahoPL commented Dec 28, 2017

pfctl -F states will kill all states causing every connection to drop. This would be far worse than pfctl -k

I don't think that we can do better than pfctl -k

@sebres
Copy link
Contributor

sebres commented Dec 28, 2017

You are wrong a bit in the abovementioned case, - I meant not pfctl but <pfctl>, thus in our case it interpolates to anchored pfctl -a ... -F states.

@sebres
Copy link
Contributor

sebres commented Dec 28, 2017

Or we'll need additionally the table, besides anchor in this case?
The documentation is very short there...

@IdahoPL
Copy link
Contributor

IdahoPL commented Dec 28, 2017

My quick test showed that:

pfctl -a f2b/named -t f2b-named -F states

and

pfctl -a f2b/named -F states

killed my active ssh connection, so I'm not sure that flush states in this case is limited only to specific anchor. Of course my ssh connection IP wasn't in f2b-named table (it was in fact empty).

@sebres
Copy link
Contributor

sebres commented Dec 28, 2017

:(

so I'm not sure that flush states in this case is limited only to specific anchor.

according to pfctl-docu it is so, as regards the anchor,... but unfortunately there is nothing about table.

@distler
Copy link
Contributor

distler commented Dec 29, 2017

The (OpenBSD) manpage on pfctl says:

To kill one specific state by its state key (as shown by pfctl -s state), use the key modifier.
To kill a state originating from 10.0.0.101:32123 to 10.0.0.1:80, protocol TCP, use:
# pfctl -k key -k 'tcp 10.0.0.1:80 <- 10.0.0.101:32123'

So, at least on OpenBSD, you have pretty fine-grained control over what state(s) get removed with the -k flag.

Unfortunately, I don't believe this feature is implemented in MacOSX's pfctl.

I have to say, though, that I have never seen the behaviour described in the bug report. It only happens if there are multiple denied requests over the same TCP connection. I'm surprised that NGINX doesn't close the connection after denying an authentication request. (I think that's what Apache does, and it kinda makes sense that it should.)

@sebres
Copy link
Contributor

sebres commented Dec 29, 2017

# pfctl -k key -k 'tcp 10.0.0.1:80 <- 10.0.0.101:32123'
So you have pretty fine-grained control over what state(s) get removed with the -k flag.

I know, but port 32123 is dynamical port on source side, so we need to find it between all the states; and think about multiport jails, so we should iterate over all target-ports to kill it (which will be not so trivial).

I'm surprised that NGINX doesn't close the connection after denying an authentication request.

I believe it does (at least after 3 attempts, also by keep-alive requests), but the attacker can build multiple connections before start authenticate process. Additionally there is several auth-modules (that can act differently), request multiplexing on HTTP/2, etc.

@sebres
Copy link
Contributor

sebres commented Dec 29, 2017

My quick test showed that pfctl -a f2b/named ... -F states killed my active ssh connection

@IdahoPL Just to be sure, how your f2b-named table resp. anchor were built, allports or multiport?
I mean block return quick ... to any port or the ports of named specified there?

Because if ports were specified, we could talk about a bug in pf, because according to pfctl.8#a, if anchor specified:

Apply flags -f, -F, and -s only to the rules in the specified anchor.

@IdahoPL
Copy link
Contributor

IdahoPL commented Dec 29, 2017

Multiport:

# pfctl -sr -a f2b/named -t f2b-named -T show
block drop quick proto udp from <f2b-named> to any port = domain
block drop quick proto udp from <f2b-named> to any port = 953
block drop quick proto tcp from <f2b-named> to any port = domain
block drop quick proto tcp from <f2b-named> to any port = rndc

named jail looks like this:

banaction = pf[actiontype=<multiport>]
banaction_allports = pf[actiontype=<allports>]
action = %(action_mwl)s

[named]
enabled = true
filter = named-refused
logpath = /var/log/daemon
protocol = { udp tcp }
port = domain rndc
ignoreip = 127.0.0.1/8 <removed>

BTW: Protocol has to use {} when using multiple ports but ports works without it (and fails when using {}).

@sebres
Copy link
Contributor

sebres commented Dec 29, 2017

So what would you say, bug of pf?
I could understand, that as long as the table not involved, all IPs are affected, but according to the documentation it should be at least only connections affected by the rules in anchor (so to the target ports specified there), or I'm wrong?

@IdahoPL
Copy link
Contributor

IdahoPL commented Dec 29, 2017

I'd go with bug in pf or in it's documentation.

@g-a-c
Copy link

g-a-c commented Jan 3, 2018

Because if ports were specified, we could talk about a bug in pf, because according to pfctl.8#a, if anchor specified:

Although you're right in that this looks like a bug in pf (pfctl -a 'f2b/*' -ss shows me states for all rules) I don't think this feature helps even if it worked as described because when the traffic is initially passed, it isn't matching a rule inside the anchor, so the state would be assigned to a rule in the main ruleset.

If you have a fully whitelisted config like

block all
anchor "nginx-basic-auth" all {
  block drop quick proto tcp from <f2b-nginx-basic-auth> to 22.33.44.55 port = https
}
pass in on em0 inet proto tcp from any to 22.33.44.55 port = https flags S/SA keep state

Then when someone new connects to your server, they are matching the pass rule outside of the nginx-basic-auth anchor. This is where the state is stored. If they then trigger fail2ban, it adds them to the f2b-nginx-basic-auth table inside the nginx-basic-auth anchor. The next time they create a new connection, they will match this rule and be blocked (with no state, as it's a block). But the process of adding an IP to that table doesn't "move" the existing state from the main ruleset into the anchor so I don't think you could use the anchor to query or flush the state of the first connection even if that feature did work as documented.

Flushing all state for the main table would be bad (for the reasons IdahoPL gave) so it does seem that killing all states for a single IP on any port is the closest, and trusting whatever process on the client was using those ports to reconnect if necessary (and either being allowed, or not, as per the new firewall config).

@g-a-c
Copy link

g-a-c commented Jan 3, 2018

In the long term maybe the project could find some use for py-pf (http://www.kernel-panic.it/programming/py-pf/pf2.html)?

This seems to have the ability to more granularly kill states based on (for example) source IP and destination port (for example, terminate SSH connections but no other ports):

import socket
import pf

filter = pf.PacketFilter()
src_addr = pf.PFAddr("99.99.99.99")
dst_addr = pf.PFAddr("0.0.0.0/0")
dst_port = pf.Port(22, socket.IPPROTO_TCP, pf.PF_OP_EQ)

filter.kill_states(src=pf.PFRuleAddr(src_addr),dst=pf.PFRuleAddr(dst_addr, dst_port))

@sebres
Copy link
Contributor

sebres commented Jan 8, 2018

Flushing all state for the main table would be bad

I've never suggested to do this.

killing all states for a single IP on any port is the closest

As already said, it's not an option for fail2ban, sorry. I'll accept such PR for pf[actiontype=<allports>], but not for single or multiport action.

But the process of adding an IP to that table doesn't "move" the existing state from the main ruleset into the anchor so I don't think you could use the anchor to query or flush the state of the first connection even if that feature did work as documented.
...

killing all states for a single IP on any port is the closest

Do you really see here no conflict?

@sebres
Copy link
Contributor

sebres commented Jan 9, 2018

@IdahoPL I tried currently the multiport test and I cannot reproduce this "pf-bug" with anchors at all.

root@test-server # echo "block return quick proto tcp from <f2b-test> to any port {8080}" | pfctl -a f2b/test -f-
...
root@test-server # pfctl -a f2b/test -t f2b-test -T add test-client; pfctl -a f2b/test -F states
1/1 addresses added.
0 states cleared
...
root@test-server # pfctl -a f2b/test -t f2b-test -T flush
1 addresses deleted.

My problem is that I cannot reproduce the issue at all (connection to 8080 will be closed immediately).
So I don't know why the command pfctl -a f2b/test -F states (as well as same with -t f2b-test) says "0 states cleared".

But strange is, that I've 2 active ssh connections from test-client to test-server, and call of pfctl -F states does nothing (regardless I specify anchor, table or both or even really nothing, both ssh-connections are still remain).
So even after:

root@test-server # pfctl -F states
0 states cleared

I get 0 states cleared and both ssh connections are still active...

Very strange is also that pfctl -s states prints nothing.

@IdahoPL
Copy link
Contributor

IdahoPL commented Jan 9, 2018

# pfctl -s
pfctl: option requires an argument -- s
usage: pfctl [-AdeghmNnOPqRrvz] [-a anchor] [-D macro=value] [-F modifier]
[...]

pfctl -s all should do "show all". What do pfctl -s info says?

@sebres
Copy link
Contributor

sebres commented Jan 9, 2018

# pfctl -s info
Status: Enabled for 0 days 00:01:48           Debug: Urgent

State Table                          Total             Rate
  current entries                        0
  searches                             447            4.1/s
  inserts                                0            0.0/s
  removals                               0            0.0/s
Counters
  match                                447            4.1/s
  bad-offset                             0            0.0/s
  fragment                               0            0.0/s
  short                                  0            0.0/s
  normalize                              0            0.0/s
  memory                                 0            0.0/s
  bad-timestamp                          0            0.0/s
  congestion                             0            0.0/s
  ip-option                              0            0.0/s
  proto-cksum                            0            0.0/s
  state-mismatch                         0            0.0/s
  state-insert                           0            0.0/s
  state-limit                            0            0.0/s
  src-limit                              0            0.0/s
  synproxy                               0            0.0/s
  map-failed                             0            0.0/s

@IdahoPL
Copy link
Contributor

IdahoPL commented Jan 9, 2018

And what pfctl -s all says? What version of FreeBSD are you using?

@sebres
Copy link
Contributor

sebres commented Jan 9, 2018

# uname -a
FreeBSD fbsddev 11.0-RELEASE-p1 FreeBSD 11.0-RELEASE-p1 #0 r306420 ... GENERIC  amd64
Details of pfctl -s all ...
# pfctl -s all
FILTER RULES:
anchor "f2b/*" all

INFO:
Status: Enabled for 0 days 00:18:25           Debug: Urgent

State Table                          Total             Rate
  current entries                        0
  searches                            3277            3.0/s
  inserts                                0            0.0/s
  removals                               0            0.0/s
Counters
  match                               3277            3.0/s
  bad-offset                             0            0.0/s
  fragment                               0            0.0/s
  short                                  0            0.0/s
  normalize                              0            0.0/s
  memory                                 0            0.0/s
  bad-timestamp                          0            0.0/s
  congestion                             0            0.0/s
  ip-option                              0            0.0/s
  proto-cksum                            0            0.0/s
  state-mismatch                         0            0.0/s
  state-insert                           0            0.0/s
  state-limit                            0            0.0/s
  src-limit                              0            0.0/s
  synproxy                               0            0.0/s
  map-failed                             0            0.0/s

TIMEOUTS:
tcp.first                   120s
tcp.opening                  30s
tcp.established           86400s
tcp.closing                 900s
tcp.finwait                  45s
tcp.closed                   90s
tcp.tsdiff                   30s
udp.first                    60s
udp.single                   30s
udp.multiple                 60s
icmp.first                   20s
icmp.error                   10s
other.first                  60s
other.single                 30s
other.multiple               60s
frag                         30s
interval                     10s
adaptive.start             6000 states
adaptive.end              12000 states
src.track                     0s

LIMITS:
states        hard limit    10000
src-nodes     hard limit    10000
frags         hard limit     5000
table-entries hard limit   200000

TABLES:

OS FINGERPRINTS:
758 fingerprints loaded

If I call it with anchor, then I see the filter and table additionally.

FILTER RULES:
block return quick proto tcp from <f2b-test> to any port = 8080
...
TABLES:
f2b-test

Still confusing:

# pfctl -k $ip_of_the_test_client
killed 0 states from 1 sources and 0 destinations

And nothing is killed from the test-client.

@koeppea
Copy link
Contributor

koeppea commented Jan 13, 2018

And nothing is killed from the test-client.

is the kill maybe also anchor sensitive?

@sebres
Copy link
Contributor

sebres commented Jan 15, 2018

is the kill maybe also anchor sensitive?

That is what I would want to test... But even calling without anchor (pfctl -k $ip) does nothing on my test-VM.

@repcsi
Copy link
Contributor

repcsi commented Jul 31, 2021

Hello All,

I made some changes and (maybe? :) ) improvements to the pf.conf action in fail2ban locally. I will open a PR if my testing shows that there are no unforeseen bugs.
I'm using FreeBSD boxes (as well as linux distros), so I did some testing with the sessions.

So without flushing any states the connection I believe will remain untouched until the TCP connection changes from established, or there are any different session timeouts that take place. In theory this could mean that an attacker could try different authentication mechanisms until he/she gets a deny from the application itself. Then when the IP is banned and the rule matches for a service no new connection can be made.
For example with ssh the number of tries is three:
TESTCASE1:
Banned myself with an active ssh session I'm 192.168.141.1:

2021-07-31 17:26:24,936 fail2ban.filter         [2405]: INFO    [ssh-pf] Found 192.168.141.1 - 2021-07-31 17:26:21
2021-07-31 17:26:24,936 fail2ban.filter         [2405]: INFO    [ssh-pf] Found 192.168.141.1 - 2021-07-31 17:26:24
2021-07-31 17:26:25,573 fail2ban.actions        [2405]: NOTICE  [ssh-pf] Ban 192.168.141.1

But I already had an established ssh session and after the ban I could still work with that despite having the same dst port:

root@freebsd:/usr/local/etc/fail2ban/action.d # w
 5:26PM  up  5:16, 1 user, load averages: 0.49, 0.54, 0.55
USER       TTY      FROM            LOGIN@  IDLE WHAT
root       pts/0    192.168.141.1   3:57PM     - w

Then I clear the session for the IP and the connection breaks:

root@freebsd:/usr/local/etc/fail2ban/action.d # pfctl -k 192.168.141.1
killed 3 states from 1 sources and 0 destinations
root@freebsd:/usr/local/etc/fail2ban/action.d #

Session breaks here as expected... and no new connection can be made

I understand that this is not how fail2ban wants to operate in case someone does not use the allports directive...

TESTCASE2:
In practice this means that an attacker can still try guessing passwords after a ban (please don't be mislead by the fact that in this case the user is invalid so that's why it gets banned on the first try - as this generates multiple lines in auth.log):

Still could try other passwords as sshd config allows 3 tries

2021-07-31 17:44:11,614 fail2ban.filter         [2405]: INFO    [ssh-pf] Found 192.168.141.140 - 2021-07-31 17:44:08
2021-07-31 17:44:11,618 fail2ban.filter         [2405]: INFO    [ssh-pf] Found 192.168.141.140 - 2021-07-31 17:44:11
2021-07-31 17:44:11,618 fail2ban.filter         [2405]: INFO    [ssh-pf] Found 192.168.141.140 - 2021-07-31 17:44:11
2021-07-31 17:44:12,211 fail2ban.actions        [2405]: NOTICE  [ssh-pf] Ban 192.168.141.140
2021-07-31 17:44:24,035 fail2ban.filter         [2405]: INFO    [ssh-pf] Found 192.168.141.140 - 2021-07-31 17:44:24
2021-07-31 17:44:24,036 fail2ban.filter         [2405]: INFO    [ssh-pf] Found 192.168.141.140 - 2021-07-31 17:44:24
2021-07-31 17:44:24,263 fail2ban.actions        [2405]: NOTICE  [ssh-pf] 192.168.141.140 already banned
2021-07-31 17:44:28,347 fail2ban.filter         [2405]: INFO    [ssh-pf] Found 192.168.141.140 - 2021-07-31 17:44:28
2021-07-31 17:44:28,348 fail2ban.filter         [2405]: INFO    [ssh-pf] Found 192.168.141.140 - 2021-07-31 17:44:28
2021-07-31 17:44:28,349 fail2ban.filter         [2405]: INFO    [ssh-pf] Found 192.168.141.140 - 2021-07-31 17:44:28
2021-07-31 17:44:28,581 fail2ban.actions        [2405]: NOTICE  [ssh-pf] 192.168.141.140 already banned
2021-07-31 17:44:28,618 fail2ban.actions        [2405]: NOTICE  [ssh-pf] 192.168.141.140 already banned

TESTCASE3:
So it seems that PF (at least the FreeBSD implementation) does not track session on a per-anchor basis, as checking the sessions will hit all of them even if you filter on the anchor.
Doing it with -F states in pf.conf is out of the question as it will flush all and every session.
Session listing wont filter or use the anchor... at least not on FreeBSD. You can even specify the table, it doesn't make a difference:

root@freebsd:~ # pfctl -a f2b/ssh-pf -t f2b-ssh-pf -ss
all tcp 192.168.141.143:22 <- 192.168.141.1:58042       ESTABLISHED:ESTABLISHED
all tcp 192.168.141.143:22 <- 192.168.141.1:58049       ESTABLISHED:ESTABLISHED
root@freebsd:~ #

root@freebsd:~ # pfctl -ss
all tcp 192.168.141.143:22 <- 192.168.141.1:58042       ESTABLISHED:ESTABLISHED
all tcp 192.168.141.143:22 <- 192.168.141.1:58049       ESTABLISHED:ESTABLISHED
root@freebsd:~ #

I tried to find a working solution and checked multiple workarounds:

  • I tried using "pfctl -k key " but that does not work or not implemented on FreeBSD. The manual does not even mention that one.

  • I tried to use labels as they do exist and work, but the problem is that how PF is managing and creating the sessions. So even if you label a "block quick" deny rule that will be matched later for the traffic when it was created it was associated with a pass rule earlier or it could not even be created in the first place. I tested this adding label f2b to the fail2ban deny rule and label sshd to my pass rule which allows ssh traffic in. When you try to flush the session for the block rule you will get the 0 states messages and nothing will happen, however if I flush the session associated with the sshd label which was used to create the sessions they did work, but the downside is that the command will flush all sessions that have the set label. You can even have some fun with this as if you create connections without first specifing the label and then reload your rules with the labels, the label won't be added to the already established connections, PF won't bother reapplying labels for the already established connections, which is I think is a good thing performance-wise...

  • What is a working solution but troublesome to implement is to try to fish out the ID of the session based on the SRC - DST and DST port, as using pfctl -ss -vv will print out the session ID, and using pfctl -k id -k IDNUMBER did work for me.

This is how it looks like:

root@freebsd:~ # pfctl -ss -vv
No ALTQ support in kernel
ALTQ related functions disabled
all tcp 192.168.141.143:22 <- 192.168.141.1:58810       ESTABLISHED:ESTABLISHED
   [3387799510 + 1050960] wscale 6  [413336120 + 65728] wscale 8
   age 00:01:14, expires in 24:00:00, 332:201 pkts, 25608:26898 bytes, rule 8
   id: 0100000061055b2e creatorid: 43d944cb
all tcp 192.168.141.143:22 <- 192.168.141.140:53780       ESTABLISHED:ESTABLISHED
   [3310081590 + 33536] wscale 6  [879275665 + 65728] wscale 7
   age 00:00:02, expires in 23:59:58, 11:8 pkts, 2253:2206 bytes, rule 8
   id: 0100000061055b30 creatorid: 43d944cb
all udp 192.168.141.143:52146 -> 192.168.141.2:53       MULTIPLE:SINGLE
   age 00:00:02, expires in 00:00:28, 1:1 pkts, 74:74 bytes, rule 2
   id: 0100000061055b31 creatorid: 43d944cb
root@freebsd:~ # pfctl -k id -k 0100000061055b30
killed 1 states

So in theory it can be implemented, but the question is, should we even bother? Or maybe it could be done with a pluggable module or configuration directive that is relying on py-pf or some other helper function.

What do you think?

@repcsi
Copy link
Contributor

repcsi commented Jul 31, 2021

Ah I also wanted to mention PF-s solution to this problem, however it's a little bit simple and oldschool.
You can create a table with the persist option to keep it in memory, then add a block rule that uses the IP-s from the table created.
Then create a pass rule that allows the traffic and add tracking options that would keep track of the connections/sessions and you could even add a rate limiting option.
If the rate is hit you can use the "overload" option to add the IP to a table and also use the "flush" option that is created to mitigate the issue that was brought up here and will flush the state. There is a global option for this "flush global" which will flush all sessions matching the IP and not just the ones created by the rule that it was added to (so flush global is basically running pfctl -k IP).

@sebres
Copy link
Contributor

sebres commented Aug 2, 2021

Just a mark for me (see how we made it in #3018 for ufw).

@ozgurkazancci
Copy link

ozgurkazancci commented Oct 27, 2021

Really interesting topic and I face the same issue under FreeBSD 13. I agree all what @ghost posts here above.
It should be implemented as, at least -under FreeBSD-;

actionban = <pfctl> -t <tablename>-<name> -T add <ip> && <pfctl> -k <ip>

The attacker should immediately get banned from the server, including his/her already-established connections/current sessions. Why? Think about a WordPress attacker, brute-forcing /wp-login.php - sessions would never get terminated, otherwise.

@sebres
Copy link
Contributor

sebres commented Oct 27, 2021

The attacker should immediately get banned from the server ...

What sounds reasonable and plausible for you, isn't necessarily acceptable for someone else.
I can only repeat the statements provided above (see #1924 (comment) for instance) - killing of any established connection of IP may be too aggressive, especially for a multi-port action.
For example few accidental false positives in service A would cause that all established connections to service B are unexpectedly closed immediately.
And then we'd get here a lot of issues like "why my sshd/sftp/scp/whatever session got closed if I became mistakenly banned in web-server?".
How your answer would be? I don't believe "think about a WordPress attacker" were satisfying enough...

Why? Think about a WordPress attacker, brute-forcing /wp-login.php - sessions would never get terminated, otherwise.

Think of possible false positives and mistakes, differently grading jails, complex fail2ban configurations (serving complicated large IDS/IPS requirements), proxying or relaying points, backwards compatibility, etc...
And why don't you ask WordPress or web-server people, for instance why they are not closing the keep-alive connection in 401/403 case? The same could be valid for pf (for FreeBSD).

Anyway, fortunately the action can be adjusted to any particular requirement by everyone (inclusive maintainers of fail2ban for FreeBSD) according their own individual needs.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

8 participants