alta

CVE

CVE-2026-13502

CWE

CWE-362, CWE-502

Sistemas afectados

  • org.antlr:antlr4-maven-plugin 4.13.0 through 4.13.2
  • Maven builds that execute antlr4:antlr4 and persist build state in target/maven-status/antlr4/dependencies.ser
  • CI/CD runners with shared workspaces, poisoned caches, or other attacker-controlled writes into the Maven build directory
  • Developer workstations that build mixed-trust repositories using the ANTLR4 Maven plugin

CVE-2026-13502 llegó a NVD el 28 de junio de 2026 como un fallo en org.antlr:antlr4-maven-plugin, el plugin de Maven que convierte gramáticas *.g4 en código de parser generado durante antlr4:antlr4. El texto de NVD describe el error como un problema time-of-check/time-of-use en GrammarDependencies.java, pero el detalle técnico más importante en la divulgación pública es que el plugin deserializa datos de estado de compilación con ObjectInputStream.readObject() y no aplica ningún ObjectInputFilter.

Esa distinción importa para los equipos de AppSec. Una etiqueta escueta de TOCTOU puede sonar como un problema de estabilidad de caso extremo. En la práctica, la ruta vulnerable se encuentra en un plugin de compilación, lee estado escribible por el atacante desde el directorio de build y se ejecuta dentro de estaciones de trabajo de desarrolladores y runners de CI que a menudo contienen credenciales de registro de paquetes, tokens de control de código fuente, claves de nube, material de firma y permisos de publicación de artefactos.

Paquete y versiones afectadas

El paquete de Maven afectado es:

  • org.antlr:antlr4-maven-plugin 4.13.0
  • org.antlr:antlr4-maven-plugin 4.13.1
  • org.antlr:antlr4-maven-plugin 4.13.2

Al momento de escribir esto, las referencias públicas describen el problema y la PoC, pero aún no señalan una versión de corrección publicada en upstream. Trata toda compilación que use esas versiones como expuesta si un atacante puede escribir en el directorio de compilación o envenenar el estado de compilación en caché.

Empieza con el inventario de dependencias:

rg -n '<artifactId>antlr4-maven-plugin</artifactId>' . --glob 'pom.xml'

Luego confirma la versión resuelta en CI o en un proyecto ya clonado:

mvn -q help:effective-pom | rg -n 'antlr4-maven-plugin|<version>'

Dónde vive el estado vulnerable

El plugin persiste metadatos de dependencias bajo el directorio de compilación de Maven. Antlr4Mojo crea el archivo de estado aquí por defecto:

@Parameter(defaultValue = "${project.build.directory}/maven-status/antlr4", readonly=true)
private File statusDirectory;

private File getDependenciesStatusFile() {
    File statusFile = new File(statusDirectory, "dependencies.ser");
    ...
    return statusFile;
}

En un proyecto Maven normal, eso se resuelve en:

target/maven-status/antlr4/dependencies.ser

Eso es importante porque muchas organizaciones ya hacen que target/, las cachés de espacio de trabajo o los artefactos previos a la compilación sean escribibles por múltiples jobs, pasos previos, generadores de código o dependencias previamente comprometidas.

La ruta de código vulnerable

El sumidero es directo:

private Map<File, Map.Entry<byte[], Collection<String>>> loadStatus(File statusFile) {
    if (statusFile.exists()) {
        try {
            ObjectInputStream in = new ObjectInputStream(new FileInputStream(statusFile));
            try {
                Map<File, Map.Entry<byte[], Collection<String>>> data =
                    (Map<File, Map.Entry<byte[], Collection<String>>>) in.readObject();
                return data;
            } finally {
                in.close();
            }
        } catch (Exception ex) {
            log.warn("Could not load grammar dependency status information", ex);
        }
    }
    return new HashMap<File, Map.Entry<byte[], Collection<String>>>();
}

Dos problemas de seguridad separados son visibles en ese fragmento:

  1. statusFile.exists() verifica el archivo antes de abrirlo, creando una ventana de carrera reemplazable.
  2. El contenido del archivo se deserializa con ObjectInputStream.readObject() sin ningún filtro de clases.

El issue público tiene razón al señalar la condición de carrera, pero el sumidero de deserialización es la parte que convierte el estado de compilación envenenado en un candidato práctico de ejecución de código.

Por qué el detalle de la deserialización importa más que la etiqueta

Si el archivo de estado se volviera a analizar simplemente como texto, la carrera trataría principalmente sobre intercambiar metadatos de dependencias. Pero la serialización de Java es diferente: readObject() es efectivamente un motor de instanciación de clases para cualquier grafo de objetos serializable que el flujo solicite, a menos que el llamador lo restrinja.

El artículo publicado demuestra la propiedad central que los defensores deben tener en cuenta: el plugin acepta tipos de objetos serializados arbitrarios e intenta cargar las clases especificadas por el atacante. En Java 9 y posteriores, ObjectInputFilter existe específicamente para acotar este límite. El plugin de ANTLR no lo usa aquí.

Eso convierte el modelo de explotación en:

el atacante obtiene acceso de escritura al directorio de compilación o a una caché de CI compartida
  -> reemplaza target/maven-status/antlr4/dependencies.ser
  -> un desarrollador o job de CI ejecuta mvn generate-sources / antlr4:antlr4
  -> GrammarDependencies.loadStatus() llama a ObjectInputStream.readObject()
  -> el grafo de objetos controlado por el atacante se carga dentro del proceso de Maven

Incluso si un entorno específico carece de una cadena de gadgets conveniente, el límite sigue siendo incorrecto: un plugin de compilación está tratando bytes de estado de build controlados por el atacante como objetos Java confiables.

Rutas de ataque realistas

El fallo es local al entorno de compilación, pero eso no lo convierte en algo de bajo impacto. Las rutas más realistas son todas relevantes para AppSec:

  • Un paso previo a la compilación comprometido escribe dependencies.ser en el espacio de trabajo antes de que se ejecute Maven.
  • Una dependencia o plugin malicioso con ejecución de código previa en el mismo job de CI envenena el archivo de estado de ANTLR para una etapa posterior.
  • Un runner autoalojado compartido reutiliza un espacio de trabajo o caché escribible entre límites de confianza.
  • Un desarrollador clona un repositorio que ya contiene un árbol target/ envenenado y luego ejecuta una compilación Maven normal.

Este es exactamente el tipo de fallo “no remoto, pero relevante para la cadena de suministro” que importa en los runners de CI. Los sistemas de compilación son de alto valor porque cualquier ejecución de código allí puede contaminar artefactos, robar claves de firma o exfiltrar credenciales de despliegue.

Detección y alcance

Encuentra proyectos que usen el plugin:

rg -n '<artifactId>antlr4-maven-plugin</artifactId>' . --glob 'pom.xml'

Encuentra estado de compilación persistido sospechoso:

rg -n 'dependencies\\.ser' . --glob 'target/**'

Revisa el diseño de tu CI en busca de problemas de confianza entre jobs:

rg -n 'cache|workspace|artifact' .github .gitlab-ci.yml .circleci .buildkite scripts

Prioriza los entornos en los que todo esto sea cierto:

  • antlr4-maven-plugin 4.13.0-4.13.2 está presente
  • las compilaciones reutilizan target/ o estado del espacio de trabajo
  • código no confiable puede ejecutarse antes del objetivo de ANTLR
  • el proceso Maven contiene secretos o puede publicar artefactos

Si sospechas manipulación, revisa los registros de CI en busca de fallos o advertencias inesperadas alrededor de “grammar dependency status information” y conserva el archivo dependencies.ser afectado para análisis forense antes de eliminar el espacio de trabajo.

Remediación

No hay una versión corregida de ANTLR publicada referenciada en los materiales públicos todavía, así que la remediación consiste en reducir el límite de confianza de inmediato:

  1. Deja de reutilizar estado de target/ o del espacio de trabajo no confiable entre compilaciones.
  2. Elimina target/maven-status/antlr4/dependencies.ser antes de que se ejecute el objetivo de ANTLR.
  3. Asegúrate de que los runners autoalojados no comparten espacios de trabajo escribibles entre repositorios o niveles de confianza.
  4. Trata cualquier compilación que pueda haber deserializado estado controlado por el atacante como un incidente de ejecución de código.

Pasos prácticos de refuerzo:

rm -f target/maven-status/antlr4/dependencies.ser
mvn clean generate-sources

En CI, prefiere espacios de trabajo nuevos en lugar de reutilizar cachés mutables para el estado generado por ANTLR. Si debes conservar cachés de compilación, delimítalas por rama o repositorio y no permitas que jobs controlados por el atacante rellenen cachés consumidas por jobs de release privilegiados.

Cómo debería ser una corrección en upstream

Hay dos correcciones distintas que los mantenedores deberían hacer:

Primero, eliminar la condición de carrera y tratar la ausencia del archivo como una ruta de error normal en lugar de llamar primero a exists():

try (ObjectInputStream in = new ObjectInputStream(new FileInputStream(statusFile))) {
    ...
} catch (FileNotFoundException e) {
    ...
}

Segundo, dejar de confiar en la serialización nativa de Java para metadatos de estado de compilación. La opción más segura es sustituirla por un formato solo de datos como JSON, CBOR u otra codificación restringida por esquema. Si se mantiene la serialización, aplicar una lista blanca estricta con ObjectInputFilter para que el flujo no pueda solicitar clases arbitrarias.

Guía de respuesta

Si un dependencies.ser potencialmente controlado por el atacante llegó a una compilación usando una versión afectada del plugin, trata el proceso Maven como expuesto:

  • rota los tokens de repositorio y de publicación de paquetes disponibles para el job
  • invalida los artefactos producidos por la compilación afectada
  • inspecciona los registros de compilación en busca de actividad de red externa inesperada
  • revisa los parsers generados y los artefactos de release posteriores en busca de manipulación

La lección más profunda es que los metadatos de compilación son datos adyacentes al código. En cuanto un plugin de Maven los lee con readObject(), tu caché de compilación pasa a formar parte de tu límite de seguridad de aplicaciones.

De la investigación a la remediación

Comprueba si este patrón existe en tu código

Convierte esta investigación en un flujo de remediación. Analiza dependencias y manifiestos para detectar riesgos similares en la cadena de suministro y prioriza las correcciones con contexto de alcanzabilidad.

Referencias