ProtoVault Breach
About this lab
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.
- 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.
- Review the other source files. Which one may have leaked the database? Provide the file name.
- 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.
- Submit the public address of the database leak, including the name of the file.
Once I explored the source files, I found hard-coded credentials.
1
2
└─$ psql 'postgresql://assetdba:8d631d2207ec1debaafd806822122250@pgsql_prod_db01.protoguard.local/pgamgt?sslmode=verify-full'
psql: error: could not translate host name "pgsql_prod_db01.protoguard.local" to address: Name or service not known
Obviously cannot access it due to the resolution problem.
The provided source files also include .git files.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
└─$ git log --all --pretty=format:'%H%x09%ai%x09%an%x09%s' --name-only | awk 'NF{print} /'"$(printf '%s' "$MSG_RE")"'/'
a230bbc140cf8a9f60b5d54ca979259c88182470 2024-10-17 10:12:00 -0500 Walter Release candidate
a230bbc140cf8a9f60b5d54ca979259c88182470 2024-10-17 10:12:00 -0500 Walter Release candidate
app/app.py
app/app.py
app/templates/login.html
app/templates/login.html
requirements.txt
requirements.txt
8fe1a4431337ecf4bc9759672579168b03351fb6 2024-10-14 09:32:00 -0500 Walter Remove backup scripts
8fe1a4431337ecf4bc9759672579168b03351fb6 2024-10-14 09:32:00 -0500 Walter Remove backup scripts
app/util/backup_db.py
app/util/backup_db.py
app/util/restore_db.py
app/util/restore_db.py
9f257d253fc6c15860d2e24941b66ab0733e72aa 2024-10-12 10:51:00 -0500 Walter Add item details and notes forms
9f257d253fc6c15860d2e24941b66ab0733e72aa 2024-10-12 10:51:00 -0500 Walter Add item details and notes forms
app/app.py
app/app.py
app/forms.py
app/forms.py
app/templates/category.html
app/templates/category.html
app/templates/index.html
app/templates/index.html
app/templates/item_detail.html
app/templates/item_detail.html
f22ba8adb8e9b1180b44744c66bfbd0ef192381c 2024-10-07 09:45:00 -0500 Walter Add data models
f22ba8adb8e9b1180b44744c66bfbd0ef192381c 2024-10-07 09:45:00 -0500 Walter Add data models
app/app.py
app/app.py
app/templates/index.html
app/templates/index.html
b32d77d6fcfcf3ee3681acc930678ad7fe2e1a29 2024-10-06 10:21:00 -0500 Walter database migration scripts
b32d77d6fcfcf3ee3681acc930678ad7fe2e1a29 2024-10-06 10:21:00 -0500 Walter database migration scripts
app/util/backup_db.py
app/util/backup_db.py
app/util/restore_db.py
app/util/restore_db.py
5fe83a81198bfb61626a52c912a841364b9aa563 2024-10-05 11:23:00 -0500 Walter Add authentication
5fe83a81198bfb61626a52c912a841364b9aa563 2024-10-05 11:23:00 -0500 Walter Add authentication
app/app.py
app/app.py
app/forms.py
app/forms.py
app/templates/index.html
app/templates/index.html
app/templates/login.html
app/templates/login.html
3654766dd5a0b7d51249247bfc26e780133cefc8 2024-10-03 10:15:00 -0500 Walter UI refactor
3654766dd5a0b7d51249247bfc26e780133cefc8 2024-10-03 10:15:00 -0500 Walter UI refactor
app/templates/base.html
app/templates/base.html
app/templates/index.html
app/templates/index.html
444eb3acdc1af6c6beab3ce8f3a6881b18a2a3e8 2024-10-01 09:00:00 -0500 Walter Initial commit: Flask app scaffold
444eb3acdc1af6c6beab3ce8f3a6881b18a2a3e8 2024-10-01 09:00:00 -0500 Walter Initial commit: Flask app scaffold
app/__init__.py
app/__init__.py
app/app.py
app/app.py
app/templates/index.html
app/templates/index.html
“Walter database migration scripts” looks significant since nothing like *_db.py is included in the provided source files.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# prints commit log so far again
└─$ git log b32d77d6fcfcf3ee3681acc930678ad7fe2e1a29
...
# shows the specified file at this point
└─$ git show 9f257d253fc6c15860d2e24941b66ab0733e72aa:app/util/backup_db.py | sed -n '1,200p'
...
# ==== CONFIGURATION ====
SSH_HOST = "pgsql_prod_db01.protoguard.local"
SSH_PORT = 22
SSH_USER = "dbadmin"
SSH_KEY = "/home/walter/.ssh/pgsql_key"
DB_NAME = "pgamgt"
DB_USER = "assetdba"
BACKUP_FILENAME = "db_backup.sql"
LOCAL_BACKUP = f"/tmp/{BACKUP_FILENAME}"
ENC_BACKUP = f"/tmp/{BACKUP_FILENAME}.xyz"
S3_BUCKET = "pgamgt-backups"
S3_KEY = "latest_backup.xyz"
...
At this point, I would need region information to get S3 access, as they don’t set up AWS access keys and make this S3 public on their configuration.
So, I would just see if we have region information.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
└─$ git grep -n 'REGION' $(git rev-list --all)
ef917bbffedf1638f5ce7747132942197ce97703:app/util/backup_db.py:22:S3_REGION = "us-east-2"
ef917bbffedf1638f5ce7747132942197ce97703:app/util/backup_db.py:61: s3 = boto3.client("s3", region_name=S3_REGION)
ef917bbffedf1638f5ce7747132942197ce97703:app/util/restore_db.py:22:S3_REGION = "us-east-2"
ef917bbffedf1638f5ce7747132942197ce97703:app/util/restore_db.py:33: s3 = boto3.client("s3", region_name=S3_REGION)
23040529e72d8797126d1c14b314ac6163b2c031:app/util/backup_db.py:22:S3_REGION = "us-east-2"
23040529e72d8797126d1c14b314ac6163b2c031:app/util/backup_db.py:61: s3 = boto3.client("s3", region_name=S3_REGION)
23040529e72d8797126d1c14b314ac6163b2c031:app/util/restore_db.py:22:S3_REGION = "us-east-2"
23040529e72d8797126d1c14b314ac6163b2c031:app/util/restore_db.py:33: s3 = boto3.client("s3", region_name=S3_REGION)
9544195474e1c25ea8415b6b78b4035cf5268d1f:app/util/backup_db.py:22:S3_REGION = "us-east-2"
9544195474e1c25ea8415b6b78b4035cf5268d1f:app/util/backup_db.py:61: s3 = boto3.client("s3", region_name=S3_REGION)
9544195474e1c25ea8415b6b78b4035cf5268d1f:app/util/restore_db.py:22:S3_REGION = "us-east-2"
9544195474e1c25ea8415b6b78b4035cf5268d1f:app/util/restore_db.py:33: s3 = boto3.client("s3", region_name=S3_REGION)
6a65249cdd767ab4faf28e4ee88d710e863c09b1:app/util/backup_db.py:22:S3_REGION = "us-east-2"
6a65249cdd767ab4faf28e4ee88d710e863c09b1:app/util/backup_db.py:61: s3 = boto3.client("s3", region_name=S3_REGION)
6a65249cdd767ab4faf28e4ee88d710e863c09b1:app/util/restore_db.py:22:S3_REGION = "us-east-2"
6a65249cdd767ab4faf28e4ee88d710e863c09b1:app/util/restore_db.py:33: s3 = boto3.client("s3", region_name=S3_REGION)
5ad3c0672f4597e59e11f09ad1d1081fa2a7afbe:app/util/backup_db.py:22:S3_REGION = "us-east-2"
5ad3c0672f4597e59e11f09ad1d1081fa2a7afbe:app/util/backup_db.py:61: s3 = boto3.client("s3", region_name=S3_REGION)
5ad3c0672f4597e59e11f09ad1d1081fa2a7afbe:app/util/restore_db.py:22:S3_REGION = "us-east-2"
5ad3c0672f4597e59e11f09ad1d1081fa2a7afbe:app/util/restore_db.py:33: s3 = boto3.client("s3", region_name=S3_REGION)
d0aa089caddae1735a476954c6c60f469c864f9a:app/util/backup_db.py:22:S3_REGION = "us-east-2"
d0aa089caddae1735a476954c6c60f469c864f9a:app/util/backup_db.py:61: s3 = boto3.client("s3", region_name=S3_REGION)
d0aa089caddae1735a476954c6c60f469c864f9a:app/util/restore_db.py:22:S3_REGION = "us-east-2"
d0aa089caddae1735a476954c6c60f469c864f9a:app/util/restore_db.py:33: s3 = boto3.client("s3", region_name=S3_REGION)
1
2
3
4
5
6
7
8
9
10
└─$ curl -I https://protoguard-asset-management.s3.us-east-2.amazonaws.com/
HTTP/1.1 403 Forbidden
x-amz-bucket-region: us-east-2
x-amz-request-id: 9Q7A7Q01HMV56A9G
x-amz-id-2: H7172bLYWYenscyr8Mt4v3PBP99a7emHccnGS8asAoPyVZxTZlTFSKfxtz0bSTembqaNHctWmHc=
Content-Type: application/xml
Transfer-Encoding: chunked
Date: Mon, 13 Oct 2025 12:41:11 GMT
Server: AmazonS3
It returns 403, then figures the bucket exists
1
2
3
4
5
└─$ curl -O https://protoguard-asset-management.s3.us-east-2.amazonaws.com/db_backup.xyz
% Total % Received % Xferd Average Speed Time Time Time Current
Dload Upload Total Spent Left Speed
100 14428 100 14428 0 0 18640 0 --:--:-- --:--:-- --:--:-- 18640
I’ve successfully got a plain-text PostgreSQL dump, but every alphabetic character has been passed through ROT13 (a simple Caesar substitution), meaning it’s just obfuscated.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
└─$ tr 'A-Za-z' 'N-ZA-Mn-za-m' < db_backup.xyz | less
...
--
-- Data for Name: users; Type: TABLE DATA; Schema: public; Owner: assetdba
--
COPY public.users (id, first_name, last_name, specialty, username, password_hash) FROM stdin;
1 Elena Kovács Virology & Biosecurity elena.kovacs pbkdf2:sha256:600000$GXTIpdzR6ciDJxCl$705204a7738bd064ad0c6fcd7698a7fe70a5764e83af066497498c019c11ab63
2 Marcus Delgado Prototype Device Engineering marcus.delgado pbkdf2:sha256:600000$2PMi96a1nzHye4Ok$3217e74f0843c6c225189bc79063bc9b441d9798c5fa682eba455b7c9e797465
3 Amina Farouk Chemical Hazard Containment amina.farouk pbkdf2:sha256:600000$3XjFZLRGReNJUytb$d77717c95f950b8e092ef2b116b27ae901eb7a05137f10eb54e5c517babda7c7
4 Jason Whitmore Secure Storage Operations jason.whitmore pbkdf2:sha256:600000$HkT85y8RRF4JkxHX$a04e5a62d356cc91e7a6da0ef5267c1e4021a20254570f22509dbdca0121f593
5 Priya Ranganathan Encryption & Data Security priya.ranganathan pbkdf2:sha256:600000$MLH1MyOznTa7TN7d$a93fd6044261cb5b2d529bc5dfacff3d20f5fd474816a3e878f886484d09fbb5
6 Daniel Foster Physical Security & Access Control daniel.foster pbkdf2:sha256:600000$HFVz3caFPERw7UZJ$6cb544e48b72dca2934d020e8bfff219b03a1c4e6894cc4d59c486a7ff6fc4af
7 Mei Zhang Microbial Ecology mei.zhang pbkdf2:sha256:600000$VDIo8BlbOyB1NpoR$04e89805d0142329b30626ecd4afd9956c537712ecb92665071a6269e89827c9
8 Rafael Moretti Energy Systems Engineering rafael.moretti pbkdf2:sha256:600000$wPu7wwCiQpuuLTBb$1982762855c8a34a237e241ff5b74918224ddaf522a38586ef79376c648d6400
9 Sarah Nwosu Risk Assessment & Compliance sarah.nwosu pbkdf2:sha256:600000$Ny8AmUPgXhaF7ZOc$c6d2b7f9e09ff8ed25e57c452ae610051d5cf0db78d4acaa0b909c000527e332
10 Viktor Petrov Containment Systems Maintenance viktor.petrov pbkdf2:sha256:600000$JdSD9J06xZ1bA2Vc$5499b25eade57f14a3839cd5716bc7848b979e69a371471f66b6543b7494d460
11 Naomi Adler Cognitive Systems Research naomi.adler pbkdf2:sha256:600000$YQqIvcDipYLzzXPB$598fe450e5ac019cdd41b4b10c5c21515573ee63a8f4881f7d721fd74ee43d59
12 Thomas Sinclair Technical Documentation & Records thomas.sinclair pbkdf2:sha256:600000$jiHdWOQtTkpCxgNR$96eca6d861e91a79f3941c42000ef8eca3c28aeece8fb7a97cbc9e687ecf5afc
13 Leila Haddad Chemical Catalysis Research leila.haddad pbkdf2:sha256:600000$6DDYtk7r2O18ELlz$c21419b605e4aa73169634144843115b44104621ee2c6f22bbad427b806c8c28
14 Gabriel Montoya Drone Systems Development gabriel.montoya pbkdf2:sha256:600000$Ko7CFWeuJ5aUTTOt$4641aeb53f381592e7040f93116a8c4675ae44f916fa44b3be61fc02ae39e0fa
15 Satoshi Tanaka Optical Cloaking Technology satoshi.tanaka pbkdf2:sha256:600000$SzxVVSviknpMk0LV$a491cd428a5d5d1a11e0eb1fc5be9843591c42b0bfb4362899b8763962fa83d1
\.
...
The following is out of scope:
- Algo:
pbkdf2:sha256 - Iterations:
600000 - Salt (ASCII):
YQqIvcDipYLzzXPB - Derived key (hex):
598fe450e5ac019cdd41b4b10c5c21515573ee63a8f4881f7d721fd74ee43d59
Hashcat’s generic PBKDF2-SHA256 wants base64 for both the salt and the derived key (not hex).
salt_b64=WVFxSXZjRGlwWUx6elhQQg==dk_b64=WY/kUOWsAZzdQbSxDFwhUVVz7mOo9IgffXIf107kPVk=
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
└─$ hashcat -m 10900 -a 0 hash.txt /usr/share/seclists/Passwords/xato-net-10-million-passwords-100000.txt
...
Session..........: hashcat
Status...........: Exhausted
Hash.Mode........: 10900 (PBKDF2-HMAC-SHA256)
Hash.Target......: sha256:600000:WVFxSXZjRGlwWUx6elhQQg==:WY/kUOWsAZzd...7kPVk=
Time.Started.....: Mon Oct 13 10:02:30 2025 (1 hour, 42 mins)
Time.Estimated...: Mon Oct 13 11:45:19 2025 (0 secs)
Kernel.Feature...: Pure Kernel (password length 0-256 bytes)
Guess.Base.......: File (/usr/share/seclists/Passwords/xato-net-10-million-passwords-100000.txt)
Guess.Queue......: 1/1 (100.00%)
Speed.#01........: 16 H/s (8.53ms) @ Accel:63 Loops:1000 Thr:1 Vec:4
Recovered........: 0/1 (0.00%) Digests (total), 0/1 (0.00%) Digests (new)
Progress.........: 100000/100000 (100.00%)
Rejected.........: 0/100000 (0.00%)
Restore.Point....: 100000/100000 (100.00%)
Restore.Sub.#01..: Salt:0 Amplifier:0-1 Iteration:599000-599999
Candidate.Engine.: Device Generator
Candidates.#01...: 08031947 -> 07012006
Hardware.Mon.#01.: Util: 94%
Started: Mon Oct 13 10:02:29 2025
Stopped: Mon Oct 13 11:45:20 2025