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-2021-39115 — La inyección de plantillas en las plantillas de correo electrónico permite la ejecución de código en Jira Service Management Server | Kitploit
Herramientas/GitHubGitHub/petrusviet/cve-2021-39115
Análisis de VulnerabilidadesAnálisis de CódigoExplotaciónExplotación de Aplicaciones WebPruebas de PenetraciónAprendizaje y Educación
GitHubpetrusviet/cve-2021-39115

CVE-2021-39115

La inyección de plantillas en las plantillas de correo electrónico permite la ejecución de código en Jira Service Management Server

Ver Repositorio
48121hace 5 añosRevisado por Kitploit

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-2021-39115

La inyección de plantillas en las plantillas de correo electrónico conduce a la ejecución de código en Jira Service Management Server

I) Construcción

He explicado cómo desplegar y depurar aquí, pueden consultarlo.

II) Análisis

En la Descripción de este CVE ya se indica claramente que el bug está en la funcionalidad Email Template. Con permisos de administrador, el usuario puede modificar libremente las plantillas de los correos de notificación. Este bug requiere permisos de administrador para ejecutarse, por lo que, aunque haya RCE, no es muy grave; aun así, decidí escribir el blog por diversión :) los que estén investigando SSTI pueden consultarlo.

  • Para ponernos manos a la obra, desplegué la versión atlassian-jira-servicedesk-4.17.0-m0006-standalone y probé a hacer un diff para ver qué diferencias hay con la versión 4.18.0: image

Al ver este montón... por supuesto no me puse a leer archivo por archivo; soy muy perezoso. Es broma; en realidad, aparte de incluir el parche de seguridad, las versiones también tienen mejoras y cambios en las funcionalidades. Hacer un diff de todo y sentarme a leerlo así consume mucho tiempo. Por supuesto, en algunos casos tengo que aguantar y hacer el diff completo para leerlo, pero en este caso no lo hice =)))

  • Como se mencionó antes, el admin tiene permiso para cambiar la plantilla de todos los correos de notificación. Por eso, el primer paso es saber qué contextos hay durante el proceso de parseo de los correos de notificación; esto es especialmente importante cuando se quiere explotar el bug de SSTI (que usa sandbox). La forma más eficaz es depurar saltando a la parte que parsea la plantilla del correo para ver el valor de la variable de contexto.
  • Por supuesto, hay muchos tipos de notificaciones distintas (SignUp, change Password,...), cada una usa un endpoint diferente. Usé la funcionalidad SendBulkMail para crear el correo de notificación. En cuanto a cómo es la pila de llamadas de este endpoint, prefiero no mencionarla para no alargarme, porque es similar a la pila de llamadas de CVE-2019-11581

En la función SimpleNote.render puedo ver la variable de contexto para ver qué puedo aprovechar:

Por mi propia experiencia, suelo fijarme en revisar los contextos/clases que contienen palabras clave como: Utils, Manager, service,... . Me llamó la atención ver que el contexto $jirautils (clase com.atlassian.jira.util.JiraUtils) contiene el método public static <T> T loadComponent(String className, Class<?> callingClass) :

Anduve buscando documentación para ver su función y cómo se usa, porque la entrada recibe un String className y la salida es una clase, así que era muy probable que este método cargara una clase de forma arbitraria:

  • Como dicen los docs, esta función sirve para cargar clases :) Me puse a trabajar de inmediato para ver si ese método realmente cargaba una clase arbitraria.

Entré en System -> Email templates y descargué el archivo de plantilla actual a mi máquina

Hay muchísimas plantillas distintas, cada una se usa para un tipo de notificación diferente, así que busqué un archivo que pudiera usarse de forma común para varios tipos de notificaciones, como email\html\includes\header.vm

Después de subir el archivo de plantilla modificado y usar SendBulkMail para enviar un correo de notificación, recibí una salida en el correo como esta:

Básicamente, esa clase no tiene constructores o no son aceptados. Probé con otra clase que sí tiene constructores (constructores sin parámetros) a ver qué pasaba:

Y el resultado fue el esperado. Obtuve la clase con éxito:

  • Así que ya podemos cargar clases casi arbitrarias, lo cual está bastante bien. Pero, ¿cómo se puede lograr el RCE? Jira usa plantillas Velocity y tiene un sandbox bastante completo, con listas negras de paquetes y listas negras de clases (con la lista negra de clases puede bloquear todas las clases que se extienden desde las clases de la lista negra). Usar clases publicadas en Internet es casi imposible. Llegado a este punto, me vi obligado a hacer un diff entre la versión vulnerable y la versión parcheada para ver en qué se diferencian; por supuesto, no haré el diff completo, solo el archivo de configuración de Velocity (\atlassian-jira\WEB-INF\classes\velocity.properties) para ver si hay algo nuevo actualizado:

Vemos que se añadieron 3 clases a la lista negra:

root@kitploit:~
org.springframework.expression.spel.standard.SpelExpressionParser,\
com.atlassian.jira.component.ComponentAccessor,\
com.atlassian.jira.plugin.ComponentClassManager

Al revisar cada clase, noté que en la clase SpelExpressionParser hay una función public SpelExpression parseRaw(String expressionString). Tras buscar un rato en Google cómo se usa, más o menos se puede usar así:

root@kitploit:~
#set($SpelExpressionParser = $jirautils.loadComponent('org.springframework.expression.spel.standard.SpelExpressionParser',$i18n.getClass()))
$SpelExpressionParser.parseRaw("T(java.lang.Runtime).getRuntime().exec('calc')")

Como pueden ver, la función parseRaw devuelve un objeto que es una clase org.springframework.expression.spel.standard.SpelExpression, pero aún no renderiza realmente la expresión que introduje. Entré en la clase SpelExpression y vi que tiene el método getValue:

root@kitploit:~
@Nullable
    public Object getValue() throws EvaluationException {
        CompiledExpression compiledAst = this.compiledAst;
        if (compiledAst != null) {
            try {
                EvaluationContext context = this.getEvaluationContext();
                return compiledAst.getValue(context.getRootObject().getValue(), context);
            } catch (Throwable var4) {
                if (this.configuration.getCompilerMode() != SpelCompilerMode.MIXED) {
                    throw new SpelEvaluationException(var4, SpelMessage.EXCEPTION_RUNNING_COMPILED_EXPRESSION, new Object[0]);
                }
            }

            this.compiledAst = null;
            this.interpretedCount.set(0);
        }

        ExpressionState expressionState = new ExpressionState(this.getEvaluationContext(), this.configuration);
        Object result = this.ast.getValue(expressionState);
        this.checkCompile(expressionState);
        return result;
    }

Viendo la entrada/salida, alguien de los que se guían por la intuición, como yo, decidió probarlo directamente en lugar de seguir mirando la documentación:

root@kitploit:~
#set($SpelExpressionParser = $jirautils.loadComponent('org.springframework.expression.spel.standard.SpelExpressionParser',$i18n.getClass()))
$SpelExpressionParser.parseRaw("T(java.lang.Runtime).getRuntime().exec('calc')").getValue()

y el resultado:

III) Conclusión

Así pues, ya podemos evadir el sandbox de Velocity para lograr RCE; sin embargo, al ponerme en el lugar del autor (la persona que encontró este bug), todavía hay muchas preguntas que no puedo explicar:

  • ¿Por qué se añadieron hasta 3 clases a la lista negra?

Cuando investigué estas 3 clases, vi que podían formar una cadena:

root@kitploit:~
ComponentAccessor.getComponentClassManager() 
  => ComponentClassManager.newInstance(String className) (Class này có thể load class tùy ý)
        => SpelExpressionParser

Si el autor usara $jirautils como yo, no necesitaría esas otras 2 clases para nada. Pero si el autor no usó $jirautils, ¿cómo obtuvo ComponentAccessor???

  • ¿Cómo pudo el autor encontrar esas 3 clases? Esta es la pregunta que más me intriga y la que más me gustaría saber, pero quizá la única forma de saberlo sea contactar directamente con el autor. Lástima que no sé quién es el autor de este bug.

IV) Continuando con el bypass del parche

Descargar herramienta