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

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

Шаг 1. Докажите, что новая функция должна быть отдельным микроприложением

Конечно, мы не хотим, чтобы новые пользователи на наших целевых страницах загружали профиль пользователя? Всего несколько сотен миллисекунд будут стоить нам миллионы долларов.

Давайте разделим профиль пользователя на отдельное микроприложение. Нам не нужно отвлекаться на наше основное приложение.

Кроме того: существует много жаргона микро-интерфейса, и нет единого мнения о том, что это такое. Для целей этой истории microapp — это независимое одностраничное приложение. У него есть собственный пакет index.html и JavaScript. В терминах Webpack это еще одна точка входа. В терминах Vite это многостраничное приложение. Демо микроприложения.

На первый взгляд, шаг 1 очень разумен. Микроприложения снижают риск развертывания, являясь независимым одностраничным приложением. Увеличенная задержка с реальной загрузкой страницы приемлема, когда мы переключаем контекст как таковой с профилем пользователя.

Шаг 2. Докажите, что микроприложение настолько маленькое, что ему не нужен фреймворк

Конечно, нам не нужно загружать тяжелую структуру, такую ​​​​как React, для простой страницы профиля пользователя? Наш пакет JavaScript был бы намного меньше и избегал бы атак на цепочку поставок, если бы мы просто написали эту функцию на ванильном JavaScript. Конечно же, мы не хотим, чтобы наши пользователи страдали от медленной загрузки или подвергались угрозе безопасности.

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

Не нужно даже думать о XSS в React, пока они избегают dangerouslySetInnerHTML. Причина, по которой настоящий злоумышленник должен выступать против таких фреймворков, как React, заключается в том, что они проделали такую ​​хорошую работу с такими API, как опасноSetInnerHTML. Эти устрашающе выглядящие API легко обнаружить в пулл-реквестах или автоматически с помощью линтеров.

Шаг 3. Реализуйте защиту от XSS с конструктивным недостатком

Нет, нет! Не беспокойтесь о XSS. Мы реализуем собственное смягчение! Смотрите, мы реализовали экранирование пользовательского ввода при изменении DOM.

const escape = (userInput) => String(userInput)
  .replaceAll('&', '&')
  .replaceAll('<', '&lt;')
  .replaceAll('>', '&gt;')
  .replaceAll('"', '&quot;')
  .replaceAll("'", '&apos;');
function createElement(tag, props, children) {
  const attributes = Object.entries(props ?? {})
    .map(([attribute, value]) =>
      `${attribute}="${escape(value)}"`
    )
    .join(' ');
  return `
    <${tag} ${attributes}>
      ${escape(children)}
    </${tag}>
  `;
}

На первый взгляд, код очень разумный. Но есть 2 возможных вектора XSS, вы их видите?

Функция escape в порядке. По сути, это то, какой будет настройка textContent. В то время как значение атрибута и дочерние элементы были правильно экранированы, тег и имя атрибута не были экранированы. Как злоумышленник воспользуется этим?

Атака на тег маловероятна, так как в реальном мире мало причин, по которым HTML-теги могут быть динамическими, однако имя атрибута может быть использовано с атрибутами данных.

Шаг 4. Используйте недостаток дизайна в плохом смягчении XSS

Конечно, нам бы хотелось, чтобы пользовательские данные отображались как атрибуты данных. Это ускорит разработку, так как было бы тривиально увидеть состояние приложения, просто взглянув на дерево DOM в инструментах разработки! Нам не нужно было бы реализовывать собственный API для извлечения данных для сквозной автоматизации тестирования. Он может просто считывать данные непосредственно из атрибутов данных. Он может даже обрабатывать настраиваемое поле для профиля пользователя.

...
<body>
  <div id="root">
    <div
      id="profile"
      data-name="Alice"
      data-fields="tagline,fav-food"
      data-tagline="No risk, no reward"
      data-fav-food="Cake"
    >
    </div>
...

Это может показаться безобидным, но теперь, когда настраиваемые поля — это пользовательский ввод, который может закончиться именем атрибута, у нас есть вектор XSS. Теперь с именем пользовательского поля, которое закрывает атрибут и div, он может вставить img с эксплойтом onload.

В сторону: innerHTML введенные script теги не запускаются по дизайну. Вот почему нам нужно использовать этот неуклюжий img onload метод.

...
<body>
  <div id="root">
    <div
      id="profile"
      data-name="Alice"
      data-fields="field=&quot;&quot;><img src=&quot;https://via.placeholder.com/1&quot;
onload=&quot;alert(document.domain)&quot;
/>"
      data-field=""
    >
      <img
        src="https://via.placeholder.com/1"
        onload="alert(document.domain)"
      >
      ="hacked"&gt;
    </div>
...

В сторону: alert(1) считается вредным.

См. полный исходный код и демо.

В реальном мире автор шага 3 может не совпадать с автором шага 4. Шаги с 1 по 3 могут быть совершенно невинными с добрыми намерениями. Вот почему для предотвращения XSS требуется бдительность, а не просто использовать фреймворк, лол. Нам нужно понять, как фреймворки защищают нас и почему мы должны уволить звездного разработчика, который хочет отказаться от фреймворка.

Хотите предотвратить XSS-атаки? Вам повезло, Battlefy набирает сотрудников.