This article describes an interactive lab teaching OS command injection through a vulnerable ping tool that concatenates user input into shell commands. The lab demonstrates how attackers can inject shell metacharacters to execute arbitrary commands on the server with web server privileges. Command injection remains a critical real-world vulnerability, especially in network appliances and admin interfaces, and the article covers both exploitation techniques and defensive strategies.
- How the vulnerability works mechanicallyThe vulnerable application concatenates user input directly into a shell command string without escaping. When the shell parses this string, it treats metacharacters like semicolons, double ampersands, and pipes as command separators rather than literal text, allowing an attacker to append a second command that executes with the web server's permissions.
- Shell metacharacters enable command chainingA semicolon terminates one command and starts another; double ampersands run the next command if the first succeeds; a pipe sends one command's output to another. The lab teaches that if one separator is filtered, others remain available, making character blocklists an ineffective defense.
- Real-world prevalence in network devicesCommand injection is especially common in routers, VPNs, and firewalls with web admin pages that wrap system tools like ping and traceroute. The article cites CVE-2024-21887 in Ivanti Connect Secure, which was actively exploited in the wild in 2024 and appears in CISA's Known Exploited Vulnerabilities catalogue.
- Direct API calls eliminate shell interpretationThe primary defense is to avoid invoking a shell entirely by using APIs that pass a program and an argument array separately, such as execFile or subprocess with a list. This prevents the shell from parsing user input as metacharacters.
- Strict input validation provides defense in depthA host field should be validated to match only IP addresses or valid hostname patterns, rejecting anything else. Combined with direct API calls and least-privilege execution, this limits what an attacker can accomplish even if validation is bypassed.
- Use native libraries instead of shell utilitiesWhere feasible, replace shelled-out system commands with native libraries—for example, using ICMP or DNS libraries instead of calling ping or traceroute. This eliminates the injection vector entirely.
- Least privilege limits compromise scopeRunning the application with minimal required permissions means a successful command injection reaches only the restricted resources available to that user, reducing the damage from full server compromise.
- Timing attacks reveal injection successWhen testing for command injection in authorized scenarios, adding a harmless sleep command can reveal whether injection is possible by observing changes in response timing, providing a safe initial probe before attempting to extract data.
Bottom line: OS command injection remains prevalent and dangerous because developers often pass unsanitized user input to shells; the only reliable fix is to call programs directly with argument arrays and validate input against strict allowlists, avoiding shells entirely. Blocklisting metacharacters is ineffective because shells offer multiple command-chaining operators, making this vulnerability especially critical to test in network appliances and admin interfaces.
📋 In this walkthrough
What this lab teaches
OS command injection happens when an application builds a shell command from user input. Shells have special characters that separate or chain commands. If your input reaches the shell unescaped, those characters let you end the developer’s command and start your own, running with the web server’s permissions.
How the ping tool works
The backend joins your host value onto a ping command and runs the whole string through a shell, then shows the output in a pane. There is a file called /flag on the server. The lab is solved when its contents appear in the output.
The attack approach
- Ping a normal address first and look at the output, so you know what success looks like.
- Think about how a shell reads a line. A semicolon ends one command; double ampersands run the next command if the first succeeds; a pipe sends one command’s output into another.
- Add a second command after a valid address using one of those separators.
- If one separator is filtered, try another. Shells offer several.
Full walkthrough (spoiler)
Stuck after a few attempts? Open the steps below. Solving it yourself first is worth more XP, and more learning.
Show the step-by-step solution
- Enter a normal IP address, such as a public DNS resolver, and run the ping to see the normal output.
- Add a command separator after the address, followed by a command that prints the contents of
/flag. - Run it. The shell runs the ping, then your command, and both outputs appear in the result pane.
- If the first separator is blocked, try the other chaining operators listed in the lab hints. When the flag text shows up in the output, the lab marks you solved.
Why it works
The developer meant the input to be one argument to ping. The shell does not know that. It receives a single line of text and splits it into commands using its own rules, so your separator is just as meaningful as anything the developer wrote. Whatever you add runs as the web server’s user, which can usually read application files, configuration and secrets.
Where this happens in the real world
Command injection is especially common in network devices and appliances, where web admin pages wrap system tools like ping and traceroute. Many entries in CISA’s Known Exploited Vulnerabilities catalogue are OS command injection flaws in VPNs, routers and firewalls; for example, CVE-2024-21887 in Ivanti Connect Secure was exploited in the wild in 2024. On a server, it usually means full compromise.
How to defend against it
- Avoid the shell entirely. Use APIs that take a program and an argument array (execFile, subprocess with a list, ProcessBuilder) so no shell parses the input.
- Validate strictly. A host field should match an IP address or host-name pattern and nothing else.
- Use a library instead of shelling out when one exists, for example a native ICMP or DNS library.
- Run with least privilege so a successful injection reaches as little as possible.
- Do not rely on blocklists of characters. There are always more separators than you expect.
Try it, then go further
Reading about it is the easy part. Open the lab, try your own approach before peeking at the spoiler, and see how many attempts it takes.
