Руководство по защите CSRF в Spring Security

Руководство по защите CSRF в Spring Security

1. обзор

В этом руководстве мы обсудим CSRF-атаки на межсайтовые запросы и способы их предотвращения с помощью Spring Security.

Дальнейшее чтение:

CSRF защита с помощью Spring MVC и Thymeleaf

Краткое и практическое руководство по предотвращению CSRF-атак с помощью Spring Security, Spring MVC и Thymeleaf.

Read more

Автоматическая настройка безопасности Spring Boot

Краткое и практическое руководство по настройке Spring Security по умолчанию в Spring Boot.

Read more

Введение в безопасность методов Spring

Руководство по безопасности на уровне методов с использованием среды безопасности Spring.

Read more

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

Существует несколько форм CSRF-атак - давайте обсудим некоторые из наиболее распространенных.

2.1. ПОЛУЧИТЬ Примеры

Давайте рассмотрим следующий запросGET, используемый зарегистрированными пользователями для перевода денег на определенный банковский счет“1234”:

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

Если злоумышленник хочет вместо этого перевести деньги со счета жертвы на свой -“5678” - ему необходимо заставить жертву инициировать запрос:

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

Есть несколько способов сделать это:

  • Link: Злоумышленник может убедить жертву перейти по этой ссылке, например, для выполнения передачи:

  • Image: Злоумышленник может использовать тег<img/> с целевым URL в качестве источника изображения, поэтому в клике даже не будет необходимости. Запрос будет автоматически выполнен при загрузке страницы:

2.2. Пример POST

Если основным запросом должен быть запрос POST - например:

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

Затем атакующему нужно, чтобы жертва запустила похожее:

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

В этом случае ни<a>, ни<img/> работать не будут. Злоумышленнику понадобится<form> - следующим образом:

Тем не менее, форма может быть отправлена ​​автоматически с использованием 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-страница, запускающая операцию банковского перевода:



    

CSRF test on Origin

Transfer Money to John

Это страница основного приложения, работающая в исходном домене.

Обратите внимание, что мы смоделировали какGET с помощью простой ссылки, так иPOST с помощью простого<form>.

А теперь посмотрим, как будет выглядетьthe attacker page:

Эта страница будет работать в другом домене - домене злоумышленника.

Наконец, давайте запустим два приложения - исходное и приложение злоумышленника - локально и сначала обратимся к исходной странице:

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

Затем давайте зайдем на страницу злоумышленника:

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

Отслеживая точные запросы, исходящие с этой страницы злоумышленника, мы сможем сразу же обнаружить проблемный запрос, попав в исходное приложение и полностью аутентифицированный.

3. Конфигурация Spring Security

Чтобы использовать защиту CSRF Spring Security, нам сначала нужно убедиться, что мы используем правильные методы HTTP для всего, что изменяет состояние (PATCH,POST,PUT иDELETE –не GET).

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

Защита CSRF -enabled by default в конфигурации 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 Disabled Test

Когда все это будет готово, мы перейдем к тестированию.

Давайте сначала попробуем отправить простой запрос 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());
    }
}

Как вы могли заметить, мы используем базовый класс для хранения общей вспомогательной логики тестирования -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)));
    }
}

Обратите внимание, что когда пользователь имел правильные учетные данные безопасности, запрос был успешно выполнен - ​​никакой дополнительной информации не требовалось.

Это означает, что злоумышленник может просто использовать любой из ранее обсужденных векторов атаки, чтобы легко скомпрометировать систему.

5. CSRF включен тест

Теперь давайте включим защиту CSRF и увидим разницу:

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

Теперь, как в этом тесте используется другая конфигурация безопасности - с включенной защитой CSRF.

Теперь запрос POST просто не удастся, если токен CSRF не включен, что, конечно, означает, что более ранние атаки больше не являются вариантом.

Наконец, обратите внимание на методcsrf() в тесте; это создаетRequestPostProcessor, который автоматически заполняет действительный токен CSRF в запросе для целей тестирования.

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

В этой статье мы обсудили пару атак CSRF и способы их предотвращения с помощью Spring Security.

full implementation этого руководства можно найти вthe GitHub project - это проект на основе Maven, поэтому его должно быть легко импортировать и запускать как есть.