#26 Hackers Love Mass Assignment
L’assegnamento massivo è molto usato fra gli sviluppatori Rails poichè fornisce un modo semplice per popolare le proprietà di un oggetto di modello da una form. Sfortunatamente la sua semplicità può rendere questo meccanismo bersaglio per gli hacker. In questo episodio spiegheremo perchè e come proteggersi.
<<<<<<< HEAD =======La nostra semplice form di registrazione.
Sopra è mostrata una semplice form di registrazione. Quando un utente inserisce il proprio user name e clicca “submit” viene creato un nuovo modello User
. Il nostro modello è definito nello schema in questo modo, con un campo string denominato name
e un campo boolean admin
che di default vale false.
create_table :users do |t| t.string :name t.boolean :admin, :default => false, :null => false end
Quando un utente riempie i campi della form e clicca su registra, viene chiamata la action create.
def create @user = User.new(params[:user]) if @user.save flash[:notice] = "Successfully registered" redirect_to :action => ’show’, :id => @user.id else render :action => :new end end
Quando l’utente viene creato, la nuova istanza di User è popolata dall’hash params
che, come visto nel precedente episodio, non dovrebbe essere presa con troppa fiducia. Passiamo tutti i parametri nell’hash params[:user]
e li impostiamo come attributi del nostro nuovo utente. Mostriamo ora come questo meccanismo possa essere compromesso da un hacker malizioso per far diventare se stesso un amministratore.
Hacking del sito
Uno strumento da linea di comando tipo curl
può essere usato per passare parametri POST ad una pagina.
curl -d "user[name]=hacker&user[admin]=1" http://localhost:3000/Users/
In questa riga stiamo mandando le informazioni per la creazione di un utente di nome ‘hacker’ che deve essere amministratore (admin vale true
). Se diamo un’occhiata ai log di sviluppo vediamo che il nuovo utente è effettivamente un amministratore.
Processing UsersController#create (for 127.0.0.1 at 2009-02-03 20:18:54) [POST] Session ID: 8daeaad6eb382c903e595e704b626ef7 Parameters: {"user"=>{"name"=>"hacker", "admin"=>"1"}, "action"=>"create", "controller"=>"users"} SQL (0.000390) INSERT INTO users ("name", "admin") VALUES(’hacker’, ’t’) Redirected to http://localhost:3000/users/show/2
L’hacker è stato in grado di fare ciò a causa del meccanismo di assegnamento massivo: abbiamo creato un nuovo utente utilizzando i parametri forniti dall’hash params e l’hacker ha fornito, fra questi, il parametro admin valorizzato a suo piacimento. In questo modo l’hacker si è reso amministratore.
Protezione degli attributi
Il modo per fermare questo tipo di attacco è quello di limitare il numero di attributi che possono essere passati con la form (o da un metodo POST). Lo facciamo nel modello, in questo caso User. Rails ha un metodo chiamato attr_protected
che permette di definire quali fra gli attributi di un metodo non possono essere impostati mediante un assegnamento massivo.
class User < ActiveRecord::Base has_many :comments attr_protected :admin end
Il nostro modello utente con l’attributo admin protetto.
Ora, rilancando il comando curl
precedente e guardando sui log, vediamo come l’attributo admin non sia stato messo a true, anche se era stato fornito fra i parametri dell’oggetto creando nell’hash params.
Processing UsersController#create (for 127.0.0.1 at 2009-02-03 20:37:49) [POST] Session ID: 381cee077c1367bf0cc410a2259adb96 Parameters: {"user"=>{"name"=>"hacker", "admin"=>"1"}, "action"=>"create", "controller"=>"users"} SQL (0.000327) INSERT INTO users ("name", "admin") VALUES(’hacker’, ’f’) Redirected to http://localhost:3000/users/show/5
L’attributo protetto admin ora non viene valorizzato dall’valore presente nell’hash params.
Non è ancora abbastanza
Ora abbiamo protetto quel metodo da possibili attacchi di hacker, ma rimane ancora un buco nel nostro sito. C’è una relazione nella nostra applicazione che dice che un utente ha molti commenti e has_many fornisce un modo per impostare i comment_ids mediante assegnamento massivo. Usiamo nuovamente curl
per fare l’hack degli id dei commenti.
curl -d "user[name]=hacker&user[admin]=1&user[comment_ids][]=1&user[comment_ids]=2" http://localhost:3000/users/create
Presa di controllo di commenti non nostri.
La relazione has_many
fra User
e Comment
fornisce al modello User un metodo comment_ids
che accetta un array di id di commenti. Abbiamo fatto l’hacking dell’hash dei parametri qui sopra per millantare la paternità dei commenti con id 1 e 2. Nei log di sviluppo possiamo vedere che il nostro hacker ora detiene quei commenti:
Processing UsersController#create (for 127.0.0.1 at 2009-02-04 20:27:36) [POST] Session ID: e6bee21260899c7dce47bc5040dcd467 Parameters: {"user"=>{"name"=>"hacker", "comment_ids"=>["1", "2"], "admin"=>"1"}, "action"=>"create", "controller"=>"users"} Comment Load (0.001) SELECT * FROM comments WHERE (comments."id" IN (1,2)) SQL (0.001) INSERT INTO users ("name", "admin") VALUES(’hacker’, ’f’) Comment Update (0.000094) UPDATE comments SET "title" = ’Comment 1’, "user_id" = 8 WHERE "id" = 1 Comment Update (0.000071) UPDATE comments SET "title" = ’Comment 2’, "user_id" = 8 WHERE "id" = 2
Il nostro hacker ora possiede commenti non suoi.
Per fermare questo furto è meglio usare attr_accessible
nel nostro modello piuttosto che attr_protected
. Ora stiamo dicendo esplicitamente quali attributi possono essere modificati mediante assegnamento massivo. Aggiorniamo il nostro modello User
per permettere l’aggiornamento del solo attributo name
.
class User < ActiveRecord::Base has_many :comments attr_accessible :name end
Un ultimo sguardo al log di sviluppo ci mostra che il nostro hacker non ha ottentuo i privilegi amministrativi e nemmeno la proprietà di commenti non suoi.
Processing UsersController#create (for 127.0.0.1 at 2009-02-04 20:39:15) [POST] Session ID: 48b9264e8da94d0a0edadce5e31ac500 Parameters: {"user"=>{"name"=>"hacker", "comment_ids"=>["1", "2"], "admin"=>"1"}, "action"=>"create", "controller"=>"users"} SQL (0.000307) INSERT INTO users ("name", "admin") VALUES(’hacker’, ’f’) Redirected to http://localhost:3000/users/show/9
Abbiamo visto in questo episodio come sia semplice, da parte di utenti maliziosi, manipolare attributi di modelli non protetti. L’uso di attr_accessible
può fermare questo genere di attacchi e mantenere al sicuro i modelli.