1

Trying to mitigate SSH brute-force attacks on my box, I copied/pasted the following rules into my iptables (without really understanding them, I must confess):

$> iptables-save
# Generated by iptables-save v1.4.14 on Wed Jun 11 15:13:01 2014
*filter
:INPUT ACCEPT [1255:139338]
:FORWARD ACCEPT [0:0]
:OUTPUT ACCEPT [1099:174390]
-A INPUT -p tcp -m tcp --dport 22 -m state --state NEW -m recent --set --name SSH --rsource -j ACCEPT
-A INPUT -p tcp -m tcp --dport 22 -m recent --rcheck --seconds 60 --hitcount 5 --rttl --name SSH --rsource -j LOG --log-prefix "[iptables] SSH brute-force "
-A INPUT -p tcp -m tcp --dport 22 -m recent --update --seconds 60 --hitcount 5 --rttl --name SSH --rsource -j REJECT --reject-with tcp-reset
COMMIT
# Completed on Wed Jun 11 15:13:01 2014

To my delight, it works as intended (repeated connections attempts are rejected, while legitimate attempts go through). I since realised that there are other ways to achieve the same effect: example 1, example 2.

I (believe I) understand how the other methods work, but I fail to understand how the first rule above matches only "new incoming connections from untracked sources".

This contradicts what I understand from the manual:

--set: This will always return success

What in this rule makes it not match repeated connection attempts? I assume the first rule does not match repeated connection attempts, otherwise the subsequent rules would never kick in.

I would expect any new incoming connection to match this rule.

In the other examples, they don't mention the target -j ACCEPT:

-A INPUT (...) -m state --state NEW -m recent --set --name SSH --rsource  

So perhaps my question could be rephrased:

Is the -j option useless when used together with --set --name [some_name]?


As requested, here are the results of iptables -L -n -v (ssh'ing from/to localhost, since I only have remote access right now):

Initial state:

Chain INPUT (policy ACCEPT 895 packets, 101K bytes)
 pkts bytes target     prot opt in     out     source               destination         
   18  1080 ACCEPT     tcp  --  *      *       0.0.0.0/0            0.0.0.0/0            tcp dpt:22 state NEW recent: SET name: SSH side: source
   19   962 LOG        tcp  --  *      *       0.0.0.0/0            0.0.0.0/0            tcp dpt:22 recent: CHECK seconds: 60 hit_count: 5 TTL-Match name: SSH side: source LOG flags 0 level 4 prefix "[iptables] SSH brute-force "
   19   962 REJECT     tcp  --  *      *       0.0.0.0/0            0.0.0.0/0            tcp dpt:22 recent: UPDATE seconds: 60 hit_count: 5 TTL-Match name: SSH side: source reject-with tcp-reset

Chain FORWARD (policy ACCEPT 0 packets, 0 bytes)
 pkts bytes target     prot opt in     out     source               destination         

Chain OUTPUT (policy ACCEPT 776 packets, 119K bytes)
 pkts bytes target     prot opt in     out     source               destination   

After attempt #4:

Chain INPUT (policy ACCEPT 1040 packets, 121K bytes)
 pkts bytes target     prot opt in     out     source               destination         
   22  1320 ACCEPT     tcp  --  *      *       0.0.0.0/0            0.0.0.0/0            tcp dpt:22 state NEW recent: SET name: SSH side: source
   19   962 LOG        tcp  --  *      *       0.0.0.0/0            0.0.0.0/0            tcp dpt:22 recent: CHECK seconds: 60 hit_count: 5 TTL-Match name: SSH side: source LOG flags 0 level 4 prefix "[iptables] SSH brute-force "
   19   962 REJECT     tcp  --  *      *       0.0.0.0/0            0.0.0.0/0            tcp dpt:22 recent: UPDATE seconds: 60 hit_count: 5 TTL-Match name: SSH side: source reject-with tcp-reset

Chain FORWARD (policy ACCEPT 0 packets, 0 bytes)
 pkts bytes target     prot opt in     out     source               destination         

Chain OUTPUT (policy ACCEPT 911 packets, 141K bytes)
 pkts bytes target     prot opt in     out     source               destination       

After attempt #5 (which was rejected):

Chain INPUT (policy ACCEPT 1063 packets, 122K bytes)
 pkts bytes target     prot opt in     out     source               destination         
   23  1380 ACCEPT     tcp  --  *      *       0.0.0.0/0            0.0.0.0/0            tcp dpt:22 state NEW recent: SET name: SSH side: source
   21  1054 LOG        tcp  --  *      *       0.0.0.0/0            0.0.0.0/0            tcp dpt:22 recent: CHECK seconds: 60 hit_count: 5 TTL-Match name: SSH side: source LOG flags 0 level 4 prefix "[iptables] SSH brute-force "
   21  1054 REJECT     tcp  --  *      *       0.0.0.0/0            0.0.0.0/0            tcp dpt:22 recent: UPDATE seconds: 60 hit_count: 5 TTL-Match name: SSH side: source reject-with tcp-reset

Chain FORWARD (policy ACCEPT 0 packets, 0 bytes)
 pkts bytes target     prot opt in     out     source               destination         

Chain OUTPUT (policy ACCEPT 933 packets, 144K bytes)
 pkts bytes target     prot opt in     out     source               destination 

Log of the "attack":

Jun 11 15:07:02 [hostname_hidden] kernel: [249278.516231] [iptables] SSH brute-force IN=lo OUT= MAC=00:00:00:00:00:00:00:00:00:00:00:00:08:00 SRC=127.0.0.1 DST=127.0.0.1 LEN=40 TOS=0x00 PREC=0x00 TTL=64 ID=59245 DF PROTO=TCP SPT=36129 DPT=22 WINDOW=0 RES=0x00 RST URGP=0 
Jun 11 15:07:18 [hostname_hidden] kernel: [249294.514964] [iptables] SSH brute-force IN=lo OUT= MAC=00:00:00:00:00:00:00:00:00:00:00:00:08:00 SRC=127.0.0.1 DST=127.0.0.1 LEN=40 TOS=0x00 PREC=0x00 TTL=64 ID=59246 DF PROTO=TCP SPT=36129 DPT=22 WINDOW=0 RES=0x00 RST URGP=0
8
  • I'm not entirely sure I understand the question, not least because of the sarcasm quotes around "it works". Does your two-line recipe work or not? Because the question completely changes depending on the answer to that.
    – MadHatter
    Jun 11, 2014 at 12:13
  • Yes it works as intended (repeated connection attempts are rejected while legitimate connections go through). I'll try to rephrase.
    – RandomSeed
    Jun 11, 2014 at 12:14
  • @MadHatter Okay I think I asked the question the wrong way around, please bear with me.
    – RandomSeed
    Jun 11, 2014 at 12:16
  • Question rephrased so as to make sense.
    – RandomSeed
    Jun 11, 2014 at 12:32
  • OK, now I get it (I think!). Could you show us the evidence that it works as intended (ie, that rate-limiting cuts in)?
    – MadHatter
    Jun 11, 2014 at 12:36

1 Answer 1

1

It seems to me that the answer is in the packet counts, and the details of the rules. Specifically, with attempt 5, the counts on both rulesets (the ACCEPT, and the LOG/REJECT pair) are incremented.

I suspect that with attempt 5, the initial SYN packet matches the ACCEPT rule, and is both ACCEPTed and bumps the counts. But because the count has been incremented by that ACCEPTed SYN packet, subsequent packets in that newly-established connection now match the later LOG/REJECT pair, and thus are rejected. The logs help to confirm this view, as you will see that neither REJECTed packet has the SYN flag set.

4
  • I understand that all rules must match, therefore something else is preventing this rule to match. Subsequent repeated connection attempts are NEW, aren't they? So which part of the rule matches the fact that the connection attempt must not be a "repeated" one?
    – RandomSeed
    Jun 11, 2014 at 12:31
  • Answer edited to address this, in the light of the new data provided.
    – MadHatter
    Jun 11, 2014 at 13:24
  • This definitely makes sense. I just realised I get "Address family not supported by protocol" from/to localhost, whereas I get "ssh_exchange_identification: read: Connection reset by peer" from a remote host or from localhost to [IP address]. I need to think this through, but it seems to confirm your explaination.
    – RandomSeed
    Jun 11, 2014 at 13:30
  • Maybe not. But after removing -j ACCEPT, I now get a regular "Connection refused" from [remote|localhost] to [IP address], so you definitely nailed it. (I still get "not supported by protocol" from/to localhost, and I hope I will figure out the reason).
    – RandomSeed
    Jun 11, 2014 at 13:38

You must log in to answer this question.

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