15

How do I avoid nginx processing a request with an undefined server name using the https protocol.

The following configuration makes this work for normal http requests. It resets the connection for requests with empty host headers which equals to server_namein the nginx configuration:

server {
    listen      80;
    server_name "";
    return      444;
}

Updated for clarity:

The server does however serve html of the default ssl supporting server block when a request over https is sent with an empty host header. How can this be avoided?

I would like to achieve the exact behaviour as for http requests with an empty host header - no html served, connection reset (return 444).

What would be the proper way to block such https requests and achieve the mentioned behaviour for https connections as well? (I would like to avoid using the proper SSL cert for this as this may just hint spammers at the real server domain.)


Update 13.03.2015

Putting an haproxy in front of nginx with the setting

strict-sni

solved my issue completely.

  • What's an "undefined server" and "undefined https request"? – domen Apr 15 '14 at 8:40
  • When you don't request for a specific host like www.abc.com but try to connect to the webserver directly with the ip. Usually scammers and bots do this only. nginx.org/en/docs/http/… – binaryanomaly Apr 16 '14 at 18:34
  • Hostname is communicated after communication throught port 443 is established. – domen Apr 16 '14 at 19:03
  • but it still slips through as i see requests like https://ip in the logs. Whereas http://ip is caught. – binaryanomaly Apr 16 '14 at 22:01
11

I've solved it by generating a fake certificate that doesn't reveal domain name and adding it as a default one on the start of the config:

server {
    listen 443 default;
    server_name     _;

    ssl     on;
    ssl_certificate         /path/to/fake.crt;
    ssl_certificate_key     /path/to/fake.key;

    return 403;
}

And yes, it requires a nginx with SNI support (check nginx -V).


Follow on: How to create self signed SSL certificate for test purposes

9
+50

Due to the nature of how SSL works, the SSL/TLS handshake is performed before the intended hostname is given to the web server. This means that the default (first) certificate is used when trying to access the site, regardless of the domain name used.

This is true with both Apache and nginx.

From the Apache Wiki:

As a rule, it is impossible to host more than one SSL virtual host on the same IP address and port. This is because Apache needs to know the name of the host in order to choose the correct certificate to setup the encryption layer. But the name of the host being requested is contained only in the HTTP request headers, which are part of the encrypted content. It is therefore not available until after the encryption is already negotiated. This means that the correct certificate cannot be selected, and clients will receive certificate mismatch warnings and be vulnerable to man-in-the-middle attacks.

From the nginx documentation:

With this configuration a browser receives the default server’s certificate, i.e. www.example.com regardless of the requested server name. This is caused by SSL protocol behaviour. The SSL connection is established before the browser sends an HTTP request and nginx does not know the name of the requested server. Therefore, it may only offer the default server’s certificate.

How can you resolve this issue?

The easiest solution is to use separate IP addresses for each site you wish to secure.

If this is not possible, it might be possible to resolve the issue using Server Name Indication (SNI, RFC 6066). This allows a browser to pass the domain name to the server during the handshake.

Both Nginx and Apache support SNI. You can find out more on nginx SNI in the documentation.

It's worth noting that SNI can only be used for domain names, and not IP addresses. You should take extra precaution when configuring your web servers to address this issue, so any request to the IP is handled properly.

Only domain names can be passed in SNI, however some browsers may erroneously pass an IP address of the server as its name if a request includes literal IP address. One should not rely on this.

The Apache Wiki has some more information on implemeting SNI. But even their documentation advises that this solution is not perfect.

Using name-based virtual hosts with SSL adds another layer of complication. Without the SNI extension, it's not generally possible (though a subset of virtual host might work). With SNI, it's necessary to consider the configuration carefully to ensure security is maintained.

With that said, you can see how this configuration isn't as simple as regular virtual hosts. In order to further come up with a solution to your problem, we would need to know more details on your configuration and the expected behavior when an IP only request is sent.

Generally, to 'block' a non configured domain or IP request, you would configure it as the default site and then display an error, redirect, exit, etc.

  • Thanks for your answer David - I understand there is some complexity. Looks to me like SNI is needed to properly handle it, following your explanations. The expected behaviour is: Every https request that is not domain.com shall not be served and the connection resetted (return 444). Exactly as it happens for undefined http requests. I would like to avoid using the proper SSL cert for this as this may just hint spammers at the real server domain. – binaryanomaly Apr 26 '14 at 11:17
  • Hmm, my question is still sort of unanswered but as you imho provided the best answer and obviously put in some effort - the bounty shall be yours :) – binaryanomaly May 2 '14 at 17:03
4

If I understand you want to deny HTTP-Requests, which don't contain a Host header, even if these requests are inside a SSL connection (e.g. https-Requests). These are old-style HTTP/1.0 requests, HTTP/1.1 requires a Host header but also most HTTP/1.0 clients already send one. Blocking these clients can be done with:

    if ( $http_host = '' ) {
            return 444;
    }

But this does not help if the client uses a Host header with junk or the IP-address in it. Thus it would better to verify, that the host header contains the expected values (as a bonus this also helps against DNS rebinding attacks), e.g.

    if ( $http_host !~* ^(example\.com|www\.example\.com)$ ) {
            return 444;
    }
  • 2
    Thanks for your answer. This is what I would consider a "workaround". Especially as ifs are evil anyway. – binaryanomaly Apr 26 '14 at 15:26
0

I used the similar method as @int_ua mentioned, but a bit tricky.

I configured my nginx to use RSA and ECDSA as TLS Authentication method, but issued a DSA certificate for the default server.
As there is no available cipher suite, the TLS handshake would fail before serving any page.
I think it would have the same result as what you configured (HAproxy strict-sni) and believe my method would have better performance.

You can view the result of my configuration:

Invalid SNI: https://207.246.127.148/
   (You can configure your hosts to this IP address and view the result as well)
Valid SNI: https://cloud.jemmylovejenny.tk/
   The Valid SNI link points to the same IP address as above

I would give my configuration here:

SSL Configuration
( I used a openssl equal ciphers patch so that ciphers is a bit strange... You can remain your configuration as well, but remember that don't contain any DSS ciphers! )

    #Protocols
    ssl_protocols TLSv1 TLSv1.1 TLSv1.2 TLSv1.3; # Dropping SSLv3, ref: POODLE
    #
    #Key Exchange
    ssl_ecdh_curve X25519:P-256:P-384:P-224:P-521;
    ssl_dhparam /var/SSL/DH-param.pem;
    #
    #Cipher Suites
    ssl_ciphers "[TLS_AES_128_GCM_SHA256|TLS_CHACHA20_POLY1305_SHA256]:[TLS_AES_256_GCM_SHA384|TLS_AES_128_CCM_8_SHA256|TLS_AES_128_CCM_SHA256]:[ECDHE-ECDSA-CHACHA20-POLY1305|ECDHE-ECDSA-AES128-GCM-SHA256|ECDHE-RSA-CHACHA20-POLY1305|ECDHE-RSA-AES128-GCM-SHA256|DHE-RSA-CHACHA20-POLY1305]:[ECDHE-ECDSA-AES256-GCM-SHA384|ECDHE-RSA-AES256-GCM-SHA384]:[ECDHE-ECDSA-AES128-SHA|ECDHE-RSA-AES128-SHA]:[ECDHE-ECDSA-AES256-SHA|ECDHE-RSA-AES256-SHA]:DHE-RSA-AES256-SHA:DHE-RSA-AES128-SHA";
    ssl_prefer_server_ciphers on;



Default Server Configuration

##
# General
##
# Listen
listen 8080 default_server;
listen 8443 ssl spdy http2 default_server;
#
# Server Name
server_name _;

##
# SSL Settings
##
#
# Certificate
#  DSA
ssl_certificate /var/SSL/certificates/DEFAULT.dsa.crt;
ssl_certificate_key /var/SSL/keys/DEFAULT.dsa.key;

root /var/www/html/nginx/;



The DSA key is generated from OpenSSL and the certificate is issued from my own PKI. You can use this certificate if you want.
( This certificate would never be sent from the nginx server, so everything of this certificate, including keylength and valid period is meaningless )

Certificate:

-----BEGIN CERTIFICATE-----
MIIFrjCCBJagAwIBAgIUHVfGi/SP2d/B4vbxZSKSVEpSoGEwDQYJKoZIhvcNAQEL
BQAwgYExCzAJBgNVBAYTAkNOMSMwIQYDVQQKDBpKZW1teUxvdmVKZW5ueSBQS0kg
U2VydmljZTEeMBwGA1UECwwVcGtpLmplbW15bG92ZWplbm55LnRrMS0wKwYDVQQD
DCRKZW1teUxvdmVKZW5ueSBTSEEyIFNlY3VyZSBTZXJ2ZXIgQ0EwHhcNMTgwMTAx
MDAwMDAwWhcNMTkxMjMxMjM1OTU5WjAiMSAwHgYDVQQDDBdERUZBVUxUIFNTTCBD
RVJUSUZJQ0FURTCCAbcwggEsBgcqhkjOOAQBMIIBHwKBgQCVcFt4xWkIBtiN/Xay
O7Ls5EjBCFckvyBQpRJ5wmjrxepQbUK9xmiXmpkXOsEYHy14UQXswzKPBWiHsNDs
8apFoGAvjBxSg9Y07wd5VBnhBGvqd0VmD92eKAFW1sZZVoSrLwizfIde0sj9bZCD
KICt/0jdz0QGBZzXTtd4MOcjUQIVAPD9cVtRAdD6al8v/SV2YVQHZmafAoGBAI+A
tCrjcvK0PFILpwpGD/3gJI8o2oxKoPjjE3SM9b4mrN/2ixEGgGiXHOrEyZ7IgSjf
1Z6X2nuN+IgsNdJAZjNbnuacxbu4z5FmnO6i9IRXEqtKNzONeHJSG+7w7zUPfsPT
8sTxHU0Z5ynjiLz4GcnO5LVcMNbf2uEsl41fObB4A4GEAAKBgHOFoU8xns0qMFLz
h3ZiYScs2Rznw7xeo2HbM8hGI9zKWRoV49f9V2vHFjdPSLkPbzgoSqOxmGD7UZep
W0TcsSPVKIuKBw07vvJkpUG8U71mcedNN5+fNbIcgjKdTlIIbrEFYRFG6zrIcZ3/
WArPchw3RC+SRrBXW8+27T4+0PMao4IB5TCCAeEwHwYDVR0jBBgwFoAU4BPpj9H7
K19wMi69RQ9t6BLyNOcwHQYDVR0OBBYEFFrpB6sLpQwodD7KWPssHj0fkqjCMAkG
A1UdEQQCMAAwDgYDVR0PAQH/BAQDAgeAMB0GA1UdJQQWMBQGCCsGAQUFBwMBBggr
BgEFBQcDAjBJBgNVHR8EQjBAMD6gPKA6hjhodHRwOi8vY3Jscy5wa2kuamVtbXls
b3ZlamVubnkudGsvU0hBMlNlY3VyZVNlcnZlckNBLmNybDCBggYDVR0gBHsweTBt
BglghkgBpKInAQIwYDAtBggrBgEFBQcCARYhaHR0cHM6Ly9wa2kuamVtbXlsb3Zl
amVubnkudGsvY3BzMC8GCCsGAQUFBwICMCMaIWh0dHBzOi8vcGtpLmplbW15bG92
ZWplbm55LnRrL3JwYTAIBgZngQwBAgEwgYYGCCsGAQUFBwEBBHoweDAtBggrBgEF
BQcwAYYhaHR0cDovL29jc3AucGtpLmplbW15bG92ZWplbm55LnRrMEcGCCsGAQUF
BzAChjtodHRwOi8vY2FjZXJ0cy5wa2kuamVtbXlsb3ZlamVubnkudGsvU0hBMlNl
Y3VyZVNlcnZlckNBLmNydDAMBgNVHRMBAf8EAjAAMA0GCSqGSIb3DQEBCwUAA4IB
AQCtgVPw2KLT/ntVYZE8AL71C0R1gsn9MKoqD6asaM16qK60G7YK8qC2I/5wWXAr
+zVeegvU/73qWq66DnIF333uZz3aeaXoIEfDRcfePVzWTG62cO2fPyTWyIczaPFf
NqQqoukM3UeBM0yIRSbiyRAFoyz0TWhdgSDXGrG+X5EvUnH6J+umnt/PNGaNnz1f
eXJBh79UnmCpMI6rW+7UG4F60xxQ/RQeRZZt//Ze5YzIDRjUQpXQ1a+9cHuPT8EY
Ft4xe4q5FgMVTBsu1zAfA/ViSDlHWAO8oioKkYDYHqYmLT6ERSoXNVK6WbNh4mem
AaF8T8DKQTHD5GKFBJSSkvF7
-----END CERTIFICATE-----



Key:

-----BEGIN DSA PRIVATE KEY-----
MIIBuwIBAAKBgQCVcFt4xWkIBtiN/XayO7Ls5EjBCFckvyBQpRJ5wmjrxepQbUK9
xmiXmpkXOsEYHy14UQXswzKPBWiHsNDs8apFoGAvjBxSg9Y07wd5VBnhBGvqd0Vm
D92eKAFW1sZZVoSrLwizfIde0sj9bZCDKICt/0jdz0QGBZzXTtd4MOcjUQIVAPD9
cVtRAdD6al8v/SV2YVQHZmafAoGBAI+AtCrjcvK0PFILpwpGD/3gJI8o2oxKoPjj
E3SM9b4mrN/2ixEGgGiXHOrEyZ7IgSjf1Z6X2nuN+IgsNdJAZjNbnuacxbu4z5Fm
nO6i9IRXEqtKNzONeHJSG+7w7zUPfsPT8sTxHU0Z5ynjiLz4GcnO5LVcMNbf2uEs
l41fObB4AoGAc4WhTzGezSowUvOHdmJhJyzZHOfDvF6jYdszyEYj3MpZGhXj1/1X
a8cWN09IuQ9vOChKo7GYYPtRl6lbRNyxI9Uoi4oHDTu+8mSlQbxTvWZx5003n581
shyCMp1OUghusQVhEUbrOshxnf9YCs9yHDdEL5JGsFdbz7btPj7Q8xoCFFfXTHSl
C7tdLJO9vQFcyKMhU8TU
-----END DSA PRIVATE KEY-----

Your Answer

By clicking "Post Your Answer", you acknowledge that you have read our updated terms of service, privacy policy and cookie policy, and that your continued use of the website is subject to these policies.

Not the answer you're looking for? Browse other questions tagged or ask your own question.