In the storm-battered depths of a discreet mountain peak range, lies ProtoVault, an arcane sanctuary governed by the secretive guild known as the Everbound Order. They safeguard some of the most hidden knowledge across the Cyber Realms. Its defenses are forged in dragonfire and sealed with runes that demand the blood, breath, and soulprint of their masters.

But magic can't guard everything.

Whispers ripple through the cyber realm. The vault's inner sanctuary has been breached. A ransom scroll claims access to the Corespell – the foundational arcane code for ProtoVault – and issues a chilling demand:

"Surrender the Archivist Verin."

Verin holds command over the hidden vaults – each safeguarding knowledge not meant to be accessed, but to protect the balance of the Cyber Realms.

If Verin isn't given over, the ProtoVault could unravel everything they were built to defend.

AnchorHelm, an OffSec Legend, has summoned you, a skilled codecaster, to stop this before it goes any further.

Preparation - working directory and provided files

  1. Create a clean workspace and copy the challenge archive into it.

mkdir -p ~/protovault_investigation && cd ~/protovault_investigation
cp /path/to/9ee32ac125677475052b3b0ea3b28112-protovault-breach.zip ./protovault-breach.zip
unzip -l protovault-breach.zip
# expected entries: ransom_email.png, source_code.zip
  1. If the inner archive is password-protected (as in this challenge), extract it using the provided password.

# inner archive password known from the lab: BloodBreathSoulFire
unzip -P 'BloodBreathSoulFire' protovault-breach.zip source_code.zip
unzip -P 'BloodBreathSoulFire' source_code.zip -d source_code
# if unzip complains, use 7z:
7z x -p'BloodBreathSoulFire' source_code.zip -osource_code

Confirm you have ransom_email.png and a source_code/ tree.

Question 1 - "Determine if the leak could have come from the application. Review the database connection string to ensure it is secure. Submit the connection string here."

Goal: Find configuration in the app that reveals DB URI.

Steps & commands

  1. Inspect the application directory for Flask/SQLAlchemy config.

cd source_code
# look for SQLAlchemy config or any 'postgres' strings
grep -RIn "SQLALCHEMY_DATABASE_URI\|postgresql:\/\/\|assetdba" . | sed -n '1,200p'
  1. Open the file that contained the match. In this repo it is app/app.py:

sed -n '1,220p' app/app.py

You should see a line similar to:

app.config['SQLALCHEMY_DATABASE_URI'] = 'postgresql://assetdba:8d631d2207ec1debaafd806822122250@pgsql_prod_db01.protoguard.local/pgamgt?sslmode=verify-full'

Answer (connection string to submit):

postgresql://assetdba:8d631d2207ec1debaafd806822122250@pgsql_prod_db01.protoguard.local/pgamgt?sslmode=verify-full

Why this proves the app could leak the DB: Embedding credentials in source is a root cause: anyone with repo access or a readable artifact can exfiltrate DB contents. The rest of this guide shows how a backup script could use those credentials.

Question 2 - "Which source file may have leaked the database? Provide the file name."

Goal: Find scripts or references to backups or uploads that could expose the DB.

Steps & commands

  1. Search the codebase for the word "backup" and for upload-related libraries (e.g., boto3, requests, awscli usage).

cd source_code
# search for backup references
grep -RIn "backup\|boto3\|upload\|s3\|aws s3\|anonfiles\|pastebin" . | sed -n '1,200p'
  1. Inspect git history (if the inner zip contained a .git folder — many CTF archives include it). Look for commits mentioning backup scripts removed.

# only if .git/ exists
cd source_code
git --no-pager log --all --grep="backup" --pretty=oneline --decorate
# or search logs directly
sed -n '1,200p' .git/logs/HEAD

You should find commit messages or traces like Remove backup scripts, which indicate a backup_db.py existed and was deleted. If the backup_db.py file is present, open it:

sed -n '1,240p' scripts/backup_db.py
# or
sed -n '1,240p' backup_db.py

Answer (file name to submit):

backup_db.py

How this indicates leakage: A backup script that uploads dumps to S3 (or other public host) is the most likely origin of a publicly accessible dump. The .git logs mentioning removal corroborate a deleted backup script.

Question 3 - "Using the results of your analysis, discover the public address of the database leak. Verify the contents of the leak by submitting the password hash for Naomi Adler."

Goal: Download the leaked file, decode it if necessary, and extract Naomi Adler’s password hash.

Step A - Discovering the public address

  1. Inspect the backup/upload logic (if available) for bucket names or domain names. If the backup script was removed, inspect the repo for S3 bucket strings or clues.

# search for probable bucket names and keys
grep -RIn "s3\.amazonaws\.com\|s3://\|bucket\|protoguard\|db_backup" . | sed -n '1,200p'
  1. If you find lines like S3_BUCKET = "protoguard-asset-management" and S3_KEY = "db_backup.xyz", construct the public HTTPS URL and the S3 URI.

  • HTTPS URL format:

https://<bucket>.s3.<region>.amazonaws.com/<key>
# e.g.
https://protoguard-asset-management.s3.us-east-2.amazonaws.com/db_backup.xyz
  • S3 URI format:

s3://protoguard-asset-management/db_backup.xyz

Step B - Download the leaked file

  1. Try with curl (HTTP GET):

# quick HEAD to check availability
curl -I 'https://protoguard-asset-management.s3.us-east-2.amazonaws.com/db_backup.xyz'
# download
curl -O 'https://protoguard-asset-management.s3.us-east-2.amazonaws.com/db_backup.xyz'
  1. Alternatively use AWS CLI (no credentials needed for public object):

aws s3 cp s3://protoguard-asset-management/db_backup.xyz ./db_backup.xyz --no-sign-request

Step C - Identify encoding and decode (ROT13 in this challenge)

  1. Inspect the top of the file:

file db_backup.xyz
head -n 20 db_backup.xyz

If it looks like alphabetic gibberish or the sample contains COPY public. but letters rotated, decode using ROT13.

# ROT13 using tr
cat db_backup.xyz | tr 'A-Za-z' 'N-ZA-Mn-za-m' > db_backup.decoded.sql
# verify decoded file
head -n 50 db_backup.decoded.sql
  1. Alternatively decode in Python (preserves encoding reliably):

python3 - <<'PY'
from codecs import decode
s = open('db_backup.xyz','r', encoding='utf-8', errors='ignore').read()
open('db_backup.decoded.sql','w', encoding='utf-8').write(decode(s, 'rot_13'))
print('wrote db_backup.decoded.sql')
PY

Step D - Extract Naomi Adler's hash

  1. Search decoded SQL for Naomi / Adler / users table details:

grep -ni "naomi\|adler" db_backup.decoded.sql -n -C 3
  1. If the users are dumped with COPY or INSERT statements, extract the line and pull the hash column (the password column). Example commands:

# print the COPY block for public.users
awk '/COPY public.users/,/\\./{print}' db_backup.decoded.sql | sed -n '1,120p'
# or show 20 lines around Naomi
grep -ni "Naomi" -n db_backup.decoded.sql -C 6
  1. The exact hash string (copy it exactly) will look like a PBKDF2/hash string. For this lab the correct Naomi Adler hash extracted is:

pbkdf2:sha256:600000$YQqIvcDipYLzzXPB$598fe450e5ac019cdd41b4b10c5c21515573ee63a8f4881f7d721fd74ee43d59

Answer (hash to submit for question 3):

pbkdf2:sha256:600000$YQqIvcDipYLzzXPB$598fe450e5ac019cdd41b4b10c5c21515573ee63a8f4881f7d721fd74ee43d59

Question 4 - "Submit the public address of the database leak, including the name of the file."

Goal: Provide the public address that points to db_backup.xyz.

Possible accepted formats (try these in this order):

  1. https full URL:

https://protoguard-asset-management.s3.us-east-2.amazonaws.com/db_backup.xyz
  1. s3:// URI form (some CTF platforms expect S3 style):

s3://protoguard-asset-management/db_backup.xyz
  1. Just the filename (some challenges accept only the file name):

db_backup.xyz
  1. Raw object path (if the platform expects bucket/key):

protoguard-asset-management/db_backup.xyz

How to confirm the file matches the dump you decoded

  • Check MD5/SHA256 of the downloaded object and compare to any provided sample or to the version embedded in the repo (if present). Example:

sha256sum db_backup.xyz db_backup.decoded.sql | sed -n '1,200p'
  • Confirm the decoded SQL contains the Naomi hash (the same hash you submitted for question 3). This proves content identity.

Deep Git recovery steps (if backup_db.py was removed)

If the script was deleted but still referenced in commit logs, you can attempt to recover deleted blobs from the .git objects and packfiles (if present). This is an advanced step — only necessary if the backup script itself is missing and you want to prove the exact upload code.

Commands:

cd source_code
# List all objects referenced by commits
git rev-list --objects --all | cut -d' ' -f1 | xargs -n1 -I{} git cat-file -p {} 2>/dev/null | strings | head -n 20

# search for the string 'backup_db' inside all reachable objects
git rev-list --objects --all | cut -d' ' -f1 | while read obj; do git cat-file -p "$obj" 2>/dev/null | grep -q "backup_db" && echo "FOUND in $obj"; done

# If .git/objects/pack/*.pack present, use git verify-pack and git rev-list
for p in .git/objects/pack/*.idx; do git verify-pack -v ${p%.*}.pack | sort -k3 -n | tail -n 50; done

If a deleted file blob still exists in the object store, you can recover it using git cat-file -p <blob-hash>.

Useful one-liners & helpers

  • ROT13 decode & search Naomi in one pipeline:

tr 'A-Za-z' 'N-ZA-Mn-za-m' < db_backup.xyz | grep -i "Naomi" -n -C 3
  • Extract the password field assuming tab-separated COPY rows (adjust column number to match schema):

awk 'BEGIN{FS="	"} /Naomi/{print $0; print "password:" $5}' db_backup.decoded.sql
  • Print the exact COPY block for the items table (example):

awk '/COPY public.items/,/\\./{print}' db_backup.decoded.sql | sed -n '1,200p'

Final verification checklist before submission

  1. Confirm app/app.py contains the exact connection string.

  2. Confirm backup_db.py (or evidence in git logs) is present or recoverable from .git objects.

  3. Confirm the downloaded file db_backup.xyz decodes with ROT13 to a valid SQL dump.

  4. Confirm the Naomi Adler hash in the decoded dump matches the one you will submit.

  5. Try different URL formats when submitting the public address (HTTPS, s3://, filename only) if the platform marks one format incorrect.

Keep Reading