From 17e082a63ec55a4eaf2527fd299ed7e80329197d Mon Sep 17 00:00:00 2001 From: cktricky Date: Sun, 18 Aug 2013 20:46:40 -0400 Subject: [PATCH] I believe the secure_compare tutorial is complete --- .../_insecure_compare.html.erb | 46 +++++++++++++++++-- 1 file changed, 43 insertions(+), 3 deletions(-) diff --git a/app/views/layouts/tutorial/broken_auth_sess/_insecure_compare.html.erb b/app/views/layouts/tutorial/broken_auth_sess/_insecure_compare.html.erb index fee6f8b..030c51e 100644 --- a/app/views/layouts/tutorial/broken_auth_sess/_insecure_compare.html.erb +++ b/app/views/layouts/tutorial/broken_auth_sess/_insecure_compare.html.erb @@ -32,7 +32,28 @@
- +

+ Within app/models/user.rb +

+
+			  	    def self.authenticate(email, password)
+			  	         auth = nil
+			  	          user = find_by_email(email)
+			  	          raise "#{email} doesn't exist!" if !(user)
+			  	           if user.password == Digest::MD5.hexdigest(password)
+			  	             auth = user
+			  	           else
+			  	            raise "Incorrect Password!"
+			  	           end 
+			  	         return auth
+			  	    end
+			  	
+

+ Ignore for a moment that the application actually tells you whether or not an email address exists :-). Instead, let's look at what would happen if this error message wasn't so specific. Even if the error message vulnerability was mitigated (because it indicates whether or not a user exists), there will be some variations in the application's response between a user that exists and one that does not (however so slight, considering MD5 is in use). +

+

+ To understand why, let's follow the flow of this code example. Firstly, the application look for a user by email. If not found, nothing else really happens. No further processing, password comparison, etc. If a user is found, we will perform a password comparison and process as normal. +

@@ -46,7 +67,26 @@
- +

Lack of Password Complexity - SOLUTION

+

+ Within app/models/user.rb: +

+
+				 def self.authenticate(email, password)
+				       user = find_by_email(email) || User.new(:password => "")
+				        if Rack::Utils.secure_compare(user.password, Digest::MD5.hexdigest(password))
+				          return user
+				        else
+				          raise "Incorrect username or password"
+				        end 
+				   end
+			  
+

+ To mitigate this attack and shore up our weakness, we do two things. The first is to find a user by email, if they don't exist, create a new user object in memory (not in the database) and assign it a blank password value. This means, regardless of whether or not a user exists, we will have a user to perform some processing on. The next is, we take the input from the user and match it against the user object's password leveraging secure_compare. This is a function (secure_compare) used to ensure that when a comparison happens, it will always take the same amount of time. +

+

+ In summary, we have ensured that regardless of whether or not a user exists, a password comparison will always occur and it will take the same amount of time to complete. +

@@ -61,7 +101,7 @@

- Test + Timing is everything. Authenticating is important too.