Securing your port-forwarded reverse proxy
Recently, I answered a question on Reddit about reverse proxies, and said answer was long enough and interesting enough to be tidied up and posted here.
The question itself is concerning port forwarded reverse proxies and internal services:
Hey everyone, I've been scratching my head over this for a while.
If I have internal services which I've mapped a subdomain like dashboard.domain.com through NGINX but haven't enabled the CNAME on my DNS which would map my dashboard.domain.com to my DDNS.
To me this seems like an external person can't access my service because dashboard.domain.com wouldn't resolve to an IP address but I'm just trying to make sure that this is the case.
For my internal access I have a local DNS that maps my dashboard.domain.com to my NGINX.
Is this right?
So to answer this question, let's first consider an example network architecture:
So we have a router sitting between the Internet and a server running Nginx.
Let's say you've port forwarded to your Nginx instance on 80 & 443, and Nginx serves 2 domains: wiki.bobsrockets.com
and dashboard.bobsrockets.com
. wiki.bobsrockets.com
might resolve both internally and externally for example, while dashboard.bobsrockets.com
may only resolve internally.
In this scenario, you might think that dashboard.bobsrockets.com
is safe from people accessing it outside, because you can't enter dashboard.bobsrockets.com
into a web browser from outside to access it.
Unfortunately, that's not true. Suppose an attacker catches wind that you have an internal service called dashboard.bobsrockets.com
running (e.g. through crt.sh, which makes certificate transparency logs searchable). With this information, they could for example modify the Host
header of a HTTP request like this with curl
:
curl --header "Host: dashboard.bobsrockets.com" http://wiki.bobsrockets.com/
....which would cause Nginx to return dashboard.bobsrockets.com
to the external attacker! The same can also be done with HTTPS with a bit more work.
That's no good. To rectify this, we have 2 options. The first is to run 2 separate reverse proxies, with all the internal-only content on the first and the externally-viewable stuff on the second. Most routers that offer the ability to port forward also offer the ability to do transparent port translation too, so you could run your external reverse proxy on ports 81 and 444 for example.
This can get difficult to manage though, so I recommend the following:
- Force redirect to HTTPS
- Then, use HTTP Basic Authentication like so:
server {
# ....
satisfy any;
allow 192.168.0.0/24; # Your internal network IP address block
allow 10.31.0.0/16; # Multiple blocks are allowed
deny all;
auth_basic "Example";
auth_basic_user_file /etc/nginx/.passwds;
# ....
}
This allows connections from your local network through no problem, but requires a username / password for access from outside.
For your internal services, note that you can get a TLS certificate for HTTPS for services that run inside by using Let's Encrypt's DNS-01 challenge. No outside access is required for your internal services, as the DNS challenge is completed by automatically setting (and then removing again afterwards) a DNS record, which proves that you have ownership of the domain in question.
Just because a service is running on your internal network doesn't mean to say that running HTTPS isn't a good idea - defence in depth is absolutely a good idea.