[Pentesterlab write-up] Web For Pentester II - Authorization & Mass Assignment

Continuamos con dos bloques más del lab 'Web for Pentester II',  esta vez con ejercicios para:

- explotar malas implementaciones en el esquema de autorización del aplicativo, que permiten acceder a información (URLs) que debería estar restringida, al menos sin previa autenticación.

- manipular registros de la aplicación para modificar elementos a los que el usuario normalmente no debería tener acceso, como contraseñas, permisos o roles. Es lo que se conoce como Mass Assignment.

Como veréis estos ejercicios son bastante sencillitos (quizás demasiado) y no requieren apenas esfuerzo, pero os recomiendo echarle al menos un vistazo rápido.


AUTHORIZATION

Ejercicio 1:

El primero ejemplo es un fallo común en el desarrollo web, el desarrollador creó la página de inicio de sesión pero no bloqueó las páginas que tienen información sensible a través de cookies u otros tokens de seguridad, esto significa que cualquiera que sepa la ruta (o la obtenga mediante un crawling o fuzzing de directorios) puede acceder sin autenticación.

http://vulnerable/authorization/example1



En esta ocasión no hace falta ni tirarle un wfuzz o un dirb, la URL es totalmente predecible:

http://vulnerable/authorization/example1/infos/2

SERVIDOR
require 'sinatra/base'
require 'active_record'
require 'digest/md5'

class AuthorizationExample1 < PBase

def self.db
"authorization_example1"
end



ActiveRecord::Base.configurations[db] = {
:adapter => "mysql2",
:host => "localhost",
:username => "pentesterlab",
:password => "pentesterlab",
:database => AuthorizationExample1.db
}

SEED = "MagicS33d_AuthorizationExample1"
class User < ActiveRecord::Base
establish_connection AuthorizationExample1.db
end

class Info < ActiveRecord::Base
establish_connection AuthorizationExample1.db
end


configure {
recreate() if $dev
ActiveRecord::Base.establish_connection AuthorizationExample1.db
unless ActiveRecord::Base.connection.table_exists?("#{db}.infos")
ActiveRecord::Migration.class_eval do
create_table "#{AuthorizationExample1.db}.infos" do |t|
t.string :title
t.text :details
end
end
end

unless ActiveRecord::Base.connection.table_exists?("#{db}.users")
ActiveRecord::Migration.class_eval do
create_table "#{AuthorizationExample1.db}.users" do |t|
t.string :username
t.string :password
end
end
end


User.create(:username => 'user1', :password => Digest::MD5.hexdigest(SEED+"pentesterlab"+SEED))
Info.create(:title => "Confidential", :details => "Do not share")
Info.create(:title => "Confidential 2", :details => "Do not redistribute")
}




def self.path
"/authorization/example1/"
end



set :views, File.join(File.dirname(__FILE__), 'example1', 'views')
use Rack::Session::Sequel

get '/' do
@infos = Info.all
if session[:user] and User.find(session[:user])
return erb :index
end
if params['username'] && params['password']
@user = User.where( :username => params['username'].to_s,
:password => Digest::MD5.hexdigest(SEED+params['password']+SEED)).first
if @user
session[:user] = @user
return erb :index
end
end
erb :login
end

get "/logout" do
session.clear
redirect "#{AuthorizationExample1.path}"
end

get "/infos/:id" do
@info = Info.find(params[:id].to_s)
erb :info
end
end


Ejercicio 2:

En el siguiente ejercicio el desarrollador corrigió el problema anterior por lo que no accederemos al recurso si no nos hemos loggeado anteriormente. Sin embargo, lo que no hizo correctamente es bloquear el acceso a las páginas según el valor de la cookie o mediante cualquier otra manera, por lo que si iniciamos sesión podemos acceder a la información de cualquier otra cuenta de usuario.

http://vulnerable/authorization/example2/infos/2


http://vulnerable/authorization/example2/infos/3

SERVIDOR
require 'sinatra/base'
require 'active_record'
require 'digest/md5'

class AuthorizationExample2 < PBase

def self.db
"authorization_example2"
end

ActiveRecord::Base.configurations[db] = {
:adapter => "mysql2",
:host => "localhost",
:username => "pentesterlab",
:password => "pentesterlab",
:database => AuthorizationExample2.db
}


SEED = "MagicS33d_AuthorizationExample2"
class User < ActiveRecord::Base
establish_connection AuthorizationExample2.db
has_many :infos
end

class Info < ActiveRecord::Base
establish_connection AuthorizationExample2.db
belongs_to :user
end

configure {
recreate() if $dev
ActiveRecord::Base.establish_connection AuthorizationExample2.db
unless ActiveRecord::Base.connection.table_exists?("#{db}.infos")
ActiveRecord::Migration.class_eval do
create_table "#{AuthorizationExample2.db}.infos" do |t|
t.string :title
t.text :details
t.integer :user_id
end
end
end

unless ActiveRecord::Base.connection.table_exists?("#{db}.users")
ActiveRecord::Migration.class_eval do
create_table "#{AuthorizationExample2.db}.users" do |t|
t.string :username
t.string :password
end
end
end


user1 = User.create(:username => 'user1', :password => Digest::MD5.hexdigest(SEED+"pentesterlab"+SEED))
user1.infos << Info.new(:title => "Confidential user1", :details => "Do not share")
user1.infos << Info.new(:title => "Confidential user1 (2)", :details => "Do not redistribute")

user2 = User.create(:username => 'user2', :password => Digest::MD5.hexdigest(SEED+"unkn0wn"+SEED))
user2.infos << Info.new(:title => "Confidential user2", :details => "Do not share")
user2.infos << Info.new(:title => "Confidential user2 (2)", :details => "Do not redistribute")
}




def self.path
"/authorization/example2/"
end



set :views, File.join(File.dirname(__FILE__), 'example2', 'views')
use Rack::Session::Sequel

get '/' do
if params['username'] && params['password']
@user = User.where( :username => params['username'].to_s,
:password => Digest::MD5.hexdigest(SEED+params['password']+SEED)).first
if @user
session[:user] = @user
@infos = @user.infos
return erb :index
end
elsif session[:user]
@user = User.find(session[:user])
@infos = @user.infos
return erb :index
end
erb :login
end

get "/logout" do
session.clear
redirect "#{AuthorizationExample2.path}"
end

get "/infos/:id" do
if session[:user] and User.find(session[:user])
@info = Info.find(params[:id].to_s)
erb :info
else
session.clear
redirect "#{AuthorizationExample2.path}"
end
end
end
Ejercicio 3: 
 
Este ejemplo sigue el hilo de los anteriores. Esta vez no podemos acceder a la información de otros usuarios... pero si podemos editarla. 
Sólo tenemos que ir a la página de edición de la cuenta user1 y cambiar el valor en la URL :.
 



http://vulnerable/authorization/example3/infos/edit/1



http://vulnerable/authorization/example3/infos/edit/3



SERVIDOR
require 'sinatra/base'
require 'active_record'
require 'digest/md5'

class AuthorizationExample3 < PBase

def self.db
"authorization_example3"
end

ActiveRecord::Base.configurations[db] = {
:adapter => "mysql2",
:host => "localhost",
:username => "pentesterlab",
:password => "pentesterlab",
:database => AuthorizationExample3.db
}


SEED = "MagicS33d_AuthorizationExample3"
class User < ActiveRecord::Base
establish_connection AuthorizationExample3.db
has_many :infos
end

class Info < ActiveRecord::Base
establish_connection AuthorizationExample3.db
belongs_to :user
attr_protected :user_id
end

configure {
recreate() if $dev
ActiveRecord::Base.establish_connection AuthorizationExample3.db
unless ActiveRecord::Base.connection.table_exists?("#{db}.infos")
ActiveRecord::Migration.class_eval do
create_table "#{AuthorizationExample3.db}.infos" do |t|
t.string :title
t.text :details
t.integer :user_id
end
end
end

unless ActiveRecord::Base.connection.table_exists?("#{db}.users")
ActiveRecord::Migration.class_eval do
create_table "#{AuthorizationExample3.db}.users" do |t|
t.string :username
t.string :password
end
end
end


user1 = User.create(:username => 'user1', :password => Digest::MD5.hexdigest(SEED+"pentesterlab"+SEED))
user1.infos << Info.new(:title => "Confidential user1", :details => "Do not share")
user1.infos << Info.new(:title => "Confidential user1 (2)", :details => "Do not redistribute")

user2 = User.create(:username => 'user2', :password => Digest::MD5.hexdigest(SEED+"unkn0wn"+SEED))
user2.infos << Info.new(:title => "Confidential user2", :details => "Do not share")
user2.infos << Info.new(:title => "Confidential user2 (2)", :details => "Do not redistribute")
}




def self.path
"/authorization/example3/"
end



set :views, File.join(File.dirname(__FILE__), 'example3', 'views')
use Rack::Session::Sequel

get '/' do
if params['username'] && params['password']
@user = User.where( :username => params['username'].to_s,
:password => Digest::MD5.hexdigest(SEED+params['password']+SEED)).first
if @user
session[:user] = @user
@infos = @user.infos
return erb :index
end
elsif session[:user]
@user = User.find(session[:user])
@infos = @user.infos
return erb :index
end
erb :login
end

get "/logout" do
session.clear
redirect "#{AuthorizationExample3.path}"
end

get "/infos/:id" do
if session[:user]
@user = User.find(session[:user])
@info = Info.find(params[:id].to_s)
if @user and @user.infos.include? @info
erb :info
else
session.clear
redirect "#{AuthorizationExample3.path}"
end
else
session.clear
redirect "#{AuthorizationExample3.path}"
end
end

get "/infos/edit/:id" do

if session[:user]
@user = User.find(session[:user])
@info = Info.find(params[:id].to_s)
if @user and @info
erb :edit_info
else
session.clear
redirect "#{AuthorizationExample3.path}"
end
else
session.clear
redirect "#{AuthorizationExample3.path}"
end
end

get "/infos/update_info/:id" do

if session[:user]
@user = User.find(session[:user])
@info = Info.find(params[:id].to_s)
if @user and @user.infos.include?(@info)
@info.update_attributes(params[:info])
@infos = @user.infos
erb :index
else
session.clear
redirect "#{AuthorizationExample3.path}"
end
else
session.clear
redirect "#{AuthorizationExample3.path}"
end
end


end
MASS ASSIGNMENT

Ejercicio 1:

Este es un ejemplo de lo que sería una escalada de privilegios en la aplicación web. Básicamente tenemos dos tipos de cuentas: el usuario estándar y el administrador. Capturando la petición y añadiendo el parámetro y varlo 'user[admin]=1' podemos conseguir el rol deseado:





require 'sinatra/base'
require 'active_record'
require 'digest/md5'

class MassAssignExample1 < PBase
def self.db
"massassign_example1"
end

ActiveRecord::Base.configurations[db] = {
:adapter => "mysql2",
:host => "localhost",
:username => "pentesterlab",
:password => "pentesterlab",
:database => MassAssignExample1.db
}

SEED = "MagicS33d_MassAssignExample1"

class User < ActiveRecord::Base
establish_connection MassAssignExample1.db
end


configure {
recreate() if $dev
ActiveRecord::Base.establish_connection MassAssignExample1.db

unless ActiveRecord::Base.connection.table_exists?("#{db}.users")
ActiveRecord::Migration.class_eval do
create_table "#{MassAssignExample1.db}.users" do |t|
t.string :username
t.string :password
t.integer :admin, :default => 0
end
end
end
}

def self.path
"/massassign/example1/"
end

set :views, File.join(File.dirname(__FILE__), 'example1', 'views')

get '/' do
erb :index
end

get "/signup" do
@user = User.create(params[:user])
erb :user
end

get "/user/:id" do
@user = User.find(params[:id])
erb :user
end



end

Ejercicio 2:

En esta ocasión no podemos crear una cuenta con privilegios de administrador, pero si podemos actualizar la información del usuario autenticado y configurarlo como admin de foma similar a la anterior:

http://vulnerable/massassign/example2/signup?user%5Busername%5D=test&user%5Bpassword%5D=test123&submit=Submit+Query




SERVIDOR
require 'sinatra/base'
require 'active_record'
require 'digest/md5'
require 'rack-session-sequel'


class MassAssignExample2 < PBase

def self.db
"massassign_example2"
end

ActiveRecord::Base.configurations[db] = {
:adapter => "mysql2",
:host => "localhost",
:username => "pentesterlab",
:password => "pentesterlab",
:database => MassAssignExample2.db
}

use Rack::Session::Sequel
SEED = "MagicS33d_MassAssignExample2"

class User < ActiveRecord::Base
establish_connection MassAssignExample2.db
end

configure {
recreate() if $dev
ActiveRecord::Base.establish_connection MassAssignExample2.db
unless ActiveRecord::Base.connection.table_exists?("#{db}.users")
ActiveRecord::Migration.class_eval do
create_table "#{MassAssignExample2.db}.users" do |t|
t.string :username
t.string :password
t.integer :admin, :default => 0
end
end
end
}

def self.path
"/massassign/example2/"
end

set :views, File.join(File.dirname(__FILE__), 'example2', 'views')

get '/' do
erb :index
end

get "/signup" do
params[:user]["admin"] = 0
@user = User.create(params[:user])
session[:user] = @user.id
erb :user
end

get "/profile" do
@user = User.find(session[:user].to_s)
erb :user
end


get "/edit_profile" do
@user = User.find(session[:user].to_s)
erb :profile
end

get "/update_profile" do
@user = User.find(session[:user].to_s)
@user.update_attributes(params[:user])
@user.save
erb :user
end



end

Ejercicio 3:

Por último, para acceder a la información de “Company 2” primero tendremos que visitar la página que permite cambiar la información de la cuenta, capturar la petición GET y agregar '&user%
5Bcompany_id%5D = 2':



http://vulnerable/massassign/example3/update_profile?user[username]=user1&user[password]=pentesterlab&user[company_id]=2&submit=Submit+Query


SERVIDOR
require 'sinatra/base'
require 'active_record'
require 'digest/md5'
require 'rack-session-sequel'


class MassAssignExample3 < PBase

def self.db
"massassign_example3"
end

ActiveRecord::Base.configurations[db] = {
:adapter => "mysql2",
:host => "localhost",
:username => "pentesterlab",
:password => "pentesterlab",
:database => MassAssignExample3.db
}

use Rack::Session::Sequel
SEED = "MagicS33d_MassAssignExample3"

class User < ActiveRecord::Base
establish_connection MassAssignExample3.db
belongs_to :company
end

class Company < ActiveRecord::Base
establish_connection MassAssignExample3.db
has_many :users
end

configure {
recreate() if $dev
ActiveRecord::Base.establish_connection MassAssignExample3.db

unless ActiveRecord::Base.connection.table_exists?("#{db}.users")
ActiveRecord::Migration.class_eval do
create_table "#{MassAssignExample3.db}.users" do |t|
t.string :username
t.string :password
t.string :company_id
end
end
end

unless ActiveRecord::Base.connection.table_exists?("#{db}.companies")
ActiveRecord::Migration.class_eval do
create_table "#{MassAssignExample3.db}.companies" do |t|
t.string :name
t.text :secret
end
end
end
company1 = Company.create(:name => "Company 1", :secret => "Company 1 secret")
company2 = Company.create(:name => "Company 2", :secret => "Company 2's secret, access not authorized for Company 1's users!!!")

company1.users << User.create(:username => 'user1', :password => Digest::MD5.hexdigest(SEED+"pentesterlab"+SEED))
}


def self.path
"/massassign/example3/"
end

set :views, File.join(File.dirname(__FILE__), 'example3', 'views')

get '/' do
if params['username'] && params['password']
@user = User.where(:username => params['username'].to_s,
:password =>Digest::MD5.hexdigest(SEED+params['password'].to_s+SEED)).first
if @user
session[:user] = @user.id
return erb :index
end
elsif session[:user]
@user = User.find(session[:user])
return erb :index
end
erb :login
end

get "/edit_profile" do
@user = User.find(session[:user].to_s)
erb :profile
end

get "/update_profile" do
@user = User.find(session[:user].to_s)
@user.update_attributes(params[:user])
@user.save
erb :index
end



end

Y hasta aquí esta serie. Os emplazo a la siguiente y última entrada del laboratorio 'Web for Pentester II" donde veremos inyecciones SQL en MongoDB y algunas otras cosillas variadas.

Via: www.hackplayers.com
[Pentesterlab write-up] Web For Pentester II - Authorization & Mass Assignment [Pentesterlab write-up] Web For Pentester II - Authorization & Mass Assignment Reviewed by Zion3R on 18:05 Rating: 5