
La inyección de plantillas en las plantillas de correo electrónico permite la ejecución de código en Jira Service Management Server
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
He explicado cómo desplegar y depurar aquí, pueden consultarlo.
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.
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:

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 =)))
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-11581En 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:
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:
Vemos que se añadieron 3 clases a la lista negra:
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í:
#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:
@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:
#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:
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:
Cuando investigué estas 3 clases, vi que podían formar una cadena:
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???