Зарегистрируйтесь для доступа к 15+ бесплатным курсам по программированию с тренажером

Безопасность Java: Веб-технологии

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

Безопасность веб-приложений — важная тема, которой мы уделим весь этот урок.

Главное правило безопасности

Оно звучит так:

Никогда не доверяйте пользователям

В первую очередь, это правило касается данных, которые пользователи вводят на сайте. Предположим, что у нас есть страница профиля пользователя, где выводится его ник, взятый из адресной строки:

package org.example.hexlet;

import io.javalin.Javalin;

public class HelloWorld {
    public static void main(String[] args) {
        var app = Javalin.create(config -> {
            config.bundledPlugins.enableDevLogging();
        });

        app.get("/users/{id}", ctx -> {
            var id = ctx.pathParam("id");
            ctx.contentType("html");
            ctx.result("<h1>" + id + "</h1>");
        });

        app.start(7070);
    }
}

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

http://localhost:7070/users/%3Cscript%3Ealert('attack!')%3B%3C%2Fscript%3E

Если мы доверимся данным от пользователей и откроем такой адрес, случится вот это:

XSS

В этом адресе закодирован JavaScript-код, который в оригинале выглядит так:

<script>
  alert('attack!');
</script>

Проблема в том, что код не отобразился, но попал в HTML страницы и выполнился. Так произошло, потому что для браузера такой JavaScript-код выглядит как часть страницы.

Если открыть получившийся HTML, то он будет выглядеть так:

<h1
  <script>alert('attack!');</script>
</h1>

Именно так происходит XSS-атака (межсайтовый скриптинг). На страницу внедряется вредоносный код, который выполняется в браузере пользователя и отправляет информацию о пользователе на сервер злоумышленника. XSS — это одна из самых распространенных атак. Очень много подобных уязвимостей есть даже на сайтах больших компаний.

Специфика подобных атак в том, что вредоносный код может использовать авторизацию пользователя в веб-системе. Так злоумышленник может получить расширенный доступ к системе или логины и пароли пользователей. Если в исходном коде встречается конструкция <текст>, то браузер автоматически считает ее тегом.

Вернемся к коду из начала урока:

package org.example.hexlet;

import io.javalin.Javalin;

public class HelloWorld {
    public static void main(String[] args) {
        var app = Javalin.create(config -> {
            config.bundledPlugins.enableDevLogging();
        });

        app.get("/users/{id}", ctx -> {
            var id = ctx.pathParam("id");
            ctx.contentType("html");
            ctx.result("<h1>" + id + "</h1>");
        });

        app.start(7070);
    }
}

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

Чтобы закрыть ее, нужно использовать не сами теги, а HTML-эквиваленты символов. Тогда код выше начнет выглядеть так:

<h1>
  &lt;script&gt;alert('attack!');&lt;/script&gt;
</h1>

Здесь мы заменили:

  • < на &lt;
  • > на &gt;

Это не экранирование, а именно замена спецсимволов на их HTML-эквиваленты. Если открыть браузер, то там мы увидим правильное отображение:

XSS

Замена символов на спецсимволы выполняется с помощью commons-text и других специальных библиотек. Один из вариантов выглядит так:

package org.example.hexlet;

import org.apache.commons.text.StringEscapeUtils;
import io.javalin.Javalin;

public class HelloWorld {
    public static void main(String[] args) {
        var app = Javalin.create(config -> {
            config.bundledPlugins.enableDevLogging();
        });

        app.get("/users/{id}", ctx -> {
            var id = ctx.pathParam("id");
            var escapedId = StringEscapeUtils.escapeHtml4(id);
            ctx.contentType("text/html");
            ctx.result(escapedId);
        });
        app.start(7070);
    }
}

Метод StringEscapeUtils.escapeHtml4() принимает на вход HTML и заменяет в нем все спецсимволы на их HTML-эквиваленты. Остальные символы остаются без изменений.

Через такую обработку нужно пропускать любые данные, которые мы выводим. Исключение составляют лишь ситуации, в которых мы точно знаем, что в данных есть HTML и мы его хотим отобразить. К таким данным могут относиться статьи в блоге, потому что они содержат HTML-часть. Например, так работает блог Хекслета.

Но даже внутрь безопасного HTML можно добавить вредоносный JavaScript-код. Вряд ли подобным будут заниматься сотрудники компании, но это могут сделать внешние авторы. Попробуем устранить эту уязвимость. В этом поможет механизм, который вырезает из HTML все <script> и другие опасные теги.

Рассмотрим пример с библиотекой java-html-sanitizer:

PolicyFactory policy = new HtmlPolicyBuilder()
    .allowElements("a")
    .allowUrlProtocols("https")
    .allowAttributes("href").onElements("a")
    .requireRelNofollowOnLinks()
    .toFactory();
String safeHTML = policy.sanitize(untrustedHTML);

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

Безопасность по умолчанию — это ключевой подход в создании надежных приложений. Один из способов ее обеспечить — использовать JTE или другие качественные шаблонизаторы. Они обрабатывают все данные внутри своих шаблонов и автоматически защищают от XSS-атак.


Самостоятельная работа

В этом задании вы на собственном опыте убедитесь, что шаблонизатор защищает от XSS-атак:

  1. Добавьте в приложение маршрут, который выводит идентификатор пользователя, основываясь на данных из строки запроса
  2. Повторите шаги из урока
  3. Экранируйте вывод идентификатора пользователя на странице пользователя, ориентируясь на теорию из урока
  4. Измените обработчик так, чтобы неэкранированные данные пользователя отправлялись в шаблон и запускали его рендеринг
  5. Запустите приложение и попробуйте передать в строке запроса какой-нибудь HTML
  6. Проверьте, что данные в шаблоне проходят предварительную обработку автоматически

Дополнительные материалы

  1. Безопасность приложений (Сам материал написан про Rails, но подходит для всех остальных языков)

Аватары экспертов Хекслета

Остались вопросы? Задайте их в разделе «Обсуждение»

Вам ответят команда поддержки Хекслета или другие студенты

Для полного доступа к курсу нужен базовый план

Базовый план откроет полный доступ ко всем курсам, упражнениям и урокам Хекслета, проектам и пожизненный доступ к теории пройденных уроков. Подписку можно отменить в любой момент.

Получить доступ
1000
упражнений
2000+
часов теории
3200
тестов

Открыть доступ

Курсы программирования для новичков и опытных разработчиков. Начните обучение бесплатно

  • 130 курсов, 2000+ часов теории
  • 1000 практических заданий в браузере
  • 360 000 студентов
Отправляя форму, вы принимаете «Соглашение об обработке персональных данных» и условия «Оферты», а также соглашаетесь с «Условиями использования»

Наши выпускники работают в компаниях:

Логотип компании Альфа Банк
Логотип компании Aviasales
Логотип компании Yandex
Логотип компании Tinkoff
Рекомендуемые программы
профессия
от 25 000 ₸ в месяц
Разработка приложений на языке Java
10 месяцев
с нуля
Старт 21 ноября

Используйте Хекслет по-максимуму!

  • Задавайте вопросы по уроку
  • Проверяйте знания в квизах
  • Проходите практику прямо в браузере
  • Отслеживайте свой прогресс

Зарегистрируйтесь или войдите в свой аккаунт

Отправляя форму, вы принимаете «Соглашение об обработке персональных данных» и условия «Оферты», а также соглашаетесь с «Условиями использования»