The Problem with shopify_app’s Session usage

An issue was recently opened on the Shopify App project because of a change in what Rails allowed you to set inside a session. This caused the project to break for people looking to play with the API simply because they couldn’t set their session up correctly.

I’ve rarely used the gem simply because its generators are very noisy, but after talking to a friend about this I became extremely aware of what the problem was. The following is the offending code:

  sess = ShopifyAPI::Session.new(
                            params[:shop],
                            response['credentials']['token'] 
                          )
  session[:shopify] = sess        
  flash[:notice] = "Logged in"
  redirect_to return_address

Now why is this a problem? By default, Rails uses cookies for storing sessions and cookies are sent off to your client. Thankfully the cookies are run through an HMAC so they cannot be tampered with, but the data is still raw and available for anyone to read.

Reading the Session

This can easily be shown by grabbing the contents of our cookie from chrome:

broken cookies yo

There are a few things that need to be massaged before we can read the data, that primarily being the trailing –ae63169593d0e2e68a72977324f5791d6d31b8b8 which I believe is the HMAC. Also the %3D needs to be converted back into a = for us to work with the data correctly. You can use a utility like Rack::Utils#unescape to take care of this if you don’t want to do it by hand. The contents of the cookie are simply Base64 encoded, so let’s decode the message and see what’s there:

require 'base64'

msg = "BAh7C0kiD3Nlc3Npb25faWQGOgZFVEkiJTZmZWVlNzlhOTA4YzBlOGQ3YzYwM2U3ZmY4OWRjMGFkBjsAVEkiDnJldHVybl90bwY7AEZJIgYvBjsAVEkiEF9jc3JmX3Rva2VuBjsARkkiMW1MVlJITnFoNjJ4UllScDA2VEpPNkVoQWpLSkZoaVF2eFlnczIyMDhrV0E9BjsARkkiE29tbmlhdXRoLnN0YXRlBjsAVEkiNWU4NTJmZTM1YzZjZWYyYmY2NWJjYjIzY2RjMDllOWRkODkyZjUwMzE4YTNlMWNiNgY7AEZJIgxzaG9waWZ5BjsARm86GFNob3BpZnlBUEk6OlNlc3Npb24HOglAdXJsSSIbanVzdG1vcHMubXlzaG9waWZ5LmNvbQY7AFQ6C0B0b2tlbkkiJWI4YmMxNjQyMmUyODFhODM3MDhhN2NlNmIxYzRhZWZiBjsAVEkiCmZsYXNoBjsAVG86JUFjdGlvbkRpc3BhdGNoOjpGbGFzaDo6Rmxhc2hIYXNoCToKQHVzZWRvOghTZXQGOgpAaGFzaHsGOgtub3RpY2VUOgxAY2xvc2VkRjoNQGZsYXNoZXN7BjsNSSIOTG9nZ2VkIGluBjsAVDoJQG5vdzA="

raw_contents = Base64.strict_decode64(msg)

raw contents

Woah… so that’s looking pretty hairy and by just looking over it visually we can see some sensitive data in there. But let’s make things even easier on ourselves. We can use the Marshal module to actually convert that data into objects that are super easy to read and use.

# require the libraries so we can unmarshal the data
require 'action_dispatch'
require 'shopify_api'

session = Marshal.load(raw_contents)
#> {
    "session_id"=>"6feee79a908c0e8d7c603e7ff89dc0ad",
    "return_to"=>"/",
    "_csrf_token"=>"mLVRHNqh62xRYRp06TJO6EhAjKJFhiQvxYgs2208kWA=",
    "omniauth.state"=>"e852fe35c6cef2bf65bcb23cdc09e9dd892f50318a3e1cb6",
    "shopify"=>
      #,
    "flash"=>
      #,
        @closed=false,
        @flashes={:notice=>"Logged in"},
        @now=nil>
   }
shopify = session['shopify']
puts "Domain: #{shopify.url}, Access Token: #{shopify.token}"
> Domain: justmops.myshopify.com, Access Token: b8bc16422e281a83708a7ce6b1c4aefb

Now it’s plain as day! We were storing raw Shopify session objects in the cookies that we are sending back to our clients and we were able to decode them. Depending on your configuration this could mean that malicious third parties could hijack your access token and do some really bad things to a merchants shop.

How do I avoid this?

The rails security guidelines suggest against storing any kind of sensitive data in the session and this style of vulnerability is the #2 in the OWASP Top 10 vulnerabilities of 2013. The default way that it was done in shopify_app was definitely something you’d want avoid in a production environment. Unfortunately nothing was put in place to help provide the education needed to ensure people moved away from this practice.

When building an application you want to ensure that sensitive information never goes anywhere you cannot trust. While it is convenient to store the data in the session, the client is about as untrustworthy as you can get. What should be stored in the session is something that you can use to identify the data you need.

How to Fix it

In a patch that has been merged into master, you instead configure a ShopifySessionRepository such that it knows how to store and retrieve sessions from your server. For example, you could simply have your Shop model respond to those two methods and you’d be set. When a session is stored you return the ID for the model such that you can retrieve a ShopifyAPI::Sesssion when you need it at a later date.

A new version of the shopify_app gem was just released, so you can easily upgrade your application by simply performing gem update shopify_app which will upgrade you to version 5.0.0. If you are starting a new app you’ll want to ensure that your Gemfile is using the newest version such that you are able to use the safer session implementation.

gem 'shopify_app', ">= 5.0.0"

If you are using the shopify_app in your project, take a look at your sessions controller to verify that your show action doesn’t have the offending code as mentioned above. If it does, you should remove that code and replace it with a safer way to access the credentials associated with a shop. Consider looking into what was added to shopify_app for one approach to this problem.