4

I have a VM running CentOS with a web server I use for hosting random services I deploy over there, so in order to make it reachable from the Internet I opened port 80 using iptables. Since the web server itself is running as a service under a dedicated user that is not root, it is not able to use port 80 directly. Thus, after giving the docs a read, I added a redirection from port 80 to 8080 so the web server could be bound to that port (I do plan to add support for HTTPS later, maybe I will buy a proper domain and then use Let's Encrypt or something).

So far it has been working fine, but more recently I have noticed that the port 8080 was left open wide as well, so any requests targeting either port 80 or 8080 would get the same response. The thing is, I need only port 80 to be reachable from outside, because somehow my provider considers leaving the port 8080 open some sort of potential abuse? Either way, I don't want external requests directed to port 8080 to get a response, only those who target port 80 should get any.

So far, this is how my config file for iptables looks like:

*nat
:PREROUTING ACCEPT [89:7936]
:INPUT ACCEPT [70:3812]
:OUTPUT ACCEPT [41:2756]
:POSTROUTING ACCEPT [41:2756]
-A PREROUTING -p tcp -m tcp --dport 80 -j REDIRECT --to-ports 8080
COMMIT
*filter
:INPUT ACCEPT [916:134290]
:FORWARD ACCEPT [0:0]
:OUTPUT ACCEPT [819:117300]
:f2b-sshd - [0:0]
-A INPUT -p tcp -m multiport --dports 22 -j f2b-sshd
-A INPUT -p tcp -m tcp --dport 80 -m state --state NEW,ESTABLISHED -j ACCEPT
-A INPUT -m state --state RELATED,ESTABLISHED -j ACCEPT
-A INPUT -p icmp -j ACCEPT
-A INPUT -p tcp -m tcp --dport 8080 -m state --state NEW,ESTABLISHED -j ACCEPT
-A INPUT -i lo -j ACCEPT
-A INPUT -p tcp -m state --state NEW -m tcp --dport 22 -j ACCEPT
-A INPUT -j REJECT --reject-with icmp-host-prohibited
-A FORWARD -j REJECT --reject-with icmp-host-prohibited
COMMIT

I tried removing the rule that opens the port 8080, but after reloading iptables the server would not respond to requests from port 80 either. More recently I have been thinking of maybe adding another redirection rule that would change the source IP to something specific to accept in port 8080, but I am not sure if that will work. I need guidance here.

Note: I'm not too experienced with this tool, that is the main source of my doubts. Also, perhaps I'm missing some rules that could be useful, so any suggestions for new rules in the comments below will be appreciated.

DragShot
  • 143
  • 1
  • 1
  • 4
  • If the port `80` is simply forwarding all connections to `8080`, blocking `8080` doesn't add security. However, it does add security when you add HTTPS. Don't concentrate too much on iptables, here. Just bind the web server to local loopback and add a *reverse proxy* that terminates the TLS connection as suggested by @KHobbits. With Let's Encrypt you can and should do it today. – Esa Jokinen May 29 '20 at 03:53
  • Yes, I am aware of that, and once I get a proper domain I'll definitely go that route. Specially since Tomcat is a bit tricky to setup for HTTPS on its own, so maybe using a reverse proxy will be for the best. – DragShot May 29 '20 at 04:31

3 Answers3

7

This schematic should help you understand how packet handling is done:

Packet flow in Netfilter and General Networking

The filter/INPUT rules see only packets after they were NATed in nat/PREROUTING, so by default can't tell the difference between receiving a packet on port 8080 because a client sent it there directly, from receiving a packet on port 8080 because a client sent it on port 80 which was then redirected to port 8080. In both cases, they see a packet arriving on port 8080. So you need an additional information to be able to tell those two cases apart.

First thing first: this rule should be removed since it's useless:

    -A INPUT -p tcp -m tcp --dport 80 -m state --state NEW,ESTABLISHED -j ACCEPT

because as explained filter/INPUT won't see a packet on port 80: it's now arriving on port 8080. Don't trust tcpdump blindly here: as seen in the schematic, tcpdump (AF_PACKET) sees packets before all this, so will see port 80.


  • You can use iptables's conntrack match which queries netfilter's connection tracking system and has thus access to the missing information: for this case whether the current incoming packet is part of a connection that underwent a DNAT transformation or not, using --ctstate DNAT (REDIRECT is a special case of DNAT, just like MASQUERADE is a special case of SNAT). There are other options for this match like --ctorigdstport etc. which can probably achieve similar results.

    I'll just put the important rules about this case, rather than all, to illustrate the explanation. Just use them, when needed, at the correct place.

    iptables -t nat -A PREROUTING -p tcp --dport 80 -j REDIRECT --to-ports 8080
    

    probably right after the generic ... ESTABLISHED,RELATED -j ACCEPT line:

    iptables -A INPUT -p tcp --dport 8080 -m conntrack --ctstate DNAT -j ACCEPT
    iptables -A INPUT -p tcp --dport 8080 -j DROP
    

    (or don't add this last DROP rule if there's later a catch-all drop rule).

    The first INPUT rule will match traffic arriving at port 8080 which was DNATed (here from initially arriving at port 80), while the second will drop what's remaining arriving at port 8080: direct connection attempts.

    Note: if you're wondering why the state match and the conntrack match look similar, that state has been superseded by conntrack. Actually, internally the kernel module xt_conntrack.ko handles the state match in addition to the conntrack match, for backward compatibility.

  • An other method to transmit this information is by using a packet mark, which is a more generic method and can be applied to many other cases. It's an arbitrary number that will mark the packet (only in the kernel, not on the wire) and can be used in a few places, including the iptables mark match to alter decisions. As the MARK target is not a terminating rule it can be used before the actual REDIRECT rule. As they are displayed back in hexadecimal, I set them also in hexadecimal. The value is yours to choose what it means. Here 0x80 (which is decimal 128) will convey the information "hit port 80 and was redirected to port 8080" all by itself, so no need to recheck the port later, checking the mark validates all. As usual it's the first packet of the connection that counts: each other packet of this connection is handled by the generic conntrack stateful rule ... ESTABLISHED,RELATED -j ACCEPT.

    iptables -t nat -A PREROUTING -p tcp --dport 80 -j MARK --set-mark 0x80
    iptables -t nat -A PREROUTING -p tcp --dport 80 -j REDIRECT --to-ports 8080
    

    After the generic ... ESTABLISHED,RELATED -j ACCEPT line:

    iptables -A INPUT -m mark --mark 0x80 -j ACCEPT
    iptables -A INPUT -p tcp --dport 8080 -j DROP
    

I must also warn you against the danger of using a REJECT rule without first dropping (rather than rejecting) INVALID state packets. This might get you random connection reset issues in certain rare congested cases when TCP packets arrive in the wrong order. Relevant documentation of this issue is currently being pondered for addition:

So, instead of:

-A INPUT ... -j REJECT

do consider using:

-A INPUT ... -m conntrack --ctstate INVALID -j DROP
-A INPUT ... -j REJECT
A.B
  • 11,090
  • 2
  • 24
  • 45
  • Thank you very much for the explanation and the extra resources, specially the potential issue with rejecting stray packets. I didn't know about that one. Also, that usage of mark is genius, I didn't even know I wanted something like that to exist until I saw it. For this case, I used conntrack as filter and it works like a charm. Once again, thank you for the help. – DragShot May 29 '20 at 04:28
3

REDIRECT just changes the port number, so if the connection was:

 client -> public_addres:80

it becomes

client -> public_address:8080

So, you will not be able to block and accept at the same time by doing what you are doing.

First remove this rule:

-A INPUT -p tcp -m tcp --dport 8080 -m state --state NEW,ESTABLISHED -j ACCEPT

And use DNAT instead of REDIRECT, something like:

-A PREROUTING -i eth0 -p tcp -m tcp --dport 80 -j DNAT --to 127.0.0.1:8080

That might work.

You could also listen just on 127.0.0.1:8080 (as opposed to 0.0.0.0:8080).

Eduardo Trápani
  • 1,210
  • 8
  • 12
  • Oh, so that explains what was going on. Thank you. Even tho I ended up using the other solution (which wasn't really that different), yours seems to work as well. Also, your answer was straight to the point, I appreciate that. – DragShot May 29 '20 at 04:19
  • nitpicking: 1/ the packet *doesn't* arrive on lo, that's an immovable fact, DNAT doesn't change this (it can be verified with `-j LOG` in INPUT) . 2/ by default kernel doesn't route 127.0.0.0/8 outside of lo. As iptables altered addresses, that's now what is asked to the routing stack, and packets are dropped (they won't even be logged by the `-j LOG` above) unless [`route_localnet`](https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git/tree/Documentation/networking/ip-sysctl.txt?h=linux-5.4.y#n1220) is changed from default settings. Once done the problem remains: both are rejected – A.B May 29 '20 at 12:38
  • Thanks @A.B. I corrected the answer. – Eduardo Trápani May 29 '20 at 15:04
2

In my experience, you would typically change the bind for the 8080 service.

IE rather than binding to 0.0.0.0:8080 you would bind the service to 127.0.0.1:8080, which means that the application doesn't listen to your public interfaces.

In this case, I would set up a reverse proxy (nginx) from port 80/443 to port 8080, allowing you to configure the redirect from http port 80, to https port 443, with the application able to run on port 8080 without having to know about SSL.

I'm not an expert at iptables, so I'll not attempt a solution to fix what you have above.

KHobbits
  • 1,138
  • 7
  • 13
  • +1 This is the best solution. I'd add a configuration example to improve this. Also, first redirect to HTTPS and then reverse proxy from HTTPS, alone. – Esa Jokinen May 29 '20 at 03:56
  • While not a straight answer to my issue with the `iptables` settings, this will be very useful when I add HTTPS support to that vm. Thank you very much for your advice! I'll keep it in mind. – DragShot May 29 '20 at 04:14