ipv6 and iptables - setting up basic rules
I have come to realise my IPv6 ports are not going through iptables, and thus are accessible for attacks. I haven't seen any yet, but I'm sure its only a matter of time. As such, I'm trying to shore up the firewall for ipv6. I came across this script that configures the ip6tables
rules:
#!/bin/bash
# ip6tables single-host firewall script
# Define your command variables
ipt6="/sbin/ip6tables"
# Flush all rules and delete all chains
# for a clean startup
$ipt6 -F
$ipt6 -X
# Zero out all counters
$ipt6 -Z
# Default policies: deny all incoming
# Unrestricted outgoing
$ipt6 -P INPUT DROP
$ipt6 -P FORWARD DROP
$ipt6 -P OUTPUT ACCEPT
# Must allow loopback interface
$ipt6 -A INPUT -i lo -j ACCEPT
# Reject connection attempts not initiated from the host
$ipt6 -A INPUT -p tcp --syn -j DROP
# Allow return connections initiated from the host
$ipt6 -A INPUT -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT
# Accept all ICMP v6 packets
$ipt6 -A INPUT -p ipv6-icmp -j ACCEPT
# Optional rules to allow other LAN hosts access to services. Delete $ipt6 -A INPUT -p tcp --syn -j DROP
# Allow DHCPv6 from LAN only
$ipt6 -A INPUT -m state --state NEW -m udp -p udp -s fe80::/10 --dport 546 -j ACCEPT
# Allow connections from SSH clients
$ipt6 -A INPUT -m state --state NEW -m tcp -p tcp --dport 22 -j ACCEPT
# Allow HTTP and HTTPS traffic
$ipt6 -A INPUT -m state --state NEW -m tcp -p tcp --dport 80 -j ACCEPT
$ipt6 -A INPUT -m state --state NEW -m tcp -p tcp --dport 443 -j ACCEPT
# Allow access to SMTP, POP3, and IMAP
$ipt6 -A INPUT -m state --state NEW -p tcp -m multiport --dport 25,110,143 -j ACCEPT
While this does stop what I wanted, it also seems to not allow 80 and 443 ports?
$ipt6 -A INPUT -m state --state NEW -m tcp -p tcp --dport 80 -j ACCEPT
$ipt6 -A INPUT -m state --state NEW -m tcp -p tcp --dport 443 -j ACCEPT
It simply hangs when I try and access from another server:
curl -v -6 http://backups.foo.org:80
* Rebuilt URL to: http://backups.foo.org:80/
* Trying 2a00:1098:80:a1::1...
* TCP_NODELAY set
ipv4 works fine:
curl -v -4 http://backups.foo.org:80
* Rebuilt URL to: http://backups.foo.org:80/
* Trying 93.93.135.111...
* TCP_NODELAY set
* Connected to backups.foo.org (93.93.135.169) port 80 (#0)
> GET / HTTP/1.1
> Host: backups.foo.org
> User-Agent: curl/7.58.0
> Accept: */*
>
< HTTP/1.1 301 Moved Permanently
< Server: nginx
< Date: Tue, 23 Feb 2021 07:52:32 GMT
< Content-Type: text/html
< Content-Length: 162
< Connection: keep-alive
< Location: https://backups.foo.org/
<
<html>
<head><title>301 Moved Permanently</title></head>
<body>
<center><h1>301 Moved Permanently</h1></center>
<hr><center>nginx</center>
</body>
</html>
* Connection #0 to host backups.foo.org left intact
What am I missing? Basically, I just want to block ipv6 ports on services that are sensitive (MySQL, Exim, SMTP etc).
UPDATE: As suggested, I have removed:
$ipt6 -A INPUT -p tcp --syn -j DROP
Then run the script again, and the ip6tables
looks like this now:
root@backups:~# ip6tables --list -n
Chain INPUT (policy DROP)
target prot opt source destination
ACCEPT all ::/0 ::/0
ACCEPT all ::/0 ::/0 ctstate RELATED,ESTABLISHED
ACCEPT icmpv6 ::/0 ::/0
ACCEPT udp fe80::/10 ::/0 state NEW udp dpt:546
ACCEPT tcp ::/0 ::/0 state NEW tcp dpt:22
ACCEPT tcp ::/0 ::/0 state NEW tcp dpt:80
ACCEPT tcp ::/0 ::/0 state NEW tcp dpt:443
ACCEPT tcp ::/0 ::/0 state NEW multiport dports 25,110,143
Chain FORWARD (policy DROP)
target prot opt source destination
Chain OUTPUT (policy ACCEPT)
target prot opt source destination
I have tested it:
curl -6 backups.foo.org
curl: (7) Failed to connect to backups.foo.org port 80: Connection refused
Again, it works with -4
. The weird thing is that it does work from here:
https://tools.keycdn.com/ipv6-ping
I can ping from the same server, and it works fine:
ping backups.foo.org
PING backups.chambresdhotes.org(2a00:1098:80:a1::1 (2a00:1098:80:a1::1)) 56 data bytes
64 bytes from 2a00:1098:80:a1::1 (2a00:1098:80:a1::1): icmp_seq=1 ttl=59 time=1.08 ms
64 bytes from 2a00:1098:80:a1::1 (2a00:1098:80:a1::1): icmp_seq=2 ttl=59 time=1.03 ms
^X^C
As requested, the output of ip6tables-save
as well:
ip6tables-save
# Generated by ip6tables-save v1.6.1 on Tue Feb 23 08:57:59 2021
*filter
:INPUT DROP [0:0]
:FORWARD DROP [0:0]
:OUTPUT ACCEPT [78:6090]
-A INPUT -i lo -j ACCEPT
-A INPUT -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT
-A INPUT -p ipv6-icmp -j ACCEPT
-A INPUT -s fe80::/10 -p udp -m state --state NEW -m udp --dport 546 -j ACCEPT
-A INPUT -p tcp -m state --state NEW -m tcp --dport 22 -j ACCEPT
-A INPUT -p tcp -m state --state NEW -m tcp --dport 80 -j ACCEPT
-A INPUT -p tcp -m state --state NEW -m tcp --dport 443 -j ACCEPT
-A INPUT -p tcp -m state --state NEW -m multiport --dports 25,110,143 -j ACCEPT
COMMIT
UPDATE 2:
As requested, the output from ss -lnpt
. Interestingly, I don't see port 80 in there.
LISTEN 0 100 [::]:993 [::]:*
LISTEN 0 100 [::]:995 [::]:*
LISTEN 0 128 [::]:22122 [::]:*
LISTEN 0 100 [::]:110 [::]:*
LISTEN 0 128 ::1]:783 [::]:*
LISTEN 0 100 [::]:143 [::]:*
LISTEN 0 128 [::]:55413 [::]:*
LISTEN 0 128 *:8181 *:*
LISTEN 0 128 ::1]:53 [::]:*
LISTEN 0 128 [::]:55414 [::]:*
LISTEN 0 128 [::]:22 [::]:*
LISTEN 0 128 [::1]:8953 [::]:*
Interestingly though, it shows up with netstat
:
sudo netstat -tulpan | grep nginx
tcp 0 0 0.0.0.0:9183 0.0.0.0:* LISTEN 1133/nginx: master
tcp 0 0 93.93.135.169:80 0.0.0.0:* LISTEN 1161/nginx: master
tcp 0 0 127.0.0.1:8084 0.0.0.0:* LISTEN 1161/nginx: master
tcp 0 0 93.93.135.169:443 0.0.0.0:* LISTEN 1161/nginx: master
tcp6 0 0 :::80 :::* LISTEN 1161/nginx: master
tcp6 0 0 :::443 :::* LISTEN 1161/nginx: master
udp 0 0 127.0.0.1:51104 127.0.0.53:53 ESTABLISHED 1135/nginx: worker
This is the lesson not to use security scripts without understanding any single line.
I suspect this part is a culprit:
# Reject connection attempts not initiated from the host
$ipt6 -A INPUT -p tcp --syn -j DROP
It really disables any incoming TCPv6 communication. It also must disable any "related" TCPv6 communication (for instance, "active" FTP), because it appears before ctstate line.
Just remove it. It is useless. All unmatched packets will be dropped by the policy anyway, so why dropping anything early in the chain, without leaving a possibility to selectively enable services? I don't understand why ever this line appeared in the script.