Ръководство за защита на CSRF в пролетната сигурност

1. Общ преглед

В този урок ще обсъдим CSRF атаки за подправяне на заявки между сайтове и как да ги предотвратим с помощта на Spring Security.

2. Две прости CSRF атаки

Има множество форми на CSRF атаки - нека обсъдим някои от най-често срещаните.

2.1. ВЗЕМЕТЕ примери

Нека разгледаме следната GET заявка, използвана от влезли потребители за превод на пари по конкретна банкова сметка „1234“ :

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

Ако нападателят иска вместо това да прехвърли пари от сметката на жертвата към собствената си сметка - „5678“ - той трябва да накара жертвата да задейства искането:

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

Има няколко начина да се случи това:

  • Връзка: Нападателят може да убеди жертвата да кликне върху тази връзка, например, за да изпълни прехвърлянето:
 Show Kittens Pictures 
  • Изображение: Нападателят може да използваетикет с целевия URL адрес като източник на изображение - така че кликването дори не е необходимо. Заявката ще бъде изпълнена автоматично, когато страницата се зареди:

2.2. POST Пример

Ако основната заявка трябва да бъде POST заявка - например:

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

Тогава нападателят трябва да накара жертвата да изпълни подобно:

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

Нито на или ще работи в този случай. Нападателят ще се нуждае от - както следва:

Формулярът обаче може да бъде изпратен автоматично с помощта на Javascript - както следва:

  ...

2.3. Практическа симулация

Сега, когато разбираме как изглежда CSRF атака, нека симулираме тези примери в приложение Spring.

Ще започнем с проста реализация на контролера - 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); ... } }

И нека имаме и основна HTML страница, която задейства операцията за банков превод:

 Transfer Money to John  Account Number  Amount     

Това е страницата на основното приложение, работещо в началния домейн.

Имайте предвид, че сме симулирали както GET чрез проста връзка, така и POST чрез проста.

Сега - нека видим как ще изглежда страницата на нападателя :

  Show Kittens Pictures 

Тази страница ще работи на различен домейн - домейн на нападателя.

И накрая, нека стартираме двете приложения - оригиналното и приложението на атакуващия - локално и нека първо да отворим оригиналната страница:

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

След това нека да осъществим достъп до страницата на нападателя:

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

Проследявайки точните заявки, които произхождат от тази страница на нападателя, ще можем незабавно да открием проблемната заявка, като ударим оригиналното приложение и бъде напълно удостоверено.

3. Пролетна конфигурация на защитата

За да използваме защитата на Spring Security CSRF, първо трябва да се уверим, че използваме правилните HTTP методи за всичко, което променя състоянието ( PATCH , POST , PUT и DELETE - не GET).

3.1. Конфигурация на Java

Защитата CSRF е активирана по подразбиране в конфигурацията на Java. Все още можем да го деактивираме, ако трябва:

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

3.2. XML конфигурация

В по-старата XML конфигурация (преди Spring Security 4) защитата на CSRF е деактивирана по подразбиране и можем да я активираме, както следва:

 ...  

Започвайки от Spring Security 4.x - защитата CSRF е активирана по подразбиране и в XML конфигурацията; разбира се, все още можем да го деактивираме, ако трябва:

 ...  

3.3. Допълнителни параметри на формуляра

И накрая, с активирана CSRF защита от страна на сървъра, ще трябва да включим и CSRF токена в нашите заявки от страна на клиента:

3.4. Използване на JSON

Не можем да изпратим маркера CSRF като параметър, ако използваме JSON; вместо това можем да изпратим маркера в заглавката.

Първо ще трябва да включим маркера в нашата страница - и за това можем да използваме мета тагове:

След това ще изградим заглавката:

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

С всичко това на място ще преминем към тестване.

Нека първо се опитаме да подадем проста POST заявка, когато CSRF е деактивиран:

@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.

И накрая, забележете метода csrf () в теста; това създава RequestPostProcessor, който автоматично ще попълни валиден CSRF маркер в заявката за целите на тестването.

6. Заключение

В тази статия обсъдихме няколко CSRF атаки и как да ги предотвратим с помощта на Spring Security.

Най- пълното прилагане на този урок може да се намери в проекта GitHub - това е Maven-базиран проект, така че трябва да бъде лесен за внос и работи като такъв.