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