Rails session id generation and verification code location - ruby-on-rails

Where is Rails generating the session id that it gives a user over a cookie? How is it verifying the session id given over a cookie? Does it save the session id, or does it use some hash function based on secret_token?
According to the Rails guide:
The session id is a 32 byte long MD5 hash value.
A session id consists of the hash value of a random string. The random string is the current time, a random number between 0 and 1, the process id number of the Ruby interpreter (also basically a random number) and a constant string. Currently it is not feasible to brute-force Rails' session ids. To date MD5 is uncompromised, but there have been collisions, so it is theoretically possible to create another input text with the same hash value. But this has had no security impact to date.
I found no links to the code that does this. I searched for uses of rand and srand, MD5 and such but found nothing useful. The closest I found was in actionpack/lib/action_dispatch/middleware/session/abstract_store.rb which does the following:
def generate_sid
sid = SecureRandom.hex(16)
sid.encode!(Encoding::UTF_8)
sid
end
This matches up with the format of session id I find in the session cookie, but not with the documentation in the guide. This also doesn't explain how sessions are verified.
According to this blog session id's are not stored or validated on the server side, but then how does it distinguish a session id that is valid or not?
Does someone know where the code that does this is, or a good guide for this? Thanks!
Other References:
Rails 3 ActiveRecordStore session_id tampering

You are correct, generate_sid1 is in charge of creating a session ID for new sessions.
When a session is first created, it generates a session id, sets it in your cookie, and caches it in CacheStore2. Once you make a request with a cookie, it re-builds the CookieStore and checks to make sure that the session id exists in the cache. If it exists, then it trusts the sesson. If it does not exist, then it does not trust the session. Since the session id is a 32 byte long value, it would be very difficult to guess an active session id that is in the cache.

It turns out that SecureRandom.hex calls SecureRandom.random_bytes which is what the paragraph from the Rails guide is describing. It may perhaps have been better for them to reference the SecureRandom function in use, as newer versions may change this algorithm.
Notice the use of current time, pid and so forth.
def self.random_bytes(n=nil)
n = n ? n.to_int : 16
if defined? OpenSSL::Random
#pid = 0 if !defined?(#pid)
pid = $$
if #pid != pid
now = Time.now
ary = [now.to_i, now.nsec, #pid, pid]
OpenSSL::Random.seed(ary.to_s)
#pid = pid
end
return OpenSSL::Random.random_bytes(n)
end
...
end
https://github.com/ruby/ruby/blob/v1_9_3_547/lib/securerandom.rb#L56

Related

Ruby/Rails: How to get same encrypted value every time we encrypt a particular string

Does ActiveSupport::MessageEncryptor support deterministic encryption so that we get the same encrypted value every time we encrypt a particular string? If not, are there any other Ruby libs that support deterministic encryption?
My goal is to get same encrypted value every time I encrypt a string and I should be able to decrypt it to original value as well.
Thanks.
You get different crypts because ActiveSupport::MessageEncryptor uses OpenSSL for encryption which requires an iv by default to prevent attackers from inferring relationships between segments of the encrypted message. I would highly recommend you to not mess around with that because you open ways for attackers to infer the encryption key.
However if you still want to do that take a look into the OpenSSL documentation of ruby. There should be a way to encrypt without vector.
Because it's a high security risk I don't add code to the answer to protect others from unnecessary loop holes.
I did not get why ActiveSupport:MessageEncryptor didn't work.
Here is another way to do it.
require 'bcrypt'
encrypted_password = BCrypt::Engine.hash_secret('password#!2#4', 'ADD SALT HERE')
you can also use it like this:
class User
SALT = 'GENERATE A STATIC SALT HERE AND KEEP IT SECURE'.freeze
include BCrypt
def password=(given_password)
#encrypted_password = Engine.hash_secret(given_password, SALT)
end
end
For the full documentation please check their repo
PS: using a static salt for all users for authentication is a bad idea.
Of course: one just need to use the same key to get the same encryption
x = ActiveSupport::MessageEncryptor.new('12345678901234567890123456789012').encrypt_and_sign('foo')
=> "bXJmRUczdjVXRFdLTitUcmkvRnk1UT09LS0vb2ZYdDRybGdWbmNXMUI1VDNnQzVBPT0=--13232bbe31d966f7d1df3aaa6fcc1cdc9eea60a1"
ActiveSupport::MessageEncryptor.new('12345678901234567890123456789012').decrypt_and_verify(x)
=> "foo"
It's hard to tell why you get different results since you didn't post any code...

Getting data out of devise

I'm migrating away from rails. I will be using the same domain, so I'll get the _session_id cookie that rails uses and I can bring over the old sessions table.I would like to use this to extract data (the user_id) from the old session. I can not tell how to do this outside of rails.
Within a controller there's current_user of course or session["warden.user.user.key"], but how can I take the id, decrypt the data in the table, and pull stuff out on my own (besides running the old rails application and creating a route on that that returns the info I need and hitting it from my new application)?
I'm not entirely sure this is the best way, but I was intrigued so went down the rabbit hole. This works for my 4.1.10 app where sessions are stored in the cookie. You'll want to look at action pack's EncryptedCookieJar class and active support's CachingKeyGenerator and MessageEncryptor classes for details.
Obviously you'll need to replace the two strings that start "THE VALUE…".
key_generator = ActiveSupport::KeyGenerator.new('THE VALUE OF SECRET_KEY_BASE FROM config/secrets.yml', iterations: 1000)
caching_key_generator = ActiveSupport::CachingKeyGenerator.new(key_generator)
caching_key_generator.generate_key('encrypted cookie')
caching_key_generator.generate_key('signed encrypted cookie')
secret = caching_key_generator.generate_key('encrypted cookie')
sign_secret = caching_key_generator.generate_key('signed encrypted cookie')
encryptor = ActiveSupport::MessageEncryptor.new(secret, sign_secret, serializer: ActionDispatch::Cookies::NullSerializer)
session_value = CGI::unescape('THE VALUE OF THE SESSION COOKIE')
serialized_result = encryptor.decrypt_and_verify(session_value)
result = Marshal.load(serialized_result)
The result, for me, is a hash that looks exactly the session hash in Rails.
If it doesn't work for you, you may be using a different serializer so need to replace Marshal.load with whatever you need. Just take a look at serialized_result and see.

Rails: How to find out logged users in my rails application?

I save user ids to session[:user_id] from database when user logs in . What should I do to find out number of logged in users?
I use ActionDispatch::Session::CacheStore for session storage.
Please guide me the correct way to achieve my objective.
Probably the best solution would be to switch to using the Active Record Session store. It's easy to do - you just need to add (and run) the migration to create the sessions table. Just run:
rake db:sessions:create
Then add the session store config to: config/initializers/session_store.rb like so:
YourApplication::Application.config.session_store :active_record_store
Once you've done that and restarted your server Rails will now be storing your sessions in the database.
This way you'll be able to get the current number of logged in users using something like:
ActiveRecord::SessionStore::Session.count
Although it would be more accurate to only count those updated recently - say the last 5 minutes:
ActiveRecord::SessionStore::Session.where("updated_at > ?", 5.minutes.ago).count
Depending on how often you need to query this value you might want to consider caching the value or incrementing/decrementing a cached value in an after create or after destroy callback but that seems like overkill.
When a session is created or destroyed, you could try implementing a session variable that increments or decrements and use some helpers to increment/decrement the counter.
def sessions_increment
if session[:count].nil?
session[:count] = 0
end
session[:count] += 1
end
def sessions_decrement
if session[:count].nil?
session[:count] = 0
end
session[:count] -= 1
end

Race condition with rails sessions

There is an array inside the session hash which I'm adding things to it. The problem is, sometimes multiple requests get processed at the same time (because ajax), and the changes a request makes to the array is then replaced by the changes made by the second request.
Example, the array first looks like this:
[63, 73, 92]
Then the first request adds something to it:
[63, 73, 92, 84]
The second request does the same thing (but works on the older version obviously):
[63, 73, 92, 102]
So in the end the array doesn't look like it should. Is there a way to avoid that?
I tried to use the cache store, the active record store and the cookie store. Same problem with all of them.
Thanks.
Session race conditions are very common in Rails. Redis session store doesn't help either!
The reason is Rails only reads and creates the session object when it receives the request and writes it back to session store when request is complete and is about to be returned to user.
Java has a solution for this called session replication. We can build our own concurrent redis based session store. The following is pretty much it. Except the update method is missing the lock. But it almost never happens to get race condition in it.
To get session hash just use concurrent_session which returns a hash.
To set some value in it, use update_concurrent_session method. It deep merges the new hash into the old value:
class ApplicationController < ActionController::Base
def concurrent_session
#concurrent_session ||= get_concurrent_session
end
def update_concurrent_session h
return unless session.id.present?
#concurrent_session = get_concurrent_session.deep_merge(h)
redis.set session_key, #concurrent_session.to_json
redis.expire session_key, session_timeout
end
private
def redis
Rails.cache.redis
end
def session_timeout
2.weeks.to_i
end
def session_key
'SESSION_' + session.id.to_s if session.id.present?
end
def get_concurrent_session
return {} unless session.id.present?
redis.expire session_key, session_timeout
redis.get(session_key).yield_self do |v|
if v
JSON.parse v, symbolize_names: true
else
{}
end
end
end
end
Usage example:
my_roles = concurrent_session[:my_roles]
update_concurrent_session({my_roles: ['admin', 'vip']})
There really isn't a great solution for this in Rails. My first suggestion would be to examine your use case and see if you can avoid this situation to begin with. If it's safe to do so, session data if often best kept on the client, as there are a number of challenges that can come up when dealing with server-side session stores. On the other hand, if this is data that might be useful long term across multiple page requests (and maybe multiple sessions), perhaps it should go in the database.
Of course, there is some data that really does belong in the session (a good example of this is the currently logged-in user). If this is the case, have a look at http://paulbutcher.com/2007/05/01/race-conditions-in-rails-sessions-and-how-to-fix-them/, and specifically https://github.com/fcheung/smart_session_store, which tries to deal with the situation you've described.
It is a simple race condition, just lock the request using any locking mechanism like
redis locker
RedisLocker.new('my_ajax').run! { session[:whatever] << number }
Load the current session, or create a new one if necessary
Save a copy of the unmodified session for future reference
Run the code of the action
Compare the modified session with the copy saved previously to determine what has changed
If the session has changed:
Lock the session
Reload the session
Apply the changes made to this session and save it
Unlock the session
From Race conditions in Rails sessions and how to fix them.

How to store find_by_sql_results in session variable

Here is my controller code to check login details of a user
def validateLogin
#email = params[:userEmail1]
#pass = params[:userPassword1]
if params[:userEmail1] != nil
valid_user = Userprofile.find_by_sql(["select * from userprofiles where userEmail=? and userPassword=?", #email, #pass])
if valid_user.count > 0
session[:email] = #email
session[:uid] = valid_user.id
session[:userType] = valid_user.userType # usertype is a column in userprofiles table
# But here i am not receiving the usertype it gives error that undefined variable usertype.
redirect_to "/userhomes/"
else
flash[:message] = "Either email or password is incorrect"
redirect_to '/'
end
else
flash[:message]="Fields can not be blank"
render :action=>'defaults'
end
Please help
session[:userType] = valid_user.userType
# Error: (usertype is a column in userprofiles table)
But here i am not receiving the usertype it gives error that undefined variable usertype.
You are seeing this error because you receive an array of objects from find_by_sql. You even check the size of the array in your if clause.
From your code I think you expect only one returned object. But you still need to get it from the array like so:
profiles = Userprofile.find_by_sql(["select * from userprofiles where userEmail=? and userPassword=?", #email, #pass])
if profiles.count > 0
user_profile = profiles[0]
#... your other stuff
end
Another variant which also much better uses Rails idioms and especially ActiveRecord as is was inteded to be used is to let it construct the SQL by itself which is generally safer, less prone to errors and cacheble.
You didn't write which version of Rails you are using, but for Rails 2.3.x, it looks like this
user_profile = Userprofile.first(:conditions => {:userEmail => #email, :userPassword => #pass})
For Rails 3.x, it looks like this:
user_profile = Userprofile.where(:userEmail => #email, :userPassword => #pass).first
Both variants expect that you have a model called Userprofile, which you generally require to effectively work with database objects in Rails. What both queries do is to create a new model instance from the first row returned from your query (that's what the first does).
Generally, you should get a book or some guide on the internet and learn how to properly use ActivRecord. Note that the API has seriously changed between Rails 2.3 and Rails 3 so make sure to use a guide for your actual Rails version.
And as a final advice, you shouldn't store actual ActiveRecord objects in the session. They would need to be serialized on store and de-serialized on access. What makes it hard (or impossible to track object references.
Also, Rails uses the cookie session store by default, which means that the whole session data is stored in a cookie on the client. The data therein in fully readyabkle to anyone with access to the cookie as it is only signed to restrict tampering with the data, but it is not encrypted. Thus, in your case anyone would be able to ready the (unecrypted) password.
Instead of storing the model object, you should store it's id instead and get the actual (and up-to-date) object from the database instead on each request. This is much easier, saves you from cache inconsistencies (what happens if the user changes her password) and is probably faster than to transfer a huge session cookie from the client on each request.

Resources