Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Reject request with IP address as hostname (unless defined in Caddyfile) #2068

Closed
henrypijames opened this issue Mar 14, 2018 · 15 comments
Closed
Labels
feature ⚙️ New feature or request

Comments

@henrypijames
Copy link

henrypijames commented Mar 14, 2018

1. What version of Caddy are you using (caddy -version)?

0.10.11

2. What are you trying to do?

Serve a HTTPS host that only answers to a specific domain name, while rejecting all other requests and not revealing the very existence of that domain name to anyone who isn't explicitly asking for it

3. What is your entire Caddyfile?

domain.example.com {
  root /var/www
  gzip
  tls [email protected]
}

4. How did you run Caddy (give the full command and describe the execution environment)?

/usr/local/bin/caddy -log stdout -agree=true -conf=/etc/caddy/Caddyfile -root=/var/tmp

5. Please paste any relevant HTTP request(s) here.

curl https://1.2.3.4 # literal IP address of domain.example.com

6. What did you expect to see?

Caddy rejecting the request during TLS server hello, since there is no host defined for the literal IP address, and not revealing the domain name of any host it is serving

Preferably, there should be an option to reject any client request for a literal IP address (note that SNI spec does not allow literal IPv4 or IPv6 address to be used in SNI), reject any client request for a domain name but without SNI, and reject any client request for a domain name with SNI but not matching any of the domain names explicitly defined in the Caddyfile

7. What did you see instead (give full error messages and/or log)?

Caddy accepting the request, finishing the TLS server hello and then sending the TLS certificate of domain.example.com to the client, thus revealing that it is serving a host at that domain name

In other words, Caddy is leaking the domain name (which cannot be acquired through reverse DNS from the IP address) to anyone who is scanning the HTTPS port of the server

8. How can someone who is starting from scratch reproduce the bug as minimally as possible?

See above

@henrypijames
Copy link
Author

henrypijames commented Mar 14, 2018

PS: The bind directive didn't help in this case since the server sits behind a NAT, and its internal IP is different from its external one. bind domain.example.com would make the server completely unreachable since the IP that correspond to that domain is not accessible to Caddy (I have tested this).

@pgaskin
Copy link

pgaskin commented Mar 14, 2018

Well, I cannot answer your question directly, but I don't think this can be accomplished at the moment if you are using let's encrypt due to certificate transparency logs. It is pretty easy to use the public logs to find every subdomain of a domain using let's encrypt unless you use the new wildcard certificates (which caddy does not support yet).

@mholt
Copy link
Member

mholt commented Mar 14, 2018

What kind of configuration do you use on other web servers to accomplish this? A "well-behaving" server, in the absence of SNI, will use a "default" certificate (presumably the only one) to serve the connection.

And yes, certificate transparency will undermine most of the secrecy you are hoping to gain. (It's still a good thing.)

@henrypijames
Copy link
Author

henrypijames commented Mar 15, 2018

I haven't tried this before and don't know how to do it with a different httpd. Can you point me to any standard that specify how a "well-behaving" server is not allowed to reject HTTPS request without SNI?

My goal is not to hide my server from someone who is looking for it - that would be enormously difficult. I only wish to hide from someone who is performing a (possibly automated) port scan over an entire IP range. If he gets a response (in form of a certificate for a domain name), he may try something else with that information; if he's rejected right away, he (or the automated scan process) may simply move on.

The fact that Let's Encrypt would reveal the domain name is not relevant here. I could simply switch to another CA that doesn't have public logs - problem solved (at least that part of the problem).

@whitestrake
Copy link
Collaborator

While I personally don't believe this kind of secrecy is important or effective against malicious port scans or crawlers, you could prevent "leakage" of valid domains served at an IP address via certificate names by specifying a site label in Caddy for that IP address as a host and using a self-signed certificate.

https://1.2.3.4 {
  tls self_signed
  status 403 /
}

A HTTPS request to this site should return something like:

curl -kv https://[snip]/
*   Trying [snip]...
[...]
* SSL connection using TLSv1.2 / ECDHE-ECDSA-AES256-GCM-SHA384
* ALPN, server accepted to use h2
* Server certificate:
*  subject: O=Caddy Self-Signed
*  start date: Mar 15 07:11:49 2018 GMT
*  expire date: Mar 22 07:11:49 2018 GMT
*  issuer: O=Caddy Self-Signed
*  SSL certificate verify result: unable to get local issuer certificate (20), continuing anyway.
* Using HTTP2, server supports multi-use
* Connection state changed (HTTP/2 confirmed)
* Copying HTTP/2 data in stream buffer to connection buffer after upgrade: len=0
* Using Stream ID: 1 (easy handle 0x7fed8d808400)
> GET / HTTP/2
> Host: [snip]
> User-Agent: curl/7.54.0
> Accept: */*
>
* Connection state changed (MAX_CONCURRENT_STREAMS updated)!
< HTTP/2 403
< content-type: text/plain; charset=utf-8
< server: Caddy
< x-content-type-options: nosniff
< content-length: 14
< date: Thu, 15 Mar 2018 07:13:18 GMT
<
403 Forbidden

This is alongside another site with a current valid LetsEncrypt certificate.

@henrypijames
Copy link
Author

henrypijames commented Mar 15, 2018

@whitestrake Yes, I thought about this, but it won't work if my server has a dynamic external IP.

Now, I'm not sure hiding the certificate would be an effective security measure, either. But one of the basic principles of information security is that you shouldn't reveal information unnecessarily. In this case, there is no good reason why a well-intentioned, legitimate client would use the IP address instead of the domain name in its request. Therefore, giving him the certificate is unnecessary, and bad. How bad in practical terms, and how much better it would be not to give him that, does not change the principle nature of the issue. How many security measures have been broken through a combination of seemingly unimportant pieces of information that all should have been kept secret? Keyword "metadata", anyone?

@whitestrake
Copy link
Collaborator

Haven't tested this one, but I think this would work the same?

https:// {
  tls self_signed
  status 403 /
}

Should catch every single HTTPS host that isn't otherwise defined in your Caddyfile.

@henrypijames
Copy link
Author

@whitestrake Yes, this works for me!

@henrypijames
Copy link
Author

I wonder if this would also work, if the server is otherwise setup to reject all (unencrypted) HTTP requests:

https:// {
  redir http://{hostonly}
}

(I can't test this right now since I have an unrelated problem on my server.)

@whitestrake
Copy link
Collaborator

Don't forget tls self_signed.

If you're rejecting HTTP requests, then redirecting to HTTP makes less sense since you're almost guaranteeing a repeat visit (that will only get rejected anyway) and you're sending more bytes (as a location header). Might as well just give them the 403 and end it there.

@mholt mholt closed this as completed Mar 19, 2018
@mholt
Copy link
Member

mholt commented Mar 19, 2018

We kind of did this in 0.10.11 but it broke quite a few legit reasons for not having valid SNI -- I need to revisit it later, and as you can see, there is a workaround. Thanks for the discussion!

@henrypijames
Copy link
Author

I'm looking at #2037 (which seems to be the exact opposite of what I want) and wondering: Is it possible (and legitimate) for Caddy to generate (intentional) invalid TLS certificates on the fly, in order to tell the client (by triggering a TLS error) that something is wrong with its request? In other word, in addition to tls serlf_signed, could there be a tls invalid_self_signed?

@henrypijames
Copy link
Author

Or better still, how about a tls reject which makes Caddy cancel the TLS handshake (before and without sending any certificate at all)?

@mholt
Copy link
Member

mholt commented Mar 27, 2018

@henrypijames That's possible, I think. Can you open a new issue to discuss it? Just state exactly your situation and what you want to achieve, and if possible how you'd like it to happen.

@henrypijames
Copy link
Author

@mholt Yes, will do (shortly).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
feature ⚙️ New feature or request
Projects
None yet
Development

No branches or pull requests

4 participants