1. Обзор
В этом уроке мы обсудим атаки CSRF на подделку межсайтовых запросов и способы их предотвращения с помощью Spring Security.
Дальнейшее чтение:
Защита CSRF с пружинным MVC и Thymeleaf
Автоматическая настройка безопасности весенней загрузки
Введение в безопасность метода 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
Есть несколько способов сделать это:
- Ссылка: Злоумышленник может убедить жертву нажать на эту ссылку, например, для выполнения передачи:
- Изображение: Злоумышленник может использовать тег с целевым URL – адресом в качестве источника изображения, поэтому щелчок даже не требуется. Запрос будет автоматически выполнен при загрузке страницы:
2.2. Пример публикации
Если основной запрос должен быть запросом POST – например:
POST http://bank.com/transfer accountNo=1234&amount=100
Затем злоумышленник должен заставить жертву выполнить аналогичное:
POST http://bank.com/transfer accountNo=5678&amount=1000
Ни , ни не будут работать в этом случае. Злоумышленнику потребуется <форма> – следующим образом:
Однако форма может быть отправлена автоматически с помощью Javascript – следующим образом:
2.3. Практическое Моделирование
Теперь, когда мы понимаем, как выглядит атака CSRF, давайте смоделируем эти примеры в приложении Spring.
Мы начнем с простой реализации контроллера – Банковского контроллера :
@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 через простую <форму> .
Теперь – давайте посмотрим, как будет выглядеть страница злоумышленника :
Эта страница будет работать в другом домене – домене злоумышленника.
Наконец, давайте запустим два приложения – исходное и атакующее – локально и сначала получим доступ к исходной странице:
http://localhost:8081/spring-rest-full/csrfHome.html
Затем давайте перейдем на страницу злоумышленника:
http://localhost:8081/spring-security-rest/api/csrfAttacker.html
Отслеживая точные запросы, исходящие с этой страницы злоумышленника, мы сможем сразу же определить проблемный запрос, попав в исходное приложение и полностью аутентифицировавшись.
3. Конфигурация безопасности Пружины
Чтобы использовать защиту CSRF Spring Security, нам сначала нужно убедиться, что мы используем правильные методы HTTP для всего, что изменяет состояние ( PATCH , POST , PUT и DELETE – not GET).
3.1. Конфигурация Java
Защита CSRF включена по умолчанию в конфигурации Java. Мы все еще можем отключить его, если понадобится:
@Override protected void configure(HttpSecurity http) throws Exception { http .csrf().disable(); }
3.2. Конфигурация XML
В более старой конфигурации XML (pre 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()); } }
Как вы, возможно, заметили, мы используем базовый класс для хранения общей вспомогательной логики тестирования – 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.
полную реализацию этого учебника можно найти в проекте GitHub – это проект на основе Maven, поэтому его должно быть легко импортировать и запускать как есть.