Holiday Hack 2023: The Blacklight District
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:
Location Layout
The Blacklight District has a Goose as well as Fitzy Shortstack with the Phishing Detection Cranberry Pi:
The Goose issues a creepy greating:
Goose of Film Noir Island
mmooooOOOO
Phish Detection Agency
Challenge
The badge objective is:
Fitzy says:
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:
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
:
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:
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:
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.
For example, this email has DMARC: Fail
, so I’ll mark it as phishing:
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:
The browser runs this and populates this data into an Indexed DB:
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:
Fitzy is pleased:
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.