Skip to main content

caas picoMini by redpwn Solution

A web challenge where unsanitized user input reaches a shell command, enabling remote code execution.

Published: April 2, 2026Updated: August 13, 2026

Description

Now presenting cowsay as a service. Find the flag at caas.mars.picoctf.net.

Remote

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.

Walk me through it
  1. Step 1Identify the command injection
    Observation
    The 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 tells sh to 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).

  2. Step 2List files on the server
    Observation
    With 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.
    bash
    curl "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 ls via 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 of flag.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.

  3. Step 3Read the flag file
    Observation
    The 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.
    bash
    curl "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, %20 becomes 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, or banner with 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.

Key takeaway

Command injection appears whenever an application concatenates user input into a string handed to a shell, letting metacharacters, semicolons and pipes and backticks and substitutions, arrive as syntax rather than data. The fix is to not invoke a shell: use an API that takes an argument array, rather than trying to escape input after the fact. Web apps, IoT firmware, network appliances, and CI pipelines all carry this wherever a convenience wrapper meets untrusted input.

Related reading

Tools used in this challenge

Where to go next