Forum Discussion

Forsaken_104807's avatar
Forsaken_104807
Icon for Nimbostratus rankNimbostratus
Aug 06, 2010

X-Forwarded-For Irule

Hi All, Hoping someone can help here... In a nut shell, I am trying to block external access to a site, but allow internal users to work. Our connections come in externally via an application firewall, so I have enabled the X-Forward-For and can see original Ip's in a TCPDump.

 

 

Here is the Irule I am starting with:

 

 

when HTTP_REQUEST { if {[HTTP::header values "X-Forwarded-For"] contains "X.X.X.X" or [HTTP::header values "X-Forwarded-For"] contains "X.X.X.X"}{ HTTP::respond 200 content "Apology Page - External BlockedWe are sorry, but the site you are looking for is temporarily out of service Please try again later ." } }

 

 

I have a default pool setup on this vip.

 

 

So, when I use this irule... I am blocking internal access and external access is open, so I wanted to specifiy if the IP's do not equal XXXX, then display the html page. When I use the not in the irule, it accepts the syntax but both internally and externally I get nothing.... Below is the Irule and the logs from the ltm:

 

 

when HTTP_REQUEST { if {not[HTTP::header values "X-Forwarded-For"] contains "X.X.X.X" or [HTTP::header values "X-Forwarded-For"] contains "X.X.X.X"}{ HTTP::respond 200 content "Apology Page - External BlockedWe are sorry, but the site you are looking for is temporarily out of service Please try again later ." } }

 

 

Aug 6 14:47:05 local/tmm err tmm[2595]: 01220001:3: TCL error: iRule-JCICSS_Test - can't use non-numeric string as operand of "!" while executing "if {not[HTTP::header values "X-Forwarded-For"] contains "X.X.X.X" or [HTTP::header values "X-Forwarded-For"] equals "X.X.X.X"}{ HTTP..."

 

 

Any help would be greatly appreciated

5 Replies

  • To logically negate something you need a binary value. If you don't use parentheses to enforce the precedence. the first thing that is evaluated is logically:

    not [HTTP::header values "X-Forwarded-For"]

    If there was valid output from that evaluation, it would then be compared against the "X.X.X.X" string.

    Here is an example of using parentheses to fix this:

    
    when HTTP_REQUEST {
       if { not ([HTTP::header values "X-Forwarded-For"] contains "X.X.X.X" or [HTTP::header values "X-Forwarded-For"] contains "X.X.X.X")}{
          HTTP::respond 200 content "Apology Page - External BlockedWe are sorry, but the site you are looking for is temporarily out of service Please try again later ." 
       }
    }
    

    Or you could use a switch statement:

    
    when HTTP_REQUEST {
    
       switch -glob [HTTP::header values "X-Forwarded-For"] {
          "*1.1.1.1*" -
          "*2.2.2.2*" {
              Do nothing
          }
          default {
              Send the block page content
             HTTP::respond 200 content "Apology Page - External BlockedWe are sorry, but the site you are looking for is temporarily out of service Please try again later ." 
          }
       }
    }
    

    But if you're depending on an HTTP header for security information, you should ideally have the firewall strip out any existing instances of the header value before inserting its own version. This ensures that a client can't inject its own X-Forwarded-For header in requests to bypass the iRule logic. If the upstream firewall doesn't provide the option to remove existing XFF headers, you might try to use a more unique header name. This would be security through obscurity which is never ideal; but at least it's a bit better than just using XFF without removing prior instances.

    Aaron
  • Thanks Aaron, your first suggestions works a treat.... I can't seem to make it work though defing a whole subnet to be allowed?

     

     

    when HTTP_REQUEST {

     

    if { not ([HTTP::header values "X-Forwarded-For"] contains 10.0.0.0/255.0.0.0 }{

     

    HTTP::respond 200 content "Apology PageWe

     

    are sorry, but the site you are looking for is temporarily out of service

     

    Please

     

    try again later.

    "

     

    }

     

    }

     

  • If you want to do address comparisons using IP::addr, you'd need to parse each XFF IP address and check it individually. This could be a bit expensive in parsing time for every HTTP request. Also keep in mind that it would be trivial for an attacker to insert any XFF header value.

    when HTTP_REQUEST {
    
        Track whether we've found a match yet
       set match 0
    
        Check if there is at least one XFF header with a value
       if {[HTTP::header values "X-Forwarded-For"] ne ""}{
    
          log local0. "XFF: [HTTP::header values "X-Forwarded-For"]"
    
           Remove spaces from the XFF header values and then split them into a list on commas
           Loop through each list item (an IP)
          foreach xff [split [string map [list " " ""] [HTTP::header "X-Forwarded-For"]] ","] {
    
             log local0. "Current XFF element: $xff"
    
              Check if the current XFF IP is in the subnet we want to check
             if {[IP::addr $xff equals 10.0.0.0/8]}{
    
                 Track that we've found a match and exit the loop
                log local0. "Matched IP::addr check"
                set match 1
                break
             }
          }
       }
        Block the request if there wasn't a 10.0.0.0/8 XFF IP in the list
       if {$match == 0}{
          HTTP::respond 200 content {Blocked!}
       }
    }
    

    Aaron
  • Colin_Walker_12's avatar
    Colin_Walker_12
    Historic F5 Account
    Hoolio's 100% right on this one, as is often the case. The XFF is far too easy to spoof to be relied upon for security. Unless you're manually stripping out the XFF somewhere upstream of the LTM this iRule is being fired on and are only concerned about an XFF being inserted inside your trusted network somewhere. That's about the only case you could trust an XFF for security reasons.

     

     

    Colin