Um guia para proteção CSRF em Spring Security

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.

Read more

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.

Read more

Introdução à Spring Method Security

Um guia para segurança em nível de método usando a estrutura Spring Security.

Read more

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á.