Monday, December 31, 2012

Basics of Rails Part 5


If you'd like to skip coding this up or are having issues, you can find the application source code here.

To start at the code which represents the completion of Parts 1-5, do the following:

$ git clone git://github.com/cktricky/attackresearch.git

$ cd attackresearch/

$ git reset --hard 3c3003d0b087d64f60446f74bcb2deedca60691f

$ bundle install

$ rake db:create db:migrate unicorn:start[4444]

=========== Part 5 Code and Walkthrough ==============

The first thing we need to do in this post is correct a mistake in the last post.

Type the following to change the model "Users" to it's singular form "User". If you don't, it will cause problems with Rails routing.

$ rails d model Users

$ rails g model User first_name:string last_name:string email:string password_hash:string password_salt:string admin:boolean

$ rake db:drop

$ rake db:create

$ rvmsudo rake db:migrate


Also, a small but important detail, please ensure within your Gemfile you change:

gem 'bcrypt-ruby', '~> 3.0.0' 

to

gem 'bcrypt-ruby', '~> 3.0.0', :require => 'bcrypt'


Now........back to the series. So, we last left off where a login page was visible when browsing to your site but it didn't really do anything.  Time to rectify that.

Within the Sessions controller, a create method was defined and in it we called the User model's method, "authenticate". We have yet to define this "authenticate" method so let's do that now.

Located at /app/models/user.rb

Also, we are going to add an encrypt method and call it using the "before_save" Rails method. Basically, we are going to instruct the User model to call encrypt_password when the "save" method is called. For example:

me = User.new
me.email = "test@ar.com"
me.password = "test"
me.password_confirmation = "test"
me.save <~ at this point, any "before_save" methods get called

So when you see something like user = User.new and user.save you know that the encrypt_password method will be called by Rails prior to saving the user data because of the "before_save" definition on line 4.


Now we have to add a few more things:

attr_accessor :password

validates_confirmation_of :password
validates_presence_of :password, :on => :create
validates_presence_of :email
validates_uniqueness_of :email

These are basically Rails validation functions that get called when attempting to save the state of an object that represents a User. The exception being "attr_accessor", which is a standard Ruby call that allows an object to be both a getter & setter.

Okay, now let's see what it looks like.


Alright, so now we have a login page that does something but we need to create users. For this application's purpose, we are going to allow user's to signup. Let's provide a link for this purpose on the login page and even further, let's create a navigation bar at the top. We want this navigation bar visible on every page visited by the user. Easiest way to do that is to make it systemic and place it within the application.html.erb file under the layouts folder. Unless overridden, all views will inherit the properties specified in this file (navigation bar, for example).


Located at /app/views/layouts/application.html.erb

Without explaining all of Twitter-Bootstrap, one important thing to note is the class names of the HTML tags (ex: <div class="nav">) are how we associate an HTML element with a Twitter-Bootstrap defined style.

The logic portion, the portion that belongs to Ruby and Rails, are Lines 13 -18. Effectively we are asking if the user (current_user) visiting the page is authenticated (exists), if they are (do exist), show a link to the logout path. Otherwise, render a login and signup path link.

You are probably wondering where link_to and current_user come from. Rails provides built-in methods and you'll notice, in the views, they are typically placed between <%= and %>. So, link_to is a built in method. However, current_user is defined by us within the application controller and is NOT a built-in method.

Located at /app/controllers/application_controller.rb
Notice on line 8 we define a method called current_user. This pulls a user_id value from the Rails session. In order to make the current_user method accessible outside of just this controller and extend it to the view, we have annotated it as a helper_method on line 4.

The next thing we need to do now is actually make the signup page. First, let's modify the attributes that are mass assignable via attr_accessilbe in the user model file.



Next, review the users_controller.rb file and add the methods new & create. When new is called, instantiate a new blank User object (@user). Under the create method, we can modify a new user element leveraging the parameters submitted by the user (email, password, password_confirmation) to create the user. 



Explanation of the Intended Flow - 

  • User clicks "signup" and is sent to /signup (GET request). 
  • User is routed to the "new" action within the "user" controller and then the HTML content is rendered from - /app/views/users/new.html.erb. 
  • Upon filling in the form data presented via new.html.erb, the user clicks "submit" and this data is sent off, this time in a POST request, to /users. 
  • The POST request to /users translates to the "create" action within the "user" controller. 


Now, obviously we are missing something.....we need a signup page! Let's code that up under new.html.erb.


/app/views/users/new.html.erb

The internals of Rails and how we are able to treat @user as an enumerable object and create label tags and text field tags might be a little too complicated for this post. That being said, basically, the @user object (defined in the User controller under the new action - ex: @user = User.new) has properties associated with it such as email, password, and password confirmation. When Rails renders the view, it generates the parameter names based off the code in this file. In the end, the parameters will look like something like user[email] and user[password_confirmation], for example. Here is what the actual request looks like in Burp...


Signup form generated by the code within /app/views/users/new.html.erb

Raw request of signup form submission captured.

Okay, so, now we have registered a user. The last piece here is to have a home page to view after successful authentication and also code the logout link logic so that it actually does something.

In order to do this, let's make a quick change in the sessions controller. Under the create method, we change home_path to home_index_path as well as create a destroy method which calls the Rails method "reset_session" and redirects the user back to the root_url. Also, remove the content within the index action under the home controller.

Okay, here is what I mean...

Session Controller - Note changes on Lines 9 and additions on Lines 16-19.

Home Controller - Note that the code contained within the index action has been removed.
You should be able to complete the authentication flow now! Stayed tuned for Part 6.

Note: If you see any security holes in some of the code shown in this series please remember that's kind of the point and refrain from commenting until later.

cktricky

6 comments:

Digininja said...

Can I be pedantic and say the encrypt_password method should be called hash_password

Digininja said...
This comment has been removed by a blog administrator.
cktricky said...
This comment has been removed by the author.
cktricky said...

Good point. Things to clean up, for sure. Good catch. Just changed some code over in the Railsgoat project because of this :-)

https://github.com/OWASP/railsgoat/commit/65eb2caeaf1271c66a6b59e67e0d3ce22b1e3455

Cheers

Digininja said...

cktricky said...

Digininja - wrote:

"With Rails 4 the code doesn't work any more. Its the introduction of strong parameters that broke things.

I was hoping to learn how they work so I could submit a patch rather than just report but haven't had time. "

Ken - Your comment was accidentally deleted, my apologies. Blogger won't let us undo :-(.

Strong parameters are basically a removal of the attr_accessible portion where content is checked against this list during mass assignment. So instead, the protection shifts from the model off to the controller and you have explicitly say "this is something that is okay to mass assign". We have a post on this here - http://carnal0wnage.attackresearch.com/2013/05/rails-strong-parameters.html.

HTH