En guide til CSRF-beskyttelse i vårsikkerhet
1. Oversikt
I denne opplæringen vil vi diskutere CSRF-angrep på tvers av sider for forfalskning og hvordan du kan forhindre dem ved hjelp av Spring Security.
2. To enkle CSRF-angrep
Det er flere former for CSRF-angrep - la oss diskutere noen av de vanligste.
2.1. FÅ eksempler
La oss vurdere følgende FÅ forespørsel brukt av innloggede brukere for å overføre penger til en spesifikk bankkonto “1234”:
FÅ //bank.com/transfer?accountNo=1234&amount=100
Hvis angriperen i stedet ønsker å overføre penger fra ofrenes konto til sin egen konto - “5678” - han må få offeret til å utløse forespørselen:
FÅ //bank.com/transfer?accountNo=5678&amount=1000
Det er flere måter å få det til:
- Lenke: Angriperen kan overbevise offeret om å klikke på denne lenken for eksempel for å utføre overføringen:
Vis kattungebilder
- Bilde: Angriperen kan bruke en tag med mål-URL som bildekilde - så klikket er ikke engang nødvendig. Forespørselen blir automatisk utført når siden lastes inn:
2.2. POST Eksempel
Hvis hovedforespørselen må være en POST-forespørsel - for eksempel:
POST //bank.com/transfer accountNo = 1234 & beløp = 100
Da må angriperen få offeret til å kjøre et lignende:
POST //bank.com/transfer accountNo = 5678 & beløp = 1000
Verken den eller vil fungere i dette tilfellet. Angriperen trenger en - som følger:
Skjemaet kan imidlertid sendes inn automatisk ved hjelp av Javascript - som følger:
...
2.3. Praktisk simulering
Nå som vi forstår hvordan et CSRF-angrep ser ut, la oss simulere disse eksemplene i en Spring-app.
Vi skal begynne med en enkel kontrollerimplementering - the 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. nr); ...} @RequestMapping (value = "/ transfer", method = RequestMethod.POST) @ResponseStatus (HttpStatus.OK) public void transfer2 (@RequestParam ("accountNo") int accountNo, @RequestParam ("amount") final int beløp) {logger.info ("Transfer to {}", accountNo); ...}}
Og la oss også ha en grunnleggende HTML-side som utløser bankoverføringsoperasjonen:
Overfør penger til John-kontonummerbeløpet
Dette er siden til hovedprogrammet som kjører på opprinnelsesdomenet.
Merk at vi har simulert både a FÅ gjennom en enkel lenke samt en POST gjennom en enkel .
Nå - la oss se hvordan angriper-siden vil se ut som:
Vis kattungebilder
Denne siden kjører på et annet domene - angriperdomenet.
Til slutt, la oss kjøre de to applikasjonene - originalen og angriperapplikasjonen - lokalt, og la oss først få tilgang til originalsiden:
//localhost:8081/spring-rest-full/csrfHome.html
La oss da få tilgang til angriperens side:
//localhost:8081/spring-security-rest/api/csrfAttacker.html
Når vi sporer de nøyaktige forespørslene som kommer fra denne angriper-siden, kan vi øyeblikkelig oppdage den problematiske forespørselen ved å treffe den opprinnelige applikasjonen og fullstendig godkjent.
3. Vårs sikkerhetskonfigurasjon
For å kunne bruke Spring Security CSRF-beskyttelse, må vi først sørge for at vi bruker de riktige HTTP-metodene for alt som endrer tilstand (LAPP, POST, SETTE, og SLETT - ikke FÅ).
3.1. Java-konfigurasjon
CSRF-beskyttelse er aktivert som standard i Java-konfigurasjonen. Vi kan fortsatt deaktivere det hvis vi trenger å:
@ Override-beskyttet tomkonfigurasjon (HttpSecurity http) kaster unntak {http .csrf (). Deaktiver (); }
3.2. XML-konfigurasjon
I den eldre XML-konfigurasjonen (pre Spring Security 4) var CSRF-beskyttelse deaktivert som standard, og vi kunne aktivere den som følger:
...
Starter fra Spring Security 4.x - CSRF-beskyttelsen er også aktivert i XML-konfigurasjonen; vi kan selvfølgelig fortsatt deaktivere det hvis vi trenger å:
...
3.3. Ekstra skjemaparametere
Til slutt, med CSRF-beskyttelse aktivert på serversiden, må vi også inkludere CSRF-token i våre forespørsler på klientsiden:
3.4. Bruke JSON
Vi kan ikke sende inn CSRF-token som en parameter hvis vi bruker JSON; i stedet kan vi sende inn tokenet i overskriften.
Vi må først ta med tokenet på siden vår - og for det kan vi bruke metatags:
Så konstruerer vi overskriften:
var token = $ ("meta [name = '_ csrf']"). attr ("innhold"); var header = $ ("meta [name = '_ csrf_header']"). attr ("content"); $ (dokument) .ajaxSend (funksjon (e, xhr, opsjoner) {xhr.setRequestHeader (header, token);});
4. CSRF deaktivert test
Med alt dette på plass, vil vi gå for å gjøre noen tester.
La oss først prøve å sende inn en enkel POST-forespørsel når CSRF er deaktivert:
@ContextConfiguration (klasser = {SecurityWithoutCsrfConfig.class, ...}) offentlig klasse CsrfDisabledIntegrationTest utvider CsrfAbstractIntegrationTest {@Test offentlig ugyldighet gittNotAuth_whenAddFoo_thenUnauthorized () kaster Exception {mvc.performat. (Type). content (createFoo ())). og Expect (status (). isUnauthorized ()); } @Test offentlig ugyldig givenAuth_whenAddFoo_thenCreated () kaster unntak {mvc.perform (post ("/ foos"). ContentType (MediaType.APPLICATION_JSON) .content (createFoo ()) .with (testUser ())). Og Expect (status () .er skapt()); }}
Som du kanskje har lagt merke til, bruker vi en baseklasse for å holde den vanlige testhjelperlogikken - CsrfAbstractIntegrationTest:
@RunWith (SpringJUnit4ClassRunner.class) @WebAppConfiguration public class CsrfAbstractIntegrationTest {@Autowired private WebApplicationContext context; @Autowired private Filter springSecurityFilterChain; beskyttet MockMvc mvc; @Før offentlig ugyldig oppsett () {mvc = MockMvcBuilders.webAppContextSetup (context) .addFilters (springSecurityFilterChain) .build (); } beskyttet RequestPostProcessor testUser () {retur bruker ("bruker"). passord ("userPass"). roller ("USER"); } beskyttet String createFoo () kaster JsonProcessingException {return new ObjectMapper (). writeValueAsString (new Foo (randomAlphabetic (6))); }}
Merk at når brukeren hadde de riktige sikkerhetsopplysningene, ble forespørselen fullført - ingen ekstra informasjon var nødvendig.
Det betyr at angriperen ganske enkelt kan bruke noen av de tidligere diskuterte angrepsvektorene for å kompromittere systemet.
5. CSRF-aktivert test
La oss nå aktivere CSRF-beskyttelse og se forskjellen:
@ContextConfiguration (klasser = {SecurityWithCsrfConfig.class, ...}) offentlig klasse CsrfEnabledIntegrationTest utvider CsrfAbstractIntegrationTest {@Test offentlig ugyldighet gittNoCsrf_whenAddFoo_thenForbidden () kaster Unntak {mvc.performat. (Type). content (createFoo ()) .with (testUser ())). andExpect (status (). isForbidden ()); } @Test offentlig ugyldighet gittCsrf_whenAddFoo_thenCreated () kaster unntak {mvc.perform (post ("/ foos"). ContentType (MediaType.APPLICATION_JSON) .content (createFoo ()). With (testUser ()). With (csrf ()) ). ogExpect (status (). isCreated ()); }}
Nå hvordan denne testen bruker en annen sikkerhetskonfigurasjon - en som har CSRF-beskyttelse aktivert.
Nå vil POST-forespørselen ganske enkelt mislykkes hvis CSRF-token ikke er inkludert, noe som selvfølgelig betyr at de tidligere angrepene ikke lenger er et alternativ.
Til slutt, legg merke til csrf () metode i testen; dette skaper en RequestPostProcessor som automatisk fyller ut et gyldig CSRF-token i forespørselen for testformål.
6. Konklusjon
I denne artikkelen diskuterte vi et par CSRF-angrep og hvordan vi kan forhindre dem ved hjelp av Spring Security.
De full gjennomføring av denne opplæringen finnes i GitHub-prosjektet - dette er et Maven-basert prosjekt, så det skal være enkelt å importere og kjøre som det er.