En guide til CSRF-beskyttelse i forårssikkerhed

1. Oversigt

I denne vejledning vil vi diskutere CSRF-angreb på tværs af anmodninger om forfalskning og hvordan man forhindrer dem ved hjælp af Spring Security.

2. To enkle CSRF-angreb

Der er flere former for CSRF-angreb - lad os diskutere nogle af de mest almindelige.

2.1. FÅ eksempler

Lad os overveje følgende anmodning, der bruges af en logget bruger til at overføre penge til en bestemt bankkonto “1234”:

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

Hvis angriberen i stedet ønsker at overføre penge fra ofrets konto til sin egen konto - “5678” - han skal få offeret til at udløse anmodningen:

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

Der er flere måder at få det til at ske:

  • Link: Angriberen kan overbevise offeret om at klikke på dette link for eksempel for at udføre overførslen:
 Vis killinger billeder 
  • Billede: Angriberen kan bruge en tag med mål-URL som billedkilde - så klik er ikke engang nødvendigt. Anmodningen udføres automatisk, når siden indlæses:

2.2. POST-eksempel

Hvis hovedanmodningen skal være en POST-anmodning - for eksempel:

POST //bank.com/transfer kontoNo = 1234 & beløb = 100

Derefter skal angriberen have offeret til at køre et lignende:

POST //bank.com/transfer accountNo = 5678 & beløb = 1000

Hverken den eller den fungerer i dette tilfælde. Angriberen har brug for en - som følger:

Formularen kan dog indsendes automatisk ved hjælp af Javascript - som følger:

  ...

2.3. Praktisk simulering

Nu hvor vi forstår, hvordan et CSRF-angreb ser ud, lad os simulere disse eksempler i en Spring-app.

Vi starter med en simpel implementering af controlleren Bankcontroller:

@Controller offentlig klasse 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 til {} ", kontoNr); ...} @RequestMapping (value = "/ transfer", method = RequestMethod.POST) @ResponseStatus (HttpStatus.OK) public void transfer2 (@RequestParam ("accountNo") int accountNo, @RequestParam ("beløb") final int beløb) {logger.info ("Overfør til {}", kontonr.); ...}}

Og lad os også have en grundlæggende HTML-side, der udløser bankoverførselsoperationen:

 Overfør penge til John-kontonummerbeløb 

Dette er siden i hovedapplikationen, der kører på oprindelsesdomænet.

Bemærk, at vi har simuleret begge a gennem et simpelt link samt en STOLPE gennem en simpel .

Lad os nu se hvordan angribersiden ville se ud som:

  Vis killinger billeder 

Denne side kører på et andet domæne - angriberdomænet.

Lad os endelig køre de to applikationer - den originale og angriberapplikationen - lokalt, og lad os først få adgang til den originale side:

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

Lad os derefter få adgang til angribersiden:

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

Når vi sporer de nøjagtige anmodninger, der stammer fra denne angriberside, kan vi straks få øje på den problematiske anmodning ved at ramme den oprindelige applikation og fuldstændigt godkendt.

3. Forårssikkerhedskonfiguration

For at kunne bruge Spring Security CSRF-beskyttelse skal vi først sørge for, at vi bruger de korrekte HTTP-metoder til alt, hvad der ændrer tilstand (LAPPE, STOLPE, SÆTTEog SLET - ikke FÅ).

3.1. Java-konfiguration

CSRF-beskyttelse er aktiveret som standard i Java-konfigurationen. Vi kan stadig deaktivere det, hvis vi har brug for:

@ Override beskyttet ugyldig konfiguration (HttpSecurity http) kaster undtagelse {http .csrf (). Deaktiver (); }

3.2. XML-konfiguration

I den ældre XML-konfiguration (før Spring Security 4) var CSRF-beskyttelse som standard deaktiveret, og vi kunne aktivere den som følger:

 ...  

Startende fra Spring Security 4.x - CSRF-beskyttelsen er også aktiveret som standard i XML-konfigurationen; vi kan selvfølgelig stadig deaktivere det, hvis vi har brug for:

 ...  

3.3. Ekstra formularparametre

Endelig, med CSRF-beskyttelse aktiveret på serversiden, bliver vi også nødt til at medtage CSRF-token i vores anmodninger på klientsiden:

3.4. Brug af JSON

Vi kan ikke indsende CSRF-token som en parameter, hvis vi bruger JSON; i stedet kan vi indsende tokenet i overskriften.

Vi bliver først nødt til at inkludere tokenet på vores side - og til det kan vi bruge metatags:

Så konstruerer vi overskriften:

var token = $ ("meta [name = '_ csrf']"). attr ("indhold"); var header = $ ("meta [name = '_ csrf_header']"). attr ("indhold"); $ (dokument) .ajaxSend (funktion (e, xhr, optioner) {xhr.setRequestHeader (header, token);});

4. CSRF deaktiveret test

Med alt dette på plads, flytter vi til at lave nogle test.

Lad os først prøve at indsende en simpel POST-anmodning, når CSRF er deaktiveret:

@ContextConfiguration (klasser = {SecurityWithoutCsrfConfig.class, ...}) offentlig klasse CsrfDisabledIntegrationTest udvider CsrfAbstractIntegrationTest {@Test offentlig ugyldighed givetNotAuth_whenAddFoo_thenUnauthorized () kaster undtagelse {mvc.performat. (.). indhold (createFoo ())). og Expect (status (). er Uautoriseret ()); } @Test offentlig ugyldighed givenAuth_whenAddFoo_thenCreated () kaster undtagelse {mvc.perform (post ("/ foos"). ContentType (MediaType.APPLICATION_JSON) .content (createFoo ()) .with (testUser ())). Og Expect (status () .isCreated ()); }}

Som du måske har bemærket, bruger vi en basisklasse til at holde den fælles testhjælperlogik - den CsrfAbstractIntegrationTest:

@RunWith (SpringJUnit4ClassRunner.class) @WebAppConfiguration offentlig klasse CsrfAbstractIntegrationTest {@Autowired private WebApplicationContext context; @Autowired privat filter springSecurityFilterChain; beskyttet MockMvc mvc; @Før offentlig tomrumsopsætning () {mvc = MockMvcBuilders.webAppContextSetup (kontekst) .addFilters (springSecurityFilterChain) .build (); } beskyttet RequestPostProcessor testUser () {returner bruger ("bruger"). adgangskode ("userPass"). roller ("USER"); } beskyttet String createFoo () kaster JsonProcessingException {returner ny ObjectMapper (). writeValueAsString (ny Foo (randomAlphabetic (6))); }}

Bemærk, at når brugeren havde de rigtige sikkerhedsoplysninger, blev anmodningen fuldført - ingen ekstra information var påkrævet.

Det betyder, at angriberen simpelthen kan bruge en af ​​de tidligere diskuterede angrebsvektorer til let at kompromittere systemet.

5. CSRF-aktiveret test

Lad os nu aktivere CSRF-beskyttelse og se forskellen:

@ContextConfiguration (klasser = {SecurityWithCsrfConfig.class, ...}) offentlig klasse CsrfEnabledIntegrationTest udvider CsrfAbstractIntegrationTest {@ Test offentlig ugyldighed givetNoCsrf_whenAddFoo_thenForbidden () kaster undtagelse {mvc.performat. (Type). indhold (createFoo ()) .med (testUser ())). og Expect (status (). isForbidden ()); } @Test offentligt ugyldigt givetCsrf_whenAddFoo_thenCreated () kaster undtagelse {mvc.perform (post ("/ foos"). ContentType (MediaType.APPLICATION_JSON) .content (createFoo ()). Med (testUser ()). Med (csrf ()) ) .andExpect (status (). isCreated ()); }}

Nu hvordan denne test bruger en anden sikkerhedskonfiguration - en, der har CSRF-beskyttelse aktiveret.

Nu vil POST-anmodningen simpelthen mislykkes, hvis CSRF-token ikke er inkluderet, hvilket naturligvis betyder, at de tidligere angreb ikke længere er en mulighed.

Endelig bemærk csrf () metode i testen dette skaber en RequestPostProcessor der automatisk udfylder et gyldigt CSRF-token i anmodningen til testformål.

6. Konklusion

I denne artikel diskuterede vi et par CSRF-angreb og hvordan man forhindrer dem ved hjælp af Spring Security.

Det fuld implementering af denne vejledning kan findes i GitHub-projektet - dette er et Maven-baseret projekt, så det skal være let at importere og køre som det er.