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