CSC/ECE 517 Spring 2015/ch1b 10 GL: Difference between revisions

From Expertiza_Wiki
Jump to navigation Jump to search
No edit summary
Line 9: Line 9:
In Rails, we may get in touch with cookies when we learn session, which can be regarded as one of cookie’s usage because cookie-based session store is dramatically faster than the alternatives. Rails cookie based session storage was introduced as the default in 2007, version 2.0.0. In Rails 2 and Rails 3, the value of the cookie is a base64 encoded serialized string with an added signature. Session data is thus almost clear text. In Rails 4, the value of the cookie is an encrypted string. But if you have access to the source code of the application you can use the built-in infrastructure to decode the session.
In Rails, we may get in touch with cookies when we learn session, which can be regarded as one of cookie’s usage because cookie-based session store is dramatically faster than the alternatives. Rails cookie based session storage was introduced as the default in 2007, version 2.0.0. In Rails 2 and Rails 3, the value of the cookie is a base64 encoded serialized string with an added signature. Session data is thus almost clear text. In Rails 4, the value of the cookie is an encrypted string. But if you have access to the source code of the application you can use the built-in infrastructure to decode the session.


===How it works===
==How it works==
If you generate a Rails application in 3.2 then ,by default, you will see a file at config/initializers/session_store.rb. The contents of this file is something like Demo::Application.config.session_store :cookie_store, key: '_demo_session'
====In Rails before 4.0====
First thing this line is telling is to use cookie to store session information. Second thing this line is telling is to use _demo_session as the key to store cookie data. A single site can have cookies under different key. For example airbnb is using 14 different keys to store cookie data.<br>
=====Getting start=====
And you can add following code to create a session in rails 3.2:
Firstly, let's create a Rails application in 3.2, then by default, you will see a file at <code>config/initializers/session_store.rb</code>. The contents of this file is something like:
<pre>session[:github_username] = 'neerajdotname'</pre> <br>
<pre>Testcookies::Application.config.session_store :cookie_store, key: '_testcookies_session'</pre>
Then by visiting the action that executes above code you can see cookies for localhost:3000:
The <code>:cookie_store</code> in this line means to use cookie to store session information. And the <code>key: '_testcookies_session'</code> is telling us to use _testcookies_session as the key to store cookie data. A single site can have cookies under different key.  
 
And you can add following code to controller to create a session:
<pre>session[:username] = 'csc517'</pre>
 
Then by visiting the action that executes above code, then you can see cookies for <code>localhost:3000</code> in Chrome Inspect Element:
 
[[File:Screenshot_2015-02-17_23.31.35.png]]
=====Deciphering content of the cookie=====
As you can see I have only one cookie with key <code>_testcookies_session</code>, and the cookie has following data:
 
<code>Value: BAh7CEkiD3Nlc3Npb25faWQGOgZFVEkiJWIwMjY1Mzk0NTM3NDE1YjNkOTYyOGY1NzlmODRiNmY1BjsAVEkiDXVzZXJuYW1lBjsARkki
        C2NzYzUxNwY7AFRJIhBfY3NyZl90b2tlbgY7AEZJIjFYdWZNZFVVWFpYNUdYSDc1a3hybGdCS21xUW5td25majBGVmRmMWtpcVk4PQY7AEY%3D
        '''--fa4da3b8f5b19e25a3d4a973f94e2e0f1524b7d6'''</code>
 
The content is escaped and Base64 encoded data(the first two lines), followed by digest of data(the third line), which is used to make sure the content is original without modification by attackers. With lots of methods decoding Base64 codes, we are able to get the data that is stored in cookie. Let's open the rails console and try to decipher this message:
<code>content = BAh7CEkiD3Nlc3Npb25faWQGOgZFVEkiJWIwMjY1Mzk0NTM3NDE1YjNkOTYyOGY1NzlmODRiNmY1BjsAVEkiDXVzZXJuYW1lBjsARkki
C2NzYzUxNwY7AFRJIhBfY3NyZl90b2tlbgY7AEZJIjFYdWZNZFVVWFpYNUdYSDc1a3hybGdCS21xUW5td25majBGVmRmMWtpcVk4PQY7AEY%3D
--fa4da3b8f5b19e25a3d4a973f94e2e0f1524b7d6</code>
 
When the content is written to cookie it is escaped automatically. So first we need to unescape it by doing:
>> content = URI.unescape(content)
  "BAh7CEkiD3Nlc3Npb25faWQGOgZFVEkiJWIwMjY1Mzk0NTM3NDE1YjNkOTYyOGY1NzlmODRiNmY1BjsAVEkiDXVzZXJuYW1lBjsARkki
  C2NzYzUxNwY7AFRJIhBfY3NyZl90b2tlbgY7AEZJIjFYdWZNZFVVWFpYNUdYSDc1a3hybGdCS21xUW5td25majBGVmRmMWtpcVk4PQY7AEY=
  --fa4da3b8f5b19e25a3d4a973f94e2e0f1524b7d6"
 
Then the data before "--" is the real data, and after the "--" is the digest. So let's do this:
>> data, digest = unescaped_content.split('--')
  ["BAh7CEkiD3Nlc3Npb25faWQGOgZFVEkiJWIwMjY1Mzk0NTM3NDE1YjNkOTYyOGY1NzlmODRiNmY1BjsAVEkiDXVzZXJuYW1lBjsARkki
  C2NzYzUxNwY7AFRJIhBfY3NyZl90b2tlbgY7AEZJIjFYdWZNZFVVWFpYNUdYSDc1a3hybGdCS21xUW5td25majBGVmRmMWtpcVk4PQY7AEY=",
  "fa4da3b8f5b19e25a3d4a973f94e2e0f1524b7d6"]
 
As the data is base64 encoded, we can use this command to decode the information:
>> Marshal.load(::Base64.decode64(data))
  {"session_id"=>"b0265394537415b3d9628f579f84b6f5", "username"=>"csc517", "_csrf_token"=>"XufMdUUXZX5GXH75kxrlgBKmqQnmwnfj0FVdf1kiqY8="}
 
So we are able to see the data stored in the cookie: <code>"username"=>"csc517"</code>. However we can’t tamper with the cookie because if we change the cookie data then the digest will not match. In order to create the digest, rails makes of use of <code>config/initializer/secret_token.rb</code>:
 
Testcookies::Application.config.secret_token = '998a88d908feae524a1f4091b3034e742bcb7863d978dc5ccc7af80f26b7129788b2c6550356c89eb452ed4f088008463239014614d0a80ac2cc0baa4de82341'
 
Use this token to create digest:
>>token = "998a88d908feae524a1f4091b3034e742bcb7863d978dc5ccc7af80f26b7129788b2c6550356c89eb452ed4f088008463239014614d0a80ac2cc0baa4de82341"
>>OpenSSL::HMAC.hexdigest(OpenSSL::Digest.const_get('SHA1').new, token, data)
  "fa4da3b8f5b19e25a3d4a973f94e2e0f1524b7d6"
 
We can see that any changes to the data will lead to a different digest:
>>data[0] = 'A'
>>OpenSSL::HMAC.hexdigest(OpenSSL::Digest.const_get('SHA1').new, token, data)
  "8bc1b0f49eb382b650992114264a807832496f94"
 
=====Using cookie with more option=====
In the previous example we used session to store and retrieve data from cookie. We can directly use cookie and that gives us a little bit more control. We can add these code to create a cookie:
cookies[:username] = 'csc517'
 
Then we can see the cookies in the browser:
[[File:Screenshot 2015-02-18 01.43.00.png]]
 
Now there are to keys in the cookie. The first is same as before and the new one is created by the code above. One thing to notice is that the data stored for key username is not Base64encoded and it also does not have the digest. It means this type of cookie data can be tampered with by the user and the Rails application will not be able to detect that the data has been tampered with. And there are some options you can use when setting a cookies:
{| class="wikitable"
|-
! Option
! Description
 
|-
| value
| The cookie's value, you can also assign a list of values, such as an array.
|-
| path
| The application path for which this cookie applies -- the cookie won't be available for other paths. The default is the root of the application.
|-
| domain
| The domain for which this cookie applies; the cookie won't be available for other domains.
|-
| expires
| Sets the time when the cookie expires. Use a Rails Time object.
|-
| secure
| Indicates whether the cookie is secure; the default is false. Note: secure cookies are only sent to secure(HTTPS) servers.
|}
=====Signed cookies=====
Writing cookies to the response via the <code>cookies.signed</code> has generates signed representations of cookies to prevent tampering of those cookies' values by the end user. If a signed cookie was tampered with, an <code>ActiveSupport::MessageVerifier::InvalidSignature</code> exception will be raised.
cookies.signed[:aother_username] = 'csc517'
Then the cookies in browser become this:
 
[[File:Screenshot 2015-02-18 02.03.14.png]]
 
The value of <code>another_username</code> has a digest in it, we can use that to detect whether the information has been tampered.
 
=====Conclusion=====
Session, by default, uses signed cookies which prevents any kind of tampering of data, but the data is still visible to users. It means we can’t store sensitive information in session.It would be nice if the session data is stored in encrypted format. And in the Rails 4, the cookie is encrypted by default.
====In Rails 4====
If we generate a Rails application in Rails 4 then,by default, we will see a file at <code>config/initializers/session_store.rb</code>. The contents of this file is something like this:
Rails.application.config.session_store :cookie_store, key: '_testcookies4_session'
This means we use cookie to store session by default. And unlike Rails 3, now we don't have the file <code>config/initializers/secret_token.rb</code>. Instead, we have <code>config/secrets.yml</code>, and it has these content:
development:
  secret_key_base: a760133dad846d239461e2575efa7ac44c68400ded807b58a81fa4517f87b402b76750d710a402b3a90a4b063bd2235fcea8a49186553c01a70db2fb5d4786ff
test:
  secret_key_base: 0131c94a859126b01285f0c744e0e9759ca9aa854da3b21799e7b2d1c555f982bc31a2e7e6e1d2327cfd735587528d1fac3cc129da8cd68e6bea9cb1e6c4e5b4
# Do not keep production secrets in the repository,
# instead read values from the environment.
production:
  secret_key_base: <%= ENV["SECRET_KEY_BASE"] %>
 
For each environment, the rails application has a different <code>secret_key_base</code>. Notice that in Rails 3.2.x the key was <code>secret_token</code>, now is <code>secret_key_base</code>. The differences are if you only have secret_token set, your cookies will be signed, but not encrypted. This means a user cannot alter their <code>username</code> without knowing your app's secret key, but can easily read their <code>username</code>. This was the default for Rails 3 apps. If you have secret_key_base set, your cookies will be encrypted. This goes a step further than signed cookies in that encrypted cookies cannot be altered or read by users. This is the default starting in Rails 4.
 
Let's put this code into a controller:
session[:username] = 'csc517
Then by visiting the action that executes above code, then you can see cookies for localhost:3000 in browser:
[[File:Screenshot 2015-02-18 02.29.10.png]]
 
Cookie has following data:
value = RjcxbnNGWVU1MnRXMWhnZ3ViOE1ObFN4SWNBaFZVaWJCUVVuWDJ0QlBxdDFpWEpBL0hxNWtRM0RpMjJYS1ROMEVkbjEyZ0piV3AxamoyVFNqUGhqTVY3engwalZFa0xFcTFwZ085cDJ
wN3Q2dFp2MzlHMDVQRzM0M1RhWE9nYVVEaUJrTGFoV1paenZTMmc2WVRtandzczZCTkgzUjhGTWwweWtsM1Y3RlhjeTFMOEI0R0o5eml3ZGo3UTZQTmVOLS1PSW91Nms0YkROdUdtZWh3WE9lMDdRPT0%3D
--fa205eafee14ec48d829b606cf27151e56da6c8e
 
Then open <code>rails console</code> to do the following operations:
>> content = "RjcxbnNGWVU1MnRXMWhnZ3ViOE1ObFN4SWNBaFZVaWJCUVVuWDJ0QlBxdDFpWEpBL0hxNWtRM0RpMjJYS1ROMEVkbjEyZ0piV3AxamoyVFNqUGhqTVY3engwalZFa0xFcTFwZ085cDJ
wN3Q2dFp2MzlHMDVQRzM0M1RhWE9nYVVEaUJrTGFoV1paenZTMmc2WVRtandzczZCTkgzUjhGTWwweWtsM1Y3RlhjeTFMOEI0R0o5eml3ZGo3UTZQTmVOLS1PSW91Nms0YkROdUdtZWh3WE9lMDdRPT0%3D
--fa205eafee14ec48d829b606cf27151e56da6c8e"
>>content = URI.unescape(content)

Revision as of 07:52, 18 February 2015

Encrypted Cookies


Introduction

Background

A cookie, sometimes called HTTP cookie, web cookie or browser cookie, is some data stored in a user’s web browser while visiting that website. Cookie is sent from web server and browser sends it back when the user loads the website, in order to notify the website of the user’s previous activity. Cookies were designed for web server to remember some user’s information or to record user’s browsing activity. (including clicking certain button, logging in, or recording which pages were visited by the user before) The most common example of this functionality is the shopping cart feature of any e-commerce site. When you visit one page of a catalog and select some items, the session cookie remembers your selection so your shopping cart will have the items you selected when you are ready to check out. Without session cookies, if you click “CHECKOUT” , the new page does not recognize your past activities on prior pages and your shopping cart will always be empty.

In Rails, we may get in touch with cookies when we learn session, which can be regarded as one of cookie’s usage because cookie-based session store is dramatically faster than the alternatives. Rails cookie based session storage was introduced as the default in 2007, version 2.0.0. In Rails 2 and Rails 3, the value of the cookie is a base64 encoded serialized string with an added signature. Session data is thus almost clear text. In Rails 4, the value of the cookie is an encrypted string. But if you have access to the source code of the application you can use the built-in infrastructure to decode the session.

How it works

In Rails before 4.0

Getting start

Firstly, let's create a Rails application in 3.2, then by default, you will see a file at config/initializers/session_store.rb. The contents of this file is something like:

Testcookies::Application.config.session_store :cookie_store, key: '_testcookies_session'

The :cookie_store in this line means to use cookie to store session information. And the key: '_testcookies_session' is telling us to use _testcookies_session as the key to store cookie data. A single site can have cookies under different key.

And you can add following code to controller to create a session:

session[:username] = 'csc517'

Then by visiting the action that executes above code, then you can see cookies for localhost:3000 in Chrome Inspect Element:

Deciphering content of the cookie

As you can see I have only one cookie with key _testcookies_session, and the cookie has following data:

Value: BAh7CEkiD3Nlc3Npb25faWQGOgZFVEkiJWIwMjY1Mzk0NTM3NDE1YjNkOTYyOGY1NzlmODRiNmY1BjsAVEkiDXVzZXJuYW1lBjsARkki
       C2NzYzUxNwY7AFRJIhBfY3NyZl90b2tlbgY7AEZJIjFYdWZNZFVVWFpYNUdYSDc1a3hybGdCS21xUW5td25majBGVmRmMWtpcVk4PQY7AEY%3D
       --fa4da3b8f5b19e25a3d4a973f94e2e0f1524b7d6

The content is escaped and Base64 encoded data(the first two lines), followed by digest of data(the third line), which is used to make sure the content is original without modification by attackers. With lots of methods decoding Base64 codes, we are able to get the data that is stored in cookie. Let's open the rails console and try to decipher this message:

content = BAh7CEkiD3Nlc3Npb25faWQGOgZFVEkiJWIwMjY1Mzk0NTM3NDE1YjNkOTYyOGY1NzlmODRiNmY1BjsAVEkiDXVzZXJuYW1lBjsARkki
C2NzYzUxNwY7AFRJIhBfY3NyZl90b2tlbgY7AEZJIjFYdWZNZFVVWFpYNUdYSDc1a3hybGdCS21xUW5td25majBGVmRmMWtpcVk4PQY7AEY%3D
--fa4da3b8f5b19e25a3d4a973f94e2e0f1524b7d6

When the content is written to cookie it is escaped automatically. So first we need to unescape it by doing:

>> content = URI.unescape(content)
 "BAh7CEkiD3Nlc3Npb25faWQGOgZFVEkiJWIwMjY1Mzk0NTM3NDE1YjNkOTYyOGY1NzlmODRiNmY1BjsAVEkiDXVzZXJuYW1lBjsARkki
 C2NzYzUxNwY7AFRJIhBfY3NyZl90b2tlbgY7AEZJIjFYdWZNZFVVWFpYNUdYSDc1a3hybGdCS21xUW5td25majBGVmRmMWtpcVk4PQY7AEY=
 --fa4da3b8f5b19e25a3d4a973f94e2e0f1524b7d6"

Then the data before "--" is the real data, and after the "--" is the digest. So let's do this:

>> data, digest = unescaped_content.split('--')
 ["BAh7CEkiD3Nlc3Npb25faWQGOgZFVEkiJWIwMjY1Mzk0NTM3NDE1YjNkOTYyOGY1NzlmODRiNmY1BjsAVEkiDXVzZXJuYW1lBjsARkki
 C2NzYzUxNwY7AFRJIhBfY3NyZl90b2tlbgY7AEZJIjFYdWZNZFVVWFpYNUdYSDc1a3hybGdCS21xUW5td25majBGVmRmMWtpcVk4PQY7AEY=",
 "fa4da3b8f5b19e25a3d4a973f94e2e0f1524b7d6"]

As the data is base64 encoded, we can use this command to decode the information:

>> Marshal.load(::Base64.decode64(data))
 {"session_id"=>"b0265394537415b3d9628f579f84b6f5", "username"=>"csc517", "_csrf_token"=>"XufMdUUXZX5GXH75kxrlgBKmqQnmwnfj0FVdf1kiqY8="}

So we are able to see the data stored in the cookie: "username"=>"csc517". However we can’t tamper with the cookie because if we change the cookie data then the digest will not match. In order to create the digest, rails makes of use of config/initializer/secret_token.rb:

Testcookies::Application.config.secret_token = '998a88d908feae524a1f4091b3034e742bcb7863d978dc5ccc7af80f26b7129788b2c6550356c89eb452ed4f088008463239014614d0a80ac2cc0baa4de82341'

Use this token to create digest:

>>token = "998a88d908feae524a1f4091b3034e742bcb7863d978dc5ccc7af80f26b7129788b2c6550356c89eb452ed4f088008463239014614d0a80ac2cc0baa4de82341"
>>OpenSSL::HMAC.hexdigest(OpenSSL::Digest.const_get('SHA1').new, token, data)
 "fa4da3b8f5b19e25a3d4a973f94e2e0f1524b7d6"

We can see that any changes to the data will lead to a different digest:

>>data[0] = 'A'
>>OpenSSL::HMAC.hexdigest(OpenSSL::Digest.const_get('SHA1').new, token, data)
 "8bc1b0f49eb382b650992114264a807832496f94"
Using cookie with more option

In the previous example we used session to store and retrieve data from cookie. We can directly use cookie and that gives us a little bit more control. We can add these code to create a cookie:

cookies[:username] = 'csc517'

Then we can see the cookies in the browser:

Now there are to keys in the cookie. The first is same as before and the new one is created by the code above. One thing to notice is that the data stored for key username is not Base64encoded and it also does not have the digest. It means this type of cookie data can be tampered with by the user and the Rails application will not be able to detect that the data has been tampered with. And there are some options you can use when setting a cookies:

Option Description
value The cookie's value, you can also assign a list of values, such as an array.
path The application path for which this cookie applies -- the cookie won't be available for other paths. The default is the root of the application.
domain The domain for which this cookie applies; the cookie won't be available for other domains.
expires Sets the time when the cookie expires. Use a Rails Time object.
secure Indicates whether the cookie is secure; the default is false. Note: secure cookies are only sent to secure(HTTPS) servers.
Signed cookies

Writing cookies to the response via the cookies.signed has generates signed representations of cookies to prevent tampering of those cookies' values by the end user. If a signed cookie was tampered with, an ActiveSupport::MessageVerifier::InvalidSignature exception will be raised.

cookies.signed[:aother_username] = 'csc517'

Then the cookies in browser become this:

The value of another_username has a digest in it, we can use that to detect whether the information has been tampered.

Conclusion

Session, by default, uses signed cookies which prevents any kind of tampering of data, but the data is still visible to users. It means we can’t store sensitive information in session.It would be nice if the session data is stored in encrypted format. And in the Rails 4, the cookie is encrypted by default.

In Rails 4

If we generate a Rails application in Rails 4 then,by default, we will see a file at config/initializers/session_store.rb. The contents of this file is something like this:

Rails.application.config.session_store :cookie_store, key: '_testcookies4_session'

This means we use cookie to store session by default. And unlike Rails 3, now we don't have the file config/initializers/secret_token.rb. Instead, we have config/secrets.yml, and it has these content:

development:
  secret_key_base: a760133dad846d239461e2575efa7ac44c68400ded807b58a81fa4517f87b402b76750d710a402b3a90a4b063bd2235fcea8a49186553c01a70db2fb5d4786ff
test:
  secret_key_base: 0131c94a859126b01285f0c744e0e9759ca9aa854da3b21799e7b2d1c555f982bc31a2e7e6e1d2327cfd735587528d1fac3cc129da8cd68e6bea9cb1e6c4e5b4
# Do not keep production secrets in the repository,
# instead read values from the environment.
production:
  secret_key_base: <%= ENV["SECRET_KEY_BASE"] %>

For each environment, the rails application has a different secret_key_base. Notice that in Rails 3.2.x the key was secret_token, now is secret_key_base. The differences are if you only have secret_token set, your cookies will be signed, but not encrypted. This means a user cannot alter their username without knowing your app's secret key, but can easily read their username. This was the default for Rails 3 apps. If you have secret_key_base set, your cookies will be encrypted. This goes a step further than signed cookies in that encrypted cookies cannot be altered or read by users. This is the default starting in Rails 4.

Let's put this code into a controller:

session[:username] = 'csc517

Then by visiting the action that executes above code, then you can see cookies for localhost:3000 in browser:

Cookie has following data:

value = RjcxbnNGWVU1MnRXMWhnZ3ViOE1ObFN4SWNBaFZVaWJCUVVuWDJ0QlBxdDFpWEpBL0hxNWtRM0RpMjJYS1ROMEVkbjEyZ0piV3AxamoyVFNqUGhqTVY3engwalZFa0xFcTFwZ085cDJ
wN3Q2dFp2MzlHMDVQRzM0M1RhWE9nYVVEaUJrTGFoV1paenZTMmc2WVRtandzczZCTkgzUjhGTWwweWtsM1Y3RlhjeTFMOEI0R0o5eml3ZGo3UTZQTmVOLS1PSW91Nms0YkROdUdtZWh3WE9lMDdRPT0%3D
--fa205eafee14ec48d829b606cf27151e56da6c8e

Then open rails console to do the following operations:

>> content = "RjcxbnNGWVU1MnRXMWhnZ3ViOE1ObFN4SWNBaFZVaWJCUVVuWDJ0QlBxdDFpWEpBL0hxNWtRM0RpMjJYS1ROMEVkbjEyZ0piV3AxamoyVFNqUGhqTVY3engwalZFa0xFcTFwZ085cDJ
wN3Q2dFp2MzlHMDVQRzM0M1RhWE9nYVVEaUJrTGFoV1paenZTMmc2WVRtandzczZCTkgzUjhGTWwweWtsM1Y3RlhjeTFMOEI0R0o5eml3ZGo3UTZQTmVOLS1PSW91Nms0YkROdUdtZWh3WE9lMDdRPT0%3D
--fa205eafee14ec48d829b606cf27151e56da6c8e"
>>content = URI.unescape(content)