homeASCIIcasts

27: Cross-site Scripting 

(view original Railscast)

Other translations: En

Other formats:

Written by Andrea Salicetti

Il cross-site scripting è un’altra vulnerabilità comune da considerare quando si sviluppano applicazioni web. Si manifesta quando si permette agli utenti del sito di inserire HTML o JavaScript direttamente. Nel sito di sotto non stiamo facendo l’escape dell’input che è immesso nel textbox dei commenti, il che rende vulnerabile il nostro sito.

Il nostro sito, mostrante il box dei commenti.

Se inseriamo del JavaScript nel box dei commenti, contornato da tag <script>, tale codice sarebbe eseguito al ricaricamento della pagina e ogni volta sarebbe visto al refresh successivo. Per esempio, se scrivessimo <script>alert(’hello’)</script> nel box dei commenti e cliccassimo il pulsante, ogni volta successiva che la pagina venisse mostrata, l’utente vedrebbe comparire un alert box.

Un alert JavaScript causato da cross-site scripting.

Se diamo un’occhiata al sorgente della pagina, vediamo il JavaScript in essa contenuto.

<h2>Comments</h2>
  <p>I'll start on this now - Paul</p>
  <p><script>alert('hello!');</script></p>
<hr/>

Il JavaScript contenuto della pagina.

Fare vedere un alert box in una pagina è seccante, ma la tecnica del cross-site scripting potrebbe essere usata per scopi molto più deleteri. Per esempio, potrebbe essere usato per leggere i cookie di altri siti visitati da altri utenti. Di seguito abbiamo solo mostrato mediante alert il cookie, ma lo stesso potrebbe essere facilmente inviato ad un server remoto dove l’informazione sul session id contenuta nel cookie potrebbe essere utilizzata per violare la sessione di un altro utente1.

Le informazioni del cookie mostrate dal JavaScript.

La chiave di sessione presente nel cookie.

Fermare gli attacchi

Per impedire questo genere di attacchi occorre fare l’escape di ogni dato immesso dall’utente prima di mostrarlo sullo schermo. Attualmente stiamo prendendo il contenuto dei commenti direttamente dal database e lo stiamo mandando direttamente nello stream di output HTML. Rails fornisce un metodo chiamato semplicemente h per fare l’escape del contenuto prima che venga mostrato2.

<% @task.comments.each do |comment| %>
  <p><%= h(comment.content) %></p>
<% end %>

Il testo inserito nel box dei commenti è ora gestita in sicurezza.

Ora, quando ricarichiamo la pagina, possiamo vedere che lo script non viene più eseguito. Il comando h fa l’escape delle parentesi angolari in modo tale che i commenti immessi dall’utente siano visualizzati in sicurezza.

I commenti sono ora mostrati in sicurezza mediante escaping.

Rails fornisce anche un metodo sanitize che permette di includere taluni tag mediante white list. Per stare dalla parte dei bottoni e fare l’escape di tutto l’HTML che può essere immesso dall’utente è più sicuro richiamare h.

Un approccio alternativo

Invece di fare l’escape dell’input dell’utente quando viene mandato al browser, potremmo farlo una volta sola al momento del salvataggio sul database. Dal momento che il metodo h è un metodo dei template3 e non è per questo motivo disponibile sul controller, occorre usare un altro metodo analogo, CGI::escapeHTML():

  def create
    @task = Task.find(params[:task_id])
    @comment = @task.comments.new(params[:comment])
    @comment.content = CGI::escapeHTML(params[:comment][:content])
    @comment.save
    redirect_to task_path(@task)
  end

L’escape dell’HTML nei commenti prima del salvataggio su database.

Salvare l’escape dell’HTML sul database va bene fintanto che si vuole mostrare quel contenuto sempre e solo in un browser. Se pensate che ci sia bisogno di avere il dato privo di escape, allora è meglio fare l’escape sull’output con il metodo h.

  1. http://en.wikipedia.org/wiki/Session_hijacking
  2. Da Rails 3 l’escape viene fatto sempre di default, per cui non è più necessario esplicitarlo in questo modo. Se di contro si volesse deliberatamente accettare il testo immesso senza farne l’escape, si dovrebbe chiamare raw(string)
  3. Il metodo h è un alias del metodo html_escape definito nel module ERB::Util.