Geography

Getting To

Before heading to Space Island, I’ll explore Film Noir Island (I’ll have to come back here first anyway). The Blacklight District is on the East side of the island’s neck:

image-20240103215512210

Location Layout

The Blacklight District has a Goose as well as Fitzy Shortstack with the Phishing Detection Cranberry Pi:

image-20240103220220068Click for full size image

The Goose issues a creepy greating:

Goose of Film Noir Island

Goose of Film Noir Island

mmooooOOOO

Phish Detection Agency

Challenge

The badge objective is:

image-20240103220337201

Fitzy says:

Fitzy Shortstack

Fitzy Shortstack

Just my luck, I thought…

A cybersecurity incident right in the middle of this stakeout.

Seems we have a flood of unusual emails coming in through ChatNPT.

Got a nagging suspicion it isn’t catching all the fishy ones.

You’re our phishing specialist right? Could use your expertise in looking through the output of ChatNPT.

Not suggesting a full-blown forensic analysis, just mark the ones screaming digital fraud.

We’re looking at all this raw data, but sometimes, it takes a keen human eye to separate the chaff, doesn’t it?

I need to get more powdered sugar for my donuts, so do ping me when you have something concrete on this.

Terminal

The challenge presents a few tabs, starting on the instructions:

image-20240103220825117

I need to work through the emails looking for ones that might be phishing based on Sender Policy Framework (SPF), DomainKeys Identified Mail (DKIM), and Domain-base Message Authentication, Reporting, and Conformance (DMARC).

ChatNPT has already taken a shot at making each email as Safe or Phishing, but it’s made some mistakes, and I need to fix them.

There’s also a DNS tab that gives the SPF, DKIM, and DMARC DNS records for geeseislands.com.

Solve

SPF

SPF allows a domain to specify from what mail servers mail from the domain might come. For and email from a geezeislands.com email address, all mail must come from mail.greeseislands.com:

image-20240103221342168

This means that any email with a geeseislands.com email address that isn’t from this server can be marked as a phish. For example, this email is a phish:

image-20240103221816638

The sender is from geeseislands.com but the server isn’t the one specified.

DKIM

DKIM is a signature method that the sending mailserver applies signing the message using a private key. The public key is then available via DNS records:

image-20240103221603297

Anyone can verify the authenticity of the email by checking the signature against the publc key.

In this case, the server does that for me, and puts the result in the DMARC header, so I don’t have to do the calculation myself.

DMARC

DMARC is an extension of SPF and DKIM that allows a conclusion to be drawn based on one or more of these.

image-20240103221913240

For example, this email has DMARC: Fail, so I’ll mark it as phishing:

image-20240103222013741

Automation

Enumeration

I’m too lazy to look at all 30-something emails manually, so I’ll write a script to do it for me. The challenge is hosted at https://hhc23-phishdetect-dot-holidayhack2023.ue.r.appspot.com, and there’s a seed.js file that has all of the email data:

image-20240103222245285

The browser runs this and populates this data into an Indexed DB:

image-20240103222340524

Scripting

Reading from Indexed DB is quite painful, so I’ll just get the seed.js file from the internet and parse it:

base_url = 'https://hhc23-phishdetect-dot-holidayhack2023.ue.r.appspot.com'
session = requests.session()
session.get(base_url)
seed_js = session.get(f'{base_url}/static/seed.js')
emails = process_emails(seed_js.text)

The process_emails function uses regex to get each email blob and parse it:

def process_emails(seed_js: str) -> dict[str, str]:
    blobs = re.findall(r'loadEmails.push\({.*?}\);', seed_js, re.DOTALL)
    emails = []
    for blob in blobs:
        email = {}
        for line in blob.splitlines()[1:-1]:
            key, value = line.strip().split(':', 1)
            email[key] = value.strip('" ,')
        if 'headers' in email:
            headers = email['headers']
            email['headers'] = {}
            for header in headers.split('\\n'):
                key, value = header.split(':', 1)
                email['headers'][key] = value.strip()
        emails.append(email)
    return emails

Then I can just loop over the emails, checking the DMARC header and the from header:

phishing = []
for email in emails:
    if email['headers']['DMARC'] != "Pass" or email['headers']['Received'] != "from mail.geeseislands.com":
        phishing.append(email['from'])

body = json.dumps(phishing)
resp = session.post(f'{base_url}/check-status', data=body, headers={"Content-Type": "application/json"})
if resp.status_code == 200:
    print('[+] Success!')
    print(body)

When this runs, it resports the emails that are phishing and that it verified that with the game:

oxdf@hacky$ python solve.py 
[+] Success!
["victor.davis@geeseislands.com", "xavier.jones@geeseislands.com", "steven.gray@geeseislands.com", "laura.green@geeseislands.com", "nancy@geeseislands.com", "rachel.brown@geeseislands.com", "ursula.morris@geeseislands.com", "quincy.adams@geeseislands.com", "michael.roberts@geeseislands.com", "oliver.thomas@geeseislands.com"]

I can use this as a key to mark the emails manually in the game, or intercept a request and replace the ones it sends with this.

Epilogue

On solving, it provides a message:

image-20240103223309660

Fitzy is pleased:

Fitzy Shortstack

Fitzy Shortstack

You’ve cracked the case! Once again, you’ve proven yourself to be an invaluable asset in our fight against these digital foes.