Проблемы с безопасностью могут привести к утечке данных пользователей или даже к полному уничтожению сайта. Исследования показывают, что большинство сайтов имеют проблемы с безопасностью и уязвимостью к атакам. Время от времени случаются громкие взломы и утечки данных сотен тысяч и миллионов пользователей.
Безопасность веб-приложений — важная тема, которой мы уделим весь этот урок.
Главное правило безопасности
Оно звучит так:
Никогда не доверяйте пользователям
В первую очередь, это правило касается данных, которые пользователи вводят на сайте. Предположим, что у нас есть страница профиля пользователя, где выводится его ник, взятый из адресной строки:
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
Если мы доверимся данным от пользователей и откроем такой адрес, случится вот это:
В этом адресе закодирован 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>
<script>alert('attack!');</script>
</h1>
Здесь мы заменили:
<
на<
>
на>
Это не экранирование, а именно замена спецсимволов на их HTML-эквиваленты. Если открыть браузер, то там мы увидим правильное отображение:
Замена символов на спецсимволы выполняется с помощью 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-атак:
- Добавьте в приложение маршрут, который выводит идентификатор пользователя, основываясь на данных из строки запроса
- Повторите шаги из урока
- Экранируйте вывод идентификатора пользователя на странице пользователя, ориентируясь на теорию из урока
- Измените обработчик так, чтобы неэкранированные данные пользователя отправлялись в шаблон и запускали его рендеринг
- Запустите приложение и попробуйте передать в строке запроса какой-нибудь HTML
- Проверьте, что данные в шаблоне проходят предварительную обработку автоматически
Дополнительные материалы
Остались вопросы? Задайте их в разделе «Обсуждение»
Вам ответят команда поддержки Хекслета или другие студенты
- Статья «Как учиться и справляться с негативными мыслями»
- Статья «Ловушки обучения»
- Статья «Сложные простые задачи по программированию»
- Вебинар «Как самостоятельно учиться»
Для полного доступа к курсу нужен базовый план
Базовый план откроет полный доступ ко всем курсам, упражнениям и урокам Хекслета, проектам и пожизненный доступ к теории пройденных уроков. Подписку можно отменить в любой момент.