Using SAML for Single Sign On? Make sure you know about this security hole & fix

The convenience of Single Sign On (SSO) protocols are becoming increasingly popular for IT folks, and for good reason: it makes life so much easier for IT pros and business users to get secure access to day-to-day software and web tools while maintaining the integrity of their password systems. With LogMeIn’s recent acquisition of Meldium, it’s more clear than ever that SSO is growing and becoming a more standardized part of the IT manager’s toolkit. This means security is becoming a greater concern as well as more people rely on SSO as a critical infrastructure piece. how to address Single Sign On security holes

We learned this firsthand a few months ago when our friends, German security researchers Vladislav Mladenov, Julian Krautwald, Florian Feldmann and Christian Mainka, alerted us to a common flaw of the verification of SSO logins related to the popular SAML standard that affected us directly, in addition to a number of other well-known and trusted SSO providers (whom we notified directly of the potential security hole). Anyone following the instructions in this common Ruby toolkit to implement SAML for SSO would experience the same security issue.

While we had no indication that the security flaw was being exploited we came up with a fix quickly because we take security seriously, and we highly recommend anyone using SAML do the same. How it works, in a nutshell: this particular flaw could in theory allow a malicious service provider (MalSP) access to your Panorama9 login if you use the same SAML identity provider (IdP) to log into both MalSP and Panorama9.

Below are step-by-step instructions on how we implemented a fix. We’d love to hear your thoughts or feedback on improving this workflow in the comments! Stay secure, everyone.

How-To address the Single Sign On security hole

First step is to validate the InResponseTo ID received by your application. This will ensure that your application was actually contacted at the URL you announced as the SAML initialization point. The SAML documentation has this initialization example:

def init
  request = OneLogin::RubySaml::Authrequest.new
  redirect_to(request.create(saml_settings))
end

This will redirect the users browser back to the Identity Provider, but is missing that you need to remember the ‘request identifier’. It’s an ID generated by your application and will be included in the reply your application later receives during the consumption step. Example how to save the ID during the initialization:

def init
  request = OneLogin::RubySaml::Authrequest.new
  request_doc_xml = request.create_authentication_xml_doc(saml_settings)
  SAMLCache.new(:time_stamp => Time.now.to_i, :request_identifier => request_doc_xml.elements[1].attributes["ID"].to_s).save
  redirect_to(auth_to_uri(request_doc_xml, saml_settings))
end

During the consumption step your application should validate that the included ID matches a recent ID. The SAML documentation gives this example, but is missing the validation process:

def consume
  response = OneLogin::RubySaml::Response.new(params[:SAMLResponse])
  response.settings = saml_settings
  
  if response.is_valid? && user = current_account.users.find_by_email(response.name_id)
    authorize_success(user)
  else
    authorize_failure(user)
  end
end

This could be your enhanced consume function:

def consume
  response     = OneLogin::RubySaml::Response.new(params[:SAMLResponse])
  response.settings = saml_settings

  #valid login response from Identity Provider?
  if !response.is_valid? && user = current_account.users.find_by_email(response.name_id)
    authorize_failure(user)
  end

  #valid InResponseTo ID
  request_identifier = response.document.elements[1].attributes["InResponseTo"]
  if !SAMLCache.validate_id(request_identifier)
    authorize_failure(user)
  end

  #authorize
  authorize_success(user)
end

This will protect against MalSP reusing a consume reply sent to MalSP’s application. This is good but it isn’t enough. A sneaky MalSP could get a valid ID from your application and then use that same ID when communicating with an user trying to access MalSP’s application. Once MalSP receives the consume reply it can also be sent to your application which will then recognize the ID as properly issued and assume that it is valid.

To foil this attack your application must also check that the consume reply is indeed intended for your application – you should validate the SAML audience element and recipient attribute. This way you can be sure that the received consume reply was issued by the Identity Provider for your application. Your already enhanced consume function should also do something like this:

#extract audience and recipient from response
audience = response.document.elements[1].elements['saml:Assertion'].elements['saml:Conditions'].elements['saml:AudienceRestriction'].elements['saml:Audience'].to_s
recipient = response.document.elements[1].elements['saml:Assertion'].elements['saml:Subject'].elements['saml:SubjectConfirmation'].elements['saml:SubjectConfirmationData'].attributes["Recipient"].to_s

#validate them
if audience !~ /YOUR_DOMAIN/ || recipient !~ /^URL_TO_CONSUME/
  authorize_failure(user)
end

The SAML audience element ( YOUR_DOMAIN in the above example, e.g. panorama9.com) and recipient attribute (URL_TO_CONSUME in the above example, e.g. https://dashboard.panorama9.com/saml/consume )values needs to be set by the Identity Provider. In our experience this is not always the case but we hope we’ve explained why this is vital to your security. We hope this guide helps you address the Single Sign On security hole. Feel free to leave a comment if you have additional questions.

Fresh Tips Directly in Your Inbox

Submit your email address below and get our updates on the most important things MSPs should know.

Trackbacks

Leave a Reply

Your email address will not be published. Required fields are marked *