Tags: bash pwn 

Rating: 5.0

echoechoechoecho, 216p, 18 solves

Echo echo echo echo, good luck

nc 35.246.181.187 1337

In this task we are able to input commands to be executed on server. The catch is there is a strict whitelist of allowed characters. The relevant code is:

    if re.search(r'[^();+$\\= \']', payload.replace("echo", "")):
        bye("ERROR invalid characters")

    # real echolords probably wont need more special characters than this
    if payload.count("+") > 1 or \
            payload.count("'") > 1 or \
            payload.count(")") > 1 or \
            payload.count("(") > 1 or \
            payload.count("=") > 2 or \
            payload.count(";") > 3 or \
            payload.count(" ") > 30:
        bye("ERROR Too many special chars.")

There is also an interesting snippet near the bottom:

payload += "|bash"*count

In other words, we can execute any string consisting of rather small number of ^();+$\= ' characters and echo strings and pipe it to bash up to ten times.

The most important thing to do is to increase number of limited characters (like brackets), since it's clearly impossible to encode useful commands in so little space. Let's say we want to have more than one ( character. We can write:

echo=\(; echo $echo$echo$echo$echo

Which will print four left brackets after one bash pipe. We can encode the other limited characters in a similar fashion (remembering to backslash-escape the inner string; thankfully backslashes are unlimited). Now that we can do that, we should think of a way to encode arbitrary characters. It turns out there is a $'abcd' syntax in bash, enabling ANSI-C quoting. Of the features, the one useful for us are escape sequences: $'\154\163' will expand to ls after passing through a layer of bash. We have all the characters needed except digits. We can make digits using bash arithmetics though: $(($$==$$)) is 1 and we can add up to nine ones to get any digit.

We wrote an automatic encoder of arbitrary commands, consisting of eight layers of encoding. We were able to execute simple commands, like ls -al and found unreadable flag file and executable getflag binary. It asked us to implement an arithmetic captcha, which turned out to be the same as here. We could copy the solver snippet from there (we had to use temporary files to remember captcha while the binary was still running, so it was not trivial).

For some reason the server seemed to break after receiving too much data (though it was pretty hard to pinpoint the exact spot). The command admittedly grew rather large, owing mostly to backslash exponential escaping. We optimized the encoding a bit (such as remembering single 1 in another $echo variable in digit encoding stage) and eventually got the full command to pass.

For fun, here's the first 1000 characters (of over 100kB) of final payload:

echo=\=; echo echo$echo\\\;\; echo echo$echo\\\\+\$echo echo echo$echo\\\\\\\\\\\\\\\)\\\\\$echo echo echo$echo\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\(\\\\\\\\\\\\\$echo echo echo$echo\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\'\\\\\\\\\\\\\\\\\\\\\\\\\\\\\$echo echo echo$echo\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\$\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\$echo\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\$echo\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\$\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\$$echo$echo\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\$\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\$\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\$echo\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\$echo\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\$echo echo echo \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\

The final encoder code is in encode.py file.

Original writeup (https://github.com/p4-team/ctf/blob/master/2019-01-19-insomnihack-quals/echoechoechoecho/README.md).