Vodič za zaštitu CSRF-a u proljetnoj sigurnosti

1. Pregled

U ovom ćemo uputstvu raspravljati o CSRF napadima krivotvorenja zahtjeva za više mjesta i kako ih spriječiti pomoću Spring Securitya.

2. Dva jednostavna CSRF napada

Postoji više oblika CSRF napada - razgovarajmo o nekim najčešćim.

2.1. DOBITI primjere

Razmotrimo sljedeće DOBITI zahtjev koji prijavljeni korisnici koriste za prijenos novca na određeni bankovni račun “1234”:

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

Ako napadač umjesto toga želi prebaciti novac s računa žrtve na svoj račun - “5678” - treba navesti žrtvu da pokrene zahtjev:

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

Postoji više načina da se to dogodi:

  • Veza: Napadač može uvjeriti žrtvu da klikne na ovu vezu, na primjer, da izvrši prijenos:
 Pokažite mačićima slike 
  • Slika: Napadač može koristiti označite s ciljnim URL-om kao izvorom slike - tako da klik nije ni potreban. Zahtjev će se automatski izvršiti kada se stranica učita:

2.2. Primjer POST

Ako glavni zahtjev treba biti POST zahtjev - na primjer:

POŠTA //bank.com/račun za prijenosNo = 1234 & iznos = 100

Tada napadač treba da žrtva pokrene slično:

POŠTA //bank.com/račun za prijenosNo = 5678 & iznos = 1000

Ni ili će raditi u ovom slučaju. Napadaču će trebati - kako slijedi:

Međutim, obrazac se može poslati automatski pomoću Javascripta - kako slijedi:

  ...

2.3. Praktična simulacija

Sad kad razumijemo kako izgleda CSRF napad, simulirajmo ove primjere u aplikaciji Spring.

Krenut ćemo s jednostavnom implementacijom kontrolera BankController:

@Controller javna klasa BankController {private Logger logger = LoggerFactory.getLogger (getClass ()); @RequestMapping (value = "/ transfer", method = RequestMethod.GET) @ResponseBody javni prijenos niza (@RequestParam ("accountNo") int accountNo, @RequestParam ("iznos") konačni int iznos) {logger.info ("Transfer do {} ", accountNo); ...} @RequestMapping (value = "/ transfer", method = RequestMethod.POST) @ResponseStatus (HttpStatus.OK) public void transfer2 (@RequestParam ("accountNo") int accountNo, @RequestParam ("iznos") final int iznos) {logger.info ("Prenesi na {}", račun br.); ...}}

Ajmo također imati osnovnu HTML stranicu koja pokreće operaciju bankovnog prijenosa:

 Prebacite novac na iznos broja John računa 

Ovo je stranica glavne aplikacije koja radi na izvornoj domeni.

Imajte na umu da smo simulirali oba a DOBITI putem jednostavne veze kao i a OBJAVI kroz jednostavan .

Sad - da vidimo kako stranicu napadača izgledalo bi kao:

  Pokažite mačićima slike 

Ova će se stranica prikazivati ​​na drugoj domeni - domeni napadača.

Napokon, pokrenimo dvije aplikacije - izvornu i napadačku - lokalno, i pristupimo prvo izvornoj stranici:

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

Zatim, pristupimo stranici napadača:

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

Praćenjem točnih zahtjeva koji potječu s ove stranice napadača, moći ćemo odmah uočiti problematični zahtjev, pogodivši izvornu aplikaciju i potpuno provjeriti autentičnost.

3. Proljetna sigurnosna konfiguracija

Da bismo koristili CSRF zaštitu Spring Security, prvo se moramo pobrinuti da koristimo odgovarajuće HTTP metode za sve što mijenja stanje (ZAKRPA, OBJAVI, STAVITI, i IZBRIŠI - ne dobiti).

3.1. Java konfiguracija

CSRF zaštita je omogućeno prema zadanim postavkama u Java konfiguraciji. I dalje ga možemo onemogućiti ako je potrebno:

@Override zaštićena void konfiguracija (HttpSecurity http) baca iznimku {http .csrf (). Disable (); }

3.2. XML konfiguracija

U starijoj XML konfiguraciji (pre Spring Security 4) CSRF zaštita bila je onemogućena prema zadanim postavkama i mogli smo je omogućiti na sljedeći način:

 ...  

Počevši od Proljetna sigurnost 4.x - CSRF zaštita omogućena je prema zadanim postavkama i u XML konfiguraciji; to naravno možemo i dalje onemogućiti ako je potrebno:

 ...  

3.3. Parametri dodatnog obrasca

Konačno, s omogućenom CSRF zaštitom na strani poslužitelja, morat ćemo uključiti i CSRF token u svoje zahtjeve na klijentskoj strani:

3.4. Korištenje JSON-a

Ne možemo poslati CSRF token kao parametar ako koristimo JSON; umjesto toga token možemo poslati unutar zaglavlja.

Prvo ćemo trebati uključiti token na našu stranicu - a za to možemo koristiti metaoznake:

Zatim ćemo konstruirati zaglavlje:

var token = $ ("meta [name = '_ csrf']"). attr ("sadržaj"); var zaglavlje = $ ("meta [name = '_ csrf_header']"). attr ("sadržaj"); $ (dokument) .ajaxSend (funkcija (e, xhr, opcije) {xhr.setRequestHeader (zaglavlje, žeton);});

4. CSRF onemogućeni test

Uz sve to na mjestu, prijeći ćemo na neko testiranje.

Pokušajmo prvo poslati jednostavan POST zahtjev kad je CSRF onemogućen:

@ContextConfiguration (classes = {SecurityWithoutCsrfConfig.class, ...}) javna klasa CsrfDisabledIntegrationTest proširuje CsrfAbstractIntegrationTest {@Test public void givenNotAuth_whenAddFoo_thenUnauthorized () content (Izbacivanje). content (createFoo ())) .andExpect (status (). isUnauthorized ()); } @Test public void givenAuth_whenAddFoo_thenCreated () baca izuzetak {mvc.perform (post ("/ foos"). ContentType (MediaType.APPLICATION_JSON) .content (createFoo ()) .with (testUser ())). AndExpect (status () .isCreate ()); }}

Kao što ste mogli primijetiti, koristimo osnovnu klasu da bismo zadržali uobičajenu pomoćnu logiku testiranja - CsrfAbstractIntegrationTest:

@RunWith (SpringJUnit4ClassRunner.class) @WebAppConfiguration javna klasa CsrfAbstractIntegrationTest {@Autowired private WebApplicationContext context; @Autowired privatni filtar springSecurityFilterChain; zaštićeni MockMvc mvc; @ Prije javne void postavke () {mvc = MockMvcBuilders.webAppContextSetup (context) .addFilters (springSecurityFilterChain) .build (); } zaštićeni RequestPostProcessor testUser () {return user ("user"). password ("userPass"). role ("USER"); } zaštićeni String createFoo () baca JsonProcessingException {return new ObjectMapper (). writeValueAsString (new Foo (randomAlphabetic (6))); }}

Napominjemo da je, kada je korisnik imao odgovarajuće sigurnosne vjerodajnice, zahtjev uspješno izvršen - nisu potrebne dodatne informacije.

To znači da napadač može jednostavno koristiti bilo koji od prethodno raspravljenih vektora napada kako bi lako kompromitirao sustav.

5. CSRF omogućeni test

Ajmo sada omogućiti CSRF zaštitu i vidjeti razliku:

@ContextConfiguration (classes = {SecurityWithCsrfConfig.class, ...}) javna klasa CsrfEnabledIntegrationTest proširuje CsrfAbstractIntegrationTest {@Test public void givenNoCsrf_whenAddFoo_thenForbidden () content. MIC. content (createFoo ()) .with (testUser ())) .andExpect (status (). isForbidden ()); } @Test public void givenCsrf_whenAddFoo_thenCreated () baca izuzetak {mvc.perform (post ("/ foos"). ContentType (MediaType.APPLICATION_JSON) .content (createFoo ()) .with (testUser ()). With (csrf ()) ) .andExpect (status (). isCreate ()); }}

Sada kako se ovaj test koristi drugom sigurnosnom konfiguracijom - onom koja ima omogućenu CSRF zaštitu.

Sada, POST zahtjev jednostavno neće uspjeti ako CSRF token nije uključen, što naravno znači da raniji napadi više nisu opcija.

Napokon, primijetite csrf () metoda u testu; ovo stvara a RequestPostProcessor koji će automatski unijeti valjani CSRF token u zahtjev za potrebe testiranja.

6. Zaključak

U ovom smo članku razgovarali o nekoliko CSRF napada i kako ih spriječiti pomoću Spring Securitya.

The puna provedba ovog vodiča možete pronaći u projektu GitHub - ovo je projekt zasnovan na Mavenu, pa bi ga trebalo lako uvesti i pokrenuti kakav jest.