Back to KPMG CTF 2026 Campus Edition
Healthy Hijack 1 (Infra) - Writeup
KPMG CTF 2026 Campus EditionInfrastructure10 ptsMedium

Healthy Hijack 1 (Infra) - Writeup

Oct 12, 2026 3 min readWriteup 9 of 22#tutorial
On this page
  1. 1.Challenge Overview
  2. 2.Step 1: Map the Filter
  3. 3.Step 2: Rule Out Semicolon Injection
  4. 4.Step 3: The Bypass — Newline Separator
  5. 5.Step 4: Read the Flag
  6. 6.Summary
  7. 7.Key Takeaways

Challenge Overview

EventKPMG CTF 2026 Campus Edition
ChallengeHealthy Hijack 1
CategoryWeb / Command Injection
Points10
Targetkicyber-...-healthyhijack-0-0.chals.io

"The application allows execution of limited network commands with strict filtering in place. Or so it seems... Explore the system, identify weaknesses, and escalate your privileges to root."

Healthy Hijack 1 challenge card

The target is a "Safe Command Runner", a web form that claims to only allow "network debugging commands." The name "Hijack" and the phrase "or so it seems" tell us the filter is beatable. This is a command injection challenge: get the runner to execute something it wasn't meant to.

Safe Command Runner form


Step 1: Map the Filter

Random input gets flatly rejected, submitting to /run returns "Command not allowed", so there's an allowlist on the first word:

Command not allowed

Testing the allowed tools shows what's permitted and reveals the execution environment:

  • ping runs but can't create its raw socket ("Operation not permitted... missing cap_net_raw+p capability"), so the container is unprivileged.
  • traceroute runs fine with normal arguments.

ping fails on raw socket

So the allowlist validates the first token (ping, traceroute, …) and then runs the tool. The question is how the rest of our input reaches the system.


Step 2: Rule Out Semicolon Injection

The classic first attempt is a shell metacharacter, chain a second command with ;:

ping -V; id
traceroute -m 1 127.0.0.1; id

Both fail to inject. With traceroute, the error is the tell:

Cannot handle "packetlen" cmdline arg 'id' on position 2 (argc 4)

traceroute treats id as an argument

id was handed to traceroute as a literal argument, not run as a separate command. That means the app isn't passing our string to a shell like sh -c "..."; it's splitting the input into an argument list (or stripping ;). Semicolons are a dead end here, and importantly, this rules out the simplest injection model and tells us to look at how the input is split.


Step 3: The Bypass — Newline Separator

If semicolons are filtered/neutralised but the app still joins or processes multi-line input, a newline may act as the separator the filter forgot about. Entering two separate lines in the textarea:

traceroute -m 1 127.0.0.1
id

This works:

Newline injection yields uid=appuser

127.0.0.1 : Name or service not known
...
uid=1000(appuser) gid=1000(appuser) groups=1000(appuser)

The second line executed as its own command. The flaw: the validator only checks the first line (it sees traceroute, an allowed tool, and approves), but the backend passes the entire multi-line payload to a shell, where the newline is a perfectly valid command terminator. Semicolons were handled; newlines were not. Incomplete blocklist, classic.

We now have arbitrary command execution as appuser.


Step 4: Read the Flag

With the newline primitive, put an allowed command on line 1 to pass validation and the real payload on line 2. Reading the flag file:

traceroute -m 1 127.0.0.1
cat flag.txt

The flag prints below the traceroute error:

Flag in command output

KPMG_CTF{uw_cezWpRozUg0wvT1Ah0eM5kDffaoewXOLrUAGZ8Ij9}

Summary

StepInputResult
1ping / tracerouteFirst-word allowlist; unprivileged container
2traceroute ... ; id; not injected (id parsed as arg)
3traceroute ... ⏎ idNewline injects → uid=appuser
4traceroute ... ⏎ cat flag.txtFlag

Key Takeaways

  • Validate exactly what you execute. The filter approved the input by looking at the first line/token, but the backend ran the whole multi-line string in a shell. Any gap between what is checked and what is run is an injection.
  • Blocklists miss separators. Handling ; but not \n (newline) is a textbook incomplete filter. Shells treat newlines, ;, &&, ||, |, $() and backticks all as command boundaries, block one and attackers use the next.
  • Let error messages classify the bug. The traceroute ... Cannot handle ... arg 'id' error proved the input was being split into arguments, not run via sh -c. That single observation redirected the whole approach from semicolons to newlines.
  • The right fix is no shell at all. Pass arguments as an exec array (execve-style), never concatenate user input into a shell command line, and allowlist on the parsed argv, not a substring of the raw request.
  • This is the foothold. We're appuser, not root. The challenge's "escalate to root" goal is the next stage (Healthy Hijack 2), enumerating sudo -l and SUID binaries from this RCE.

Flag: KPMG_CTF{uw_cezWpRozUg0wvT1Ah0eM5kDffaoewXOLrUAGZ8Ij9}