Tags: hash-collision 2023 remote java fe-ctf 

Rating:

# Admin CLI

* remote

## Description

> A (very) early version of the administration tool used for FE-CTF was found. Looks like they only just started making
> it, but maybe it's already vulnerable?

```shell
nc admin-cli.hack.fe-ctf.dk 1337
```

---

## Challenge

We are given a Java class file, `Main.class`, a Dockerfile that runs the class file, and a netcat command to connect to
the server where the Docker container is running.

Looking at the Dockerfile, the first intention is that it seems to be a Log4shell challenge, since it uses the
vulnerable Log4j version 2.14.1.

```dockerfile
RUN wget http://archive.apache.org/dist/logging/log4j/2.14.1/apache-log4j-2.14.1-bin.zip
RUN unzip apache-log4j-2.14.1-bin.zip
```

However, looking at the Java class file, it seems not necessary to exploit the Log4j vulnerability, since the class file
simply replaces ``API_KEY`` with the flag.

```java
public class Main {

/* flag{....} */
private static String API_KEY = Base64.getUrlEncoder().encodeToString(System.getenv("FLAG").getBytes());

/* Doesn't seem to be authorized, I don't know why... */
/* https://backend.fe-ctf.local/removePoints?teamId=0&amount=1000&key=api_key */
private static int HASH_CODE = -615519892;

/* Should be safe, right? */
private static Logger logger = LogManager.getLogger(Main.class);

public static void main(String[] args) {
Configurator.setLevel(Main.class.getName(), Level.INFO);
Scanner s = new Scanner(System.in);
System.out.print("Enter URL: ");
String input = s.nextLine();
s.close();
try {
URL url = new URL(input.replaceAll("API_KEY", API_KEY));
if (url.hashCode() == HASH_CODE && url.getHost().equals("backend.fe-ctf.local")) {
logger.info("URLs Matched, sending request to {}", url);
/* TODO: Figure out how to send request
HttpURLConnection con = (HttpURLConnection) url.openConnection();
con.setRequestMethod("GET")
*/
} else {
logger.warn("URLs are not equal!");
}
} catch (MalformedURLException e) {
logger.error("Invalid URL");
System.exit(1);
}
}
}
```

At a second glance it looks more like a hash collision challenge. The class file takes a URL as input,
replaces ``API_KEY`` with the flag, and then checks if the hash code of the URL is equal to ``-615519892`` and if the
host is ``backend.fe-ctf.local``. If both conditions are true, it will log the URL.

So the goal is to create a URL that has the same hash code as ``-615519892`` and has ``backend.fe-ctf.local`` as host.
The URL also needs to contain the flag, so we can see it in the logging message.

The hash code is taken from the ``java.net.URL`` class, which uses the following hash function:

```java
protected int hashCode() {
int h = 0;

// Generate the protocol part.
String protocol = u.getProtocol();
if (protocol != null) h += protocol.hashCode();

// Generate the host part.
String host = u.getHost();
if (host != null) h += host.toLowerCase().hashCode();

// Generate the file part.
String file = u.getFile();
if (file != null) h += file.hashCode();

// Generate the port part.
if (u.getPort() == -1) h += u.getDefaultPort();
else h += u.getPort();

// Generate the ref part.
String ref = u.getRef();
if (ref != null) h += ref.hashCode();

return h;
}
```

After some testing around with the hash function, we realized that the hash code function does not take the authority
part of the URL into account.

By simply setting the authority part of the URL to ``API_KEY``, we can create a URL with a hash code independent of the
flag.

Luckily in the comments of the class file, we can find a URL that has the hash code ``-615519892``:

```java
/* https://backend.fe-ctf.local/removePoints?teamId=0&amount=1000&key=api_key */
```

So by just taking that URL and adding the authority we can get the flag.

```
https://[email protected]/removePoints?teamId=0&amount=1000&key=api_key
```

## Useful links

* https://en.wikipedia.org/wiki/Uniform_Resource_Identifier

## Alternative solutions

## Option 1

During the CTF we also realized that the port changes the hash code by its exact value.

```java
// Generate the port part.
if (u.getPort() == -1) h += u.getDefaultPort();
else h += u.getPort();
```

So it would be possible to just calculate the difference between the url hash code and the target hash code and then add
the difference as the port. This opens up the possibility to craft a URL that abuses the Log4j vulnerability.

## Option 2

After the CTF we came across a writeup that used the fact that apparently already when creating the URL object, a DNS
lookup is performed. So it is possible to create a URL that contains ``API_KEY`` as a subdomain which will be replaced
by the flag and then the DNS request will just leak the flag in the subdomain as the hostname ist first checked after
the URL object is created.

```java
URL url = new URL(input.replaceAll("API_KEY", API_KEY));
if (url.hashCode() == HASH_CODE && url.getHost().equals("backend.fe-ctf.local")) {
```