CSC/ECE 517 Spring 2015/ch1b 10 GL

From Expertiza_Wiki
Jump to navigation Jump to search

Encrypted Cookies


Introduction

Background

A cookie, sometimes called HTTP cookie<ref>http://en.wikipedia.org/wiki/HTTP_cookie</ref>, 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<ref>http://www.allaboutcookies.org/</ref>.

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.

Why using encrypted cookies

There are some other methods that sometimes are considered to replace encrypted cookies<ref>http://pothibo.com/2013/9/sessions-and-cookies-in-ruby-on-rails</ref>. Session data can be stored in server’s database. However this will cost more time and space when requesting session. Using cookies will dramatically increase the execution speed. As for unencrypted cookie, its content can be seen without any restriction, which means anyone can see the content of cookies’ content. So it is absolutely unsafe to carry sensitive content in cookies. However, with encrypted cookies, it is possible that cookie can carry some sensitive data because the secure encryption algorithm. And you probably are used to key-derivation encryption(bcrypt, pbkdf2, etc). But these are not as useful as in passwords because they cannot be decrypted! However cookies need to be decrypted when they are received. So instead of a key-derivation function, Rails uses a cipher(by default AES-256) to encrypt and decrypt data.

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<ref>http://big-elephants.com/2014-01/handling-rails-4-sessions-with-go/</ref>. 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:

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. 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<ref>http://blog.bigbinary.com/2013/03/19/cookies-on-rails.html</ref>, 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"] %>
Deciphering content of the cookie

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)
>> secret_key_base = 'a760133dad846d239461e2575efa7ac44c68400ded807b58a81fa4517f87b402b76750d710a402b3a90a4b063bd2235fcea8a49186553c01a70db2fb5d4786ff'
>> key_generator = ActiveSupport::KeyGenerator.new(secret_key_base, iterations: 1000)
>> key_generator = ActiveSupport::CachingKeyGenerator.new(key_generator)
>> secret = key_generator.generate_key('encrypted cookie')
>> sign_secret = key_generator.generate_key('signed encrypted cookie')
>> encryptor = ActiveSupport::MessageEncryptor.new(secret, sign_secret)
>> data =  encryptor.decrypt_and_verify(content)
>> puts data
=> csc517

As you can see we need the secret_key_base to make sense out of cookie data. So in Rails 4 the session data will be encrypted ,by default.

Attention

Usually cookie has limit size which is 4K. A CookieOverflow exception is raised if you attempt to store more than 4K of data<ref>http://api.rubyonrails.org/classes/ActionDispatch/Session/CookieStore.html</ref>. If you are upgrading an existing Rails 3 app, you should leave your existing secret_token in place and simply add the new secret_key_base. Note that you should wait to set secret_key_base until you have 100% of your userbase on Rails 4 and are reasonably sure you will not need to rollback to Rails 3. This is because cookies signed based on the new secret_key_base in Rails 4 are not backwards compatible with Rails 3. You are free to leave your existing secret_token in place, not set the new secret_key_base, and ignore the deprecation warnings until you are reasonably sure that your upgrade is otherwise complete. Additionally, you should take care to make sure you are not relying on the ability to decode signed cookies generated by your app in external applications or JavaScript before upgrading. Note that changing the secret key will invalidate all existing sessions! And successful encryption and signing are based on administrators’ secure operation. For example, if attackers finally have the access to secret_token or encryption key, then it is possible that they can see or modify the cookie data, then pretend to be a legal user to accomplish various operations, which is disastrous for Rails security.

Conclusion

Cookies are used to store some small data that can notify web server of the user’s previous activity. It can be executed very quickly and will not cost much space. Encrypted cookies make sure cookies’ data will not be seen by any unauthorized users so that information security gets protected. It uses AES-256 to encrypt data by default. Using encrypted cookies sometimes will not be better if some secret keys cannot be protected properly. With access to these keys, attacker can easily see the content in cookie even modify it. So we need to pay attention to obey rules when using encrypted cookies.

References

<references/>