Opened 6 years ago

Last modified 3 weeks ago

#195 accepted enhancement

Close connection if SSL not enabled for vhost

Reported by: svario.it/gioele Owned by: somebody
Priority: minor Milestone:
Component: nginx-module Version: 1.3.x
Keywords: Cc:
uname -a: Linux camelia.svario.it 3.2.0-29-virtual #46-Ubuntu SMP Fri Jul 27 17:23:50 UTC 2012 x86_64 x86_64 x86_64 GNU/Linux
nginx -V: nginx version: nginx/1.1.19 TLS SNI support enabled configure arguments: --prefix=/etc/nginx --conf-path=/etc/nginx/nginx.conf --error-log-path=/var/log/nginx/error.log --http-client-body-temp-path=/var/lib/nginx/body --http-fastcgi-temp-path=/var/lib/nginx/fastcgi --http-log-path=/var/log/nginx/access.log --http-proxy-temp-path=/var/lib/nginx/proxy --lock-path=/var/lock/nginx.lock --pid-path=/var/run/nginx.pid --with-http_gzip_static_module --with-http_ssl_module --with-ipv6 --without-http_browser_module --without-http_geo_module --without-http_limit_req_module --without-http_limit_zone_module --without-http_memcached_module --without-http_referer_module --without-http_scgi_module --without-http_split_clients_module --with-http_stub_status_module --without-http_ssi_module --without-http_userid_module --without-http_uwsgi_module --add-module=/build/buildd/nginx-1.1.19/debian/modules/nginx-echo

Description

Instead of using the default SSL certificate, nginx should (by default or when configured) close the SSL connection as soon as it realizes that the requested domain has not been configured to be served over HTTPS.

For example,

server {
    listen 80 default_server;
    listen 443 ssl;

    server_name     aaa.example.net;

    ssl_certificate     /etc/ssl/certs/aaa.example.net.pem;
    ssl_certificate_key /etc/ssl/private/aaa.example.net.key;
}

server {
    listen 80;

    server_name     bbb.example.net;
}

If a client starts an HTTPS request for bbb.example.net, it will be greeted with a (correct) error/warning: "This certificate is untrusted, wrong domain". This is correct because nginx is serving the aaa.example.net certificate.

What nginx should do is to close the connection as soon as it discovers the domain that is being requested (after reading the SNI data, I suppose). This will communicate to the client and the user that there is HTTPS connectivity on bbb.example.net. Also, this solution will not disclose information about the fact that aaa.example.net is served by the same nginx server.

Change History (12)

comment:1 Changed 6 years ago by mdounin

  • Resolution set to worksforme
  • Status changed from new to closed

Nobody stops you from configuring nginx in such a way which won't return anything for hosts not explicitly configured (on a listen socket in question) or will return some preconfigure error page. See here for an example.

comment:2 Changed 6 years ago by svario.it/gioele

  • Resolution worksforme deleted
  • Status changed from closed to reopened

The examples you posted do not address at all the problem I raised.

Before nginx send an error page, the browser has to pass though the SSL handshake. If domain in the certificate does not match the requested domain it will first show the user a big fat warning about insecure connections. Only after the user has accepted the mismatching certificate it will finally show the error page.

The linked page show examples that work for plain HTTP traffic, not HTTPS traffic.

It would be OK if I could do

server {
    listen 80;

    server_name     bbb.example.net;
}

server {
    listen 443 ssl;

    server_name     bbb.example.net;

    refuse_connections;
}

This possibility does not currently exist, this is what I was requesting.

comment:3 Changed 6 years ago by mdounin

  • Status changed from reopened to accepted

Ah, sorry, I see now what you are trying to achieve. This might indeed make sense as it might result in less scary messages in some cases.

comment:4 Changed 6 years ago by mdounin

See also #214, which is somewhat related.

comment:5 Changed 3 years ago by ghprince@…

Hi, it's been 3 years, any update on this?

comment:6 Changed 3 years ago by mdounin

  • sensitive set to 0

Readily available workaround is to use unsatisfiable cipher specification. E.g., something like this will work:

server {
    listen 443 ssl;
    server_name bbb.example.com;
    ssl_ciphers aNULL;
    ssl_certificate /path/to/dummy.crt;
    ssl_certificate_key /path/to/dummy.key;
    return 444;
}

Given the workaround available, it's not a surprise that there are no developers willing to work on this. At least no one tried to submit a patch. Probably this ticket should be closed.

comment:7 Changed 3 years ago by jonashaag@…

I want to work on this, but I'm not familiar with the nginx code base or OpenSSL in general. My first attempt on finding the correct place to insert the new check lead me to ngx_http_ssl_servername; more specifically, I thought to insert the check here:

if (ngx_http_find_virtual_server(c, hc->addr_conf->virtual_names, &host,
                                 NULL, &cscf)
    != NGX_OK)
{
    return SSL_TLSEXT_ERR_NOACK;
}

/* HERE */

I decided to go for this place because we need SNI information to safely reject such 'wrong' SSL connection. (Is that assumption even correct?)

Turns out that in this place the routine always returns with SSL_TLSEXT_ERR_NOACK, but "the HTTPS connection still works" as I'm seeing the document I'm accessing in the browser. Doesn't SSL_TLSEXT_ERR_NOACK mean that we want to close the SSL connection? Why does it continue?

Could someone point me to the right place(s) where the new checks have to be inserted, and maybe even provide me with a very rough sketch of how that would look like?

comment:8 follow-up: Changed 19 months ago by BertrandBordage@…

Is this reasonable to use the snakeoil certificate generated by openssl as a workaround?
Something like this (paths found on Ubuntu 16.04):

server {
  listen 443 ssl default_server;
  listen [::]:443 ssl default_server;
  ssl_certificate /etc/ssl/certs/ssl-cert-snakeoil.pem;
  ssl_certificate_key /etc/ssl/private/ssl-cert-snakeoil.key;
  return 444;
}

comment:9 in reply to: ↑ 8 Changed 19 months ago by mdounin

Replying to BertrandBordage@…:

Is this reasonable to use the snakeoil certificate generated by openssl as a workaround?

Use ssl_ciphers aNULL; as suggested in comment:6, it works fine. Just an incorrect certificate will result in a warning in the browser due to incorrect certificate.

comment:10 Changed 5 weeks ago by mdounin

See also #1571.

comment:11 Changed 3 weeks ago by nh2@…

I came here to mention that the described workaround

server {
    listen 443 ssl;
    server_name bbb.example.com;
    ssl_ciphers aNULL;
    ssl_certificate /path/to/dummy.crt;
    ssl_certificate_key /path/to/dummy.key;
    return 444;
}

can be problematic for HTTPS clients that don't support server name indication (SNI).

Notably, nginx's own HTTP client used for proxy_pass does not support SNI by default unless you enable proxy_ssl_server_name on; (docs).

So if you point a default proxy_pass at an HTTPS server that uses the above workaround, then that proxying will fail immediately with ssl3_get_client_hello:no shared cipher.

This is because if the client doesn't support SNI, the two sides have to establish the SSL connection before the Host field is sent to nginx which matches it against server_name.

A workaround is to enable the mentioned (for nginx's own HTTP client) is as mentioned

    proxy_pass https://myrealdomain.../
    proxy_ssl_server_name on;

but other TLS clients without SNI support will still have this problem.

And as per the the the issue description

after reading the SNI data, I suppose

it's not only the workaround that's problematic, but the "real" solution would suffer from the same problem.


This is not to discourage anybody from working on this issue; I think giving nginx a clean way to shut down SNI with something refuse_connections; can make for better error messages in case SNI is supported (as most clients do support it).

I just wanted to leave this info here for others that, like me, mistakenly thought that this (or the workaround) might work for all (including non-SNI) clients; in retrospect it's quite obvious that it cannot.

(I also wonder why proxy_ssl_server_name isn't on by default.)

comment:12 Changed 3 weeks ago by mdounin

can be problematic for HTTPS clients that don't support server name indication (SNI).

This depends on how you've configured things. If you've used ssl_ciphers aNULL; in the default server block, this will prevent non-SNI clients from connecting. If you've used it in a name-based virtual server, it will only block SNI-enabled connections to the server in question.

Note: See TracTickets for help on using tickets.