Spring MVC, proteja-se do Mass Assignment

Vários sites possuem formulários de cadastro para novos usuários. Também não é incomum que esses mesmo sites, para que possam diferenciar os tipos de  usuários, usem um atributo para guardar os diferentes perfis.

	@Entity
	public class SystemUser {

		@Id
		private String login;
		private String password;
		@ManyToMany(fetch = FetchType.EAGER)
		private List<Role> roles = new ArrayList<>();

		//getter e setters

	}

O formulário de cadastro do site, para usuários que estejam apenas interessados em receber newsletter, poderia ser algo como segue:

	<form action="/admin/users" method="post">
		<input type="text" name="login"/>
		<input type="password" name="password"/>

                ...
	</form>

Abaixo temos um exemplo de código de controller para tratar esse cadastro.

	@RequestMapping(method=RequestMethod.POST)
	public ModelAndView savePossibleBuyer(SystemUser systemUser,RedirectAttributes redirectAttributes){
		systemUser.addRole(new Role("ROLE_NEWS"));
		userDAO.save(systemUser);
		redirectAttributes.addFlashAttribute("success", "Usuário cadastrado com sucesso");
		return new ModelAndView("redirect:/produtos/");
	}

Esse trecho de implementação talvez nos passe a impressão que todo novo usuário cadastrado, através desse formulário, vai possuir apenas o perfil ROLE_NEWS. Agora imagine que a pessoa que esteja preenchendo o cadastro seja um pouco mais maliciosa do que de costume e, por conta disso, tente fazer a seguinte alteração no seu form.

	<form action="/admin/users" method="post">
		<input type="text" name="login"/>
		<input type="password" name="password"/>
		<input type="hidden" name="roles[0].name" value="ADMIN"

		...
	</form>

Lembre que isso é facilmente atingível através da manipulação do HTML apresentado no navegador. E agora, o que vai acontecer? O Spring MVC vai fazer o bind do input cujo name é  roles[0].name com os métodos de acesso do atributo roles. Com isso, ao invés de você adicionar apenas um perfil associado com o novo usuário, você vai acabar inserindo dois e, ainda por cima, um deles talvez seja de administrador. Essa é uma técnica de invasão conhecida como Mass Assignment e que, inclusive, já foi explorada uma vez para hackearem o github.

Existem algumas formas de você se defender dela, mas a primeira é entender o motivo que você ficou exposto. Quase todos os frameworks que conhecemos permitem que associemos valores dos formulários diretamente com nossas classes de modelo, e aí é que mora o problema. Várias vezes criamos getters e setters indiscriminadamente e, como os mesmos são usados para receberem os valores dos parâmetros, o sistema acaba ficando suscetível a este tipo de situação. Então uma primeira saída é justamente prestar atenção nos métodos de acesso que criamos.

Só que de vez em quando, por conta de algum framework que está sendo utilizado, somos obrigados a criar mais métodos de acesso do que gostaríamos e aí é que entra uma segunda estratégia. Há muito tempo atrás o tão famoso Struts nos forçava sempre a criar uma classe que representasse o form para receber os dados enviados através das páginas e aí, só depois, que construíamos o objeto final. Era um pouco mais trabalhoso, mas em compensação nos fazia pensar com carinho para cada formulário que existisse no sistema.  Para esse nosso caso, poderíamos seguir essa sugestão.

	public class NewsletterUserForm {

		private String login;
		private String password;

		//getters e setters

		public SystemUser buildSystemUser(){
			SystemUser user = new SystemUser();
			user.setLogin(login);
			user.setPassword(password);
			user.addRole(new Role("ROLE_NEWS"));
			return user;
		}

	}

E agora podemos alterar o código do controller, para tratar do cadastro.

	@RequestMapping(method=RequestMethod.POST)
	public ModelAndView savePossibleBuyer(NewsletterUserForm formUser,...){
		userDAO.persist(formUser.buildSystemUser());
                ....
	}

Caso você ache que é um pouco exagerado já começar se protegendo desse jeito, pode usar um terceiro recurso, provido pelo próprio Spring MVC.

    @InitBinder
    protected void initBinder(WebDataBinder binder) {
	  binder.setAllowedFields("login","password");
    }

Lembre que o método anotado com @InitBinder é responsável por realizar configurações que serão aplicadas sobre os parâmetros que serão recebidos nos métodos do controller. Nesse caso, estamos dizendo que só aceitamos os parâmetros cujos nomes são login e password. Qualquer outro valor que seja passado será ignorado! A classe WebDataBinder ainda permite que você faça diversas outras configurações, por exemplo configurar uma validação customizada.

Pronto, desse jeito você se protege de receber valores indesejados e ainda consegue aproveitar sua classe de modelo entre vários formulários. E você, como faz para se proteger de valores não esperados? Tem alguma outra sugestão?

Advertisements

One thought on “Spring MVC, proteja-se do Mass Assignment

  1. Muito bacana, Alberto!

    Acredito que todos os frameworks MVC action-based tem esse problema, onde a responsabilidade de tratar os parâmetos fica na mão do desenvolvedor. Por outro lado, em frameworks component-based como JSF isso não acontece, já que ele mantém o estado da página no lado servidor e ignora qualquer informação adicional no relatório, resolvendo o problema do mass-assignment.

    Um abraço e excelente artigo!

    Like

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s