XSS Security даже для «старых» приложений.

Нажмите здесь, чтобы опубликовать эту статью в LinkedIn »

Повышение безопасности

Если бы я рассказал вам о простом способе сделать ваш сайт почти неуязвимым для атак межсайтового скриптинга (XSS), вы бы использовали его?

Что ж, Политика безопасности контента (CSP) может это сделать! Это так же просто, как добавить дополнительный заголовок при передаче HTML-файла с сервера.

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

Content-Security-Policy: default-src 'self'; form-action 'self';

На основе этого примера браузеры заголовков предотвращают запуск любых встроенных скриптов или стилей и разрешают загружать только такие ресурсы, как скрипты, стили, шрифты и т. Д., Из нашего собственного домена.

И в этом кроется загвоздка: нет встроенного JavaScript

Пример использования

Позвольте мне подготовить почву. Мы создаем веб-приложение, и нам нужны данные с сервера. Мы переносим существующий сервис в чудеса Нового Света. Прогрессивное веб-приложение и все такое прочее. Однако эти данные, которые нам нужны, очень неудобно получать, потому что они недоступны через какой-либо веб-API.

Наш сервер использует шаблоны, чтобы показывать пользователю динамический контент, такой как его имя, возраст и что их собака ела на завтрак! 🐶

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

<!-- user-template.html - On the server -->
<script type="text/javascript">
  var firstName = "{{ user.firstName }}";
  var age = {{ user.age }};
  // the 'dump' filter calls JSON.stringify on the object
  var pet = {{ user.pet | dump }};
  document.addEventListener("DOMContentLoaded", function() {
    //do work with the variables once the page is ready...
  });
</script>

Но теперь, если мы введем приведенный выше код в наше веб-приложение с заголовком CSP, как указано выше, мы увидим в консоли браузера сообщение об ошибке о том, что этот скрипт не был выполнен.

Примечание. В большинстве случаев эти данные могут (и, вероятно, должны) поступать из вызовов API, однако есть случаи, когда это невозможно.

Решение: встроенный JSON

К счастью, есть еще одно простое и безопасное решение.

Вместо встроенного JavaScript мы можем создать тег <script> с типом application/json. Браузер не будет оценивать это как JavaScript, поэтому он пройдет CSP.

<!-- user-template.html -->
<script type="application/json" data-my-app-selector="user-data">
{
  "firstName": "{{ user.firstName }}",
  "age": {{ user.age }},
  "pet": {{ user.pet | dump }}
}
</script>
<script src="main.js"></script>

Затем в нашем основном файле JavaScript мы проанализируем JSON и возьмем значение для использования в нашем приложении.

// main.js
function getData(dataSelect) {
  try {
    const inlineJsonElement = document.querySelector(
      `script[type="application/json"][data-my-app-selector="${dataSelect}"]`
    );
    const data = JSON.parse(inlineJsonElement.textContent);
    return data;
  } catch (err) {
    console.error(`Couldn't read JSON data from ${dataSelect}`, err);
  }
}
document.addEventListener("DOMContentLoaded", function() {
  // do work with the variables once the page is ready
  const user = getData("user-data");
  console.log(user.name);
  // ...
});

Этот шаблон позволяет нам иметь безопасный CSP и при этом сохранять данные с сервера! Миссия выполнена!

Весь код в этом посте доступен в репозитории GitHub. Вы можете проверить это и поиграть с различными безопасными / небезопасными вариантами.

Некоторые альтернативы и их недостатки

Атрибуты данных

Я видел, как в некоторых проектах используются html-теги с data-xyz атрибутами для хранения значений. Обратной стороной этого подхода является потеря информации о типе данных. Например, должно ли значение быть числом 4 или строкой "4"? По этой причине я считаю, что JSON - лучший подход.

CSP на основе nonce

«Nonce» - это причудливое название для «числа, использованного один раз». Он должен быть случайным и непредсказуемым. В контексте веб-страниц мы должны генерировать новый одноразовый номер для каждого запроса, иначе его легко обойти.

CSP действительно позволяют перечислить одноразовый номер, который могут указывать теги сценария, и если одноразовый номер тега сценария совпадает с одноразовым номером в CSP, его можно запустить.

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

Это подводит нас к основной проблеме этого метода. Его часто неправильно понимают, и его очень легко сделать неправильно. Я видел множество потоков, предлагающих использовать одноразовые номера, сгенерированные во время компиляции / сборки / сборки. Это хуже, чем полное отсутствие CSP, потому что люди думают, что получают защиту, но на самом деле являются уязвимыми. Если злоумышленник знает, каким будет одноразовый номер, он может предоставить его как допустимый атрибут для внедренного скрипта, и браузер с радостью запустит его.

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

Лучшей альтернативой использованию одноразовых номеров является использование хэшей SHA.

CSP на основе хэша SHA

Другой вариант с CSP - предоставить список хэшей SHA, который соответствует содержимому встроенных блоков скрипта, которые, как мы ожидаем, будут на странице. Браузер проверяет содержимое скрипта на соответствие предоставленным хешам.

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

Если вы хотите узнать больше о политиках безопасности контента (CSP), я рекомендую заглянуть в Документы MDN.

Вы можете создать свой собственный CSP, используя этот интерактивный инструмент от людей из Report URI

Пример репо для этого сообщения: https://github.com/Graham42/sample-csp-with-inline-data

Спасибо за чтение! Если вы хотите видеть больше моих сообщений в будущем, подпишитесь на меня в Twitter @ graham42x.