Ruby: Fazendo a segurança de seus IDs

Uma coisa que todo desenvolvedor web acaba aprendendo, cedo ou tarde, é a velha máxima “não confie nos parâmetros”.

Por | @oficinadanet Programação

Uma coisa que todo desenvolvedor web acaba aprendendo, cedo ou tarde, é a velha máxima “não confie nos parâmetros”.

A forma mais simples de atacar um site é salvar um “form” como uma página local e alterar os valores. Uma vez que a página local pode ser alterada a gosto, ignorando javascripts, campos hidden ou qualquer outra limitação “client side”, confiar em algo que venha do usuário é muito perigoso. (Paranóico, não?)

Um dos ataques mais comuns desse tipo é alterar o “id” de um formulário. Ele normalmente vem em um campo “hidden” e basta alterá-lo para que os parâmetros do formulário sejam gravados sobre outro registro qualquer.

Exemplo prático: você vai até o site http://forum.rubyonbr.org e se cadastra. Sem ter muito o que fazer, resolve pentelhar o sistema. Vai até a página “configurações”, salva a página no disco, vai no código-fonte da página e verifica que no form a action aponta para “users/1”. Conhecendo o Rails, vc sabe que 1 é o ID do usuário.

Na sua página gravada, vc troca o action para “http://forum.rubyonbr.org/users/5”. Aí abre a página, preenche o formulário com o que quiser e submete. O que acontece?

Você acabou de salvar os dados do usuário com ID 5, sobreescreveu todas as informações de um usuário que não era você mesmo.

Uau… isso é muito grave!

Na verdade, o mecanismo do fórum impede isso. Mas e o sistema que você está desenvolvendo hoje? Ele checa os ids pra ver se o usuário pode realmente alterar os valores daquele formulário? Será que o usuário não alterou o ids “no braço” e o sistema está salvando dados que não deveria?

Isso não é uma vulnerabilidade exclusiva do RubyOnRails. Qualquer sistema web está sujeito a isso, seja ele feito em Ruby, Python, Java, PHP, C# ou qualquer outra linguagem que ainda estejam pra inventar.

O que vamos mostrar aqui é como evitar esse tipo de problema no Rails.

Versão 1 – Checando na força bruta


Antes de exibir ou gravar algum dado “sensível”, verifique se o usuário logado pode ter acesso àquele registro.

Informações “sensíveis” normalmente têm um id de usuário ou id de grupo associados (ao menos, deveriam ter). No conditions de suas cláusulas find adicione uma checagem extra pra ver se o usuário logado é dono do registro (ou se pertence a um grupo autorizado)

Exemplo
Digamos que somente o autor do post pode editá-lo, ninguém mais.

Versão original, sem segurança:
def update
    @post = Post.find(params[:id])
    @post.attributes = params[:post]
    if @post.save
        redirect_to :action => ‘list’
    else
        render :action => ‘edit’, :id => @post
    end
end


Versão segura:
def update
    @post = Post.find_by_id_and_author_id(params[:id], session[:user][:id])
    if @post
        @post.attributes = params[:post]
        if @post.save
            redirect_to :action => ‘list‘
        else
            render :action => ‘edit’, :id => @post
        end
    else
        redirect_to :action => ‘not_found‘
    end
end


Observe que na versão segura, nós adicionamos uma cláusula extra ao find, para garantir que somente o post do usuário logado possa ser recuperado. Adicionamos um if extra para verificar se o post realmente foi encontrado e redirecionar para uma página de “not_found” caso tenha vindo vazio, ou por id inválido ou pelo id não pertencer ao usuário corrente.

Esse é o funcionamento básico e funciona para a maior parte dos casos. Mas como a mesma coisa tem que ser feita para o update, para o edit e algumas vezes para o show (ou index) o código começa a ficar repetitivo, violando o princípio DRY.

Versão 2 – Eliminando a duplicação


Vamos eliminar a repetição do modelo anterior. Essa é a maneira que o Beast usa. Aqui nós mostramos uma versão simplificada dele.
before_filter :find_post, :only => [:edit, :update, :show]
def update
    @post.attributes = params[:post]
    if @post.save
        redirect_to :action => ‘list‘
    else
        render :action => ‘edit’, :id => @post
    end
end
protected
def find_post
    @post = Post.find_by_id_and_author_id(params[:id], session[:user][:id]) || raise ActiveRecord::RecordNotFound
end


O before_filter é usado para recuperar o @post nas actions necessárias. Se ele não for encontrado levantamos uma exceção ActiveRecord::RecordNotFound que impede que o resto da action seja executada. Nesse exemplo está faltando configurar a página de erro, podemos fazer isso facilmente com o método rescue_action_in_public .

Essa abordagem é excelente e pode ser usada em mais lugares do que aparenta. Seu problema é que a coisa fica um pouco espalhada e quem não conhece a estratégia pode se perder um pouco. É possível eliminar o before_filter simplesmente chamando o método find_post para se recuperar o post como se faria com um finder qualquer. Ou então, mover o método find_post para a classe Post, o que, em termos de orientação a objeto, seria mais correto.

De uma maneira ou de outra, o importante é não deixar seus objetos expostos. Alguns podem achar que isso é uma coisa óbvia e nem mereceria ser mencionada. Bom… não achei tão óbvio na primeira vez que isso bateu na minha cara, e eu nem usava Rails ainda.

Outras variações disso podem ser feitas com o around_filter e o with_scope, mas segundo esse artigo ela não é uma maneira recomendada (O DHH está pensando em colocar with_scope como protected). No entanto, para um caso desses, eu acho muito apropriado.

Autor: Ronie Uliana | Fonte: Ruby on Br

Mais sobre: ruby segurança
Share Tweet
Recomendado
Comentários
Carregar comentários
Destaquesver tudo