Руководство по защите CSRF в Spring Security
1. обзор
В этом руководстве мы обсудим CSRF-атаки на межсайтовые запросы и способы их предотвращения с помощью Spring Security.
Дальнейшее чтение:
CSRF защита с помощью Spring MVC и Thymeleaf
Краткое и практическое руководство по предотвращению CSRF-атак с помощью Spring Security, Spring MVC и Thymeleaf.
Автоматическая настройка безопасности Spring Boot
Краткое и практическое руководство по настройке Spring Security по умолчанию в Spring Boot.
Введение в безопасность методов Spring
Руководство по безопасности на уровне методов с использованием среды безопасности Spring.
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, поэтому его должно быть легко импортировать и запускать как есть.