Malibu (Huntress CTF 2024)

By: yukna on
CTFComSecCyberSecurityWrite-upHuntressHuntress-CTF-2024

Author: Truman Kain

What do you bring to the beach?

NOTE: There are two things to note for this challenge. This service takes a bit more time to start. If you see a Connection refused, please wait a bit more. This service will not immediately respond or prompt you… it is waiting for your input. If you just hit Enter, you will see what it is. Extra tip, once you know what the service is, try connecting in a better way. Then use some of the context clues and critical thinking based off its response and the challenge description. You don’t need any bruteforcing once you understand the infrastructure and enumerate. ;)

What is happening?

Diving head first, I used netcat as suggested. Pressing enter without any input yields:

HTTP/1.1 400 Bad Request
Content-Type: text/plain; charset=utf-8
Connection: close

400 Bad Request

Anyways, I understand that the server is expecting an HTTP request. Posting a valid request:

GET / HTTP/1.1
Host: challenge.ctf.games:PORT

Yields:

HTTP/1.1 403 Forbidden
Accept-Ranges: bytes
Content-Length: 254
Content-Type: application/xml
Server: MinIO
Strict-Transport-Security: max-age=31536000; includeSubDomains
Vary: Origin
Vary: Accept-Encoding
X-Amz-Id-2: dd9025bab4ad464b049177c95eb6ebf374d3b3fd1af9251148b658df7ac2e3e8
X-Amz-Request-Id: 17FB84042B3F0BFF
X-Content-Type-Options: nosniff
X-Ratelimit-Limit: 59
X-Ratelimit-Remaining: 59
X-Xss-Protection: 1; mode=block
Date: Sat, 05 Oct 2024 09:20:35 GMT

And a response payload:

<?xml version="1.0" encoding="UTF-8"?>
<Error>
<Code>AccessDenied</Code>
<Message>Access Denied.</Message>
<Resource>/</Resource>
<RequestId>17FB83CDD3C44A1E</RequestId>
<HostId>dd9025bab4ad464b049177c95eb6ebf374d3b3fd1af9251148b658df7ac2e3e8</HostId>
</Error>

so anyways, like curl, POSTing a normal GET request yields a 403 error. The respond header is informative, showing that the server is running MinIO with Amazon S3.

For now I will use: curl -v -H 'Host:challenge.ctf.games:PORT' challenge.ctf.games:PORT to interact, with will nicely delimit the response header, as well as do the dirty work of crafting the request for me.

Trying other post methods:

Instead of GET I tried others, by using the -X flag in curl.

  • HEAD received a 400 BAD REQUEST
  • POST also gives a bad request
  • OPTIONS gave something:
* Host challenge.ctf.games:PORT was resolved.
* IPv6: (none)
* IPv4: 35.193.148.143
*   Trying 35.193.148.143:PORT...
* Connected to challenge.ctf.games (35.193.148.143) port PORT
> OPTIONS / HTTP/1.1
> Host: challenge.ctf.games
> User-Agent: curl/8.9.1
> Accept: */*
>
* Request completely sent off
< HTTP/1.1 200 OK
< Vary: Origin
< Date: Sat, 05 Oct 2024 09:28:03 GMT
< Content-Length: 0
<
* Connection #0 to host challenge.ctf.games left intact

Interesting. At least there is confirmation that the server is working and accepting HTTP requests instead of just denying everything (I mean there was not a need to check but good to know).

What do you bring to the beach?

The challenge now is to find a common access point that is exposed to the public without needing authentication. So i enumerated various endpoints like sunscreen, towel, bucket.

bucket” is such a web storage term. and yes. /bucket turns out to be publicly facing without needing authentication! The xml file details some files, all arbitrary named. Trying to manually download one of the files at bucket/FILENAME gives a long ascii text file. Time to investigate!

Download all the file

I extracted all the file keys with a little bit of (rip)grep, and then curl them out. I had to first check that all the files were of unique names by comparing

while read -r line; do echo $(basename $line); done < filepaths | wc -l

with

while read -r line; do echo $(basename $line); done < filepaths | uniq | wc -l

and I find 122 files. Next is to curl them all. I test first, and run:

while read -r line; do curl -v "challenge.ctf.games:30942/bucket/$line" -o "files/$(basename $line)"; done < filepaths

All the files are ASCII text, with very long lines, with no line terminators according to the file command. Looking at some of them, I am guessing it is base64 encoded, so time to decode them.

Some failed, some succeeded, and running file on the succeeded files show most are data but some are OpenPGP public keys, with one secret key. hmhm.

gpg?

I start to process the OpenPGP keys using gpg. Firstly I needed to create a temporary configuration directory with permissions set to 700 so that gpg does not pollute my home directory. I can then use the --homedir flag to point to this folder instead. Using --list-packets, the secret key file gives gpg: packet(5) with unknown version 141. The other 2 errors out. trying to import them also errors out

now what?

I try to decode the files and it just gets worse and worse. I am lost. I stop and go all the way back to the base file where I had all the files I had initially downloaded.

As a last ditch effort, I tried to recursively search for a flag in the files (rg is ripgrep, a faster grep alternative):

rg 'flag'

and there it was. Lurking deep. The flag.

Moral?

This taught me a valuable lesson: don’t overthink, and be careful of red herrings. I overtrusted file and it was not good.