Ein Leitfaden zum CSRF-Schutz in Spring Security

1. Übersicht

In diesem Lernprogramm werden CSRF-Angriffe mit Cross-Site Request Forgery und deren Verhinderung mithilfe von Spring Security erläutert.

2. Zwei einfache CSRF-Angriffe

Es gibt mehrere Formen von CSRF-Angriffen - lassen Sie uns einige der häufigsten diskutieren.

2.1. GET Beispiele

Betrachten wir die folgende GET- Anforderung, die von angemeldeten Benutzern verwendet wird, um Geld auf ein bestimmtes Bankkonto "1234" zu überweisen :

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

Wenn der Angreifer stattdessen Geld von einem Opferkonto auf sein eigenes Konto überweisen möchte - "5678" -, muss er das Opfer dazu bringen, die Anfrage auszulösen:

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

Es gibt mehrere Möglichkeiten, dies zu erreichen:

  • Link: Der Angreifer kann das Opfer überzeugen, beispielsweise auf diesen Link zu klicken, um die Übertragung auszuführen:
 Show Kittens Pictures 
  • Bild: Der Angreifer kann eine verwendenTag mit der Ziel-URL als Bildquelle - der Klick ist also nicht einmal erforderlich. Die Anforderung wird automatisch ausgeführt, wenn die Seite geladen wird:

2.2. POST Beispiel

Wenn die Hauptanforderung eine POST-Anforderung sein muss - zum Beispiel:

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

Dann muss der Angreifer das Opfer ein ähnliches ausführen lassen:

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

Weder die oder der wird in diesem Fall funktionieren. Der Angreifer benötigt eine - wie folgt:

Das Formular kann jedoch automatisch mit Javascript eingereicht werden - wie folgt:

  ...

2.3. Praktische Simulation

Nachdem wir nun verstanden haben, wie ein CSRF-Angriff aussieht, simulieren wir diese Beispiele in einer Spring-App.

Wir beginnen mit einer einfachen Controller-Implementierung - dem 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); ... } }

Und lassen Sie uns auch eine grundlegende HTML-Seite haben, die den Banküberweisungsvorgang auslöst:

 Transfer Money to John  Account Number  Amount     

Dies ist die Seite der Hauptanwendung, die in der Ursprungsdomäne ausgeführt wird.

Beachten Sie, dass wir sowohl ein GET über einen einfachen Link als auch einen POST über einen einfachen Link simuliert haben.

Nun - mal sehen, wie die Angreiferseite aussehen würde:

  Show Kittens Pictures 

Diese Seite wird in einer anderen Domäne ausgeführt - der Angreifer-Domäne.

Lassen Sie uns abschließend die beiden Anwendungen - die Originalanwendung und die Angreiferanwendung - lokal ausführen und zuerst auf die Originalseite zugreifen:

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

Rufen wir dann die Angreiferseite auf:

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

Wenn wir die genauen Anforderungen verfolgen, die von dieser Angreiferseite stammen, können wir die problematische Anforderung sofort erkennen, die ursprüngliche Anwendung treffen und vollständig authentifiziert werden.

3. Spring Security-Konfiguration

Um den CSRF-Schutz von Spring Security verwenden zu können, müssen Sie zunächst sicherstellen, dass für alle Statusänderungen ( PATCH , POST , PUT und DELETE - nicht GET) die richtigen HTTP-Methoden verwendet werden .

3.1. Java-Konfiguration

Der CSRF-Schutz ist in der Java-Konfiguration standardmäßig aktiviert . Wir können es immer noch deaktivieren, wenn wir:

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

3.2. XML-Konfiguration

In der älteren XML-Konfiguration (vor Spring Security 4) war der CSRF-Schutz standardmäßig deaktiviert und konnte wie folgt aktiviert werden:

 ...  

Ab Spring Security 4.x ist der CSRF-Schutz auch in der XML-Konfiguration standardmäßig aktiviert. Wir können es natürlich immer noch deaktivieren, wenn wir:

 ...  

3.3. Zusätzliche Formularparameter

Wenn der CSRF-Schutz auf der Serverseite aktiviert ist, müssen wir das CSRF-Token auch auf der Clientseite in unsere Anforderungen aufnehmen:

3.4. Verwenden von JSON

Wir können das CSRF-Token nicht als Parameter senden, wenn wir JSON verwenden. Stattdessen können wir das Token innerhalb des Headers senden.

Wir müssen zuerst das Token in unsere Seite aufnehmen - und dafür können wir Meta-Tags verwenden:

Dann konstruieren wir den Header:

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. CSRF Disabled Test

Mit all dem werden wir einige Tests durchführen.

Versuchen wir zunächst, eine einfache POST-Anfrage zu senden, wenn CSRF deaktiviert ist:

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

As you might have noticed, we're using a base class to hold the common testing helper logic – the 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))); } }

Note that, when the user had the right security credentials, the request was successfully executed – no extra information was required.

That means that the attacker can simply use any of the previously discussed attack vectors to easily compromise the system.

5. CSRF Enabled Test

Now, let's enable CSRF protection and see the difference:

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

Now how this test is using a different security configuration – one that has the CSRF protection enabled.

Now, the POST request will simply fail if the CSRF token isn't included, which of course means that the earlier attacks are no longer an option.

Schließlich beachte die csrf () Methode im Test; Dadurch wird ein RequestPostProcessor erstellt , der zu Testzwecken automatisch ein gültiges CSRF-Token in die Anforderung einfügt.

6. Fazit

In diesem Artikel haben wir einige CSRF-Angriffe und deren Verhinderung mithilfe von Spring Security erläutert.

Die vollständige Implementierung dieses Tutorials finden Sie im GitHub-Projekt - dies ist ein Maven-basiertes Projekt, daher sollte es einfach zu importieren und auszuführen sein.