Skip to main content

Vantage - HTB Sherlock Writeup (DFIR / Network Forensics)

Vantage Banner

Challenge Description

A small company moved some of its resources to a private cloud installation. The developers left the redirect to the dashboard on their web server. The security team got an email from the alleged attacker stating that the user data was leaked. It is up to you to investigate the situation.
Difficulty: Very Easy
Category: DFIR / Network Forensics
Evidence Files: web-server_2025-07-01.pcap (15 MB), controller_2025-07-01.pcap (6.9 MB)

Initial Analysis Methodology

Before diving into the challenge questions, the goal is to understand what happened using only the evidence. This section walks through a repeatable approach you can apply to any unknown PCAP - the same steps, in the same order, every time. We have two PCAP files from two different machines. We don’t know yet what those machines are, how they relate to each other, or what happened. The methodology below will answer all three.

Step 1 - Protocol Hierarchy: What kind of machines are these?

The very first thing to do with any unknown PCAP is find out what protocols it contains. This tells you what kind of machine was captured and what kind of activity to expect - before you read a single packet. Wireshark: Statistics > Protocol Hierarchy

šŸ“ web-server_2025-07-01.pcap

Open the web-server PCAP first and go to Statistics > Protocol Hierarchy. Here’s what stands out:
ProtocolFramesWhat it tells you
TCP21,586Nearly all traffic is TCP-based
HTTP7,482A web server - this is almost entirely HTTP
HTML Form URL Encoded8Only 8 form submissions out of 7,482 HTTP requests
Why the 8 form submissions matter: The content type application/x-www-form-urlencoded is what browsers send when you submit a login form via POST. Out of 7,482 HTTP requests, only 8 were form POSTs - meaning the vast majority of traffic was automated GETs (scanning/fuzzing), and only a handful were deliberate human interactions (login attempts). That ratio is the first red flag, and you’ve spotted it without reading a single packet. Why 8 and not 4? You might expect 4 login attempts = 4 POSTs. The count is doubled because this machine acts as a reverse proxy: it captures both the external POST from the attacker (117.200.21.26 to 157.230.81.229) and the internally forwarded POST from proxy to backend app (10.116.0.3 to 10.116.0.4). Each login attempt is seen twice in the one capture file. This matters in Step 5 when we filter for login packets.

šŸ“ controller_2025-07-01.pcap

Now open the controller PCAP and check the same view:
ProtocolFramesWhat it tells you
HTTP + JSON626REST API traffic
MySQL8,978Database backend
AMQP849Message queue (RabbitMQ)
Data on port 23791,418etcd - distributed key-value store
Data on port 11211-memcached - in-memory cache
What can we say from this? The protocol hierarchy alone does not tell you what platform this machine runs. HTTP + MySQL + AMQP + etcd + memcached could be many things - a Django app with Celery workers, a Kubernetes control plane, or any message-driven platform with a database backend. What it does tell you is the general shape of this machine: it’s an application controller node - something that exposes a REST API, stores state in a relational database, coordinates work through a message queue, and uses an in-memory cache. That’s a useful narrowing. The actual platform will become clear in Step 3 when we read the HTTP request paths. The takeaway after Step 1: We have a web server / reverse proxy (almost entirely HTTP) and a multi-service controller node (API + database + message queue + cache). The web-server PCAP has the suspicious traffic volume, so that’s where we focus next.

Step 2 - Endpoints & Conversations: Who’s talking, and how much?

Now we have a rough picture of each machine. Next question: who is communicating with them, and is any of that traffic abnormal? There are two related views in Wireshark that answer this differently:
  • Statistics > Endpoints > IPv4 - One row per IP, total packets aggregated across all connections. Best for quickly identifying dominant talkers.
  • Statistics > Conversations > TCP - One row per TCP stream (unique source port to destination port pair). Best for understanding how the traffic is structured.
Start with Endpoints for triage, then drill into Conversations for detail.

šŸ“ web-server_2025-07-01.pcap - Endpoints view

Open Statistics > Endpoints > IPv4 tab and sort by the Packets column (descending):
AddressPackets% of totalRole
157.230.81.22921,348-Web server (this machine)
117.200.21.2620,89797.9%??? (suspicious)
10.116.0.32561.2%Internal IP
10.116.0.42561.2%Internal IP
60+ other IPs2–243 eachunder 1%Internet background noise
The red flag is immediate: One external IP - 117.200.21.26 - accounts for 97.9% of all traffic. Every other external IP has negligible volume. That ratio alone identifies the attacker before you’ve looked at a single HTTP request.

šŸ“ web-server_2025-07-01.pcap - Conversations view

Now switch to Statistics > Conversations > TCP tab to understand how that traffic is structured. You’ll see approximately 187 rows from 117.200.21.26, all connecting to 157.230.81.229:80, each from a different source port:
Address ADirectionAddress BPacketsDuration
117.200.21.26:64728to157.230.81.229:8049722.6s
117.200.21.26:64727to157.230.81.229:8048922.7s
117.200.21.26:64756to157.230.81.229:8048722.6s
… 184 more rows …
What this pattern means: 187 parallel TCP connections, all nearly identical in duration (~22 seconds) and packet count (~480–500), all hitting port 80. This is not a human browsing - this is a tool running concurrent threads. Each thread opens its own TCP connection to fire HTTP requests in parallel. Tools like ffuf, gobuster, and dirsearch all behave exactly this way. There are also ~10 smaller streams between 10.116.0.3 and 10.116.0.4:80. These two internal IPs have nearly identical packet counts in the Endpoints view, and their timing aligns with the external wave - this is the hallmark of a reverse proxy chain (e.g., Nginx or Apache fronting a backend app server).

šŸ“ controller_2025-07-01.pcap - What about this one?

We’ll come back to the controller PCAP after we understand the web-server traffic. The methodology is: follow the attacker’s footsteps in order. The web server is where the attack started, the controller is where it escalated. Analysing them chronologically tells a coherent story.

Step 3 - Filter HTTP Requests: What was the attacker doing?

We’ve identified 117.200.21.26 as suspicious. Now let’s see exactly what they requested. Wireshark display filter (in web-server_2025-07-01.pcap):
http.request && ip.src == 117.200.21.26
Tip: To make the packet list more readable, add custom columns. Right-click any column header > Column Preferences > add:
  • http.host (titled ā€œHostā€)
  • http.request.uri (titled ā€œURIā€)
  • http.user_agent (titled ā€œUser-Agentā€)
With those columns visible, the pattern jumps out immediately:
#HostMethodURI
10241.vantage.techGET/
10312.vantage.techGET/
1038a.vantage.techGET/
1052abc.vantage.techGET/
…3,579 unique subdomainsGET/
1072cloud.vantage.techGET/
-(User-Agent changes here)
18201cloud.vantage.techGET/dashboard/
18215cloud.vantage.techGET/dashboard/auth/login/
18240cloud.vantage.techPOST/dashboard/auth/login/
Two distinct phases are visible:
  1. Fuzzing phase: Thousands of GETs, each to a different *.vantage.tech subdomain, all requesting /. The Host header cycles through a wordlist alphabetically. This is subdomain enumeration.
  2. Exploitation phase: The Host locks onto cloud.vantage.tech and the attacker starts browsing the dashboard manually - navigating to the login page, submitting POST requests.
And here’s where the platform reveals itself. While scrolling through the exploitation-phase requests, you’ll spot URIs like /dashboard/i18n/js/horizon+openstack_dashboard/. That path literally contains the words horizon (OpenStack’s web dashboard) and openstack_dashboard. Remember the controller PCAP from Step 1 that we couldn’t identify beyond ā€œapplication controller nodeā€? Now we know - this is an OpenStack deployment. The web server is fronting an OpenStack Horizon dashboard, and the controller is the OpenStack control plane behind it. The transition between phases is also visible in the User-Agent column (which we’ll examine next).

Step 4 - Check User-Agents: What tools were used?

User-Agent strings are one of the quickest wins in web traffic analysis. Legitimate browsers all send Mozilla/5.0... headers. Attack tools often identify themselves - either by name or by sending unusual/missing User-Agents. Wireshark (in web-server_2025-07-01.pcap): Click any packet from the fuzzing phase, expand Hypertext Transfer Protocol > User-Agent in the packet detail pane. Two User-Agents appear from the attacker’s IP:
PhaseUser-AgentMeaning
Fuzzing (3,695 reqs)Fuzz Faster U Fool v2.1.0-devffuf - a Go-based web fuzzing tool
Manual browsingMozilla/5.0 (Macintosh Intel Mac OS X 10_15_7) AppleWebKit/537.36 ... Chrome/137.0.0.0 Safari/537.36Real Chrome browser on macOS
The pivot moment: The switch from ffuf to Chrome is the exact moment the attacker discovered cloud.vantage.tech returned something interesting (the Horizon dashboard) and decided to explore it manually. To filter for just the non-browser traffic:
http.user_agent && !(http.user_agent contains "Mozilla")
This isolates every request made by tools rather than browsers - useful for quickly spotting automated activity in any PCAP.

Step 5 - Follow TCP Streams: Read the full conversations

So far we’ve been looking at metadata - source IPs, URIs, User-Agents. Now we need to see the actual content of the most interesting requests: what credentials were submitted, what files were downloaded, what the server responded with.

Reading the login attempts

Wireshark filter (in web-server_2025-07-01.pcap):
http.request.method == POST && http.request.uri contains "/dashboard/auth/login" && ip.src == 117.200.21.26
Why the ip.src clause is essential: Without it, this filter returns 8 packets, not 4. Because this machine is a reverse proxy, every inbound request from the attacker (117.200.21.26 → 157.230.81.229) is immediately forwarded internally to the backend app server (10.116.0.3 → 10.116.0.4). Both hops are captured in this one PCAP file, so each login attempt appears twice. Adding ip.src == 117.200.21.26 limits results to the original attacker-side packets only, giving you the complete request bodies.
This gives you exactly 4 POST packets - one per login attempt. Click the first one, then right-click > Follow > TCP Stream. What you see in the stream window: Wireshark colours the two sides of the conversation differently. By default, the client’s data (the attacker’s request) is shown in red and the server’s response in blue. The stream shows everything in one continuous scroll: the HTTP request headers, then a blank line, then the POST body, then the server’s response headers, then the response body. The POST body you will see (line-wrapped here for readability):
csrfmiddlewaretoken=UX4dve2DwuDo171EEgnpZaxni395EA5ijYsOoOoHwPUbGqAOKcR7Y4qns2yWIb74
&fake_email=
&fake_password=
&next=%2Fdashboard%2F
&region=default
&username=admin
&password=admin
What each field means:
  • csrfmiddlewaretoken - Django’s Cross-Site Request Forgery protection token. It rotates with every new session and is not useful for our investigation. Ignore it.
  • fake_email / fake_password - Honeypot fields that OpenStack Horizon adds to its login form to catch naive credential-stuffing bots. A real browser always leaves them empty. Ignore them.
  • username / password - The credentials you’re looking for.
  • region - The OpenStack region name, always default here.
How to tell success from failure: The server’s response appears in blue in the same stream window, immediately below the request. You do not need to read the HTML body. Just look at the first line of the response:
  • HTTP/1.1 200 OK: the server re-rendered the login page. Login failed.
  • HTTP/1.1 302 Found with Location: /dashboard/: the server redirected to the dashboard. Login succeeded.
Repeat this for all four POST packets to build the full picture:
AttemptCredentialsResponseResult
09:39:16admin / admin200 OKFailed
09:39:27demo / demo200 OKFailed
09:39:33root / root200 OKFailed
09:40:07admin / StrongAdminSecret302 FoundSuccess

Reading the OpenRC file download

Filter (in web-server_2025-07-01.pcap):
http.request.uri contains "openrc" && ip.src == 117.200.21.26
One packet matches. Click it, then right-click > Follow > TCP Stream. The request headers are clearly readable in red. You can see the GET to /dashboard/project/api_access/openrc/. Scrolling down to the blue response, the headers confirm:
Content-Disposition: attachment; filename="admin-openrc.sh"
Content-Encoding: gzip
After the headers, the response body is binary garbage (compressed bytes that Wireshark cannot display as text). Do not try to read the file content from the TCP stream window. This is the most common point of confusion for this task. To read the actual file, use Wireshark’s HTTP object export, which automatically decompresses the response:
  1. Go to File > Export Objects > HTTP…
  2. Find the row with admin-openrc.sh in the Filename column.
  3. Click Save, then open the file in a text editor.
The decompressed content confirms everything we’ve identified so far:
export OS_AUTH_URL=http://134.209.71.220/identity
export OS_PROJECT_ID=9fb84977ff7c4a0baf0d5dbb57e235c7
export OS_PROJECT_NAME="admin"
export OS_USERNAME="admin"
export OS_REGION_NAME="RegionOne"
export OS_IDENTITY_API_VERSION=3
This file is the bridge between the two PCAPs: it gives the attacker the controller’s direct IP address (134.209.71.220) and everything needed to authenticate to the OpenStack API without going through the web dashboard. Everything that happens in the controller PCAP after this moment is a consequence of this one download.

Step 6 - Cross-Reference PCAPs: Connect the timeline

This is where the two PCAP files merge into a single story. The question is: does the activity in the web-server PCAP lead to activity in the controller PCAP, and can we prove it? Method: Compare timestamps. Look at when the last meaningful action occurs in the web-server file, then look at when new activity begins in the controller file.

šŸ“ web-server_2025-07-01.pcap: Last meaningful action

The attacker’s last request in this file is the OpenRC file download at 09:40:29 UTC. After that there is no more attacker-initiated traffic in the web-server PCAP.

šŸ“ controller_2025-07-01.pcap: What’s in this file before the attacker arrives?

Open the controller PCAP and apply:
http.request && ip.src == 117.200.21.26
Before 09:41:44 there are zero results. The attacker has no presence in this file yet. Without the filter, the controller PCAP contains only internal background traffic, just the OpenStack services talking to each other. You’ll see periodic POST /v3/lease/keepalive requests from 134.209.71.220 to itself on port 2379 (etcd, a key-value store used internally by OpenStack). These are automated heartbeats with no relation to the attacker. Everything else is MySQL and AMQP traffic between backend services. There is no Horizon dashboard traffic in this file.

The pivot moment

The first packet in the controller PCAP with the attacker’s IP is at 09:41:44 UTC:
GET /identity HTTP/1.1
Host: 134.209.71.220
User-Agent: openstacksdk/4.6.0 keystoneauth1/5.11.1 python-requests/2.32.4 CPython/3.13.5
This is the OpenStack CLI’s initial service discovery call, which is the first thing it does every time you run any openstack command. The attacker is no longer using a browser. They are now running command-line tools directly against the controller using the credentials from the OpenRC file. The 75-second gap between 09:40:29 (OpenRC download) and 09:41:44 (first CLI call) is exactly the time it takes to: open a terminal, run source admin-openrc.sh, enter the password when prompted, and type the first openstack command. The causal chain is unambiguous.

What the attacker sequence looks like in the controller PCAP

Apply this filter to isolate only the attacker’s CLI activity:
http.request && http.user_agent contains "openstacksdk"
The full sequence of commands plays out in order, with timestamps 30–90 seconds apart, at the pace of a human exploring an environment manually:
Time (UTC)RequestWhat the attacker ran
09:41:44GET /identityFirst CLI command, discovery
09:41:45POST /identity/v3/auth/tokensAuthentication
09:41:45GET /identity/v3/servicesList all services
09:42:11GET /identity/v3/usersList all users
09:42:27GET /compute/v2.1/servers/detailList VMs
09:43:27GET /v1/AUTH_9fb84977...?format=jsonList Swift containers
09:43:47GET /v1/AUTH_.../user-data?format=jsonList user-data contents
09:44:05GET /v1/AUTH_.../employee-data?format=jsonList employee-data contents
09:45:23GET /v1/AUTH_.../user-data/user-details.csvDownload user data
09:45:47GET /v1/AUTH_.../employee-data/employee-details.csvDownload employee data
09:48:02POST /identity/v3/usersCreate backdoor user
09:49:15PUT /identity/v3/projects/.../users/.../roles/...Grant admin role
Each row is a separate openstack CLI command. The fact that every request carries the same openstacksdk User-Agent and the same auth token confirms they all belong to the same session.

Methodology Summary

Every time you open an unknown PCAP, follow this sequence:
  1. Protocol Hierarchy tells you what kind of machine this is
  2. Endpoints tell you who’s suspicious (disproportionate traffic volume)
  3. Conversations tell you how the traffic is structured (parallelism, ports)
  4. Build protocol-specific filters tell you what they did (HTTP paths, DNS queries, SMB shares, RDP sessions, etc.)
  5. Inspect client/application metadata tells you what tool or app generated the traffic (User-Agent strings, protocol banners, client names, request patterns)
  6. Reconstruct key sessions and relevant artifacts tell you the full content and sequence of interesting exchanges (Follow TCP Stream, File > Export Objects > HTTP for compressed responses, key request/response bodies)
  7. Cross-reference timestamps across hosts/files connects activity into one timeline and separates cause from effect
Each step narrows your focus. You go from ā€œI have 24,000 packetsā€ to a complete narrative of the attack, even when the protocol is not HTTP.

Attack Timeline

Time (UTC)Source PCAPEvent
09:38–09:40web-serverSubdomain fuzzing with ffuf (3,579 subdomains tested, 3,695 total requests)
09:39:07web-serverAttacker discovers cloud.vantage.tech, switches to Chrome
09:39:16web-serverLogin attempt: admin / admin failed (HTTP 200)
09:39:27web-serverLogin attempt: demo / demo failed (HTTP 200)
09:39:33web-serverLogin attempt: root / root failed (HTTP 200)
09:40:07web-serverLogin attempt: admin / StrongAdminSecret succeeded (HTTP 302)
09:40:29web-serverDownloads admin-openrc.sh from API Access page
- 75-second gap: attacker reads file, sources it, enters password -
09:41:44controllerFirst direct API call - OpenStack CLI service discovery (GET /identity)
09:41:45controllerEnumerates services and endpoints via Keystone
09:42:11controllerLists all users via Keystone (GET /identity/v3/users)
09:42:27controllerLists compute servers (GET /compute/v2.1/servers/detail)
09:43:27controllerLists Swift containers - finds 3: dev-files, employee-data, user-data
09:43:47controllerLists contents of user-data container
09:44:05controllerLists contents of employee-data container
09:45:23controllerDownloads user-details.csv (28 PII records)
09:45:47controllerDownloads employee-details.csv
09:48:02controllerCreates backdoor user ā€œjellibeanā€ with password ā€œP@$$wordā€
09:49:15controllerGrants jellibean admin role on the project

Questions & Answers

Task 1: What tool did the attacker use to fuzz the web server?

PCAP: web-server_2025-07-01.pcap
Filter: http.request && ip.src == 117.200.21.26
Where to look: Click any packet from the early GETs to expand Hypertext Transfer Protocol > User-Agent in the packet detail pane.
The User-Agent field reads Fuzz Faster U Fool v2.1.0-dev - this is ffuf, a popular web fuzzing tool written in Go.
Answer: ffuf@2.1.0-dev

Task 2: Which subdomain did the attacker discover?

PCAP: web-server_2025-07-01.pcap
Filter: http.request && ip.src == 117.200.21.26 && http.user_agent contains "Chrome"
After the ffuf scan, the attacker switched to Chrome and manually browsed to the discovered subdomain. Every Chrome request targets the same Host header value.
Answer: cloud.vantage.tech

Task 3: How many login attempts did the attacker make before successfully logging in?

PCAP: web-server_2025-07-01.pcap
Filter: http.request.method == POST && http.request.uri contains "/dashboard/auth/login" && ip.src == 117.200.21.26
Four POST packets appear. The response codes distinguish success from failure: HTTP 200 means the login page was re-rendered (failed), while HTTP 302 means authentication succeeded and the server is redirecting to the dashboard. The first three returned 200, and the fourth returned 302.
AttemptCredentialsResponseResult
1 (09:39:16)admin : admin200 OKFailed
2 (09:39:27)demo : demo200 OKFailed
3 (09:39:33)root : root200 OKFailed
4 (09:40:07)admin : StrongAdminSecret302 FoundSuccess
Answer: 3

Task 4: When did the attacker download the OpenStack API remote access config file?

PCAP: web-server_2025-07-01.pcap
Filter: http.request.uri contains "openrc" && ip.src == 117.200.21.26
The OpenRC file is a shell script that sets environment variables for OpenStack CLI authentication. The attacker downloaded it from Project > API Access > Download OpenStack RC File in the Horizon dashboard. The response header confirms the filename in Content-Disposition as admin-openrc.sh. To read the file content itself, use File > Export Objects > HTTP (the response is gzip-compressed and unreadable in the Follow TCP Stream window). The decompressed file contains the auth URL (http://134.209.71.220/identity), the project ID, and the username - everything needed to pivot from the web server to direct API access on the controller.
Answer: 2025-07-01 09:40:29

Task 5: When did the attacker first interact with the API on the controller node?

PCAP: controller_2025-07-01.pcap
Filter: http.request && http.user_agent contains "openstacksdk"
Before 09:40:29, controller API traffic is mostly dashboard-generated background activity. After a 75-second gap (the attacker reading and sourcing the OpenRC file), the first request with the OpenStack SDK User-Agent appears - GET /identity at 09:41:44 UTC - which is the SDK’s service discovery call, always the first thing the CLI does when you run any openstack command.
Answer: 2025-07-01 09:41:44

Task 6: What is the project ID of the default project accessed by the attacker?

PCAP: Both - appears in web-server (OpenRC file) and throughout controller (API URLs)
Easiest source: Export the OpenRC file via File > Export Objects > HTTP (Task 4)
export OS_PROJECT_ID=9fb84977ff7c4a0baf0d5dbb57e235c7
It also appears embedded in dozens of API URLs throughout the controller PCAP: compute usage queries, Swift storage paths (/v1/AUTH_9fb84977...), and networking quota requests.
Answer: 9fb84977ff7c4a0baf0d5dbb57e235c7

Task 7: Which OpenStack service provides authentication and authorization?

PCAP: controller_2025-07-01.pcap
Filter: http.request.uri == "/identity/v3/services" && http.user_agent contains "openstacksdk"
Click the matching packet and follow the TCP stream (Follow > TCP Stream). The response body is a JSON service catalog. The entry with "type": "identity" is Keystone - OpenStack’s authentication and authorization service. Every API call hitting the /identity/ path in the controller PCAP is talking to Keystone. The relevant JSON entry:
{"name": "keystone", "type": "identity", "enabled": true, ...}
Answer: Keystone

Task 8: What is the endpoint URL of the Swift service?

PCAP: controller_2025-07-01.pcap
Filter: http.request.uri == "/identity/v3/endpoints" && http.user_agent contains "openstacksdk"
Follow the TCP stream on the matching packet. The response body is a JSON endpoint catalog listing all service endpoints. Find the entries with service_id matching the Swift service (f9194820052d4788b09157bf0a0dfdd0). The public-interface endpoint URL is a template:
http://134.209.71.220:8080/v1/AUTH_$(project_id)s
When the $(project_id)s template variable is resolved using the admin project ID from the OpenRC file, the full URL becomes:
Answer: http://134.209.71.220:8080/v1/AUTH_9fb84977ff7c4a0baf0d5dbb57e235c7

Task 9: How many containers were discovered by the attacker?

PCAP: controller_2025-07-01.pcap
Filter: http.request.uri contains "AUTH_9fb84977ff7c4a0baf0d5dbb57e235c7?format=json" && ip.src == 117.200.21.26
One packet matches: the attacker’s GET to the Swift proxy at port 8080 listing all containers. Follow the TCP stream. The response includes:
  • X-Account-Container-Count: 3 in the headers (confirms the count)
  • A JSON body listing all three containers:
[
  { "name": "dev-files", "count": 0, "bytes": 0 },
  { "name": "employee-data", "count": 0, "bytes": 0 },
  { "name": "user-data", "count": 0, "bytes": 0 }
]
Answer: 3

Task 10: When did the attacker download the sensitive user data file?

PCAP: controller_2025-07-01.pcap
Filter: http.request.uri contains "user-details.csv" && ip.src == 117.200.21.26
One packet matches: the attacker’s GET to the Swift proxy at port 8080. (You may also see an internal forwarding request from the Swift proxy to the object storage server on a path beginning with /sdb1/ - filter by ip.src == 117.200.21.26 to see only the original request.)
Answer: 2025-07-01 09:45:23

Task 11: How many user records are in the sensitive user data file?

PCAP: controller_2025-07-01.pcap
Method: Follow the TCP stream on the user-details.csv GET packet from Task 10.
The response headers confirm:
Content-Type: text/csv
Content-Length: 1367
The response body is the file itself - readable plaintext (Swift object storage does not compress responses). The file contains:
  • A header row: Full Name,Email,Phone Number
  • 28 data records (John Doe through Zoe Lee), each containing full name, email address, and phone number - personally identifiable information.
Answer: 28

Task 12: What is the username of the new user created for persistence?

PCAP: controller_2025-07-01.pcap
Filter: http.request.method == POST && http.request.uri == "/identity/v3/users"
Follow the TCP stream. The request body is a JSON payload sent to Keystone:
{
  "user": {
    "name": "jellibean",
    "password": "P@$$word",
    "enabled": true,
    "default_project_id": "9fb84977ff7c4a0baf0d5dbb57e235c7"
  }
}
The server responded with HTTP 201 CREATED, confirming the account was successfully created. A subsequent PUT request at 09:49:15 then granted this user the admin role on the project.
Answer: jellibean

Task 13: What is the password of the new user?

PCAP: controller_2025-07-01.pcap
Method: Same TCP stream as Task 12 - the password is in the JSON request body in cleartext.
Answer: P@$$word

Task 14: What is the MITRE ATT&CK technique ID for the persistence method?

The attacker created a new user account in a cloud platform (OpenStack Keystone) and granted it administrative privileges. This maps to:
  • Tactic: Persistence
  • Technique: T1136 - Create Account
  • Sub-technique: .003 - Cloud Account
T1136.003 specifically covers adversaries creating accounts in cloud environments (AWS IAM, Azure AD, GCP IAM, OpenStack Keystone) to maintain persistent access, distinguishing it from local account creation (.001) or domain account creation (.002).
Answer: T1136.003

Key Wireshark Filters Reference

PurposePCAPDisplay Filter
All attacker HTTP requestsweb-serverhttp.request && ip.src == 117.200.21.26
Attacker fuzzing traffic onlyweb-serverhttp.request && ip.src == 117.200.21.26 && http.user_agent contains "Fuzz"
Attacker browser traffic onlyweb-serverhttp.request && ip.src == 117.200.21.26 && http.user_agent contains "Chrome"
Login attempts (attacker only)web-serverhttp.request.method == POST && http.request.uri contains "login" && ip.src == 117.200.21.26
OpenRC download (attacker only)web-serverhttp.request.uri contains "openrc" && ip.src == 117.200.21.26
Direct OpenStack CLI callscontrollerhttp.request && http.user_agent contains "openstacksdk"
Keystone API callscontrollerhttp.request.uri contains "/identity/v3"
Swift account listingcontrollerhttp.request.uri contains "AUTH_" && http.request.uri contains "format=json"
CSV downloads (attacker)controllerhttp.request.uri contains ".csv" && ip.src == 117.200.21.26
User creationcontrollerhttp.request.method == POST && http.request.uri == "/identity/v3/users"
Role assignmentcontrollerhttp.request.method == PUT && http.request.uri contains "roles"

Tools Identified

ToolVersionUser-Agent StringPurpose
ffuf2.1.0-devFuzz Faster U Fool v2.1.0-devSubdomain enumeration
Chrome137.0.0.0Mozilla/5.0 (Macintosh Intel Mac OS X 10_15_7) AppleWebKit/537.36 ... Chrome/137.0.0.0 Safari/537.36Manual dashboard exploitation
OpenStack SDK4.6.0openstacksdk/4.6.0 keystoneauth1/5.11.1 python-requests/2.32.4 CPython/3.13.5API enumeration and data exfiltration

Network Topology

Attacker (117.200.21.26) - macOS, Chrome 137, ffuf 2.1.0-dev
    |
    v  HTTP :80
Web Server / Reverse Proxy (157.230.81.229 / 10.116.0.3)
    |
    v  HTTP :80 (internal forwarding)
OpenStack Controller (134.209.71.220 / 10.116.0.4)
    |- Keystone (Identity)    - port 80  /identity
    |- Nova (Compute)         - port 80  /compute
    |- Neutron (Networking)   - port 80  /networking
    |- Glance (Images)        - port 80  /image
    |- Cinder (Block Storage) - port 80  /volume
    |- Swift (Object Storage) - port 8080
    |- MySQL                  - port 3306 (localhost)
    |- RabbitMQ (AMQP)        - port 5672 (localhost)
    |- etcd                   - port 2379
    '- Memcached              - port 11211 (localhost)

MITRE ATT&CK Mapping

PhaseTechniqueEvidence
ReconnaissanceT1595.003 - Active Scanning: Wordlist Scanningffuf subdomain enumeration (3,579 subdomains)
Initial AccessT1078.004 - Valid Accounts: Cloud AccountsBrute-forced admin:StrongAdminSecret on Horizon
DiscoveryT1580 - Cloud Infrastructure DiscoveryEnumerated services, endpoints, users, VMs, containers
CollectionT1530 - Data from Cloud StorageDownloaded user-details.csv and employee-details.csv from Swift
PersistenceT1136.003 - Create Account: Cloud AccountCreated ā€œjellibeanā€ with admin role

Corrections from Previous Version

The following errors were identified by verifying each claim against the raw PCAP data and have been corrected in this version:
SectionPrevious (incorrect)Corrected
Step 1No explanation for why ā€œ8 form submissionsā€ when there are only 4 login attemptsAdded explanation: the reverse proxy doubles every POST - both the external and internal hops are captured
Step 5 login filterhttp.request.method == POST && http.request.uri contains "/dashboard/auth/login" then ā€œFour POST requests appearā€Added && ip.src == 117.200.21.26, without it the filter returns 8 packets and selecting the wrong one gives the wrong stream
Step 5 POST bodyShowed simplified username=admin&password=admin&region=defaultShows the real body: includes csrfmiddlewaretoken, fake_email, and fake_password fields
Step 5 OpenRCImplied the file is readable in the Follow TCP Stream windowCorrected: response is gzip-compressed (binary in stream), use File > Export Objects > HTTP instead
Step 4 User-AgentShowed partial UA string Chrome/137.0.0.0Shows full UA string including macOS platform: Mozilla/5.0 (Macintosh Intel Mac OS X 10_15_7) ...
Attack Timeline09:44:27 - ā€œLists contents of user-data and employee-data containersā€ (single entry)Split into two correct entries: 09:43:47 (user-data) and 09:44:05 (employee-data)
Attack Timeline09:48:28 - ā€œGrants jellibean admin role on the projectā€Corrected to 09:49:15 - actual timestamp verified in the PCAP