homeASCIIcasts

204: Protezione XSS in Rails 3 

(view original Railscast)

Other translations: En Es Cn

Other formats:

Written by Andrea Salicetti

Nell’episodio 27 [guardalo in inglese, oppure leggilo in italiano] abbiamo trattato il cross-site scripting. E’ un argomento importante che ogni sviluppatore di applicazioni web dovrebbe comprendere a fondo. Uno dei punti deboli delle applicazioni web nei confronti degli attacchi XSS è quando si mostrano dei campi di input per l’utente. E’ perciò importante fare l’escape di tutto l’input generato dall’utente che viene mostrato e nelle applicazioni Rails ciò viene tipicamente svolto dal metodo h:

<%= h comment.content %>

L’utilizzo del metodo h per fare l’escape dell’output.

Comunque sia in Rails 3, viene fatto automaticamente l’escape dell’output, per cui non c’è alcun bisogno di inserire a mano il metodo h nelle viste e in questo episodio vi mostreremo come Rails 3 gestisce l’escaping dell’output.

Per dimostrare l’escaping dell’output useremo una semplice applicazione di blogging scritta in Rails 3. In questa applicazione, ci sono degli articoli e ogni articolo ha una serie di commenti associati. Per provare come il sistema di commento fa fronte ai tentativi di cross-site scripting, inseriamo <script>alert('I steal cookies!')</script> in ogni campo nella form dei commenti e facciamo il submit del nostro commento malevolo.

Immissione di un commento malefico.

Aggiungendo il commento, ci rendiamo subito conto che da come compare nella pagina ricaricata, che Rails 3 ha automaticamente fatto l’escape dei tag HTML presenti nei campi di input immessi per noi. Vediamo come fa a farlo.

Escape automatico dei commenti HTML.

Il codice che mostra ogni commento è contenuto in un partial e se diamo un’occhiata al codice, noteremo che non cè alcun output che viene in effetti filtrato con il metodo h.

/app/views/comments/_comment.html.erb

<div class="comment">
  <strong><%= link_to comment.name, comment.url %></strong>
  <p><%= comment.content %></p>
</div>

In Rails 2, questo avrebbe significato che l’alert inserito poc’anzi sarebbe stato interpretato come Javascript e quindi mostrato, ma invece tutto l’output del partial viene gestito correttamente e in modo sicuro da Rails 3, anche quando passa attraverso dei metodi helper come il link_to, per cui non c’è più alcun bisogno di usare il metodo h qui.

Cosa accade, tuttavia, se stiamo migrando una applicazione già esistente da Rails 2, e abbiamo i nostri output filtrati con h? Possiamo verificarlo provando a fare l’escape dell’output nel partial di prima e ricaricando la pagina:

/app/views/comments/_comment.html.erb

<div class="comment">
  <strong><%= link_to h(comment.name), comment.url %></strong>
  <p><%= h comment.content %></p>
</div>

Al ricaricamento, vediamo come appaia tutto uguale a prima, e dunque come l’output non sia stato filtrato due volte (come avremmo potuto attenderci). Rails è furbo in questo caso; anche se per sbaglio usiamo inutilmente il metodo h, l’escape viene giustamente eseguito una volta sola.

Si potrebbe pensare che il metodo h non faccia nulla in Rails 3, ma non è vero. Ha ancora una sua funzione, e più tardi vi faremo vedere qual è. Ma prima, diamo un’occhiata alle funzionalità associate. Sebbene sia bello avere l’output filtrato automaticamente, potrebbero esserci delle volte in cui si vuole mostrare il contenuto "grezzo", senza filtri. Se ci fidiamo del contenuto che l’utente immette, perchè per esempio è amministratore, e vogliamo mostrare esattamente ciò che è stato immesso, potremo usare il nuovo metodo raw:

<div class="comment">
  <strong><%= link_to comment.name, comment.url %></strong>
  <p><%= raw comment.content %></p>
</div>

Se ora ricarichiamo la pagina, il JavaScript che abbiamo scritto come commento verrà eseguito.

Con l’uso del metodo raw, l’alert viene mostrato.

Per cui in Rails 3, ogniqualvolta non si voglia che l’HTML venga filtrato, si può usare il metodo raw. Ma come funziona? Rails 3 sembra essere piuttosto furbo su quando filtrare i contenuti e quando no. Ora vi mostriamo come fa.

Vediamo il funzionamento dell’escaping da console, che in Rails 3 possiamo invocare con il comando rails c.

$ rails c
Loading development environment (Rails 3.0.0.beta)
ruby-1.9.1-p378 >

Rails 3 ha il concetto di stringa HTML-safe. Possiamo controllare ogni stringa per vedere se è sicura da mostrare come HTML, chiamando il nuovo metodo html_safe? sulla stessa:

> "foo".html_safe?
 => false 

Possiamo identificare una certa stringa come HTML-safe chiamando su di essa il metodo html_safe:

> safe = "safe".html_safe
 => "safe" 
> safe.html_safe?
 => true 

Non avviene alcun escaping all’invocazione di questo metodo. Tutto ciò che accade è che viene impostata una property booleana sull’oggetto stringa, per determinare se debba essere filtrata prima di essere mostrata.

Dunque, come si applica tutto ciò nella nostra vista? Ebbene, Rails controlla ogni pezzo di output per vedere se è marcato come "HTML-safe". Se non lo è, viene automaticamente filtrato quando viene riportato nella vista HTML. Se invece è sicuro, viene passato alla vista HTML così com’è. Se usiamo il metodo h per fare l’escape di una stringa, il filtraggio verrà eseguito dal metodo, che marcherà anche la stringa come "HTML-safe", per cui all’atto del rendering Rails 3 non rifarà lo stesso lavoro su quella stringa che già risulta "sicura".

Il metodo raw, analogamente, marca una stringa come HTML-safe, ma senza farne l’escape, assicurando così il fatto che il contenuto passi intatto dal successivo tentativo di escape.

Questa è una cosa molto importante da capire quando si usano i metodi helper. Cerchiamo di capire perchè, creando un metodo helper chiamato strong, che racchiude ogni cosa gli sia passata fra tag <strong>. Lo usiamo poi nel nostro template in questo modo:

/app/views/comments/_comment.html.erb

<div class="comment">
  <%= strong link_to(comment.name, comment.url) %>
  <p><%= raw comment.content %></p>
</div>

Creiamo ora il metodo strong sopra descritto nel module ApplicationHelper:

/app/helpers/application_helper.rb

module ApplicationHelper
  def strong(content)
    "<strong>#{content}</strong>"
  end
end

Quando ricarichiamo la pagina, tuttavia, vediamo che il tutto non ha prodotto ciò che forse ci aspettavamo.

Il tag strong aggiunto dal nostro helper è stato filtrato.

Rails 3 ha automaticamente fatto l’escape del tag <strong> in questo caso e lo ha fatto perchè il nostro metodo non creava una stringa HTML-safe.

Ci sono due semplici regole da tenere presenti quando si lavora con i metodi helper che restituiscono HTML.
Prima di tutto, dobbiamo assicurarci che ogni stringa restituita sia marcata come HTML-safe:

/app/helpers/application_helper.rb

module ApplicationHelper
  def strong(content)
    "<strong>#{content}</strong>".html_safe
  end
end

Questo corregge il problema che si manifestava prima, per cui il tag <strong> veniva filtrato. Però così facendo occorre tenere presente che anche il contenuto fra i tag non verrà filtrato. Possiamo tuttavia risolvere questo secondo problema, facendo (manualmente) l’escape del contenuto con il metodo h:

/app/helpers/application_helper.rb

module ApplicationHelper
  def strong(content)
    "<strong>#{h(content)}</strong>".html_safe
  end
end

In questo modo, fintanto che ci ricorderemo di fare l’escape di tutto l’input con h e lo marcheremo subito dopo come html_safe, potremo stare tranquilli che il tutto verrà renderizzato correttamente. Se ora ricarichiamo la pagina dei commenti, potremo vedere che i tag <strong> non sono stati filtrati (questo infatti era ciò, che volevamo), ma lo è stato il contenuto in essi racchiuso (come si vede dal secondo commento).

Il metodo helper viene ora filtrato correttamente.

Questo è quanto per questo episodio. L’escaping automatico è una funzionalità utile delle viste in Rails 3 e svincola il programmatore dalla necessità di ricordarsi di far a mano l’escape di ogni pezzo dell’output con il metodo h, riducendo le possibilità che il vostro sito cada vittima di attacchi basati sul cross-site scripting.