11

Similar to this and this question I want to block users from using the IP to access my server.

For HTTP (port 80) this works fine, but not for HTTPS. So users can still enter https://<myip> to access the webserver and nginx returns the default certificate. BTW I use HTTP2 in my "usual" server blocks (with my domain names), so I use:

listen 443 ssl http2;
listen [::]:443 ssl http2;

I tried this to block HTTPS ip access now:

server {
   listen 443 ssl;
   listen [::]:443 ssl;

   server_name _;
   return 444;
}

However unfortunately this blocks all HTTPS requests no matter whether the domain is used or not. I know it might be necessary to block non-SNI clients as these of course do not deliver the used domain name to the server, so I am fine with that. (I mean clients not supporting SNI are old anyway...)

I generally prefer to block this in nginx, but if you would have some ideas about blocking this at the firewall level (iptables) I would also happily appreciate these too. And if you want to argue why blocking with iptables is better you can also do this and convince me to also block HTTP [or all other] requests to the IP too. Generally dropping the connection is fine (as nginx status code 444 does).

However there is one requirement: I do not want to explicitly mention the IP address of the server in the config as it is a dynamic IP and the server uses a dynamic dns service.

So in short, here is what I want to achieve:

  • block access via IP
  • allow access via domain name
  • it is fine to block non-SNI clients
  • it is fine to use a firewall blocking for this
  • dropping the connection is fine
  • without mentioning the IP address of the server
  • no exposing of the domain name via HTTPS

Edit: Another failed try. I tried to follow this suggestion and use this config snippet, which seems logically as a good thing:

if ($host != "example.com") {
        return 444;
}

It also basically works, but when I access "https://" I see that nginx at first already sends the HTTPS certificate (containing the domain name) and only when I skip the connection warning, I see that it blocks access. This is logical as nginx can only read the Host header when the HTTPS connection is there, but at this point nginx already send the server certificate containing the domain name, so a user now has the domain name and can reconnect using it making the whole IP blocking useless. I am also a bit concerned about the performance aspect of this solution as it causes nginx to check the host header for every request, so I am still looking for a solution here.

rugk
  • 506
  • 2
  • 6
  • 18
  • Please edit your post to include nginx.conf and the full configuration for each server. Something you think isn't relevant may be. – Tim Aug 04 '16 at 21:53
  • What's the actual problem you're trying to solve? People seeing the name of the server? – Joshua DeWald Aug 05 '16 at 12:38
  • "What's the actual problem you're trying to solve?" - People accessing the IP of my server to find out which domain it uses. So some kind of "reverse dns" prevention. (although this of course does not have anything to do with dns) – rugk Aug 05 '16 at 21:09
  • Any luck in finding a solution here? I also have this problem. – abarax May 25 '17 at 04:13
  • any update, guys ? – Phung D. An Dec 30 '17 at 18:38
  • This StackExchange [answer](https://stackoverflow.com/a/14267011/4536292) mitigates the issue. It prevents leaking the domain name in the sent certificate by creating a fake one. – Season Feb 27 '18 at 14:33

3 Answers3

9

Today, I encountered the same problem with this block:

server {
  listen 443 ssl default_server;
  server_name <SERVER-IP>;
  return 444;
}

The nginx-logs says:

no "ssl_certificate" is defined in server listening on SSL port while SSL handshaking

As you mentioned, this seems to disable the other server-blocks as well. It 's possible to fix this by using a (self-signed) certificate for the server-ip-address. I did this using openssl:

openssl req -x509 -nodes -days 365 -newkey rsa:2048 -keyout privateKey.key -out certificate.crt -subj '/CN=<SERVER-IP>'

Then change the server-block to:

server {
    listen 443 ssl default_server;
    server_name <server-ip>;
    ssl_certificate         /path/to/certificate.crt;
    ssl_certificate_key     /path/to/privateKey.key;
    return 444;
}

When someone uses the SERVER-IP over https to access the server, nginx presents the (self-signed) certificate and NOT the domain-name-certificate you want to hide.

By the way, make your server-block-with-IP the default_server. This way, a client who has SNI disabled, will get the IP-certificate and NOT the domain-name-certificate. This can also be tested with openssl (the -servername option, which will enable SNI, is omitted):

openssl s_client -connect <SERVER-IP>:443
BvE
  • 91
  • 1
  • 1
2

Try this - the default_server bit is the important part. If it doesn't work please update your question showing the configuration for other servers listening on 443/SSL.

server {
  listen 443 ssl default_server;
  listen [::]:443 ssl default_server; # not sure if you want/need it here as well. Try both ways.

 server_name _;
 return 444;
}
Tim
  • 31,888
  • 7
  • 52
  • 78
  • Thanks, did not knew about this "default_server". The HTTP solution also worked without it. But this here does not work. It still blocks all HTTPS requests. I already mentioned the conf for other servers: Basically it's `listen 443 ssl http2;`. – rugk Aug 04 '16 at 21:24
  • 2
    default_server is recommended as it tells nginx what to do rather than relying on nginx defaults, which could change in future. – Tim Aug 04 '16 at 21:54
0

You can reject connections for unknown domains, when somebody tries to use IP:80 and IP:443.

Response for HTTP(80): Connection closed without response. Response for HTTPS(443): Reject SSL connection.

Include this config to /etc/nginx/nginx.conf:

# /etc/nginx/sites-enabled/default_server.conf

# HTTP:80 = catch-all server block, resulting in a 444 response for unknown domains.
# HTTPS:443 = catch-all server block, resulting in reject SSL connection for unknown domains.

server {
    listen 80 default_server;
    listen [::]:80 default_server;
    server_name "_";
    return 444; # Connection closed without response
}

server {
    listen 443 default_server;
    listen [::]:443 default_server;
    server_name "_";
    ssl_reject_handshake on; # Reject SSL connection
}
Sergey
  • 11
  • 1