Mitigating MITM for Let's Encrypt HTTP Challenge
Let's Encrypt is a non-profit that issues free Domain Validated SSL certificates, and is probably the most important push towards securing the world's HTTP traffic. However, Domain Validation has always been vulnerable to man-in-the-middle (MITM) attack by several entities, including the VPS provider, HTTP proxy, DNS provider, ISP, or government organizations. Currently, common validation methods used by SSL Certificate Authorities (CA) include: a) the ability to receive an email at a specific address, e.g. email@example.com, b) the ability to serve a special file over port 80 or port 443, and c) the ability to create a TXT record on your DNS nameserver. Methods a) and b) are the most vulnerable to MITM, while method c) requires a more difficult MITM between the DNS nameserver and the CA. In this article, I will show how to prevent unauthorized SSL certificates from being issued, and disable HTTP Challenges on Let's Encrypt.
1. Limit Allowed Certificate Authorities
The easiest way to limit malicious SSL certificate issuance is to use a root CAA record. CAA records are DNS records that provide limits on which CAs are able to issue SSL certificates for your domain. For every domain and subdomain, CAA records can either limit issuance to specific CAs, or ban SSL certificate issuance outright (we will be using this feature later). If you only require Domain Validation, then go ahead and create a CAA record to mandate Let's Encrypt as your sole CA. Assuming you are creating a record for your root domain (and therefore all subdomains), this is the CAA record:
example.com. CAA 0 issue "letsencrypt.org"
Note that CAA domain matching works recursively, meaning that if
mail.example.com doesn't have a record, then it will match
Great! Assuming that DNS records aren't being modified, we have limited our attack surface to that of Let's Encrypt validation methods, which are: the HTTP-01 challenge, the DNS-01 challenge, and the TLS-ALPN-01 challenge. Unfortunately, both the HTTP-01 and TLS-ALPN-01 challenge are vulnerable to MITM attack, as explained by Mike Gualtieri on his blog (Chaining Remote Web Vulnerabilities to Abuse Let's Encrypt). Let's try to disable them.
2. Disable HTTP/TLS Challenges
Let's Encrypt does not officially provide a way to disable HTTP/TLS challenges for your domain or subdomain. Although they are developing an IETF standard to provide that functionality, it is still in draft status.
Fortunately, HTTP/TLS challenges by Let's Encrypt have a helpful limitation: HTTP/TLS challenges only issue certificates with a non-wildcard domain name e.g.
example.com. That means DNS challenges are the sole method to issue wildcard certificates e.g.
*.example.com. Furthermore, CAA records have the ability to disallow non-wildcard SSL certificate issuance for a specific domain/subdomain, simply by presenting a null CA!
For our use case, let's assume that we don't care about unauthorized certificates being generated for the root domain
example.com, but want to protect our subdomains:
donthurtme.example.com. With the DNS Challenge, we first generate a wildcard certificate
*.example.com. Next, we disallow non-wildcard certificate issuance for each subdomain with the following CA records:
example.com. CAA 0 issue "letsencrypt.org" secure.example.com. CAA 0 issue ";" donthurtme.example.com. CAA 0 issue ";"
That's it! A HTTP Challenge MITM can still issue an unauthorized certificate for
example.com, but is unable to for your subdomains. If you don't need SSL certificates for the root domain, disable non-wildcard certificates for it with the following:
example.com. CAA 0 issue ";" example.com. CAA 0 issuewild "letsencrypt.org" secure.example.com. CAA 0 issue ";" donthurtme.example.com. CAA 0 issue ";"
However, this method has its own shortcomings, mainly due to the danger of the wildcard. If a single private key happens to leak from any of your servers, then this means that an adversary can impersonate all of your subdomains. Fortunately, we can fix that problem by going even deeper...
2.1. Switch to Sub-subdomains
For this method, you will need to change your subdomains to sub-subdomains (a small price to pay!). Let our new hostnames be
baby.donthurtme.example.com. We want to allow wildcard certificates for
*.donthurtme.example.com, while disallowing non-wildcard certificates for the sub-subdomains. These are the new CAA records:
example.com. CAA 0 issue "letsencrypt.org" very.secure.example.com. CAA 0 issue ";" baby.donthurtme.example.com. CAA 0 issue ";"
No longer can the SSL certificates of
*.donthurtme.example.com impersonate the other! You can lock down the CAA records even further, but it shouldn't make a difference unless you accidentally use the wrong hostname.
3. Monitor Certificate Logs
As part of the "Certificate Transparency" initiative, CAs are required to submit any new certificates that are issued to a public log. You can monitor these logs yourself, or use free services such as sslmate's Cert Spotter, or Cloudflare's Certificate Transparency Monitoring. Although this won't stop a MITM, it will at least notify you of any certificate being issued to your domain.
Until the IETF draft is complete, this might be the only way to disable the HTTP/TLS challenge for Let's Encrypt. Interestingly, I couldn't find any mention of my method online, so hopefully this article is helpful to some!