This Sherlock involves investigating network traffic to identify abuse of the OpenStack API. Participants will analyze packet captures to discern misconfigurations and unauthorized access attempts. The investigation focuses on tracing API interactions and pinpointing the technical vulnerabilities that allowed the breach.
An external host fuzzed the public web server, found a hidden subdomain hosting the OpenStack dashboard, and walked in with a guessed admin password after a few default-credential attempts. From the dashboard the attacker downloaded the project’s RC file, then used it to talk to the controller’s identity and API endpoints directly. They listed the object storage endpoint and its containers, pulled a CSV of user records, and finished by creating a new admin user through the identity API for persistence. The whole chain is plaintext HTTP in two pcaps, so every request and response body is readable.
web-server.2025-07-01.pcap → open with Wireshark or tshark. The public-facing traffic: fuzzing, dashboard login, RC file downloadcontroller.2025-07-01.pcap → open with Wireshark or tshark. The attacker’s OpenStack API calls (identity, object storage)Timestamp note: Packet times and HTTP
Dateheaders are not the same zone. The capture renders local time at+10:00, whileDateheaders are GMT. Questions ask for UTC, so subtract ten hours from anything read off the packet list. The web capture also holds proxied duplicate requests, so one action can show up twice.
Summarise User-Agent values per source in the web server capture. One external address sends thousands of requests in a short window with a single non-browser agent string, the signature of a wordlist run. The agent string spells out the tool name and its version.
tshark -r web-server.2025-07-01.pcap -Y http.request -T fields -e ip.src -e http.user_agent | sort | uniq -c | sort -rn | head
Tip: The agent string carries the long form of the name, followed by a
-devsuffix on the version. The answer format wants the short tool name and the plain version number.
Fuzzing against an IP or the base domain mostly returns the same response size. Filter the fuzz phase by Host header and look at which virtual host stops returning the default page: ffuf is brute forcing the Host header, and one value answers differently. The attacker then keeps using that host, moving from the fuzz run into /dashboard/auth/login, which confirms it is the real target.
tshark -r web-server.2025-07-01.pcap -Y http.request -T fields -e http.host -e http.request.uri | sort | uniq -c | sort -rn | head -20
List the POSTs to the dashboard login from the attacker address and read each form body. The attacker tries default pairs first, then a stronger-looking admin password. Failed attempts return the login form again with a 200; the successful one returns a 302 redirect into the dashboard. Count the attempts that precede the redirect.
tshark -r web-server.2025-07-01.pcap -Y 'http.request.method == "POST"' -T fields -e frame.number -e http.host -e http.request.uri -e urlencoded-form.value
Gotcha: The question is ambiguous. The attacker sent one more POST than the number of failures, because the successful login is itself a POST. Count only the attempts that failed, i.e. those before the 302.
The dashboard serves the RC file from /dashboard/project/api_access/openrc/. Find the GET for that path in the web capture after the successful login. The packet time is local +10:00, so convert to UTC. The RC file holds the credentials and auth URL the attacker uses against the controller next.
tshark -r web-server.2025-07-01.pcap -Y 'http.request.uri contains "openrc"' -t ad -T fields -e frame.time -e http.request.uri
Switch to the controller capture and filter on the attacker address as the source. The first request to the controller is a call under /identity, the Keystone path on the API host. Convert the packet time from +10:00 to UTC.
tshark -r controller.2025-07-01.pcap -Y 'ip.src == 117.200.21.26 && http.request' -t ad -T fields -e frame.number -e frame.time -e http.request.uri | head
The attacker’s first calls hit /identity/v3/... and request authentication tokens. In OpenStack the identity service behind that path issues tokens and holds the service catalog, which is also reference knowledge you can confirm in the OpenStack docs.
Follow the token response from the controller. It names the project the token is scoped to, and the same 32-character ID reappears in later AUTH_ object storage paths, which cross-checks it.
tshark -r controller.2025-07-01.pcap -q -z follow,tcp,ascii,<stream> | grep -i project
Find the response to GET /identity/v3/endpoints. The object storage entry holds a URL template ending in AUTH_$(project_id)s. Substitute the project ID from Q6 to get the concrete endpoint.
tshark -r controller.2025-07-01.pcap -q -z follow,tcp,ascii,<stream>
Hint: The response lists many endpoints. Match on the service type for object storage, and remember the answer wants the template filled in, not the raw placeholder.
After the endpoint lookup the attacker sends a GET to the storage account URL. The response body is the container listing, one name per line. Follow the TCP stream and count the names.
tshark -r controller.2025-07-01.pcap -q -z follow,tcp,ascii,<stream>
The attacker requests a CSV object from one of the containers found in Q9. Read the Date header on that response, which is already GMT, so no zone conversion is needed here.
tshark -r controller.2025-07-01.pcap -Y 'http.request.uri contains ".csv"' -T fields -e frame.number -e http.request.uri
Dump the response stream for the CSV request from Q10 and count the data rows, excluding the header line.
tshark -r controller.2025-07-01.pcap -q -z follow,tcp,ascii,<stream> | grep -c ","
Filter the controller capture for POSTs. A POST /identity/v3/users to the identity API carries a JSON body with the new account’s name.
tshark -r controller.2025-07-01.pcap -Y 'http.request.method == "POST"' -T fields -e frame.number -e http.request.uri
The same user-creation body from Q12 holds the password in clear text, because the API call travels over plain HTTP.
tshark -r controller.2025-07-01.pcap -q -z follow,tcp,ascii,<stream>
A new account in the cloud identity service, not on a host, is the Cloud Account sub-technique of Create Account.
| Phase | Action |
|---|---|
| Reconnaissance | Wordlist fuzzing of the web server’s Host header and paths from one external IP |
| Reconnaissance | Hidden dashboard subdomain discovered |
| Credential Access | Default and guessed credentials tried against the dashboard login until one succeeds |
| Credential Access | Project RC file downloaded from the dashboard |
| Discovery | RC file used against the controller identity API; project and service catalog enumerated |
| Discovery | Object storage endpoint and container list retrieved |
| Collection | User data CSV downloaded from a container |
| Persistence | New admin user created through the identity API |
| Type | Value |
|---|---|
| IP (attacker) | 117.200.21[.]26 |
| IP (OpenStack host) | 134.209.71[.]220 |
| Domain | cloud[.]vantage[.]tech |
| User-Agent | Fuzz Faster U Fool v2.1.0-dev |
| URL path | /dashboard/project/api_access/openrc/ |
| File | user-details.csv (container user-data) |
| Account (backdoor) | jellibean / P@$$word |
| Technique | ID | Description |
|---|---|---|
| Active Scanning: Wordlist Scanning | T1595.003 | ffuf run against the web server to find a hidden virtual host |
| Brute Force: Password Guessing | T1110.001 | Default and guessed admin credentials against the dashboard |
| Unsecured Credentials: Credentials In Files | T1552.001 | RC file downloaded for API access |
| Account Discovery: Cloud Account | T1087.004 | Identity API queried for project and token details |
| Cloud Service Discovery | T1526 | Service catalog and endpoints enumerated |
| Cloud Storage Object Discovery | T1619 | Containers listed on the object store |
| Data from Cloud Storage | T1530 | User data CSV downloaded |
| Create Account: Cloud Account | T1136.003 | Admin user created via POST /identity/v3/users |
A dashboard that accepts admin/admin-style guesses and has no lockout is the whole initial compromise. The attacker needed a handful of attempts. Enforce a strong admin password policy, rate limit or lock the login endpoint, and put the dashboard behind a VPN or allow list rather than a guessable public subdomain.
Hidden subdomains are not access control. Host header fuzzing found the dashboard from one external address in a single run. Alert on thousands of requests with a tool user-agent and a flat response size, and block the source at the edge.
The RC file is a credential, and the APIs were plaintext HTTP. Once downloaded it gave full API access, and every token, project ID and the new account’s password crossed the wire in cleartext. Terminate TLS on the controller and object storage endpoints, and alert on RC file downloads from external IPs.
Account creation through the identity API needs its own detection. A POST /identity/v3/users followed by an admin role assignment from an address that never used the API before should page someone. Keystone audit events carry this, and the pcap shows how little effort it took.
One weak dashboard password gave the attacker an RC file, and the RC file gave them the whole cloud: data theft from object storage and a new admin account for staying in. Nothing in the chain needed an exploit, and none of it was encrypted, so the captures read like a transcript.