Forum Discussion
How to properly insert HttpOnly and Secure cookie directives?
My load balancer has an iRule that adds the HttpOnly and Secure cookie directives. The rules is adding the directives multiple times, and in the incorrect places. How can I get the directives added correctly?
The rule is:
when HTTP_RESPONSE {
log local0. "from response uri: $uri"
set uri [URI::query [HTTP::uri]]
foreach cookie [HTTP::cookie names] {
if { $uri starts_with "/sputnik" or $uri starts_with "/en-us" } {
}
else {
set value [HTTP::cookie value $cookie];
if { "" != $value } {
set testvalue [string tolower $value]
set valuelen [string length $value]
log local0. "Cookie found: $cookie = $value";
switch -glob $testvalue {
"*;secure*" -
"*; secure*" { }
default { set value "$value; Secure"; }
}
switch -glob $testvalue {
"*;httponly*" -
"*; httponly*" { }
default { set value "$value; HttpOnly"; }
}
if { [string length $value] > $valuelen} {
log local0. "Replacing cookie $cookie with $value"
HTTP::cookie value $cookie "${value}"
}
}
}
}
}
}
`
Cookies from the host look like:
`Set-Cookie: sso.auth_token=deleted; Expires=Thu, 01-Jan-1970 00:00:10 GMT; Path=/
`
Cookies through the load balancer look like:
`Set-Cookie: sso.auth_token=deleted; Secure; HttpOnly; Expires=Thu,; Secure; HttpOnly 01-Jan-1970 00:00:10; Secure; HttpOnly GMT; Path=/
`
I _expect_ cookies through the load balancer to look like:
`Set-Cookie: sso.auth_token=deleted; Expires=Thu, 01-Jan-1970 00:00:10 GMT; Path=/; Secure; HttpOnly
Is there something in the iRule that could make it add the directives multiple times, an in the incorrect place?
I am not very familiar with F5 load balancers. If the shown iRule isn't causing the issue, where else would you recommend I look?
Addition: The iRules are running on LTM v11.2
23 Replies
- nitass_89166
Noctilucent
can you try to change "Expires" to "expires"? you may use irule similar to the following to change it.
e.g.
root@ve10(Active)(tmos) list ltm virtual bar ltm virtual bar { destination 172.28.19.252:http ip-protocol tcp mask 255.255.255.255 pool foo profiles { http { } tcp { } } rules { myrule } snat automap } root@ve10(Active)(tmos) list ltm pool foo ltm pool foo { members { 200.200.200.101:http { } } } root@ve10(Active)(tmos) list ltm rule myrule ltm rule myrule { when HTTP_REQUEST { set uri [string tolower [HTTP::path]] } when HTTP_RESPONSE priority 100 { set setckval [HTTP::header values "Set-Cookie"] HTTP::header remove "Set-Cookie" foreach asetckval $setckval { HTTP::header insert "Set-Cookie" [string map {Expires expires} $asetckval] } } when HTTP_RESPONSE { if { $uri starts_with "/sputnik" or $uri starts_with "/en-us" } { } else { foreach cookie [HTTP::cookie names] { set value [HTTP::cookie value $cookie]; if { "" != $value } { set testvalue [string tolower $value] set valuelen [string length $value] switch -glob $testvalue { "*;secure*" - "*; secure*" { } default { set value "$value; Secure"; } } switch -glob $testvalue { "*;httponly*" - "*; httponly*" { } default { set value "$value; HttpOnly"; } } if { [string length $value] > $valuelen} { HTTP::cookie value $cookie "${value}" } } } } } } to server directly [root@ve10:Active] config curl -I http://200.200.200.101 HTTP/1.1 200 OK Date: Mon, 26 Aug 2013 02:09:30 GMT Server: Apache/2.2.3 (CentOS) Last-Modified: Thu, 23 May 2013 00:28:46 GMT ETag: "4185a8-59-c3efab80" Accept-Ranges: bytes Content-Length: 89 Set-Cookie: sso.auth_token=deleted; Expires=Thu, 01-Jan-1970 00:00:10 GMT; Path=/ Set-Cookie: anothercookie=something; Expires=Thu, 01-Jan-1970 00:00:10 GMT; Path=/ Content-Type: text/html; charset=UTF-8 via virtual server [root@ve10:Active] config curl -I http://172.28.19.252 HTTP/1.1 200 OK Date: Mon, 26 Aug 2013 02:09:36 GMT Server: Apache/2.2.3 (CentOS) Last-Modified: Thu, 23 May 2013 00:28:46 GMT ETag: "4185a8-59-c3efab80" Accept-Ranges: bytes Content-Length: 89 Content-Type: text/html; charset=UTF-8 Set-Cookie: sso.auth_token=deleted; Secure; HttpOnly; expires=Thu, 01-Jan-1970 00:00:10 GMT; Path=/ Set-Cookie: anothercookie=something; Secure; HttpOnly; expires=Thu, 01-Jan-1970 00:00:10 GMT; Path=/- matt_12671
Nimbostratus
I don't have control of the Expires directive. Also, RFCs referring the the directive have it in upper case. Why would the case make a difference with the rule that is being used? - nitass_89166
Noctilucent
i understand it is a bug. Bug 343455 - cookie attribute names should be case-insensitive by the way, doesn't the replacing (from Expires to expires) irule work? - matt_12671
Nimbostratus
Ah, I mis-understood what your iRule was doing. After further debugging, I think the issue is with the [HTTP::header values "Set-Cookie"] and [HTTP::cookie names] commands.
- nitass
Employee
can you try to change "Expires" to "expires"? you may use irule similar to the following to change it.
e.g.
root@ve10(Active)(tmos) list ltm virtual bar ltm virtual bar { destination 172.28.19.252:http ip-protocol tcp mask 255.255.255.255 pool foo profiles { http { } tcp { } } rules { myrule } snat automap } root@ve10(Active)(tmos) list ltm pool foo ltm pool foo { members { 200.200.200.101:http { } } } root@ve10(Active)(tmos) list ltm rule myrule ltm rule myrule { when HTTP_REQUEST { set uri [string tolower [HTTP::path]] } when HTTP_RESPONSE priority 100 { set setckval [HTTP::header values "Set-Cookie"] HTTP::header remove "Set-Cookie" foreach asetckval $setckval { HTTP::header insert "Set-Cookie" [string map {Expires expires} $asetckval] } } when HTTP_RESPONSE { if { $uri starts_with "/sputnik" or $uri starts_with "/en-us" } { } else { foreach cookie [HTTP::cookie names] { set value [HTTP::cookie value $cookie]; if { "" != $value } { set testvalue [string tolower $value] set valuelen [string length $value] switch -glob $testvalue { "*;secure*" - "*; secure*" { } default { set value "$value; Secure"; } } switch -glob $testvalue { "*;httponly*" - "*; httponly*" { } default { set value "$value; HttpOnly"; } } if { [string length $value] > $valuelen} { HTTP::cookie value $cookie "${value}" } } } } } } to server directly [root@ve10:Active] config curl -I http://200.200.200.101 HTTP/1.1 200 OK Date: Mon, 26 Aug 2013 02:09:30 GMT Server: Apache/2.2.3 (CentOS) Last-Modified: Thu, 23 May 2013 00:28:46 GMT ETag: "4185a8-59-c3efab80" Accept-Ranges: bytes Content-Length: 89 Set-Cookie: sso.auth_token=deleted; Expires=Thu, 01-Jan-1970 00:00:10 GMT; Path=/ Set-Cookie: anothercookie=something; Expires=Thu, 01-Jan-1970 00:00:10 GMT; Path=/ Content-Type: text/html; charset=UTF-8 via virtual server [root@ve10:Active] config curl -I http://172.28.19.252 HTTP/1.1 200 OK Date: Mon, 26 Aug 2013 02:09:36 GMT Server: Apache/2.2.3 (CentOS) Last-Modified: Thu, 23 May 2013 00:28:46 GMT ETag: "4185a8-59-c3efab80" Accept-Ranges: bytes Content-Length: 89 Content-Type: text/html; charset=UTF-8 Set-Cookie: sso.auth_token=deleted; Secure; HttpOnly; expires=Thu, 01-Jan-1970 00:00:10 GMT; Path=/ Set-Cookie: anothercookie=something; Secure; HttpOnly; expires=Thu, 01-Jan-1970 00:00:10 GMT; Path=/- matt_12671
Nimbostratus
I don't have control of the Expires directive. Also, RFCs referring the the directive have it in upper case. Why would the case make a difference with the rule that is being used? - nitass
Employee
i understand it is a bug. Bug 343455 - cookie attribute names should be case-insensitive by the way, doesn't the replacing (from Expires to expires) irule work? - matt_12671
Nimbostratus
Ah, I mis-understood what your iRule was doing. After further debugging, I think the issue is with the [HTTP::header values "Set-Cookie"] and [HTTP::cookie names] commands.
- nitass
Employee
by the way, you know there are HTTP::cookie secure and HTTP::cookie httponly (in 11.x), don't you?
HTTP::cookie wiki
https://devcentral.f5.com/wiki/iRules.http__cookie.ashx
- matt_12671
Nimbostratus
I will investigate using those instead. I still want to understand why the headers are getting mangled with the rule that is currently being used.
- nitass_89166
Noctilucent
by the way, you know there are HTTP::cookie secure and HTTP::cookie httponly (in 11.x), don't you?
HTTP::cookie wiki
https://devcentral.f5.com/wiki/iRules.http__cookie.ashx
- matt_12671
Nimbostratus
I will investigate using those instead. I still want to understand why the headers are getting mangled with the rule that is currently being used.
- Kevin_Stewart
Employee
Depending on your application and LTM version, sometimes this is the easiest thing to do:
when HTTP_RESPONSE { foreach aCookie [HTTP::cookie names] { HTTP::cookie secure $aCookie enable HTTP::cookie httponly $aCookie enable } }I have seen problems with this though when the server sends an incompatible cookie version. Here then is a more brute-force way of adding the secure and httponly options to response Set-Cookie headers:
when SERVER_CONNECTED { TCP::collect } when SERVER_DATA { set indices [regexp -all -inline -indices {Set-Cookie: [^\r]+} [TCP::payload]] set cookielist [list] foreach idx $indices { lappend cookielist [string range [TCP::payload] [lindex $idx 0] [lindex $idx 1]] } foreach x $cookielist { if { not ( [string tolower $x] contains "secure" ) and not ( [string tolower $x] contains "httponly" ) } { if { [regsub $x [TCP::payload] [string map {"path=" "secure; httponly; path="} $x] newdata] } { TCP::payload replace 0 [TCP::payload length]] "" TCP::payload replace 0 0 $newdata } } elseif { not ( [string tolower $x] contains "secure" ) } { if { [regsub $x [TCP::payload] [string map {"path=" "secure; path="} $x] newdata] } { TCP::payload replace 0 [TCP::payload length]] "" TCP::payload replace 0 0 $newdata } } elseif { not ( [string tolower $x] contains "httponly" ) } { if { [regsub $x [TCP::payload] [string map {"path=" "httponly; path="} $x] newdata] } { TCP::payload replace 0 [TCP::payload length]] "" TCP::payload replace 0 0 $newdata } } } TCP::collect TCP::release }It grabs all of the Set-Cookie headers into a list and then parses them individually to find existing secure and httponly options, replacing the payload if the options don't exist.
- matt_12671
Nimbostratus
The F5 (running LTM 11.2) does not separate HTTP headers correctly, which means it also can't successfully separate HTTP Set-Cookie headers.
Given a header:
Set-Cookie: sso.auth_token=deleted; Expires=Thu, 01-Jan-1970 00:00:10 GMT; Path=/The LB appears to do something funny with the semicolons and/or equals signs. It thinks "Expires" and "01-Jan-1970" are also cookie names using the [HTTP::cookie names] iRule command.
Using the [HTTP::header "Set-Cookie"] command doesn't do any better. The LB also thinks strings that are not cookie headers (e.g. "Expires") actually are.
I haven't looked for an existing bug for this, but may do so in the future. If I don't find one, I'll open one.
- matt_12671
Nimbostratus
A co-worker and I figured out something that works for us:
set unsafe_cookie_headers [HTTP::header values "Set-Cookie"] HTTP::header remove "Set-Cookie" foreach set_cookie_header $unsafe_cookie_headers { HTTP::header insert "Set-Cookie" "${set_cookie_header}; Secure; HttpOnly" }- matt_12671
Nimbostratus
Evidently, I can't pick my own answer as the answer. If I could, I would pick this. - BinaryCanary_19Historic F5 AccountBe careful about setting the secure attribute if the connection is not HTTPS: http://en.wikipedia.org/wiki/HTTP_cookieCookie_attributes
- nitass
Employee
HTTP::header insert "Set-Cookie" "${set_cookie_header}; Secure; HttpOnly"shouldn't you check whether secure or httponly exists before inserting?
- DanW_88524
Nimbostratus
There are countless posts on this topic on devcentral. I'm sure that most of the proposed solutions worked fine for the contributors specific situations. We've been using one of the earliest solutions (the one that uses switch -glob to set Secure and HttpOnly attributes), since 2009. It satisfied our security department's requirements and served it's purpose, but it always bothered me that if either attribute was already set, we ended up with cookies that contained duplicate HttpOnly and/or Secure attributes.
During our migration from version 9.4.8 to new hardware running 11.4.1, I discovered that this problem still existed. F5 support referenced bug 343455 and pointed me to this forum for a solution. They recommended that we change our application to set cookies with a lower-case expires attribute, or change it to lower case using an iRule. I have no idea how long the bug has been open, or when a fix will be available. It bothers me a bit that support pointed me back to the first place that I looked to for a solution.
At any rate, thank you all for your suggestions. I'm hoping that support has a clear handle on the problem, but I don't know if that's the case. I haven't seen an official SOL document or clear definition of the problem and suggested work around. After considerable testing, I came to the following conclusions:
1) HTTP::cookie parsing is flawed at best. It works fine in some circumstances, but the presence of an unrecognized cookie attribute ( like Expires in v11.4.1, or HttpOnly in 9.4.8) causes HTTP:cookie names to return an invalid list. In our case, a java application was setting the following cookie: PERSID=""; Expires=Thu, 01-Jan-1970 00:00:10 GMT;
HTTP::cookie functions reported that we had 3 cookies named PERSID, 01-Jan-1970 and GMT. This might have something to do with the value of the Expires attribute containing embedded spaces and not being enclosed in quotes (which the specification doesn't require).
2) Even the solution that we've been using since 2009 has one basic flaw. It incorrectly assumes that HTTP::cookie value returns the value from the Set-Cookie header (including attributes).
3) Attempts to use version 11 specific functions ( HTTP::cookie httponly enable ) resulted in illegal argument exceptions being thrown by the iRule. Attempts to change the version of the cookie resulted in the same exception. I suspect that those functions would work if we deleted the original cookie before trying to set those values.
4) Changing the Expires attribute to lower-case does work, but I can't be sure that it won't cause problems with some browsers, or ASM security policies for that matter.
5) At this point, I'm choosing to avoid use of HTTP::cookie functions for now. With a few modifications to our existing iRule and suggestions from the above posts, I came up with the following iRule.
It's working well so far and eliminates the presence of duplicate HttpOnly and Secure attributes on cookies that already have them.
when HTTP_RESPONSE priority 100 { if { [catch { set setckval [HTTP::header values "Set-Cookie"] HTTP::header remove "Set-Cookie" foreach value $setckval { if { "" != $value } { set testvalue [string tolower $value] set valuelen [string length $value] switch -glob $testvalue { "*;secure*" - "*; secure*" { } default { set value "$value; Secure"; } } switch -glob $testvalue { "*;httponly*" - "*; httponly*" { } default { set value "$value; HttpOnly"; } } HTTP::header insert "Set-Cookie" $value F5 support recommended that we change the Expires attribute to lower-case When this is done, HTTP::cookie functions appear to work correctly. Cookie specifications rfc2109, rfc2165 and rfc6265 all show attribute names starting with an upper-case letter, but specifies that browsers must treat them as case-insensitive. If you want to change the expires attribute to lower-case, the following statement can be used instead of the header insert statement above. HTTP::header insert "Set-Cookie" [string map {Expires expires} $value] log local0. "client=[IP::client_addr] server=[IP::remote_addr] cookie=$value" } } } ] } { log local0. "Exception thrown, client_addr=[client_addr] HttpOnly and/or Secure cookie attributes may not have been set" } }- Lostgravity_174
Nimbostratus
Isn't the set valuelen statement superfluous?
- Ashish_Gupta_15
Nimbostratus
Specify the version while you are inserting the cookie and then set the httponly flag. Works with 11.5.x.
when HTTP_RESPONSE { HTTP::cookie insert name “UserName” value “john.doe” path “/” domain “xyz.com” version 1 HTTP::cookie version “UserName” 1 HTTP::cookie httponly “UserName” enable }More details here:
https://guptaashish.com/2016/10/24/f5-irules-setting-the-httponly-flag-on-a-http-cookie/
Help guide the future of your DevCentral Community!
What tools do you use to collaborate? (1min - anonymous)Recent Discussions
Related Content
* Getting Started on DevCentral
* Community Guidelines
* Community Terms of Use / EULA
* Community Ranking Explained
* Community Resources
* Contact the DevCentral Team
* Update MFA on account.f5.com
