Рубрики
Без рубрики

Предотвращение межсайтовых сценариев (XSS) в приложении Spring

Автор оригинала: baeldung.

1. Обзор

При создании веб-приложения Spring важно сосредоточиться на безопасности. Cross-site scripting (XSS) является одной из самых критических атак на веб-безопасность.

Предотвращение атаки XSS-это сложная задача в весеннем приложении. Spring предоставляет некоторую помощь, но нам нужно реализовать дополнительный код для полной защиты.

В этом уроке мы будем использовать доступные функции безопасности Spring и добавим наш собственный фильтр XSS.

2. Что такое атака межсайтового скриптинга (XSS)?

2.1. Определение проблемы

XSS-это распространенный тип инъекционной атаки. В XSS злоумышленник пытается выполнить вредоносный код в веб-приложении. Они взаимодействуют с ним через веб – браузер или HTTP-клиентские инструменты, такие как Postman .

Существует два типа XSS атак:

  • Отраженный или непостоянный XSS
  • Сохраненный или постоянный XSS

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

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

2.2. Защита От Нападения

Основной стратегией предотвращения XSS-атак является очистка пользовательского ввода.

В веб-приложении Spring вход пользователя представляет собой HTTP-запрос. Чтобы предотвратить атаку, мы должны проверить содержимое HTTP-запроса и удалить все, что может быть исполнено сервером или браузером.

Для обычного веб-приложения, доступ к которому осуществляется через веб-браузер, мы можем использовать встроенные функции Spring Security (Reflected XSS). Для веб-приложения, предоставляющего API, Spring Security не предоставляет никаких функций, и мы должны реализовать пользовательский фильтр XSS для предотвращения хранимых XSS.

3. Обеспечение безопасности приложения XSS с помощью Spring Security

Spring Security по умолчанию предоставляет несколько заголовков безопасности. Он включает в себя заголовок X-XSS-Protection . X-XSS-Protection сообщает браузеру блокировать то, что выглядит как XSS. Spring Security может автоматически добавить этот заголовок безопасности в ответ. Чтобы активировать эту функцию, мы настраиваем поддержку XSS в классе конфигурации Spring Security.

Используя эту функцию, браузер не выполняет рендеринг при обнаружении попытки XSS. Однако некоторые веб-браузеры не реализовали аудитор XSS. В этом случае они не используют заголовок X-XSS-Protection . Чтобы преодолеть эту проблему, мы также можем использовать функцию Content Security Policy (CSP) .

CSP-это дополнительный уровень безопасности, который помогает смягчить атаки XSS и инъекции данных. Чтобы включить его, нам нужно настроить наше приложение на возврат заголовка Content-Security-Policy , предоставив компонент WebSecurityConfigurerAdapter bean:

@Configuration
public class SecurityConf extends WebSecurityConfigurerAdapter {

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
          .headers()
          .xssProtection()
          .and()
          .contentSecurityPolicy("script-src 'self'");
    }
}

Эти заголовки действительно защищают REST API от хранимых XSS. Чтобы решить эту проблему, нам также может потребоваться реализовать фильтр XSS.

4. Создание фильтра XSS

4.1. Использование фильтра XSS

Чтобы предотвратить атаку XSS, мы удалим все подозрительные строки из содержимого запроса перед передачей запроса в RestController :

Содержимое HTTP – запроса включает в себя следующие части:

  • Заголовки
  • Параметры
  • Тело

В общем, мы должны удалить вредоносный код из заголовков, параметров и тел для каждого запроса.

Мы создадим фильтр для оценки значения запроса. Фильтр XSS проверяет параметры, заголовки и тело запроса.

Давайте создадим фильтр XSS, реализовав интерфейс фильтра:

@Component
@Order(Ordered.HIGHEST_PRECEDENCE)
public class XSSFilter implements Filter {
 
    @Override 
    public void doFilter(ServletRequest request, ServletResponse response,
      FilterChain chain) throws IOException, ServletException {
        XSSRequestWrapper wrappedRequest = 
          new XSSRequestWrapper((HttpServletRequest) request);
        chain.doFilter(wrappedRequest, response);
    }

    // other methods
}

Мы должны настроить фильтр XSS в качестве первого фильтра в приложении Spring. Поэтому мы установим порядок фильтра в HIGHEST_PRECEDENCE .

Чтобы добавить очистку данных в наш запрос, мы создадим подкласс HttpServletRequestWrapper под названием XSSRequestWrapper, который переопределяет методы getParameterValues , getParameter и getHeaders для выполнения проверки XSS перед предоставлением данных контроллеру.

4.2. Удаление XSS из параметра запроса

Теперь давайте реализуем методы getParameterValues и getParameter в нашей оболочке запросов:

public class XSSRequestWrapper extends HttpServletRequestWrapper {

    @Override
    public String[] getParameterValues(String parameter) {
        String[] values = super.getParameterValues(parameter);
        if (values == null) {
            return null;
        }
        int count = values.length;
        String[] encodedValues = new String[count];
        for (int i = 0; i < count; i++) {
            encodedValues[i] = stripXSS(values[i]);
        }
        return encodedValues;
    }
    @Override
    public String getParameter(String parameter) {
        String value = super.getParameter(parameter);
        return stripXSS(value);
    }
}

Мы будем писать функцию stripes для обработки каждого значения. Мы скоро это осуществим.

4.3. Удаление XSS из заголовка запроса

Нам также нужно удалить XSS из заголовков запросов. Поскольку getHeaders возвращает Перечисление нам нужно будет создать новый список, очистив каждый заголовок:

@Override
public Enumeration getHeaders(String name) {
    List result = new ArrayList<>();
    Enumeration headers = super.getHeaders(name);
    while (headers.hasMoreElements()) {
        String header = headers.nextElement();
        String[] tokens = header.split(",");
        for (String token : tokens) {
            result.add(stripXSS(token));
        }
    }
    return Collections.enumeration(result);
}

4.4. Удаление XSS из Тела запроса

Наш фильтр должен удалить опасный контент из тела запроса. Поскольку у нас уже есть wrappedRequest с изменяемым InputStream , давайте расширим код для обработки тела и сбросим значение в InputStream после его очистки:

@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
  throws IOException, ServletException {
    XSSRequestWrapper wrappedRequest = new XSSRequestWrapper((HttpServletRequest) request);
    String body = IOUtils.toString(wrappedRequest.getReader());
    if (!StringUtils.isBlank(body)) {
        body = XSSUtils.stripXSS(body);
        wrappedRequest.resetInputStream(body.getBytes());
    }
    chain.doFilter(wrappedRequest, response);
}

5. Использование внешних библиотек для очистки данных

Весь код, считывающий запрос, теперь выполняет функцию stripes для любого пользовательского контента. Давайте теперь создадим эту функцию для выполнения проверки XSS.

Во-первых, этот метод получит значение запроса и канонизирует его. Для этого шага мы используем ESAPI . ESAPI-это библиотека управления безопасностью веб-приложений с открытым исходным кодом, доступная в OWASP.

Во-вторых, мы проверим значение запроса по шаблонам XSS. Если значение подозрительно, оно будет установлено в пустую строку. Для этого мы будем использовать Jsoup , который предоставляет некоторые простые функции очистки. Если бы мы хотели больше контроля, мы могли бы создавать свои собственные регулярные выражения, но это может быть более подвержено ошибкам, чем использование библиотеки.

5.1. Зависимости

Во-первых, мы добавляем зависимость esapi maven к нашему pom.xml файл:


    org.owasp.esapi
    esapi
    2.2.2.0

Кроме того, нам нужен jsoup :


    org.jsoup
    jsoup
    1.13.1

5.2. Его Реализация

Теперь давайте создадим метод stripes :

public static String stripXSS(String value) {
    if (value == null) {
        return null;
    }
    value = ESAPI.encoder()
      .canonicalize(value)
      .replaceAll("\0", "");
    return Jsoup.clean(value, Whitelist.none());
}

Здесь мы установили для Jsoup Whitelist значение none , разрешающее только текстовые узлы. Таким образом, весь HTML будет удален.

6. Тестирование XSS-профилактики

6.1. Ручное тестирование

Теперь давайте с помощью Почтальона отправим подозрительный запрос в наше приложение. Мы отправим почтовое сообщение в службу URI /person/person . Кроме того, мы включим некоторые подозрительные заголовки и параметры.

На рисунке ниже показаны заголовки и параметры запроса:

Поскольку наш сервис принимает данные JSON, давайте добавим в тело запроса некоторое подозрительное содержимое JSON:

Поскольку наш тестовый сервер возвращает очищенный ответ, давайте рассмотрим, что произошло:

Заголовки и значения параметров заменяются пустой строкой. Кроме того, тело ответа показывает, что наше подозрительное значение в поле фамилия было удалено.

6.2. Автоматизированное тестирование

Теперь давайте напишем автоматизированный тест для нашей фильтрации XSS:

// declare required variables
personJsonObject.put("id", 1);
personJsonObject.put("firstName", "baeldung ");
personJsonObject.put("lastName", "baeldung click me!");

builder = UriComponentsBuilder.fromHttpUrl(createPersonUrl)
  .queryParam("param", "");
headers.add("header_4", "

Your search for 'flowers '"); HttpEntity request = new HttpEntity<>(personJsonObject.toString(), headers); ResponseEntity personResultAsJsonStr = restTemplate .exchange(builder.toUriString(), HttpMethod.POST, request, String.class); JsonNode root = objectMapper.readTree(personResultAsJsonStr.getBody()); assertThat(root.get("firstName").textValue()).isEqualTo("baeldung "); assertThat(root.get("lastName").textValue()).isEqualTo("baeldung click me!"); assertThat(root.get("param").textValue()).isEmpty(); assertThat(root.get("header_1").textValue()).isEmpty(); assertThat(root.get("header_2").textValue()).isEmpty(); assertThat(root.get("header_3").textValue()).isEmpty(); assertThat(root.get("header_4").textValue()).isEqualTo("Your search for 'flowers '");

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

В этой статье мы рассмотрели, как предотвратить атаки XSS с помощью функций безопасности Spring и пользовательского фильтра XSS.

Мы увидели, как он может защитить нас как от отражающих, так и от постоянных атак XSS. Мы также рассмотрели, как протестировать приложение как с помощью Postman, так и с помощью JUnit-теста.

Как всегда, исходный код можно найти на GitHub .