Una guida alla protezione CSRF in Spring Security

1. Panoramica

In questo tutorial, discuteremo degli attacchi CSRF di falsificazione delle richieste tra siti e di come prevenirli utilizzando Spring Security.

2. Due semplici attacchi CSRF

Esistono diverse forme di attacchi CSRF: esaminiamo alcune delle più comuni.

2.1. OTTIENI esempi

Consideriamo la seguente richiesta GET utilizzata da un utente connesso per trasferire denaro a un conto bancario specifico "1234" :

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

Se invece l'aggressore desidera trasferire denaro dal conto di una vittima al proprio conto - "5678" - deve fare in modo che la vittima attivi la richiesta:

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

Ci sono diversi modi per farlo accadere:

  • Link: l'aggressore può convincere la vittima a fare clic su questo link, ad esempio, per eseguire il trasferimento:
 Show Kittens Pictures 
  • Immagine: l'attaccante può utilizzare un filetag con l'URL di destinazione come origine dell'immagine, quindi il clic non è nemmeno necessario. La richiesta verrà eseguita automaticamente al caricamento della pagina:

2.2. Esempio POST

Se la richiesta principale deve essere una richiesta POST, ad esempio:

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

Quindi l'attaccante deve fare in modo che la vittima esegua un simile:

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

Nemmeno il o il funzionerà in questo caso. L'attaccante avrà bisogno di un file - come segue:

Tuttavia, il modulo può essere inviato automaticamente utilizzando Javascript, come segue:

  ...

2.3. Simulazione pratica

Ora che abbiamo compreso l'aspetto di un attacco CSRF, simuliamo questi esempi all'interno di un'app Spring.

Inizieremo con una semplice implementazione del controller, il BankController :

@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 disponiamo anche di una pagina HTML di base che attiva l'operazione di bonifico bancario:

 Transfer Money to John  Account Number  Amount     

Questa è la pagina dell'applicazione principale, in esecuzione sul dominio di origine.

Nota che abbiamo simulato sia un GET tramite un semplice link sia un POST tramite un semplice.

Ora, vediamo come apparirebbe la pagina dell'attaccante :

  Show Kittens Pictures 

Questa pagina verrà eseguita su un dominio diverso: il dominio dell'aggressore.

Infine, eseguiamo le due applicazioni - l'applicazione originale e quella dell'aggressore - localmente e accediamo prima alla pagina originale:

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

Quindi, accediamo alla pagina dell'aggressore:

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

Tracciando le richieste esatte che provengono da questa pagina attaccante, saremo in grado di individuare immediatamente la richiesta problematica, colpendo l'applicazione originale e completamente autenticati.

3. Configurazione Spring Security

Per poter utilizzare la protezione CSRF di Spring Security, dobbiamo prima assicurarci di utilizzare i metodi HTTP appropriati per tutto ciò che modifica lo stato ( PATCH , POST , PUT e DELETE - non GET).

3.1. Configurazione Java

La protezione CSRF è abilitata per impostazione predefinita nella configurazione Java. Possiamo ancora disabilitarlo se dobbiamo:

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

3.2. Configurazione XML

Nella configurazione XML precedente (precedente a Spring Security 4), la protezione CSRF era disabilitata per impostazione predefinita e potevamo abilitarla come segue:

 ...  

A partire da Spring Security 4.x - la protezione CSRF è abilitata per impostazione predefinita anche nella configurazione XML; ovviamente possiamo ancora disabilitarlo se dobbiamo:

 ...  

3.3. Parametri di forma extra

Infine, con la protezione CSRF abilitata sul lato server, dovremo includere il token CSRF nelle nostre richieste anche sul lato client:

3.4. Utilizzando JSON

Non possiamo inviare il token CSRF come parametro se stiamo usando JSON; invece, possiamo inviare il token all'interno dell'intestazione.

Dovremo prima includere il token nella nostra pagina e per questo possiamo usare i meta tag:

Quindi costruiremo l'intestazione:

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 CSRF disabilitato

Con tutto ciò a posto, ci muoveremo per fare alcuni test.

Proviamo prima a inviare una semplice richiesta POST quando CSRF è disabilitato:

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

Come avrai notato, stiamo usando una classe base per contenere la logica di helper di test comune : CsrfAbstractIntegrationTest :

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

Notare che, quando l'utente aveva le giuste credenziali di sicurezza, la richiesta è stata eseguita con successo e non sono state richieste informazioni aggiuntive.

Ciò significa che l'attaccante può semplicemente utilizzare uno qualsiasi dei vettori di attacco discussi in precedenza per compromettere facilmente il sistema.

5. Test CSRF abilitato

Ora abilitiamo la protezione CSRF e vediamo la differenza:

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

Ora come questo test utilizza una configurazione di sicurezza diversa, una con la protezione CSRF abilitata.

Ora, la richiesta POST fallirà semplicemente se il token CSRF non è incluso, il che ovviamente significa che gli attacchi precedenti non sono più un'opzione.

Infine, nota il metodo csrf () nel test; questo crea un RequestPostProcessor che popolerà automaticamente un token CSRF valido nella richiesta a scopo di test.

6. Conclusione

In questo articolo, abbiamo discusso un paio di attacchi CSRF e come prevenirli utilizzando Spring Security.

L' implementazione completa di questo tutorial può essere trovata nel progetto GitHub: questo è un progetto basato su Maven, quindi dovrebbe essere facile da importare ed eseguire così com'è.