OpenWrt Forum Archive

Topic: Protect SSH using iptables?

The content of this topic has been archived on 9 Apr 2018. There are no obvious gaps in this topic, but there may still be some posts missing at the end.

The logs of my Openwrt router are showing a lot of dictionary attacks on port 22 ssh.   Looking for a way to protect against this, one website suggested modfying the iptable firewall to drop repeated attempts from same ip.  The suggested changes are:

iptables -A INPUT -m recent --update --seconds 40 --hitcount 5 --name SSH --rsource -j DROP
iptables -A INPUT -m recent --set --name SSH --rsource -j ACCEPT

My familarity with iptables settings is limited.  Does the above look valid and would it work with openwrt based iptable firewall?

Thanks.

iptables -A INPUT -m recent --update --seconds 40 --hitcount 5 --name SSH --rsource -j DROP
iptables -A INPUT -m recent --set --name SSH --rsource -j ACCEPT

I don't know...

This is what I have, and it does work!

iptables -t nat -A prerouting_rule -i $WAN -p tcp -m state --state NEW --dport 22 -m recent --set --name DEFAULT --rsource
iptables -t nat -A prerouting_rule -i $WAN -p tcp -m state --state NEW --dport 22 -m recent --update --seconds 90 --hitcount 4 --name DEFAULT --rsource -j DROP
iptables -t nat -A prerouting_rule -i $WAN -p tcp --dport 22 -j ACCEPT
iptables        -A input_rule      -i $WAN -p tcp --dport 22 -j ACCEPT

If a connection is new on port 22, it will match the first rule and count as one hit.
If the same IP tries to connect more than 3 times, it will match the second rule, and connections from that IP to port 22 will be dropped for 90 seconds.
If it passes the second rule, it matches the others, and gets in (to the router).

This is on /etc/firewall.user:

#!/bin/sh
. /etc/functions.sh

# You need kmod-ipt-extra for this:
insmod /lib/modules/2.4.30/ipt_recent.o

WAN=$(nvram get wan_ifname)
LAN=$(nvram get lan_ifname)

iptables -F input_rule
iptables -F output_rule
iptables -F forwarding_rule
iptables -t nat -F prerouting_rule
iptables -t nat -F postrouting_rule
iptables -t nat -A prerouting_rule -i $WAN -p tcp -m state --state NEW --dport 22 -m recent --set --name DEFAULT --rsource
iptables -t nat -A prerouting_rule -i $WAN -p tcp -m state --state NEW --dport 22 -m recent --update --seconds 90 --hitcount 4 --name DEFAULT --rsource -j DROP
iptables -t nat -A prerouting_rule -i $WAN -p tcp --dport 22 -j ACCEPT
iptables        -A input_rule      -i $WAN -p tcp --dport 22 -j ACCEPT

-- jp

(Last edited by jp on 28 Sep 2006, 00:47)

Thanks for the information.  I installed those iptables and it works great.

FYI - According to Snowman's webpage, he recommends a configuration more like this:

iptables -t nat -A prerouting_rule -i $WAN -p tcp -m state --state NEW --dport 22 -m recent --set --name SSH_ATTACKER --rsource
iptables        -A input_rule      -i $WAN -p tcp -m state --state NEW --dport 22 -m recent --update --seconds 180 --hitcount 3 --name SSH_ATTACKER --rsource -j DROP
iptables -t nat -A prerouting_rule -i $WAN -p tcp --dport 22 -j ACCEPT
iptables        -A input_rule      -i $WAN -p tcp --dport 22 -j ACCEPT

The difference being that the hitcount check is in the INPUT chain of the filter table.  The reason for this, according to his webpage, is that this will put it after the checks for "-m state --state RELATED,ESTABLISHED -j ACCEPT", and prevent the rare instance where someone could create a denial of service attack on your existing connection, by spoofing your address.

Really a pretty rare corner case, but I thought I'd point it out for the archives...

G

Hello everybody,

I need your wisdom and opinion on this. Thanks in advance and please read on.

I adopted bluesguy's version from Snowman with "prerouting_wan" and "input_wan", therefore no need to use/define variable $WAN or call ". /etc/functions.sh".
". /etc/functions.sh" and defining the variables shouldn't be necessary at all according to the wiki - at least for RC9.

Also the best way to start a module is to add it to "/etc/modules", therefore no need to add a insmod command into your firewall script (keeping it clean from non-iptables stuff).
You can easily add the module with the following command:

echo "ipt_recent" >> /etc/modules

And finally I needed SSH to be available on port 443 from outside, because of a restrictive firewall at work.
I wanted to keep port 22 for the LAN, therefore I had to redirect port 443 from WAN to 22 with "-j DNAT --to :22".
I know if I wanted port 22 to stay reachable I had to double all "prerouting_wan" rules, but I want to avoid any unnecessary open ports.

Here's where you come in:

1. As I'm a total newbie to iptables I don't know if there's a security gap left in my firewall script, due to the port redirection?

2. According to the wiki the package "kmod-ipt-extra" is also necessary. Is this still correct for RC9?

Here are my rules for SSH in "/etc/firewall.user":

# IPTables structure of OpenWRT: http://wiki.openwrt.org/OpenWrtDocs/IPTables

# Drop packets from WAN directly to port 22
iptables -t nat -A prerouting_wan -p tcp --dport 22 -j DROP

# Accepted packets from WAN on port 443 and redirect to port 22
iptables -t nat -A prerouting_wan -p tcp -m state --state NEW --dport 443 -m recent --set --name SSH_ATTACKER --rsource -j DNAT --to :22
iptables        -A input_wan      -p tcp -m state --state NEW --dport 22  -m recent --update --seconds 180 --hitcount 3 --name SSH_ATTACKER --rsource -j DROP
iptables -t nat -A prerouting_wan -p tcp --dport 443 -j DNAT --to :22
iptables        -A input_wan      -p tcp --dport 22  -j ACCEPT

Kind Regards
Maddes

P.S.:
I'm using X-Wrt/OpenWRT WR 0.9 for just some days and it is pretty amazing what is possible and how easy it is - except iptables big_smile

(Last edited by maddes.b on 3 Jul 2009, 17:21)

I allow SSH only from trusted (whitelisted) IP's, and drop any other request to port 22.

/etc/lists/whitelist.conf

1.2.3.4                        ## A trusted IP
2.3.4.5                        ## Another trusted IP

/etc/firewall.user

WHITELIST=$(sed 's/#.*//' /etc/lists/whitelist.conf)

for IP in ${WHITELIST}; do
        iptables -t nat -A prerouting_rule -i $WAN -s $IP -p tcp --dport 22 -j ACCEPT
        iptables        -A input_rule      -i $WAN -s $IP -p tcp --dport 22 -j ACCEPT
done

You could also replace 22 with $PORT, then loop for PORT in ${WHITELIST_PORTS} inside the first for loop -- that way you could list more ports than just one (22).

(Last edited by Duon on 12 Apr 2007, 23:29)

You really should avoid splitting the --set and --update across a DNAT like that. Here's my $0.02:

#block direct from wan to port 22
iptables -t nat -A prerouting_wan -p tcp --dport 22 -j DROP

# match connections already seen >3 times and DROP, otherwise DNAT
iptables -t nat -A prerouting_wan -p tcp -m state --state NEW --dport 443 -m recent --update --seconds 180 --hitcount 3 --name SSH_ATTACKER --rsource -j DROP
iptables -t nat -A prerouting_wan -p tcp -m state --state NEW --dport 443 -m recent --set --name SSH_ATTACKER --rsource -j DNAT --to :22

# accept connections from the wan which made it through the prerouting checks
iptables        -A input_wan      -p tcp -m state --state NEW --dport 22 -j ACCEPT

The following is what I'm currently using in WR 0.9 and it works perfectly.
I allow to connect from the WAN on the standard port tcp-22, but also on port tcp-443. Port tcp-443 is then redirected to port tcp-22 via the DNAT target. This allows to access SSH even through very restrictive firewalls (e.g. at work):

### SSH (Dropbear running on port 22 only)
## SSH: Rules for new incoming connections on tcp-443
iptables -t nat -A prerouting_wan -p tcp --dport 443 -m state --state NEW -m recent --update --seconds 60 --hitcount 10 --name ATTACKER_SSH --rsource -j DROP
iptables -t nat -A prerouting_wan -p tcp --dport 443 -m state --state NEW -m recent --set --name ATTACKER_SSH --rsource
iptables -t nat -A prerouting_wan -p tcp --dport 443 -j DNAT --to :22
## SSH: Rules for new incoming connections on tcp-22
iptables -t nat -A prerouting_wan -p tcp --dport 22  -m state --state NEW -m recent --update --seconds 60 --hitcount 10 --name ATTACKER_SSH --rsource -j DROP
iptables -t nat -A prerouting_wan -p tcp --dport 22  -m state --state NEW -m recent --set --name ATTACKER_SSH --rsource
## SSH
iptables        -A input_wan      -p tcp --dport 22  -m state --state NEW -j ACCEPT

mbm's comment about not splitting --set and --update is correct, although it works with ipt_recent, it is extremely complicated to get the logic right and working as wanted.
Hence use ipt_recent primarily on the prerouting rules, and always have a bundle of two lines: one for checking against the wanted boundary and the second for updating with the new connection.

(Last edited by maddes.b on 3 Jul 2009, 17:23)

maddes.b wrote:

mbm's comment about not splitting --set and --update is correct, although it works with ipt_recent, it is extremely complicated to get the logic right and working as wanted.

actually it's trivial -

# bounce to port 22
iptables -t nat -A prerouting_wan -p tcp --dport 443 -j DNAT --to :22

# set restrictions on port 22
iptables        -A input_wan      -p tcp --dport 22 -m state --state NEW -m recent --update --seconds 60 --hitcount 10 --name ATTACKER_SSH --rsource -j DROP
iptables        -A input_wan      -p tcp --dport 22 -m state --state NEW -m recent --set --name ATTACKER_SSH --rsource -j ACCEPT

Another interesting way to accomplish this. Thanks mbm.
Here mbm also didn't split "--update" and "--set" between different tables (nat, filter). The ipt_recent rules are all for input_wan and have the same conditions (=protocol, port, state).
You see iptables is extremly flexible and there's not only one way to solve something.

I just prefer to reject the packets in the prerouting chain, before they reach the input or forward chains.

(Last edited by maddes.b on 8 Jul 2009, 11:30)

hi all,
I work at several places during the week - I'm trying to combine both "limiting to whitelist" and, even then, "limiting to hits/min".

Please have a look if this is correct.
I'm not sure how to test line-by-line:

WHITELIST=$(sed 's/#.*//' /etc/lists/whitelist.conf)

for IP in ${WHITELIST}; do
 iptables -t nat -A prerouting_wan -p tcp -s  $IP --dport 443 -j DNAT --to :22
     # set restrictions on port 22    (does "!22" work as "NOT 22"?)
 iptables        -A input_wan      -p tcp -s !$IP --dport 22 -m state --state NEW -m recent --update --seconds 240 --hitcount 3 --name ATTACKER_SSH --rsource -j DROP
 iptables        -A input_wan      -p tcp -s  $IP --dport 22 -m state --state NEW -m recent --set --name ATTACKER_SSH --rsource -j ACCEPT
done

cheers, stefan

@zi4
Stefan, I'm not an expert, but I think "!" for negation doesn't work.
Also the negation is wrong.

You only allow the whitelisted source IPs, so you have to measure their hits/min, as all other IPs are rejected or dropped (depending on the defined policies). Remove the "!" and you should be good to go.

The port redirection doesn't have to check for the source IP, but this avoids redirection for other IPs (could be used as SSH for whitelisted IPs, HTTPS for others).

The pages I used to learn IPTables are all listed at http://www.maddes.net/software/debian.htm#iptables

Regards
Maddes

(Last edited by maddes.b on 24 Sep 2008, 20:15)

maddes.b wrote:

Remove the "!" and you should be good to go.

thanks, Maddes.  You are correct smile
I just removed the !  from my last post and it works nicely now.

Best way to protect ssh access is to not allow it to the internet and use a openvpn on the router to connect to ssh.

belrpr wrote:

Best way to protect ssh access is to not allow it to the internet and use a openvpn on the router to connect to ssh.

Disabling password based logins and only connecting with keys would be equivalently secure. Either way, it's key-based access exposed to the internet. Dropping failing dictionary attacks is just a nicety.

VPN might be better in terms of convenience, depending on what you are trying to do.

I expose SSH on all my OpenWRT routers to the world.

HOWEVER I RUN IT ON ANOTHER PORT.

Pick some port above 1,024 that you can remember like 22222 or something.  Run dropbear there instead.  All the ssh port-scans I see look for port 22 as they are searching for easy targets.  Scanning all ports takes too much time.

Hello,
I followed this wiki : http://wiki.openwrt.org/ThrottleConnectionsHowTo on OpenWRT 8.09 but it doesn't seem to work.

In /etc/config/firewall I added:

config include
        option path /etc/firewall.user

# and commented the previous 22 accept
#config rule
#        option src              wan
#        option dest_port        22
#        option target           ACCEPT
#        option proto            tcp

In /etc/firewall.user I put the rules from the wiki i.e.

iptables -t nat -A prerouting_wan -p tcp --dport 22  -m state --state NEW \
  -m recent --name ATTACKER_SSH --rsource --update --seconds 180 --hitcount 5 -j DROP
iptables -t nat -A prerouting_wan -p tcp --dport 22  -m state --state NEW \
  -m recent --name ATTACKER_SSH --rsource --set


iptables        -A input_wan      -p tcp --dport 22  -m state --state NEW -j ACCEPT

but when I still can login from my lan even after more that 5 failed attempts.

What's wrong ?

Thanks,
Tex

You only throttle connections from the outside (WAN = wide area network) and not from the inside (LAN = local area network).
Try your brute force tests from a friend's pc.

hth
Maddes

(Last edited by maddes.b on 30 Jan 2009, 20:28)

Tex-Twil wrote:

Hello,
I followed this wiki : http://wiki.openwrt.org/ThrottleConnectionsHowTo on OpenWRT 8.09 but it doesn't seem to work.

Tex

Works great in whiterussian with 2 packages.
Does not work in 8.09 RC2 with 4 packages. So much for upgrade.

Please try the final 8.09 version and check that all modules are loaded (via lsmod), especially ipt_recent.

i tryed this with r14828 trunk(x86), but cant get it working. in which package ipt_recent is included?

i tryed
kmod-ipt-conntrack
kmod-ipt-conntrack-extra
iptables-mod-conntrack
iptables-mod-conntrack-extra

but i always get: Couldn't load match `recent':File not found

insmod ipt_recent
insmod: Loading module failed: No such file or directory

(Last edited by wurststulle on 11 Mar 2009, 03:14)

For "-m recent" in Kamikaze 8.0.9 the packages "iptables-mod-conntrack-extra" and "kmod-ipt-conntrack-extra" are needed (files: libxt_recent.so & xt_recent.ko).
It will be automatically loaded when referenced in /etc/firewall.user, check with "lsmod | grep recent".

[s]Note that Kamikaze 8.0.9 has no "prerouting_wan" chain to my knowledge, so you have to use "input_wan".[/s]

(Last edited by maddes.b on 4 Jul 2009, 13:46)

There is a "prerouting_wan" chain, but you can not see it with iptables -S or iptables -L as it defaults to the table "filter".
You have to specify the correct table "nat":
iptables -t nat -S  and  iptables -t nat -L

(Last edited by maddes.b on 8 Jul 2009, 11:32)

Also the more recent iptables doesn't allow DROP or REJECT inside table nat (just fell into this some minutes ago).
Just redirect to an unused port and reject it in the filter chains input and forward:
Example:

# Brute Force Protection (BFP) adapted from http://forum.openwrt.org/viewtopic.php?pid=34522#p34522
# For "-m recent" in Kamikaze 8.0.9 the packages "iptables-mod-conntrack-extra" and "kmod-ipt-conntrack-extra" are needed (files: libxt_recent.so & xt_recent.ko)

# As target REJECT is not allowed in nat/prerouting, redirect to an unused port and reject it in filter/input and filter/forwarding
echo ' ...establishing reject rules for prerouting'
BFP_REJECTPORT=55555
# nat chain that redirects to reject port
iptables -t nat -N prerouting_REJECT
iptables -t nat -A prerouting_REJECT -p tcp -j DNAT --to :${BFP_REJECTPORT}
iptables -t nat -A prerouting_REJECT -p udp -j DNAT --to :${BFP_REJECTPORT}
# filter rules that will finally reject
iptables -A input_wan      -p tcp  --dport ${BFP_REJECTPORT} -j reject
iptables -A forwarding_wan -p tcp  --dport ${BFP_REJECTPORT} -j reject
iptables -A input_wan      -p udp  --dport ${BFP_REJECTPORT} -j reject
iptables -A forwarding_wan -p udp  --dport ${BFP_REJECTPORT} -j reject


### SSH/Dropbear (TCP-22)
SERVICE=SSH
PROTO=tcp
PORT=22
BFP_START=6
echo " ...establishing ${SERVICE} rules for ${PROTO}-${PORT}"
## nat chain that updates connections and rejects
iptables -t nat -N prerouting_wan_BFP_${PROTO}${PORT}
iptables -t nat -A prerouting_wan_BFP_${PROTO}${PORT} -p ${PROTO} --dport ${PORT}  -m recent --name ATTACKER_${PROTO}${PORT} --rsource --update                                      -j prerouting_REJECT
## nat/prerouting
# immediately jumps to update+reject chain if remote source already in BFP mode (avoids multiple bruteforce log entries)
iptables -t nat -A prerouting_wan -p ${PROTO} --dport ${PORT} -m state --state NEW -m recent --name ATTACKER_${PROTO}${PORT} --rsource --rcheck --seconds 60 --hitcount ${BFP_START} -j prerouting_wan_BFP_${PROTO}${PORT}
# adds/updates remote source in list
iptables -t nat -A prerouting_wan -p ${PROTO} --dport ${PORT} -m state --state NEW -m recent --name ATTACKER_${PROTO}${PORT} --rsource --set
# check if BFP mode for remote source applies, if so log bruteforce and reject
iptables -t nat -A prerouting_wan -p ${PROTO} --dport ${PORT} -m state --state NEW -m recent --name ATTACKER_${PROTO}${PORT} --rsource --update --seconds 60 --hitcount ${BFP_START} -j LOG --log-level warn --log-prefix "BRUTEFORCE-${SERVICE} "
iptables -t nat -A prerouting_wan -p ${PROTO} --dport ${PORT} -m state --state NEW -m recent --name ATTACKER_${PROTO}${PORT} --rsource --rcheck --seconds 60 --hitcount ${BFP_START} -j prerouting_wan_BFP_${PROTO}${PORT}
## filter/input
iptables        -A input_wan      -p ${PROTO} --dport ${PORT} -m state --state NEW -j LOG --log-level info --log-prefix "${SERVICE} "
iptables        -A input_wan      -p ${PROTO} --dport ${PORT} -m state --state NEW -j ACCEPT

Works perfectly.

P.S.:
There's another thread also dealing with this.

Update:
Created a flexible plugin for this, that can be used for any service.

(Last edited by maddes.b on 8 Jul 2009, 11:27)

Why do you think it is important to have a rule in the PREROUTING chain? I have added following two rules to my firewall.user and it works fine:

iptables -A input_wan -p tcp --dport 22 -m state --state NEW \
  -m recent --name ATTACKER_SSH --rsource \
  --update --seconds 180 --hitcount 5 -j DROP
iptables -A input_wan -p tcp --dport 22 -m state --state NEW \
  -m recent --name ATTACKER_SSH --rsource --set -j ACCEPT

The discussion might have continued from here.