Skip to content

joevin-slq-docto/IDORails

Repository files navigation

IDORails

This README will you make able to rebuild this app, without source code, by following this tutorial.

IDORs has been left on articles#show and articles#destroy.

Create a basic application

rails new IDORails
cd IDORails

bin/rails generate controller Articles index --skip-routes
bin/rails generate model Article title:string body:text

bin/rails db:migrate

# append in config/routes.rb
Rails.application.routes.draw do
  root "articles#index"

  resources :articles
end

# append in app/controllers/articles_controller.rb
class ArticlesController < ApplicationController
  def index
    @articles = Article.all
  end

  def show
    @article = Article.find(params[:id])
  end

  def new
    @article = Article.new
  end

  def create
    @article = Article.new(article_params)

    if @article.save
      redirect_to @article
    else
      render :new, status: :unprocessable_entity
    end
  end

  def edit
    @article = Article.find(params[:id])
  end

  def update
    @article = Article.find(params[:id])

    if @article.update(article_params)
      redirect_to @article
    else
      render :edit, status: :unprocessable_entity
    end
  end

  def destroy
    @article = Article.find(params[:id])
    @article.destroy

    redirect_to root_path, status: :see_other
  end

  private
    def article_params
      params.require(:article).permit(:title, :body)
    end
end

# append in app/models/article.rb
class Article < ApplicationRecord
  validates :title, presence: true
  validates :body, presence: true, length: { minimum: 5 }
end

# append in app/views/articles/index.html.erb
<h1>Articles</h1>

<ul>
  <% @articles.each do |article| %>
    <li>
      <%= link_to article.title, article %>
    </li>
  <% end %>
</ul>

<%= link_to "New Article", new_article_path %>

# create file and append in app/views/articles/show.html.erb
<h1><%= @article.title %></h1>

<p><%= @article.body %></p>

<ul>
  <li><%= link_to "Edit", edit_article_path(@article) %></li>
  <li><%= link_to "Destroy", article_path(@article), data: {
                    turbo_method: :delete,
                    turbo_confirm: "Are you sure?"
                  } %></li>
</ul>

# create file and append in app/views/articles/new.html.erb
<h1>New Article</h1>

<%= render "form", article: @article %>

# create file and append in app/views/articles/edit.html.erb
<h1>Edit Article</h1>

<%= render "form", article: @article %>

# create and append in app/views/articles/_form.html.erb
<%= form_with model: article do |form| %>
  <div>
    <%= form.label :title %><br>
    <%= form.text_field :title %>
    <% article.errors.full_messages_for(:title).each do |message| %>
      <div><%= message %></div>
    <% end %>
  </div>

  <div>
    <%= form.label :body %><br>
    <%= form.text_area :body %><br>
    <% article.errors.full_messages_for(:body).each do |message| %>
      <div><%= message %></div>
    <% end %>
  </div>

  <div>
    <%= form.submit %>
  </div>
<% end %>

Execute bin/rails server and check that everything works.

Installing Devise

# More info -> https://github.com/heartcombo/devise
bundle add devise

rails generate devise:install
rails generate devise user

bin/rails db:migrate

# append this in app/controllers/articles_controller.rb
before_action :authenticate_user!

# set this in config/initializers/devise.rb
config.sign_out_via = :get

# append this body of app/views/layouts/application.html.erb
  <body>
    <p style="color: green"><%= notice %></p>
    <p style="color: red"><%= alert %></p>
    <hr />
    <div>
      <%= link_to "Home", root_path, :class => 'navbar-link'  %> |
      <% if user_signed_in? %>
        Logged in as <strong><%= current_user.email %></strong>.
        <%= link_to "Logout", destroy_user_session_path, :class => 'navbar-link'  %>
      <% else %>
        <%= link_to "Sign up", new_user_registration_path, :class => 'navbar-link'  %> |
        <%= link_to "Sign in", new_user_session_path, :class => 'navbar-link'  %>
      <% end %>
    </div>
    <hr />
    <%= yield %>
  </body>

# add one-to-many relation between articles and users
rails g migration AddUserRefToArticles user:references
bin/rails db:reset ; bin/rails db:migrate

# modify models/article.rb
belongs_to :user

# modify models/user.rb
has_many :articles

# add in app/controllers/articles_controller.rb#create, after `@article = Article.new(article_params)`:
@article.user = current_user

# add in app/controllers/articles_controller.rb#update, after `@article = Article.find(params[:id])`:
@article.user = current_user

Execute bin/rails server and check that everything works.

Installing Pundit

# More info -> https://github.com/varvet/pundit
bundle add pundit

# replace app/controllers/application_controller.rb content with this
class ApplicationController < ActionController::Base
  include Pundit::Authorization
  after_action :verify_authorized, except: :index, unless: :devise_controller?
  after_action :verify_policy_scoped, only: :index
  
  rescue_from Pundit::NotAuthorizedError, with: :pundishing_user

  private

  def pundishing_user
    flash[:alert] = "You are not authorized to perform this action."
    redirect_to articles_path
  end
end

# run thoses commands
rails g pundit:install
rails g pundit:policy article

# replace app/policies/application_policy.rb content with this
class ApplicationPolicy
  class Scope
    attr_reader :user, :scope

    def initialize(user, scope)
      @user = user
      @scope = scope
    end
  end
end

# replace app/policies/article_policy.rb content with this
class ArticlePolicy < ApplicationPolicy
  attr_reader :user, :article

  def initialize(user, article)
    @user = user
    @article = article
  end

  def index?
    true
  end

  def show?
    true # @article.user_id == @user.id
  end

  def create?
    true
  end

  def new?
    create?
  end

  def update?
    @article.user_id == @user.id
  end

  def edit?
    update?
  end

  def destroy?
    @article.user_id == @user.id
  end

  class Scope < Scope
    def resolve
      scope.where(articles: {user_id: user.id})
    end
  end
end

# inside app/controllers/articles_controller.rb#index, replace '@articles = Article.all' by:
@articles = policy_scope(Article)
# inside app/controllers/articles_controller.rb, for show, new, create, edit, update and destroy methods, append:
authorize @article


# append this in db/seeds.rb
user = User.create! :email => 'first@user.fr', :password => 'secret', :password_confirmation => 'secret'
user = User.create! :email => 'second@user.fr', :password => 'secret', :password_confirmation => 'secret'

10.times do |i|
  Article.create({title: "Post #{i + 1}", body: 'test body', user_id: i < 5 ? 1 : 2})
end

# run this
bin/rails db:reset

Execute bin/rails server and check that everything works.

About

Basic Rails application with IDOR vulnerabilities

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published