I've talked a lot about revocation checking before on my blog and made various arguments against it and explained why it's not such a great idea, but there's nothing like demonstrating something. I decided to do a little experiment and show just how easy it is for an attacker to disable revocation checking and why, as it stands, it really is pointless.
Revocation checking: a brief history
I have a super detailed blog post all about the 2 main mechanism for revocation checking that people are often familiar with. We have Certificate Revocation Lists (CRL) and the Online Certificate Status Protocol (OCSP) which let a client check if a certificate has been revoked and the client should no longer trust that otherwise valid certificate. You should read my blog post on why revocation is broken for a very detailed explanation of both CRL and OCSP. It will explain what they are and why they are no longer reliable. CRL stop being useful many years ago and there were efforts to save OCSP, like OCSP Stapling supported further by OCSP Must-Staple and OCSP Expect-Staple, but eventually even OCSP couldn't survive. The purpose of this blog post is not to talk about those details though, the linked blog posts do that, the purposes of this blog post is to demonstrate that revocation checking really is pointless as it currently stands.
Just do what an attacker would do...
One of the biggest problems with existing revocation mechanisms is their soft-fail nature. If the client can't do the check, it gives up and accepts the certificate as valid. All an attacker needs to do is make the client give up. Given that fetching both the CRL or OCSP response are done with HTTP requests, an attacker can simply block them on the network, but I had a slightly different and easier way to do it.
Using a Pi-hole
I run a Pi-hole on my network that runs as my local DNS resolver and also does DNS-over-HTTPS to an upstream so my DNS traffic is more private. You can read how to set one up for yourself (you really should) and how I forced all devices on my network to use it. What this means is I can control whether or not certain domains resolve when you're using my network. The main goal of the Pi-hole is network-wide ad blocking and I wanted to extend that further myself.
Where do CRL/OCSP requests get sent?
The crt.sh website keeps a handy list of all OCSP responders which is a great starting point and you just need a list of CRL endpoints after that. There isn't a handy page to go to grab all of those but you can connect to the PostgreSQL database behind crt.sh and run the query yourself.
psql -h crt.sh -p 5432 -U guest certwatch
certwatch=> SELECT distribution_point_url FROM public.crl;
If you'd like to avoid manually gathering this data then I create a public GitHub repository called revocation-endpoints that contains both the CRL and OCSP list for you.
Blocking revocation checks
Now that we know where CRL and OCSP requests get sent, all we need to do is block them. The files provided in the repository above are already formatted as hosts lists which means you can use them directly from the Pi-hole!
Simply add these lists directly in the Settings -> Blocklists menu and then 'Save and Update' to put them into action.
Noticing more DNS blocks
My setup has been static for quite some time and it was normal for me to hover around ~10% of DNS queries being blocked by my Pi-hole. You can see a more recent tweet here where I had a screenshot of my dashboard.
The list is in action already. I will leave it a little while and see what DNS queries for OCSP look like and publish some numbers! pic.twitter.com/ME4wskXU9P
— Scott Helme (@Scott_Helme) March 4, 2020
If I grab a screenshot from my dashboard now we can see that my current block % is much higher at ~13%.
The only thing that's changed any time recently is the addition of the new block lists.
Let's see it (not)working!
All certificate authorities are required to make a series of test sites available that use a valid certificate, an expired certificate and a revoked certificate. This comes in really handy for demonstrating my point! Let's Encrypt keep their revoked example at https://revoked-isrgrootx1.letsencrypt.org/ and all you need to do is click the link and open it in your favourite browser. Here's what I get.
It seems the site loads in all of the browsers I have to hand, but let's dig deeper and prove that this is definitely a revoked certificate. Looking in the certificate inspector in Chrome we can see the CN of the certificate is revoked-isrgrootx1.letsencrypt.org
and the serial number is 0318e75e54cf72127e34b3342d2e2452c0c2
.
Here is the crt.sh link directly to that specific certificate and crt.sh indeed confirms that the certificate is revoked.
Prove it ourselves
Let's not simply trust crt.sh though, we can test this ourselves easily too. Using OpenSSL s_client we can connect to the site and fetch the certificate that it serves for analysis.
openssl s_client -showcerts -servername revoked-isrgrootx1.letsencrypt.org -connect revoked-isrgrootx1.letsencrypt.org:443 </dev/null > revoked-isrgrootx1.letsencrypt.org.crt
Here we're connecting with s_client using SNI and storing the output in a file called revoked-isrgrootx1.letsencrypt.org.crt
. We can now examine that file with OpenSSL x509.
openssl x509 -in revoked-isrgrootx1.letsencrypt.org.crt -noout -text
This will take the certificate file, which contains the PEM encoded certificate, and parse it to give us the human-readable output. The specific section we want is the Authority Information Access
(AIA) field which will contain the URI for the OCSP responder.
Authority Information Access:
OCSP - URI:http://ocsp.int-x3.letsencrypt.org
CA Issuers - URI:http://cert.int-x3.letsencrypt.org/
If you're not interested in any other certificate data at all you can parse out the OCSP endpoint specifically.
openssl x509 -in revoked-isrgrootx1.letsencrypt.org.crt -noout -ocsp_uri
http://ocsp.int-x3.letsencrypt.org
We are going to need an extra piece of information from the AIA in this case though and that's information on the issuing certificate which is present in the CA Issuers
field.
Authority Information Access:
OCSP - URI:http://ocsp.int-x3.letsencrypt.org
CA Issuers - URI:http://cert.int-x3.letsencrypt.org/
This is the address to download the certificate that issued the certificate we are currently looking at, what is known as AIA Fetching. We will need the intermediate to query the OCSP responder so let's fetch that first and then convert it to the right format.
wget -O intermediate.crt http://cert.int-x3.letsencrypt.org/
openssl x509 -in intermediate.crt -inform DER -out intermediate.crt
Now that we have the intermediate we can query the OCSP responder to get a status for this particular certificate!
openssl ocsp -issuer intermediate.crt -cert revoked-isrgrootx1.letsencrypt.org.crt -url http://ocsp.int-x3.letsencrypt.org -no_nonce -text
OCSP Request Data:
Version: 1 (0x0)
Requestor List:
Certificate ID:
Hash Algorithm: sha1
Issuer Name Hash: 7EE66AE7729AB3FCF8A220646C16A12D6071085D
Issuer Key Hash: A84A6A63047DDDBAE6D139B7A64565EFF3A8ECA1
Serial Number: 0318E75E54CF72127E34B3342D2E2452C0C2
OCSP Response Data:
OCSP Response Status: successful (0x0)
Response Type: Basic OCSP Response
Version: 1 (0x0)
Responder Id: C = US, O = Let's Encrypt, CN = Let's Encrypt Authority X3
Produced At: Mar 10 16:01:00 2020 GMT
Responses:
Certificate ID:
Hash Algorithm: sha1
Issuer Name Hash: 7EE66AE7729AB3FCF8A220646C16A12D6071085D
Issuer Key Hash: A84A6A63047DDDBAE6D139B7A64565EFF3A8ECA1
Serial Number: 0318E75E54CF72127E34B3342D2E2452C0C2
Cert Status: revoked
Revocation Time: Mar 4 16:00:44 2020 GMT
This Update: Mar 10 16:00:00 2020 GMT
Next Update: Mar 17 16:00:00 2020 GMT
Signature Algorithm: sha256WithRSAEncryption
83:d1:fb:18:1f:50:7e:f4:8b:5b:0d:60:62:0d:40:97:8f:d7:
e8:cb:18:58:ba:89:a2:fa:a0:04:9e:32:a0:19:a0:82:47:e6:
0a:85:95:fd:9a:d8:59:65:31:f6:d0:46:60:fc:fd:10:59:4c:
54:94:cc:70:04:44:e8:09:08:44:e0:bd:04:ad:96:20:0d:7a:
4c:2b:3a:6a:c9:19:88:78:42:83:7e:aa:8b:b5:06:f5:d6:44:
27:ee:2a:66:59:6f:8a:0a:82:f7:85:d0:29:f0:c9:c8:1a:78:
f6:98:b5:a8:c7:c6:21:85:46:e6:c4:18:3c:fe:dd:8a:35:75:
88:04:c7:1f:93:c6:da:46:26:28:bd:c4:a8:7a:29:3e:d3:27:
dc:13:12:d7:c5:13:5f:84:d9:d1:61:91:60:ba:50:42:b2:7b:
c8:40:19:b2:15:2f:72:94:f2:f6:f3:62:d7:54:ef:91:04:6b:
a8:45:b5:07:8f:c5:1a:7a:bc:ad:0d:0f:ed:2e:6f:8d:0e:8d:
95:0d:f5:9d:68:cf:c5:e4:ff:a4:f8:35:6c:c9:7b:83:3b:a3:
d2:17:15:95:f5:c5:ee:cb:0f:77:41:a9:45:bf:1e:ae:64:eb:
19:8e:ab:09:d0:63:5f:fd:b8:a4:16:1d:0d:92:39:32:60:bc:
b8:1e:5c:5f
Response verify OK
revoked-isrgrootx1.letsencrypt.org.crt: revoked
This Update: Mar 10 16:00:00 2020 GMT
Next Update: Mar 17 16:00:00 2020 GMT
Revocation Time: Mar 4 16:00:44 2020 GMT
There we have it! Right at the end you can see the response came back ok and it was verified, and the response confirmed a status of revoked
. If I try to run these queries from a local system on my network they don't work at all and I can see the Pi-hole is blocking the DNS queries for the OCSP responder.
That filer there is quite specific for int-x3
to look for the Let's Encrypt OCSP responder but looking more broadly at ocsp
I can see that a lot of DNS queries have been filtered.
Nothing has broken yet
This is one thing that has surprised me and one thing that I'm still keeping an eye on. So far I haven't noticed any change in behaviour from completely disabling all revocation checking on my internal network. Many may say this is a stupid idea and of course we should want revocation checking, why would I block such a useful thing. The question really then becomes, is it useful if it can be blocked so easily? All an attacker would need to do is block my DNS requests or the HTTP requests themselves and if they're in control of my network in some way then I'm screwed. I wrote another article recently called CRLite: Finally a fix for broken revocation? and maybe that will provide a future for revocation, so check it out, but for now, I guess this proves just how pointless it really is...