Um guia para proteção CSRF em Spring Security
1. Visão geral
Neste tutorial, discutiremos os ataques CSRF entre solicitações entre sites e falsificações e como evitá-los usando o Spring Security.
Leitura adicional:
Proteção CSRF com Spring MVC e Thymeleaf
Guia rápido e prático para impedir ataques de CSRF com Spring Security, Spring MVC e Thymeleaf.
Configuração Automática de Segurança de Inicialização Spring
Um guia rápido e prático para a configuração padrão do Spring Security do Spring Boot.
Introdução à Spring Method Security
Um guia para segurança em nível de método usando a estrutura Spring Security.
2. Dois ataques simples de CSRF
Existem várias formas de ataques CSRF - vamos discutir algumas das mais comuns.
2.1. Exemplos GET
Vamos considerar a seguinte solicitaçãoGET usada por um usuário conectado para transferir dinheiro para uma conta bancária específica“1234”:
GET http://bank.com/transfer?accountNo=1234&amount=100
Se o invasor quiser transferir dinheiro da conta de uma vítima para sua própria conta -“5678” - ele precisa fazer a vítima acionar a solicitação:
GET http://bank.com/transfer?accountNo=5678&amount=1000
Existem várias maneiras de fazer isso acontecer:
-
Link: O invasor pode convencer a vítima a clicar neste link, por exemplo, para executar a transferência:
-
Image: O invasor pode usar uma tag<img/> com o URL de destino como a fonte da imagem - então o clique nem é necessário. A solicitação será executada automaticamente quando a página carregar:

2.2. Exemplo de POST
Se a solicitação principal precisar ser uma solicitação POST - por exemplo:
POST http://bank.com/transfer
accountNo=1234&amount=100
Em seguida, o invasor precisa que a vítima execute uma ação semelhante:
POST http://bank.com/transfer
accountNo=5678&amount=1000
Nem<a> ou<img/> funcionarão neste caso. O invasor precisará de um<form> - da seguinte forma:
No entanto, o formulário pode ser enviado automaticamente usando Javascript - da seguinte maneira:
2.3. Simulação Prática
Agora que entendemos como é um ataque CSRF, vamos simular esses exemplos dentro de um aplicativo Spring.
Vamos começar com uma implementação de controlador simples - oBankController:
@Controller
public class BankController {
private Logger logger = LoggerFactory.getLogger(getClass());
@RequestMapping(value = "/transfer", method = RequestMethod.GET)
@ResponseBody
public String transfer(@RequestParam("accountNo") int accountNo,
@RequestParam("amount") final int amount) {
logger.info("Transfer to {}", accountNo);
...
}
@RequestMapping(value = "/transfer", method = RequestMethod.POST)
@ResponseStatus(HttpStatus.OK)
public void transfer2(@RequestParam("accountNo") int accountNo,
@RequestParam("amount") final int amount) {
logger.info("Transfer to {}", accountNo);
...
}
}
E vamos ter também uma página HTML básica que aciona a operação de transferência bancária:
CSRF test on Origin
Transfer Money to John
Esta é a página do aplicativo principal, em execução no domínio de origem.
Observe que simulamosGET por meio de um link simples, bem comoPOST por meio de um<form> simples.
Agora - vamos ver comothe attacker page ficaria:
Esta página será executada em um domínio diferente - o domínio do invasor.
Por fim, vamos executar os dois aplicativos - o aplicativo original e o do invasor - localmente e acessar a página original primeiro:
http://localhost:8081/spring-rest-full/csrfHome.html
Então, vamos acessar a página do invasor:
http://localhost:8081/spring-security-rest/api/csrfAttacker.html
Rastreando as solicitações exatas originadas desta página do invasor, seremos capazes de localizar imediatamente a solicitação problemática, atingindo o aplicativo original e totalmente autenticado.
3. Configuração de segurança da primavera
Para usar a proteção CSRF do Spring Security, primeiro precisamos nos certificar de que usamos os métodos HTTP adequados para qualquer coisa que modifique o estado (PATCH,POST,PUT eDELETE – não GET).
3.1. Configuração Java
A proteção CSRF éenabled by default na configuração Java. Ainda podemos desativá-lo se precisarmos:
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.csrf().disable();
}
3.2. Configuração XML
Na configuração XML mais antiga (anterior ao Spring Security 4), a proteção CSRF era desativada por padrão e nós poderíamos ativá-la da seguinte maneira:
...
A partir deSpring Security 4.x - a proteção CSRF é ativada por padrão na configuração XML também; podemos, é claro, ainda desativá-lo se precisarmos:
...
3.3. Parâmetros de formulário extras
Finalmente, com a proteção CSRF habilitada no lado do servidor, precisaremos incluir o token CSRF em nossas solicitações no lado do cliente também:
3.4. Usando JSON
Não podemos enviar o token CSRF como um parâmetro se estivermos usando JSON; em vez disso, podemos enviar o token dentro do cabeçalho.
Primeiro, precisamos incluir o token em nossa página - e para isso podemos usar metatags:
Em seguida, construiremos o cabeçalho:
var token = $("meta[name='_csrf']").attr("content");
var header = $("meta[name='_csrf_header']").attr("content");
$(document).ajaxSend(function(e, xhr, options) {
xhr.setRequestHeader(header, token);
});
4. Teste CSRF desativado
Com tudo isso no lugar, iremos fazer alguns testes.
Vamos primeiro tentar enviar uma solicitação POST simples quando o CSRF está desativado:
@ContextConfiguration(classes = { SecurityWithoutCsrfConfig.class, ...})
public class CsrfDisabledIntegrationTest extends CsrfAbstractIntegrationTest {
@Test
public void givenNotAuth_whenAddFoo_thenUnauthorized() throws Exception {
mvc.perform(
post("/foos").contentType(MediaType.APPLICATION_JSON)
.content(createFoo())
).andExpect(status().isUnauthorized());
}
@Test
public void givenAuth_whenAddFoo_thenCreated() throws Exception {
mvc.perform(
post("/foos").contentType(MediaType.APPLICATION_JSON)
.content(createFoo())
.with(testUser())
).andExpect(status().isCreated());
}
}
Como você deve ter notado, estamos usando uma classe base para manter a lógica auxiliar de teste comum - oCsrfAbstractIntegrationTest:
@RunWith(SpringJUnit4ClassRunner.class)
@WebAppConfiguration
public class CsrfAbstractIntegrationTest {
@Autowired
private WebApplicationContext context;
@Autowired
private Filter springSecurityFilterChain;
protected MockMvc mvc;
@Before
public void setup() {
mvc = MockMvcBuilders.webAppContextSetup(context)
.addFilters(springSecurityFilterChain)
.build();
}
protected RequestPostProcessor testUser() {
return user("user").password("userPass").roles("USER");
}
protected String createFoo() throws JsonProcessingException {
return new ObjectMapper().writeValueAsString(new Foo(randomAlphabetic(6)));
}
}
Observe que, quando o usuário tinha as credenciais de segurança corretas, a solicitação foi executada com êxito - nenhuma informação extra foi necessária.
Isso significa que o invasor pode simplesmente usar qualquer um dos vetores de ataque discutidos anteriormente para comprometer facilmente o sistema.
5. Teste de CSRF habilitado
Agora, vamos habilitar a proteção CSRF e ver a diferença:
@ContextConfiguration(classes = { SecurityWithCsrfConfig.class, ...})
public class CsrfEnabledIntegrationTest extends CsrfAbstractIntegrationTest {
@Test
public void givenNoCsrf_whenAddFoo_thenForbidden() throws Exception {
mvc.perform(
post("/foos").contentType(MediaType.APPLICATION_JSON)
.content(createFoo())
.with(testUser())
).andExpect(status().isForbidden());
}
@Test
public void givenCsrf_whenAddFoo_thenCreated() throws Exception {
mvc.perform(
post("/foos").contentType(MediaType.APPLICATION_JSON)
.content(createFoo())
.with(testUser()).with(csrf())
).andExpect(status().isCreated());
}
}
Agora, como esse teste está usando uma configuração de segurança diferente - com a proteção CSRF ativada.
Agora, a solicitação POST simplesmente falhará se o token CSRF não estiver incluído, o que obviamente significa que os ataques anteriores não são mais uma opção.
Finalmente, observe o métodocsrf() no teste; isso cria umRequestPostProcessor que preencherá automaticamente um token CSRF válido na solicitação para fins de teste.
6. Conclusão
Neste artigo, discutimos alguns ataques de CSRF e como evitá-los usando o Spring Security.
Ofull implementation deste tutorial pode ser encontrado emthe GitHub project - este é um projeto baseado em Maven, portanto, deve ser fácil de importar e executar como está.