// HackTheBox  ·  writeup

OpTinselTrace24-2: Cookie Consumption

HackTheBox Easy grep
DFIR

Scenario

In this scenario, players investigate a Kubernetes breach by analyzing artifacts to track the attacker’s actions, focusing on tasks such as investigating compromised namespaces and detecting malicious behavior.

Executive Summary

An attacker found a Flask application exposed outside the cluster through a NodePort service and fuzzed its /system/ path until one endpoint answered. That endpoint ran commands for them, and they used it to install curl inside the application pods and pipe a script from their own web server into a shell. One replica became the foothold. From there they created a separate pod of their own and left a root cron job on the node pointing at a backdoor script. The whole chain is readable from the application log, the per-pod process dumps and the node journal.

Artifacts

Timestamp note: Everything is UTC and the fuzzing starts on 2024-11-08. The fuzzing answer wants the very first probe, not the burst that follows.

Discovery

Q1 — How many replicas are configured for the flask-app deployment?

The deployment description records the desired replica count. Search it for Replicas and read the desired figure, which also tells you how many flask-app-* pod logs to expect.

grep Replicas describes/deployments.log
Click flag to reveal 3
Q2 — What is the NodePort through which the flask-app is exposed?

The service description for flask-app-service is of type NodePort. The port on the right of the mapping is the one reachable from outside the cluster, and it is the one the attacker hit.

grep NodePort describes/services.log
Click to reveal answer 30000/TCP

Initial Access

Q3 — What time (UTC) did the attacker first initiate fuzzing on the /system/ endpoint?

Grep every replica’s flask-app.log for /system/ and sort by time. The earliest hit is a single GET /system/ls that returns 404, followed minutes later by a burst of same-second 404s as the wordlist ran.

grep "/system/" default/flask-app-*/flask-app.log

Gotcha: The burst of same-second 404s looks like the start of the fuzzing, but it is the tool running at speed. The first manual-looking probe a few minutes earlier is the answer.

Click flag to reveal 2024-11-08 22:02:48
Q4 — Which endpoint did the attacker discover through fuzzing and subsequently exploit?

Same log, filtered past the 404s. After the fuzzing run, one path under /system/ starts returning 200 to POST requests, which is the discovered endpoint being used rather than probed.

grep "/system/" default/flask-app-*/flask-app.log
Click to reveal answer /system/execute

Execution

Q5 — Which program did the attacker attempt to install to access their HTTP pages?

The pod logs show apt installing a package through the execute endpoint, and the process dumps show what it was for: a sh -c that fetches from the attacker’s web server and pipes the result into bash. The program is the HTTP client.

grep -a "sh -c" default/processes/*
Click flag to reveal curl

Command and Control

Q6 — What is the IP address of the attacker?

The same sh -c process line from Q5 carries the target of the curl: the attacker’s host on port 8080, fed straight into bash.

grep -a "sh -c" default/processes/*
Click to reveal answer 10.129.231.112
Q7 — What is the name of the pod that was compromised and used by the attacker as the initial foothold?

Three replicas are running (Q1) and the process dumps are split per pod. Only one of them holds the curl | bash process from Q5 and Q6, so it is the pod the attacker actually landed in.

grep -a "sh -c" default/processes/*
Click flag to reveal flask-app-77fbdcfcff-2tqgw

Persistence

Q8 — What is the name of the malicious pod created by the attacker?

Outside the flask-app replicas there is an alpine pod whose container is named evil, and the node journal shows a useradd evil on the same name. The format hint (***l) is the container and account name, which matches the pod the attacker spun up from the foothold.

Click to reveal answer evil
Q9 — What is the absolute path of the backdoor file left behind by the attacker?

Search the node journals for backdoor. Cron logs a root CMD entry on schedule for a script under /opt, which is the persistence the attacker left on the node.

grep -rha backdoor system_logs/*.log
Click flag to reveal /opt/backdoor.sh

Attack Summary

PhaseAction
DiscoveryAttacker reaches the Flask app on the NodePort and fuzzes /system/
Initial AccessFuzzing finds /system/execute, which accepts POST and returns 200
ExecutionCommands through the endpoint install curl in the pods
Command and Controlcurl from the attacker’s web server piped into bash in one replica
PersistenceMalicious alpine pod created with a container named evil
PersistenceRoot cron job runs /opt/backdoor.sh

IOCs

TypeValue
IP10.129.231[.]112
Endpoint/system/execute
Pod (foothold)flask-app-77fbdcfcff-2tqgw
Pod (malicious)evil
File/opt/backdoor.sh

MITRE ATT&CK

TechniqueIDDescription
Active Scanning: Wordlist ScanningT1595.003Fuzzing of /system/ paths
Exploit Public-Facing ApplicationT1190Command-execution endpoint on the NodePort service
Command and Scripting Interpreter: Unix ShellT1059.004sh -c commands through the endpoint
Ingress Tool TransferT1105curl pull of a script from the attacker’s server
Deploy ContainerT1610Rogue alpine pod with container evil
Scheduled Task/Job: CronT1053.003Root cron entry for /opt/backdoor.sh

Defender Takeaways

A debug-style execute endpoint should never ship. The app exposed a path that ran commands on request, reachable from outside the cluster through a NodePort. Remove it, or put authentication in front of anything that can run code.

Fuzzing is loud and the log shows it. A run of 404s on one prefix, ending in a first 200 on a new path, is a clean detection. Alert on bursts of 404s per source against a single service, and keep per-pod access logs.

Package installs inside running pods are a signal. apt pulling curl into an application container, followed by curl | bash, is not normal pod behaviour. Use read-only root filesystems and egress policies so a pod cannot fetch from an arbitrary internal host.

Watch for pods that don’t belong to a deployment. A bare alpine pod alongside the application replicas, plus cron entries under /opt on the node, should be caught by admission control and file integrity checks.

Conclusion

One unauthenticated command-execution endpoint turned a public NodePort into a root cron backdoor on the node. The detection value sat in the first few probes of the fuzzing run, well before the exploit landed.