Forum Discussion

Stefan_126426's avatar
Stefan_126426
Icon for Nimbostratus rankNimbostratus
Jan 02, 2013

Confusing ASP.NET session cookie rewriting with HttpOnly flag version 10

Hi Everyone, first post here so a little introduction.

 

I am a sysadmin/developer for a large insurance company and have just taken ownership of our F5 box. 12 Years IT experience so I can usually figure stuff out.. But not today.

 

 

We have been flagged on a pentest recently that we are not adding the Secure flag and HttpOnly flag to our cookies. Our site is very diverse so getting all the devlopment depts up to speed is a challange.

 

So I should be able to do this with an Irule right? (V10 so no HTTP::cookie HttpOnly "name" Enable, I can't use that) Well, I have a working solution, but I'd like some help on the behaviour described below:

 

Using Ncat on backtrack to see the raw traffic this is a standard response from the server for a .NET session: (No rule manipulation at this point)

 

-------------------------------------------------------------------------------------

 

user@bt:~ ncat mydomain.com 443 --ssl

 

HEAD /test.aspx HTTP/1.1

 

Host:mydomain.com

 

HTTP/1.1 200 OK

 

Cache-Control: no-cache

 

Pragma: no-cache

 

Content-Length: 21216

 

Content-Type: text/html; charset=utf-8

 

Expires: -1

 

X-Powered-By: ASP.NET

 

X-AspNet-Version: 2.0.50727

 

Set-Cookie: ASP.NET_SessionId=d4or5si4ezfo3oiienjmzjug; path=/; HttpOnly

 

Date: Wed, 02 Jan 2013 15:26:51 GMT

 

-------------------------------------------------------------------------------------

 

OK so we have a pretty standard server response here.

 

Now when I write the following Irule Code:

 

when HTTP_RESPONSE {

 

set myValues [HTTP::cookie names]

 

foreach mycookies $myValues {

 

 

set currentValue [HTTP::cookie $mycookies]

 

HTTP::cookie remove $mycookies

 

HTTP::header insert "Set-Cookie" "$mycookies=$currentValue; HttpOnly; Secure; Path=/;"

 

 

}

 

}

 

This code seems simple enough, get a list of cookie names. Loop through them. Extract the value of the cookie. Delete it, recreate from stored name and value with the required flags. What I get is this:

 

-------------------------------------------------------------------------------------

 

 

X-AspNet-Version: 2.0.50727

 

Set-Cookie: HttpOnly

 

Date: Web, 02 Jan 2013 15:36:14 GMT

 

Set-Cookie: ASP.NET_SessionId=qmalso4555a4bjbuspcgsj55

 

Set-Cookie: HttpOnly=; HttpOnly; Secure; Path=/;

 

----------------------------------------------------------------------------------------

 

Errr OK, I now have 3 cookies? and two are called "HttpOnly"? This seems rather odd, lets do some debugging. It appears to be looping twice even though there is one cookie. So Ill add some header inserts as debug statements to show where I am in the loop:

 

when HTTP_RESPONSE {

 

set myValues [HTTP::cookie names]

 

foreach mycookies $myValues {

 

HTTP::header insert "Test" $mycookies

 

set currentValue [HTTP::cookie $mycookies]

 

HTTP::cookie remove $mycookies

 

HTTP::header insert "Set-Cookie" "$mycookies=$currentValue; HttpOnly; Secure; Path=/;"

 

 

}

 

}

 

So a header called test should print out everytime the loop is hit...

 

Response:

 

-------------------------------------------------------------------------------------

 

 

X-AspNet-Version: 2.0.50727

 

Set-Cookie: HttpOnly

 

Date: Web, 02 Jan 2013 15:41:14 GMT

 

Test: ASP.NET_SessionId

 

Set-Cookie: ASP.NET_SessionId=pwkwy1452plfijbhlqqtre45

 

Test: HttpOnly

 

Set-Cookie: HttpOnly=; HttpOnly; Secure; Path=/;

 

-------------------------------------------------------------------------------------

 

OK this is curious. We are looping twice for sure as the Test header appears twice and it seems that its seeing the HttpOnly flag as a cookie???? Also, Why has that Set-Cookie appeared ABOVE the timestamp????

 

What is going on here? Have I fundamentally misunderstood this programing language? Well since we are looping twice, lets force it to skip the second loop:

 

when HTTP_RESPONSE {

 

set myValues [HTTP::cookie names]

 

foreach mycookies $myValues {

 

if {$mycookies == "HttpOnly"} {

 

donothing DO NOT REMOVE THIS LINE!

 

}

 

else {

 

set currentValue [HTTP::cookie $mycookies]

 

HTTP::cookie remove $mycookies

 

HTTP::header insert "Set-Cookie" "$mycookies=$currentValue; HttpOnly; Secure; Path=/;"

 

}

 

}

 

}

 

Lets see what we get now:

 

-------------------------------------------------------------------------------------

 

 

X-AspNet-Version: 2.0.50727

 

Set-Cookie: HttpOnly

 

Date: Web, 02 Jan 2013 15:41:14 GMT

 

Set-Cookie: ASP.NET_SessionId=fc403oqe1aneu4fxcokdfup; HttpOnly; Secure; Path=/;

 

-------------------------------------------------------------------------------------

 

Wait a minute....wat? OK so the header insert for the session cookie now works. But we still have this mysterious extra Set-Cookie: HttpOnly. Where and why is this happening?

 

Now doing a little research I discovered that all .NET session cookies come with the HttpOnly flag by default and cannot be changed in IIS to remove this. OK that means I don't have to add the flag at all, infact I can use the built in "secure" function in HTTP::cookie, Great!

 

when HTTP_RESPONSE {

 

set myValues [HTTP::cookie names]

 

foreach mycookies $myValues {

 

switch $mycookies {

 

"ASP.NET_SessionId" {

 

HTTP::cookie secure "ASP.NET_SessionId" enable

 

}

 

default {

 

set currentValue [HTTP::cookie $mycookies]

 

if {$mycookies == "HttpOnly"} {

 

donothing DO NOT REMOVE THIS LINE!

 

}

 

else {

 

HTTP::cookie remove $mycookies

 

HTTP::header insert "Set-Cookie" "$mycookies=$currentValue; HttpOnly; Secure; Path=/"

 

}

 

}

 

}

 

}

 

}

 

So the above will loop through all cookies, check if the name is ASPNET etc and ONLY add the secure flag. Any other cookie will be rewritten and deleted.

 

This works fine for the .NET session cookie. All other cookies seem to work fine too, but I still get the odd "Set-Cookie: HttpOnly" for any other cookie thats being rewritten here alway appearing above the date field.

 

I have now fully tested this with session cookies from ASP.NET, standard ASP (VB6), and custom written cookies. The above code seems to work fine in all situations.

 

So my real question is... Am I a complete idiot and have missed something obvious here... or does this code loop twice for one cookie and seems to think that the HttpOnly flag is a cookie?

 

OK now add multiple cookies:

 

If I have 2 or more cookies AND add a HTTP::cookie remove "HttpOnly" statement to the code, behaviour gets odd depending where. If its in the httponly if statement, the first cookie that gets written has only its name and value (Like above). If its after the loop, the LAST Set-Cookie has the flags removed and only its name and value. In both these scenarios it seems to think that httponly is a cookie name and not a flag.

 

 

I'm guessing here that this is a v10 bug thats fixed in v11?

 

 

 

7 Replies

  • what about this one?

    [root@ve10:Active] config  b virtual bar80 list
    virtual bar80 {
       snat automap
       pool foo
       destination 172.28.19.252:80
       ip protocol 6
       rules myrule
       profiles {
          http {}
          tcp {}
       }
    }
    [root@ve10:Active] config  b pool foo list
    pool foo {
       members 200.200.200.101:80 {}
    }
    [root@ve10:Active] config  b rule myrule list
    rule myrule {
       when HTTP_RESPONSE {
      set myValues [HTTP::header values "Set-Cookie"]
      HTTP::header remove "Set-Cookie"
      foreach mycookies $myValues {
        scan [lindex $mycookies 0] {%[^=]=%[^;]} currentName currentValue
        HTTP::header insert "Set-Cookie" "$currentName=$currentValue; HttpOnly; Secure; Path=/"
      }
    }
    }
    
     response from server (not passing bigip)
    
    [root@ve10:Active] config  curl -I http://200.200.200.101
    HTTP/1.1 200 OK
    Cache-Control: no-cache
    Pragma: no-cache
    Content-Length: 21216
    Content-Type: text/html; charset=utf-8
    Expires: -1
    X-Powered-By: ASP.NET
    X-AspNet-Version: 2.0.50727
    Set-Cookie: ASP.NET_SessionId=d4or5si4ezfo3oiienjmzjug; path=/; HttpOnly
    Set-Cookie: testcookie=123456; path=/
    Set-Cookie: mycookie=abcdef; path=/; HttpOnly
    Date: Wed, 02 Jan 2013 15:26:51 GMT
    
     response from bigip
    
    [root@centos251 ~] curl -I http://172.28.19.252
    HTTP/1.1 200 OK
    Cache-Control: no-cache
    Pragma: no-cache
    Content-Length: 21216
    Content-Type: text/html; charset=utf-8
    Expires: -1
    X-Powered-By: ASP.NET
    X-AspNet-Version: 2.0.50727
    Date: Wed, 02 Jan 2013 15:26:51 GMT
    Set-Cookie: ASP.NET_SessionId=d4or5si4ezfo3oiienjmzjug; HttpOnly; Secure; Path=/
    Set-Cookie: testcookie=123456; HttpOnly; Secure; Path=/
    Set-Cookie: mycookie=abcdef; HttpOnly; Secure; Path=/
    
    
  • your 1st irule seems working fine in 11.3.0.

    root@(ve11a)(cfg-sync Changes Pending)(Active)(/Common)(tmos) show sys version
    
    Sys::Version
    Main Package
      Product  BIG-IP
      Version  11.3.0
      Build    2806.0
      Edition  Final
      Date     Tue Nov 13 22:34:00 PST 2012
    
    root@(ve11a)(cfg-sync Changes Pending)(Active)(/Common)(tmos) list ltm virtual bar
    ltm virtual bar {
        destination 172.28.20.14:80
        ip-protocol tcp
        mask 255.255.255.255
        pool foo
        profiles {
            http { }
            tcp { }
        }
        rules {
            myrule
        }
        source 0.0.0.0/0
        source-address-translation {
            type automap
        }
        vlans-disabled
    }
    root@(ve11a)(cfg-sync Changes Pending)(Active)(/Common)(tmos) list ltm pool foo
    ltm pool foo {
        members {
            200.200.200.101:80 {
                address 200.200.200.101
            }
        }
    }
    root@(ve11a)(cfg-sync Changes Pending)(Active)(/Common)(tmos) list ltm rule myrule
    ltm rule myrule {
        when HTTP_RESPONSE {
      set myValues [HTTP::cookie names]
      foreach mycookies $myValues {
        set currentValue [HTTP::cookie $mycookies]
        HTTP::cookie remove $mycookies
        HTTP::header insert "Set-Cookie" "$mycookies=$currentValue; HttpOnly; Secure; Path=/;"
      }
    }
    }
    
     response from server (not passing bigip)
    
    [root@ve11a:Active:Changes Pending] config  curl -I http://200.200.200.101
    HTTP/1.1 200 OK
    Cache-Control: no-cache
    Pragma: no-cache
    Content-Length: 21216
    Content-Type: text/html; charset=utf-8
    Expires: -1
    X-Powered-By: ASP.NET
    X-AspNet-Version: 2.0.50727
    Set-Cookie: ASP.NET_SessionId=d4or5si4ezfo3oiienjmzjug; path=/; HttpOnly
    Set-Cookie: testcookie=123456; path=/
    Set-Cookie: mycookie=abcdef; path=/; HttpOnly
    Date: Wed, 02 Jan 2013 15:26:51 GMT
    
     response from bigip
    
    [root@centos251 ~] curl -I http://172.28.20.14
    HTTP/1.1 200 OK
    Cache-Control: no-cache
    Pragma: no-cache
    Content-Length: 21216
    Content-Type: text/html; charset=utf-8
    Expires: -1
    X-Powered-By: ASP.NET
    X-AspNet-Version: 2.0.50727
    Date: Wed, 02 Jan 2013 15:26:51 GMT
    Set-Cookie: testcookie=123456; HttpOnly; Secure; Path=/;
    Set-Cookie: ASP.NET_SessionId=d4or5si4ezfo3oiienjmzjug; HttpOnly; Secure; Path=/;
    Set-Cookie: mycookie=abcdef; HttpOnly; Secure; Path=/;
    
    
  • Hi Nitass

     

     

    That works perfectly, many thanks for your time and effort.

     

     

    Could you just confirm I understand this code correctly please? (apologies, this is a new language for me and I like to learn ;-))

     

     

    set myValues [HTTP::header values "Set-Cookie"] <-- This creates array or equivilent of "Set-Cookie" Headers and values.

     

    HTTP:: header remove "Set-Cookie" <-- removes all headers with "Set-Cookie"

     

    For each, well thats obvious

     

    Now the scan line is a bit more interesting:

     

    scan [lindex $mycookies 0] {%[^=]=%[^;]} currentname currentvalue

     

    OK scan is for parsing strings. I am guessing that lindex $mycookes 0 will actually return the whole string of the cookie including flags and is just a different way of getting these values. There would not be anything in position 1?

     

    Now the next bit looks like a regular expression..But not quite? It seems to say "anything up to the first equals From the first equals anything up to the first semicolon" What I don't get is how this ties into the two variables. How does it define the start and end of a string to go into the variable? Is it simple on the ^=? As ^ appears to be a "stop here"?

     

    The rest of course makes sense.

     

     

    Once again, many thanks for your time and effort on this, it is greatly appreciated.
  • Ahhh, begining to see:

     

    lindex 0 contains: cookiename=value;

     

    lindex 1 contains: flags, i.e. "path=/; HttpOnly;" etc

     

    Oh just found the TCL Reference on sourceforge, thats a big help. Curly braces are so the Irule does not interpret it as a command.

     

    OK so for the format used in scan:

     

    % specifies a conversion

     

    [^chars] Substring is one or more characters not in chars

     

    So {%[^=] Match anything up to the first = match

     

    =% OK the % starts the next conversion, but I am confused by the =. Is it indicating "Match the first = then start the new conversion" after?

     

    Then match on [^;] as the end of the conversion, these conversions are placed in the subsequent variables in order of matching. So unless the optional positional specifier is called.. Is it possible if there is no first match i.e. blah= is missing than this would mean that the second match would become the first and enter the first var?

     

     

    This is what I get for being a Windows admin and C developer... sounds like I should have been learning TCL and lisp ;-)

     

  • [root@ve10:Active] config  b rule myrule list
    rule myrule {
       when HTTP_RESPONSE {
      set myValues [HTTP::header values "Set-Cookie"]
      log local0. "myValues: $myValues"
      HTTP::header remove "Set-Cookie"
      foreach mycookies $myValues {
        log local0. "-----"
        log local0. "mycookies: $mycookies"
        log local0. "\[lindex $mycookies 0\]: [lindex $mycookies 0]"
        log local0. "scan \[lindex $mycookies 0\] {%\[^=\]=%\[^;\]} currentName currentValue: [scan [lindex $mycookies 0] {%[^=]=%[^;]} currentName currentValue]"
        log local0. "currentName: $currentName"
        log local0. "currentValue: $currentValue"
        scan [lindex $mycookies 0] {%[^=]=%[^;]} currentName currentValue
        HTTP::header insert "Set-Cookie" "$currentName=$currentValue; HttpOnly; Secure; Path=/"
      }
    }
    }
    
     client
    
    [root@centos251 ~] curl -I http://172.28.19.252
    HTTP/1.1 200 OK
    Cache-Control: no-cache
    Pragma: no-cache
    Content-Length: 21216
    Content-Type: text/html; charset=utf-8
    Expires: -1
    X-Powered-By: ASP.NET
    X-AspNet-Version: 2.0.50727
    Date: Wed, 02 Jan 2013 15:26:51 GMT
    Set-Cookie: ASP.NET_SessionId=d4or5si4ezfo3oiienjmzjug; HttpOnly; Secure; Path=/
    Set-Cookie: testcookie=123456; HttpOnly; Secure; Path=/
    Set-Cookie: mycookie=abcdef; HttpOnly; Secure; Path=/
    
     log on bigip
    
    [root@ve10:Active] config  tail -f /var/log/ltm
    Jan  4 08:10:03 local/tmm info tmm[4876]: Rule myrule : myValues: {ASP.NET_SessionId=d4or5si4ezfo3oiienjmzjug; path=/; HttpOnly} {testcookie=123456; path=/} {mycookie=abcdef; path=/; HttpOnly}
    Jan  4 08:10:03 local/tmm info tmm[4876]: Rule myrule : -----
    Jan  4 08:10:03 local/tmm info tmm[4876]: Rule myrule : mycookies: ASP.NET_SessionId=d4or5si4ezfo3oiienjmzjug; path=/; HttpOnly
    Jan  4 08:10:03 local/tmm info tmm[4876]: Rule myrule : [lindex ASP.NET_SessionId=d4or5si4ezfo3oiienjmzjug; path=/; HttpOnly 0]: ASP.NET_SessionId=d4or5si4ezfo3oiienjmzjug;
    Jan  4 08:10:03 local/tmm info tmm[4876]: Rule myrule : scan [lindex ASP.NET_SessionId=d4or5si4ezfo3oiienjmzjug; path=/; HttpOnly 0] {%[^=]=%[^;]} currentName currentValue: 2
    Jan  4 08:10:03 local/tmm info tmm[4876]: Rule myrule : currentName: ASP.NET_SessionId
    Jan  4 08:10:03 local/tmm info tmm[4876]: Rule myrule : currentValue: d4or5si4ezfo3oiienjmzjug
    Jan  4 08:10:03 local/tmm info tmm[4876]: Rule myrule : -----
    Jan  4 08:10:03 local/tmm info tmm[4876]: Rule myrule : mycookies: testcookie=123456; path=/
    Jan  4 08:10:03 local/tmm info tmm[4876]: Rule myrule : [lindex testcookie=123456; path=/ 0]: testcookie=123456;
    Jan  4 08:10:03 local/tmm info tmm[4876]: Rule myrule : scan [lindex testcookie=123456; path=/ 0] {%[^=]=%[^;]} currentName currentValue: 2
    Jan  4 08:10:03 local/tmm info tmm[4876]: Rule myrule : currentName: testcookie
    Jan  4 08:10:03 local/tmm info tmm[4876]: Rule myrule : currentValue: 123456
    Jan  4 08:10:03 local/tmm info tmm[4876]: Rule myrule : -----
    Jan  4 08:10:03 local/tmm info tmm[4876]: Rule myrule : mycookies: mycookie=abcdef; path=/; HttpOnly
    Jan  4 08:10:03 local/tmm info tmm[4876]: Rule myrule : [lindex mycookie=abcdef; path=/; HttpOnly 0]: mycookie=abcdef;
    Jan  4 08:10:03 local/tmm info tmm[4876]: Rule myrule : scan [lindex mycookie=abcdef; path=/; HttpOnly 0] {%[^=]=%[^;]} currentName currentValue: 2
    Jan  4 08:10:03 local/tmm info tmm[4876]: Rule myrule : currentName: mycookie
    Jan  4 08:10:03 local/tmm info tmm[4876]: Rule myrule : currentValue: abcdef
    
    

    hope this helps. 🙂
  • This is my final version as I needed to exlude some cookies from being manipulated. Hopefully someone may find it useful

     

     

    This is working perfectly, so thanks again nitass

     

     

    when HTTP_RESPONSE {

     

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

     

    HTTP::header remove "Set-Cookie"

     

    foreach mycookies $myValues {

     

    scan [lindex $mycookies 0] {%[^=]=%[^;]} currentName currentValue

     

    set myflags [lindex $mycookies 1]

     

    switch $currentName {

     

    "scrubbed" -

     

    "redacted" -

     

    "deleted" -

     

    "notforyoutoknow"

     

    {

     

    HTTP::header insert "Set-Cookie" "$currentName=$currentValue; $myflags"

     

    }

     

    default {

     

    HTTP::header insert "Set-Cookie" "$currentName=$currentValue; HttpOnly; Secure; Path=/"

     

    }

     

    }

     

    }

     

    }