A structured lesson workspace with readable content, hands-on examples, and a clean path to completion.
Every web language has a function that says "run this string as a shell command." PHP has exec, system, shell_exec, passthru, popen. NodeJS has child_process.exec and child_process.spawn. Python has os.system and subprocess. Ruby has backticks and system. The names differ. The danger is identical.
The pattern is always the same: user input from a request parameter gets concatenated into a string that is then passed to a command execution function. No sanitization. No escaping. No allowlist validation. The interpreter receives the full concatenated string and executes everything in it.
<?phpif(isset($_GET['filename'])){system("touch /tmp/" . $_GET['filename'] . ".pdf");}?>
That PHP snippet looks innocent. A developer wanted to create a PDF file with a user-chosen name. But the filename parameter goes straight into system() with zero filtering. An attacker supplies a filename like report; whoami and the shell executes touch /tmp/report; whoami.pdf. Two commands run. The server just told the attacker which user the web process runs as.
app.get("/createfile", function(req, res){child_process.exec(`touch /tmp/${req.query.filename}.txt`);res.send("File created");});
Template literals make this look modern and clean. They do not make it safe. The req.query.filename value is interpolated directly into the shell command string. Same vulnerability, different syntax. The exploitation payload is identical regardless of whether the backend is PHP, Node, Python, or .NET.
Command injection is not a language-specific bug. It is an architecture-level failure: untrusted data reaching a command interpreter without a trust boundary. The exploitation techniques in this course work across all languages and frameworks.
When system() or exec() receives a string, it hands that string to /bin/sh (on Linux) or cmd.exe (on Windows). The shell parses metacharacters like semicolons, pipes, ampersands, and backticks as control operators. Your injected payload exploits this parsing behavior to break out of the intended command and append a new one.
The shell saw the semicolon, treated everything before it as command one, and everything after as command two. It does not care that the semicolon came from user input. It just parses and executes. That is the entire vulnerability in one terminal output.
You now know the anatomy. Next, we compare vulnerable patterns side by side across languages so you can spot them during code review in under ten seconds.
Finish the lesson once you have worked through the material. This awards ★ 30 XP.