Guide sur la protection des CSRF dans la sécurité de printemps

Guide sur la protection des CSRF dans la sécurité de printemps

1. Vue d'ensemble

Dans ce didacticiel, nous aborderons les attaques de type CSRF par falsification de requêtes entre sites et leur prévention à l'aide de Spring Security.

Lectures complémentaires:

Protection CSRF avec Spring MVC et Thymeleaf

Guide rapide et pratique sur la prévention des attaques CSRF avec Spring Security, Spring MVC et Thymeleaf.

Read more

Configuration automatique de la sécurité de démarrage du printemps

Un guide rapide et pratique de la configuration Spring Security par défaut de Spring Boot.

Read more

Introduction à la sécurité de la méthode Spring

Guide sur la sécurité au niveau des méthodes à l'aide de la structure Spring Security.

Read more

2. Deux attaques CSRF simples

Il existe plusieurs formes d'attaques CSRF. Voyons quelques-unes des plus courantes.

2.1. OBTENIR des exemples

Prenons la demandeGET suivante utilisée par un utilisateur connecté pour transférer de l'argent vers un compte bancaire spécifique“1234”:

GET http://bank.com/transfer?accountNo=1234&amount=100

Si l'attaquant souhaite transférer de l'argent du compte d'une victime vers son propre compte à la place -“5678” - il doit obliger la victime à déclencher la requête:

GET http://bank.com/transfer?accountNo=5678&amount=1000

Il y a plusieurs façons d'y arriver:

  • Link: L'attaquant peut convaincre la victime de cliquer sur ce lien par exemple, pour exécuter le transfert:

  • Image: L'attaquant peut utiliser une balise<img/> avec l'URL cible comme source d'image - le clic n'est donc même pas nécessaire. La demande sera automatiquement exécutée lors du chargement de la page:

2.2. Exemple POST

Si la requête principale doit être une requête POST, par exemple:

POST http://bank.com/transfer
accountNo=1234&amount=100

Ensuite, l'attaquant doit demander à la victime d'exécuter un processus similaire:

POST http://bank.com/transfer
accountNo=5678&amount=1000

Ni les<a> ni les<img/> ne fonctionneront dans ce cas. L'attaquant aura besoin d'un<form> - comme suit:

Toutefois, le formulaire peut être soumis automatiquement à l'aide de Javascript - comme suit:


...

2.3. Simulation pratique

Maintenant que nous comprenons à quoi ressemble une attaque CSRF, simulons ces exemples dans une application Spring.

Nous allons commencer par une implémentation de contrôleur simple - lesBankController:

@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);
        ...
    }
}

Et nous allons également avoir une page HTML de base qui déclenche l'opération de virement bancaire:



    

CSRF test on Origin

Transfer Money to John

Ceci est la page de l'application principale, s'exécutant sur le domaine d'origine.

Notez que nous avons simulé à la fois unGET via un lien simple et unPOST via un simple<form>.

Maintenant, voyons à quoi ressembleraitthe attacker page:

Cette page fonctionnera sur un autre domaine, le domaine de l'attaquant.

Enfin, exécutons les deux applications - l’application d’origine et l’application attaquante - localement, et accédons d’abord à la page d’origine:

http://localhost:8081/spring-rest-full/csrfHome.html

Ensuite, accédons à la page de l'attaquant:

http://localhost:8081/spring-security-rest/api/csrfAttacker.html

En suivant les demandes exactes provenant de cette page de l'attaquant, nous serons en mesure de détecter immédiatement la demande problématique, d'atteindre l'application d'origine et d'être entièrement authentifiés.

3. Configuration de sécurité de printemps

Afin d'utiliser la protection CSRF de Spring Security, nous devons d'abord nous assurer que nous utilisons les méthodes HTTP appropriées pour tout ce qui modifie l'état (PATCH,POST,PUT etDELETE –n'est pas GET).

3.1. Configuration Java

La protection CSRF estenabled by default dans la configuration Java. Nous pouvons toujours le désactiver si nous devons:

@Override
protected void configure(HttpSecurity http) throws Exception {
    http
      .csrf().disable();
}

3.2. Configuration XML

Dans l'ancienne configuration XML (avant Spring Security 4), la protection CSRF était désactivée par défaut et nous pouvions l'activer comme suit:


    ...
    

À partir deSpring Security 4.x - la protection CSRF est également activée par défaut dans la configuration XML; nous pouvons bien sûr toujours le désactiver si nous devons:


    ...
    

3.3. Paramètres de forme supplémentaires

Enfin, avec la protection CSRF activée côté serveur, nous devrons également inclure le jeton CSRF dans nos requêtes côté client:

3.4. Utilisation de JSON

Nous ne pouvons pas soumettre le jeton CSRF en tant que paramètre si nous utilisons JSON. à la place, nous pouvons soumettre le jeton dans l'en-tête.

Nous devons d'abord inclure le jeton dans notre page - et pour cela, nous pouvons utiliser des balises Meta:


Ensuite, nous construirons l'en-tête:

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. Test désactivé CSRF

Avec tout cela en place, nous allons passer à des tests.

Essayons d'abord de soumettre une simple requête POST lorsque CSRF est désactivé:

@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());
    }
}

Comme vous l'avez peut-être remarqué, nous utilisons une classe de base pour contenir la logique d'assistance de test commune - lesCsrfAbstractIntegrationTest:

@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)));
    }
}

Notez que, lorsque l'utilisateur disposait des informations d'identification de sécurité adéquates, la demande a été exécutée avec succès - aucune information supplémentaire n'était requise.

Cela signifie que l'attaquant peut simplement utiliser l'un des vecteurs d'attaque décrits précédemment pour compromettre facilement le système.

5. Test activé par CSRF

Maintenant, activons la protection CSRF et voyons la différence:

@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());
    }
}

Maintenant, comment ce test utilise une configuration de sécurité différente - une configuration sur laquelle la protection CSRF est activée.

Désormais, la requête POST échouera simplement si le jeton CSRF n'est pas inclus, ce qui signifie bien sûr que les attaques précédentes ne sont plus une option.

Enfin, notez la méthodecsrf() dans le test; cela crée unRequestPostProcessor qui remplira automatiquement un jeton CSRF valide dans la demande à des fins de test.

6. Conclusion

Dans cet article, nous avons présenté quelques attaques CSRF et comment les empêcher avec Spring Security.

Lesfull implementation de ce didacticiel se trouvent dansthe GitHub project - il s'agit d'un projet basé sur Maven, il devrait donc être facile à importer et à exécuter tel quel.