Description
Now presenting cowsay as a service. Find the flag at caas.mars.picoctf.net.
Setup
The challenge runs at https://caas.mars.picoctf.net - no download needed.
Try visiting /cowsay/hello in your browser to see the cowsay output.
Solution
Want to try it yourself first?
The guided walkthrough reveals hints one step at a time.
Step 1Identify the command injection
ObservationThe description says the URL path goes straight to cowsay, so the server concatenates it into a shell command with no sanitization. A semicolon appends a second command.The server runs /usr/games/cowsay followed by the URL path parameter directly in a shell. Because the input is unsanitized, a semicolon appends a second shell command after the cowsay call.Learn more
OS command injection occurs when a web application passes user-controlled data to a shell interpreter without sanitization. The server-side code is likely something like
exec('cowsay ' + req.params.message)in Node.js, which hands the entire string to/bin/sh -c. The shell interprets special characters in the user's input as control syntax rather than data.The semicolon (
;) is a shell command separator - it tellsshto execute the next command regardless of whether the previous one succeeded. Other injection characters include&&(run if previous succeeded),||(run if previous failed),|(pipe output), and backticks or$()for command substitution.This is classified as a CWE-78 (Improper Neutralization of Special Elements used in an OS Command) and appears in the OWASP Top 10 under "Injection." The correct fix is to never pass user input to a shell at all - use language-native APIs (in Node.js:
child_process.execFile()with an argument array, which bypasses the shell entirely).Step 2List files on the server
ObservationWith injection confirmed, the flag's filename is still unknown. Run ls through the same path first.Append ;ls to the URL path to run ls after cowsay. This reveals a file called falg.txt - note the deliberate typo.bashcurl "https://caas.mars.picoctf.net/cowsay/hello;ls"Expected output
falg.txt
What didn't work first
Tried: Immediately try to read flag.txt without listing first
The file is actually named falg.txt, not flag.txt, so cat flag.txt returns an error like 'No such file or directory' and nothing appears in the cowsay response. Enumerating the directory with ls first reveals the correct filename before attempting to read it.
Tried: Use the browser address bar directly instead of curl to send the semicolon injection
Browsers percent-encode the semicolon before sending, so the server receives it as literal text and cowsay simply says it back. curl passes it through unencoded, which is what triggers the injection.
Learn more
Enumeration is the process of discovering what resources exist on a target system before trying to access them. Running
lsvia command injection shows the current working directory's contents - file names, which point toward where interesting data lives. This is the first step in post-injection reconnaissance.The deliberate typo
falg.txt(instead offlag.txt) is a common CTF trick: it prevents people from guessing the filename without actually exploiting the vulnerability. It also tests whether solvers actually read the directory listing or just blindly try common filenames.In real penetration testing, command injection of this severity is a critical finding. From an
ls, a real attacker would escalate to reading/etc/passwd, exfiltrating credentials, establishing persistence, or pivoting to other internal systems. The cowsay wrapper is irrelevant - it's just the vector through which the shell receives the injection.Step 3Read the flag file
ObservationThe listing shows the file is named falg.txt, and a URL path cannot carry a literal space. Percent-encode it so the shell receives a real separator.Use ;cat with %20 for the space character (URL encoding) to read the flag file.bashcurl "https://caas.mars.picoctf.net/cowsay/hello;cat%20falg.txt"What didn't work first
Tried: Use a raw space in the curl URL instead of %20
A literal space splits the argument in your own shell, so curl sees the filename as a separate word and errors out on a malformed URL without sending anything. Percent-encoding keeps the path as one token, and the server decodes it back to a space before handing it to sh.
Tried: Use cat%20flag.txt (correct filename spelling) rather than cat%20falg.txt
The file is deliberately misspelled, so asking for flag.txt gets a no-such-file error wrapped in the cowsay output. The listing in the previous step gives the real name.
Learn more
URL encoding (percent-encoding) represents characters that have special meaning in URLs by replacing them with a
%followed by two hexadecimal digits. A space is%20. This is necessary because raw spaces in a URL path are invalid - browsers and HTTP clients either reject them or encode them automatically. When the server decodes the URL before passing it to the shell,%20becomes a literal space.cat(concatenate) reads and prints file contents to standard output. In a web context, standard output from the injected command is captured and included in the HTTP response - the server's cowsay output and the flag file contents both appear in the same response body.This challenge is named "CaaS" - Cowsay as a Service - a playful reference to "Software as a Service (SaaS)." It illustrates how wrapping any command-line tool as a web service without input sanitization is dangerous regardless of how harmless the tool itself is. The same vulnerability would exist if the server ran
fortune,figlet, orbannerwith user input.
Interactive tools
- Reverse Shell GeneratorGenerate reverse shell payloads (bash, nc, python, perl, ruby, php, node, powershell) and matching listeners. Set host and port once, copy any variant.
Flag
Reveal flag
picoCTF{moooooooooooooooooooooooooooooooooooooooooooooooooooooooooooo0o}
Node.js exec() with unsanitized user input in a shell context allows arbitrary command injection - semicolons separate commands in bash, so ;cat falg.txt runs as a second shell command.