Technical Articles
F5 SMEs share good practice.
cancel
Showing results for 
Search instead for 
Did you mean: 
Steve_McCarthy_
Historic F5 Account

HTTP cookies are an essential part of many web based applications, useful for tracking session and state information.  But they can also be exploited to leak information to third party sites using a method known as Cross Site Request Forgery (CSRF).  A CSRF attack takes advantage of the web browser behavior which results in cookies being sent to third party sites when a page contains mixed content.  This results in cross-site information leakage, and depending on the content of the cookies, could provide an attacker with information to hijack a user session.

SameSite Attribute

As of this writing, there is a Internet draft standard for directing clients to only send ‘first party’ cookies. In a nutshell, the standard defines a new, backwards-compatible attribute for the Set-Cookie header named SameSite.  When the SameSite attribute is present, compliant browsers will only send that cookie with requests where the requested resource and the top-level browsing context match the cookie.  This becomes another layer of a “defense in depth” strategy, mitigating CSRF and cross-site script including (XSSI) attacks.  SameSite is supported in recent Chrome and Firefox browsers.

SameSite can be specified alone, or with explicit values “Strict” or “Lax”, corresponding to differing levels of lock-down.  

Specifying SameSite can increase security, but it is not appropriate for all applications.  One example would be “mash-up” applications, those which intentionally pull and embed content from different sites, may require cross-site cookies to function correctly.  Also, some single sign-on features may require cross-context authentication that involves cookies.

So how can you secure your apps?  Big IP provides 3 ways to add SameSite attribute to Set-Cookie headers, two of which are described below: iRules and LTM Policy.  Mentioned in another article, the Application Security module also provides a setting to enable SameSite.

iRule to add SameSite attribute

Here is iRule which can handle multiple Set-Cookie headers in a response.  If a Set-Cookie header already has SameSite attribute present, it is passed through unmodified.  This allows an administrator to set a baseline security level, say by specifying “SameSite=Lax” in an iRule, but allows for individual apps to control their security level by generating headers with their own Set-Cookie header, with say “SameSite=Strict”.

when HTTP_RESPONSE {
    # Set-Cookie header can occur multiple times, treat as list
    set num [HTTP::header count Set-Cookie]
    if {$num > 0} {
        foreach set_cookie [HTTP::header values Set-Cookie] {
            # only modify if header does not have SameSite attribute
            set foundSameSite [string match -nocase "*SameSite*" $set_cookie ]
            if {[expr {!$foundSameSite} ]} {
                set set_cookie [concat $set_cookie "; SameSite"]
            }
            # collect modified and unmodified values in list newcookies
            lappend newcookies $set_cookie
        }

        if {$num == 1} {
            # overwrite existing
            HTTP::header Set-Cookie [lindex $newcookies 0]
        } else {
            # remove and replace
            HTTP::header remove Set-Cookie
            foreach set_cookie $newcookies {
                HTTP::header insert Set-Cookie $set_cookie
            }
        }
        
    }
}

 

LTM Policy

Below is a sample LTM Policy which will tag “; SameSite” to the end of a Set-Cookie header that doesn’t have one already.  One limitation to be aware of is that there can be multiple Set-Cookie headers in an HTTP response, and LTM policy can only replace the last one. 

Here is a screenshot from the GUI showing an LTM Policy rule which 

0151T000003d7AtQAI.png

Here is the resulting policy as it would appear in the /config/bigip.conf configuration file:

ltm policy first-party-cookies {
    requires { http }
    rules {
        r1 {
            actions {
                0 {
                    http-header
                    response
                    replace
                    name Set-Cookie
                    value "tcl:[HTTP::header Set-Cookie]; SameSite"
                }
            }
            conditions {
                0 {
                    http-header
                    response
                    name Set-Cookie
                    not
                    contains
                    values { SameSite }
                }
            }
        }
    }
    status published
    strategy first-match
}

 

 

Comments
DannyG
Nimbostratus
Nimbostratus

Having problems getting the above iRule to work on 11.6. is there something special that requires a newer release? thanks

Chris_Olson
Nimbostratus
Nimbostratus

This is failing for me too. However, in my case, we have a collaboration portal and must ALLOW third party cookies. This is tied to Googles browser change. I've tried modifying the above irule and changed the set cookie line to:

set set_cookie [concat $set_cookie "; SameSite=None"]

However, this resulted in duplicate cookies and the collaboration portal connection failed. Changing the policy rule to:

value "tcl:[HTTP::header Set-Cookie]; SameSite=None"

also failed to work.

There has to be an easy way to modify the cookie value to Samesite=None to allow our Chrome users to continue to use the portal. I am a network admin and not a developer so any assistance is appreciated.

DannyG
Nimbostratus
Nimbostratus

Hi Chris, I did get it working on 11.6 at least. Our use case was to set samesite=none as well. The issue was on this line "  HTTP::header Set-Cookie [lindex $newcookies 0]" . Its started working when I added replace between HTTP:header and Set-Cookie like so "  HTTP::header replace Set-Cookie [lindex $newcookies 0]". Not sure if this is a syntax thing with my version but it cleared out the errors I was seeing. Below is what I am using including the the samesite=none.

 

when HTTP_RESPONSE {

  # Set-Cookie header can occur multiple times, treat as list

  set num [HTTP::header count Set-Cookie]

 

  if {$num > 0} {

    foreach set_cookie [HTTP::header values Set-Cookie] {

      # only modify if header does not have SameSite attribute

      set foundSameSite [string match -nocase "*SameSite*" $set_cookie ]

      if {[expr {!$foundSameSite} ]} {

        set set_cookie [concat $set_cookie "; SameSite=None"]

      }

      # collect modified and unmodified values in list newcookies

      lappend newcookies $set_cookie

    }

    if {$num == 1} {

      # overwrite existing

      HTTP::header replace Set-Cookie [lindex $newcookies 0]

    } else {

      # remove and replace

      HTTP::header remove Set-Cookie

      foreach set_cookie $newcookies {

        HTTP::header insert Set-Cookie $set_cookie

 

      }

    }

  }

}

Chris_Olson
Nimbostratus
Nimbostratus

Hi Danny, just a note to THANK YOU for your suggestion. So far, this looks like it is working in all our environments and it's saved us countless hours working on a server side solution. I owe you a beer (or ten)!

Thanks again!

Chris

Dan_Cutler
Nimbostratus
Nimbostratus

​Hi All,

 

I'm hoping to solve an upcoming change in Chrome where SameSite cookie processing is undergoing a change in a Chrome release in Feb 2020.  As I understand it, I must address this or some of my sites will fail if the user is using the Feb release of Chrome 80.

 

I tried the above iRule but it seems to fail immediately.

Does anyone have any other iRule type suggestions here? 

 

Thanks all!!

Dan

 

Chris_Olson
Nimbostratus
Nimbostratus

The above worked initially for us too but then caused errors with different parts of the application. We just went through about 7 different iterations before we finally got one to work. Below is the one that was successful. To be clear, this is to ALLOW third-party-cookies and changes the same site attribute to None.

 

ltm rule generic_samesite_none {

  when HTTP_RESPONSE {

set cookie_headers [HTTP::header values "Set-Cookie"]

HTTP::header remove "Set-Cookie"

 

foreach set_cookie_header $cookie_headers {

  HTTP::header insert "Set-Cookie" "${set_cookie_header}; SameSite=None"

}

}

}

 

 

 

 

 

mmueller78
Nimbostratus
Nimbostratus

We had a similar experience as Mr. Olson. We had a modified (for our specific needs) version of the original iRule (with the ^'replace' fix added, per the comments) which appeared to be working but was ultimately breaking persistence to the pooled webservers (tomcat).  

 

Additionally, we need to only add SameSite set-cookie header to tomcat (JSESSIONID) and F5 (BIGipServer~) session cookies, when it is missing.

 

Lastly, we have to check against a vendor supplied blacklist of clients/user-agents that do not honor or do not correctly interpret the SameSite attribute. Of note, not all browsers who do honor SameSite interpret it the same way, in the case of multiple SameSite headers, Edge will use the most strict value, while Chrome uses the last occurring value in the set-cookie string. Also of note, a recent Windows patch added SameSite=Lax to IIS requests, so keep that in mind if IIS/ASP is in play.

 

Here is our working iRule for the above scenario:

 

when HTTP_REQUEST {

 

  # assume client request is samesite compatible, set isCompatClient True

  set isCompatClient 1

 

  # check user-agent against vendor provided blacklist of incompatible user-agents

  if { ([string tolower [HTTP::header "User-Agent"]] matches_regex {^.*cpu os 1[0-2].*version\/1[0-2].*safari.*$}) || 

     ([string tolower [HTTP::header "User-Agent"]] matches_regex {^.*cpu iphone os 1[0-2].*version\/1[0-2].*safari.*$}) || 

     ([string tolower [HTTP::header "User-Agent"]] matches_regex {^.*cpu os 12.*like mac os x.*$}) || 

     ([string tolower [HTTP::header "User-Agent"]] matches_regex {^.*cpu iphone os 12.*$}) || 

     ([string tolower [HTTP::header "User-Agent"]] matches_regex {^.*macintosh; intel mac os x 10_1[0-4].*version\/1[0-3].*safari.*$}) || 

     ([string tolower [HTTP::header "User-Agent"]] matches_regex {^.*chrom[^ \/]+\/(5[1-9]|6[0-6])[\.\d]*.*$}) } {

    set isCompatClient 0

  }

}

 

when HTTP_RESPONSE {

 

  # SameSite value: None, Lax, Strict

  set samesiteVal "None"

 

  # variable definitions

  set num_cookie_headers 0

  set new_cookie_headers ""

 

  # only modify cookies if browser is compatible with SameSite, otherwise do nothing

  if { ($isCompatClient) } {

 

    # check if set-cookie header exists. otherwise nothing to do

    if { [HTTP::header exists "Set-Cookie"] } {

 

      # set-cookie header can occur multiple times, get a count and treat them as list

      set num_cookie_headers [HTTP::header count Set-Cookie]

 

      # iterate through each Set-Cookie header and evaluate

      foreach aCookieHeader [HTTP::header values Set-Cookie] {

 

        # only consider session cookies

        if { $aCookieHeader starts_with "JSESSIONID" or $aCookieHeader starts_with "BIGipServer" } {

 

          # only consider cookie values not containing the string "samesite"

          if { !([string tolower $aCookieHeader] contains "samesite") } {

 

            # append samesite to original value

            set aCookieHeader [concat $aCookieHeader "; SameSite=$samesiteVal"]

          }

        }

 

        # collect modified values in list new_cookie_headers

        lappend new_cookie_headers $aCookieHeader

      }

 

      # if there is only one cookie

      if { $num_cookie_headers == 1 } {

 

        # overwrite the single existing Set-Cookie header because there is only one in list

        HTTP::header replace Set-Cookie [lindex $new_cookie_headers 0]

 

      # else there is more than 1 cookie

      } else {

 

        # remove all Set-Cookie headers

        HTTP::header remove Set-Cookie

 

        # iterate through each Set-Cookie header in our new list

        foreach aCookieHeader $new_cookie_headers {

 

          # Re-create a Set-Cookie header that we just removed

          HTTP::header insert Set-Cookie $aCookieHeader

        }

      }

    }

  }

}

 

DannyG
Nimbostratus
Nimbostratus

We were fortunate enough to be able to declare our supported browsers so not having to handle the non-compatible helped out. In the end we found we were also defining a cookie in our javascript(not in header) so our dev had to make a code change for that and also decided to use our apache webserver in front of java to do the cookie insert for JSESSION and other related cookies. I do have a rule to just modify the bigipserver cookie for persistence but that is much more straight forward since it predictable never has the flag..

Dan_Cutler
Nimbostratus
Nimbostratus
I found this which was really helpful for pre-testing: What do publishers need to do in order to get ready for February? Publishers can begin testing whether their sites are affected by going to chrome://flags<> and enabling #same-site-by-default-cookies and #cookies-without-same-site-must-be-secure to see whether anything breaks. They should also migrate to HTTPS secure pages, if they haven’t done so already. lifted from https://digiday.com/media/what-is-chrome-samesite/
Simon_Blakely
F5 Employee
F5 Employee

If you need to modify BigIP-added cookies (persistence cookies, ASM cookies, etc), you need to modify those in the

HTTP_RESPONSE_RELEASE event.

CruelSocket
Nimbostratus
Nimbostratus

I was having a ton of trouble with this until I figured out that irule variables scope is the whole connection, not just the response even if all your code is in `HTTP_RESPONSE`. The sample code appears to keep accumulating cookies in $newcookies over the life the of the connection.

 

Also, why use `[expr {!$foundSameSite} ]`? what's the benefit over just `!$foundSameSite`?

Chris_Olson
Nimbostratus
Nimbostratus

For those of you still struggling, we have a little more time (see link below). However, once released, other browsers are sure to follow suit. I am hoping F5 comes up with a feature similar to httpsonly and secure cookies which can be enabled or disabled in the peristence profile.

 

https://www.chromium.org/updates/same-site

 

hooleylist
Cirrostratus
Cirrostratus

From that page:

 

  • February, 2020: The SameSite-by-default and SameSite=None-requires-Secure behaviors will begin rolling out to Chrome 80 Stable for an initial limited population starting the week of February 17, 2020. We will be closely monitoring and evaluating ecosystem impact from this initial limited phase through gradually increasing rollouts.

 

DannyG
Nimbostratus
Nimbostratus

We seem to be successful modifying the bigipserver persistence cookie just fine in the HTTP_RESPONSE. It seems HTTP_RESPONSE_RELEASE is needed if you are using ASM or some other module that is making modifications.

Hoolio
F5 Employee
F5 Employee

This updated iRule handles more scenarios including User-Agents which cannot handle SameSite=None:

 

https://devcentral.f5.com/s/articles/iRule-to-set-SameSite-for-compatible-clients-and-remove-it-for-...

Adrian_Pina
Nimbostratus
Nimbostratus

Hi  Chris_Olson,

If you only need to insert the SameSite attribute with the None value to every cookie, I think that the follwing iRule is simpler and could help you:

 

when HTTP_RESPONSE_RELEASE {
    foreach mycookie [HTTP::cookie names] {
    ​​    HTTP::cookie attribute $mycookie insert "SameSite" "None"
   }
}
 
Kind regards.
Version history
Last update:
‎30-Mar-2018 13:27
Updated by:
Contributors