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
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
http://vulnerable/authorization/example3/infos/edit/1
http://vulnerable/authorization/example3/infos/edit/3
SERVIDOR
- 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
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:Ejercicio 3: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
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.
[Pentesterlab write-ups by Hackplayers] Web For Pentester II:
SQL Injections | |
---|---|
Authentication | |
Captcha | |
Authorization & Mass Assignment | |
Randomness Issues & MongoDB injection |
Estos ejercicios sin un panel detrás que nos conduzca a un RCE no resultan tan espectaculares, pero son imprescindibles.
ResponderEliminar¿Hay algún candidato a ser una nueva serie tras el WebForPentest2?
imagino que publicaremos algún writeup de Vulnhub pero se admiten sugerencias... ;)
EliminarPues hombre, por sugerir... bWAPP es bastante completito.
EliminarCreo que yo tenía por aquí un writeup de alguno de sus ejercicios. Si no lo perdí en una catástrofe digital que sufrí hace unos meses os lo haré llegar.
¡Buen Write-UP! me a servido de hayuda!
ResponderEliminarMuchas gracias por vuestra labor! se agradece mucho.
¡Un saludo desde Guyana Francesa fariseos!