Неожиданные HTTP-заголовки
Пару дней назад я ковырялся в блоге Creditkarma и заметил такой HTTP-заголовок:
X-hacker: If you’re reading this, you should visit wpvip.com/careers and apply to join the fun, mention this header.
(X-hacker: если вы это читаете, то вам следует зайти на wpvip.com/careers и подать заявку, чтобы присоединиться к веселью, упомяните этот заголовок).
Первой мыслью было: «Ничего себе, когда-то программисты ввели баг тысячелетия, чтобы сэкономить несколько бит на дате, и теперь у компании публикуют целые предложения о работе в заголовке HTTP!»
Мне стало очень интересно, и я провёл некоторые исследования.
Похоже, этот конкретный заголовок является «стандартным», если вы размещаете сайт на WordPress VIP, корпоративном хостинге WordPress под управлением Automattic. Тот же заголовок можно найти на многих известных сайтах, таких как:
- nypost.com
- techcrunch.com
- www.nbcolympics.com
- www.thesun.co.uk
- ещё многих тысячах
Результаты просто поразительные!
В заголовках HTTP можно найти разные предложения о работе
Да! Самые крутые компании мира, похоже, публикуют предложения о работе в «стандартном» HTTP-заголовке x-recruiting .
Вот некоторые примеры:
x-recruiting: If you are reading this, maybe you should be working at PayPal instead! Check out www.paypal.com/us/webapps/mpp/paypal-jobs
x-recruiting: Like HTTP headers? Come write ours: careers.booking.com
x-recruiting: Is code your craft? www.etsy.com/careers
x-recruiting: Seems you like http headers. To write ours, apply at job.otto.de and mention this header.
Хотите увидеть полный список? Для этого я открыл репозиторий на GitHub.
Помимо предложений о работе, в процессе исследования нашлись и более креативные вещи. Они очень меня взволновали как большого фаната таинственных бессмыслиц.
Таинственные HTTP-заголовки
Веб-сайт 9kw.eu, который вроде бы является провайдером капчи, выводит секретное сообщение «42»:
Сайт istreetview.com уже не поддерживается, но в заголовке можно найти адрес секретной веб-формы.
Я написал им свой ответ…
На thetradersdomain.com «скрытый соус» в заголовках, но это конфиденциально:
На images-dnxlive.com ещё несколько «секретных» ссылок в одном из HTTP-заголовков:
Если вы любите роскошные автомобили, вот вам jaguar.ro с заголовком для обнаружения ботов:
Но этот метод не очень хорошо работает и терпит неудачу, если подменить user-agent (извините, ребята из Jaguar).
И всё же… вы когда-нибудь видели сервер с прозвищем? Вот тут есть парочка:
X-ServerNickName: The Internet
И последний, но тоже интересный пример. Наши друзья из m.bidorbuy.co.ke выразили в HTTP-заголовках всю свою страсть:
x-powered-by: Passion and tiny cute kittens
x-servernickname: The Beast
x-hacker: If you are reading this, maybe you should be working at bidorbuy instead
Бонус
Похоже, что у многих модных IT-компаний есть дополнительные HTTP-заголовки, большинство из которых содержат предложения о работе.
Поэтому я подумал, что было бы здорово добавить дополнительный заголовок и на этот сайт!
Интересно? Проверьте сами!
Спасибо за чтение!
- HTTP-заголовки
- WordPress VIP
Ненужные HTTP-заголовки
Заголовки HTTP важны для контроля, как кэш и браузеры обрабатывают ваш контент. Но многие из них используются неправильно или бессмысленно, затрачивая лишние ресурсы в критический момент загрузки страницы, и они могут работать не так, как вы думаете. В серии статей о лучших практиках сначала рассмотрим ненужные заголовки.
Большинство разработчиков знают о важных и нужных HTTP-заголовках. Самые известные — Content-Type и Content-Length , это почти универсальные хедеры. Но в последнее время для повышения безопасности начали использоваться заголовки вроде Content-Security-Policy и Strict-Transport-Security , а для повышения производительности — Link rel=preload . Несмотря на широкую поддержку в браузерах, лишь немногие их используют.
В то же время есть много чрезвычайно популярных заголовков, которые вообще не новые и не очень полезные. Мы можем это доказать с помощью HTTP Archive, проекта под управлением Google и спонсируемого Fastly, который каждый месяц при помощи WebPageTest скачивает 500 000 сайтов и выкладывает результаты в BigQuery.
Вот 30 самых популярных заголовков ответов по данным HTTP Archive (на основе количества доменов в архиве, которые выдают каждый заголовок), и примерная оценка полезности каждого из них:
Заголовок | Запросов | Доменов | Статус |
---|---|---|---|
date | 48779277 | 535621 | Требуется по протоколу |
content-type | 47185627 | 533636 | Обычно требуется браузером |
server | 43057807 | 519663 | Необязателен |
content-length | 42388435 | 519118 | Полезен |
last-modified | 34424562 | 480294 | Полезен |
cache-control | 36490878 | 412943 | Полезен |
etag | 23620444 | 412370 | Полезен |
content-encoding | 16194121 | 409159 | Требуется для сжатого контента |
expires | 29869228 | 360311 | Необязателен |
x-powered-by | 4883204 | 211409 | Необязателен |
pragma | 7641647 | 188784 | Необязателен |
x-frame-options | 3670032 | 105846 | Необязателен |
access-control-allow-origin | 11335681 | 103596 | Полезен |
x-content-type-options | 11071560 | 94590 | Полезен |
link | 1212329 | 87475 | Полезен |
age | 7401415 | 59242 | Полезен |
x-cache | 5275343 | 56889 | Необязателен |
x-xss-protection | 9773906 | 51810 | Полезен |
strict-transport-security | 4259121 | 51283 | Полезен |
via | 4020117 | 47102 | Необязателен |
p3p | 8282840 | 44308 | Необязателен |
expect-ct | 2685280 | 40465 | Полезен |
content-language | 334081 | 37927 | Спорно |
x-aspnet-version | 676128 | 33473 | Необязателен |
access-control-allow-credentials | 2804382 | 30346 | Полезен |
x-robots-tag | 179177 | 24911 | Не имеет значения для браузеров |
x-ua-compatible | 489056 | 24811 | Необязателен |
access-control-allow-methods | 1626129 | 20791 | Полезен |
access-control-allow-headers | 1205735 | 19120 | Полезен |
Давайте посмотрим на необязательные заголовки, почему они нам не нужны и что с этим делать.
Тщеславие (server, x-powered-by, via)
Вы можете очень гордиться своим выбором серверного ПО, но большинству людей на это наплевать. В худшем случае эти заголовки могут разглашать конфиденциальные данные, что упрощает атаку на ваш сайт.
Server: apache
X-Powered-By: PHP/5.1.1
Via: 1.1 varnish, 1.1 squid
RFC7231 позволяет включать в ответ сервера заголовок Server , указывая конкретный софт на сервере, который используется для выдачи контента. Чаще всего это строка вроде «apache» или «nginx». Хотя заголовок разрешён, он не обязательный и не особо ценный для разработчиков или конечных пользователей. Тем не менее, сегодня это третий по популярности серверный HTTP-заголовок в интернете.
X-Powered-By — самый популярный заголовок из тех, что не определены никаким стандартом, и у него похожая цель: обычно он указывает платформу приложений, на которой работает сервер. Самые популярные ответы включают в себя «ASP.net», «PHP» и «Express». Опять же, это не несёт никакой ощутимой пользы и просто занимает место.
Возможно, более спорным можно считать Via . Его обязан (по RFC7230) добавлять в запрос каждый прокси, через который проходит запрос — для идентификации прокси. Это может быть имя хоста прокси, но чаще общий идентификатор вроде «vegur», «varnish» или «squid». Удаление (или не добавление) такого заголовка может привести к циклу переадресации прокси. Но интересно, что он также добавляется в ответ на обратном пути к браузеру — и здесь выполняет просто информационную функцию: никакие браузеры ничего не с ним делают, поэтому достаточно безопасно избавиться от него, если хотите.
Устаревшие стандарты (P3P, Expires, X-Frame-Options, X-UA-Compatible)
Другая категория заголовков — это те, которые действительно вызывают эффект в браузере, но (уже) представляют собой не лучший способ достижения данного эффекта.
P3P: cp=»this is not a p3p policy»
Expires: Thu, 01 Dec 1994 16:00:00 GMT
X-Frame-Options: SAMEORIGIN
X-UA-Compatible: IE=edge
P3P — забавная штучка. Я понятия не имел, что это такое. Ещё забавнее, что одно из самых распространённых содержаний заголовка P3P — «Это не правило P3P». Ну так это оно или нет?
Тут история восходит к попытке стандартизировать машиночитаемые правила приватности. Были разногласия по поводу того, как отображать данные в браузерах, и только один браузер реализовал поддержку этого заголовка — Internet Explorer. Но даже в нём P3P не имел никакого визуального эффекта для пользователя; он просто должен был присутствовать, чтобы разрешить доступ к сторонним кукам во фреймах. Некоторые сайты даже установили правила несоблюдения P3P, как в примере выше, хотя делать так весьма сомнительно.
Нечего и говорить, что чтение сторонних куков вообще обычно плохая идея, так что если вы этого не делаете, то вам и не нужно устанавливать заголовок P3P !
Expires просто невероятно популярен с учётом того, что Cache-Control имеет преимущество перед Expires уже в течение 20 лет. Если заголовок Cache-Control содержит директиву max-age , то любой заголовок Expires в том же ответе игнорируется. Но огромное количество сайтов устанавливает оба этих заголовка, а Expires чаще всего ставят на дату Thu, 01 Dec 1994 16: 00: 00 GMT , чтобы контент не кэшировался. Конечно, ведь проще всего копипастнуть дату из спецификаций.
Но просто незачем это делать. Если у вас заголовок Expires с датой из прошлого, просто замените его на:
Cache-Control: no-store, private
( no-store — слишком строгая директива не записывать контент в постоянное хранилище, так что вы можете предпочесть no-cache ради лучшей производительности, например, для навигации назад/вперёд или возобновления «спящих» вкладок в браузере)
Некоторые инструменты проверки сайтов посоветуют добавить заголовок X-Frame-Options со значением ‘SAMEORIGIN’. Он говорит браузерам, что вы отказываетесь отдавать контент во фрейм на другом сайте: как правило, это хорошая защита от кликджекинга. Но того же эффекта можно достигнуть другим заголовком с более последовательной поддержкой и более надёжным поведением:
Content-Security-Policy: frame-ancestors ‘self’
Здесь есть дополнительная выгода, потому что заголовок CSP вы всё равно должны отдавать по иным причинам (о них позже). Вероятно, в наше время можно обойтись без X-Frame-Options .
Наконец, ещё в IE9 компания Microsoft представила «режим совместимости», который отображал страницу с помощью движка IE8 или IE7. Даже в нормальном режиме браузер думал, что для правильного рендеринга может понадобиться более ранняя версия движка. Эти эвристики не всегда работали корректно, и разработчики могли переопределить их, используя заголовок или метатег X-UA-Compatible . Фактически, это стало стандартной частью многих фреймворков вроде Bootstrap. Сейчас этот заголовок практически бесполезен: очень мала доля браузеров, которые понимают его. И если вы активно поддерживаете сайт, то очень маловероятно, что на нём используются технологии, которые запустят режим совместимости.
Данные для отладки (X-ASPNet-Version, X-Cache)
В каком-то роде удивительно, что некоторые из самых популярных заголовков вообще не упоминаются ни в каком стандарте. По сути это значит, что тысячи веб-сайтов каким-то образом внезапно договорились использовать определённый заголовок определённым образом.
X-Cache: HIT
X-Request-ID: 45a336c7-1bd5-4a06-9647-c5aab6d5facf
X-ASPNet-Version: 3.2.32
X-AMZN-RequestID: 0d6e39e2-4ecb-11e8-9c2d-fa7ae01bbebc
На самом деле, эти «неизвестные» заголовки не выдумали разработчики. Обычно это артефакты использования определённых серверных фреймворков, софта или сервисов конкретных поставщиков (например, последний заголовок типичен для AWS).
Заголовок X-Cache добавляет Fastly (и другие CDN), вместе с другими специфичными хедерами, такими как X-Cache-Hits и X-Served-By . Когда отладка включена, то добавляется ещё больше, например, Fastly-Debug-Path и Fastly-Debug-TTL .
Эти заголовки не распознаются ни одним браузером, а их удаление совершенно не отразится на отображении страниц. Но поскольку они могут предоставить вам, разработчику, полезную информацию, то можете их сохранить.
Недоразумения (Pragma)
Не ожидал, что в 2018 году придётся упоминать заголовок Pragma , но согласно HTTP Archive он по-прежнему в топе (11-е место). Его не только объявили устаревшим ещё в 1997 году, но он вообще никогда не задумывался как заголовок ответа, а только как часть запроса.
Тем не менее его настолько широко используют в качестве заголовка ответа, что некоторые браузеры даже распознают его и в этом контексте. Но сегодня практически нулевая вероятность, что кто-то понимает Pragma в контексте ответа, но не понимает Cache-Control . Если хотите запретить кэширование, всё что вам нужно — это Cache-Control: no-store, private .
Не-браузеры (X-Robots-Tag)
Один заголовок в нашем топ-30 не является заголовком для браузера. X-Robots-Tag предназначен для краулеров, таких как боты Google или Bing. Поскольку для браузера он бесполезен, то можете установить такой ответ только на запросы краулеров. Или вы решите, что это затрудняет тестирование или нарушает условия использования поисковой системы.
Баги
Наконец, стоит закончить почётном упоминанием простых ошибок. Заголовок Host имеет смысл в запросе, но если он встречается в ответе, то вероятно ваш сервер неправильно настроен (и я хотел бы знать, как именно). Тем не менее 68 доменов в HTTP Archive возвращают заголовок Host в своих ответах.
Удаление заголовков на edge-сервере CDN
К счастью, если ваш сайт работает у нас на Fastly, то удалить заголовки довольно просто с помощью VCL. Если хотите сохранить команде разработчиков действительно полезные данные для отладки, но скрыть их от общей публики, то это легко делается по куки или входящему заголовку HTTP:
unset resp.http.Server;
unset resp.http.X-Powered-By;
unset resp.http.X-Generator;
В следующей статье я расскажу о лучших практиках для действительно нужных заголовков и как активировать их на edge-сервере CDN.
Памятка и туториал по HTTP-заголовкам, связанным с безопасностью веб-приложений
В этой статье я хочу поделиться с вами результатами небольшого исследования, посвященного HTTP-заголовкам, которые связаны с безопасностью веб-приложений (далее — просто заголовки).
Сначала мы с вами кратко разберем основные виды уязвимостей веб-приложений, а также основные виды атак, основанные на этих уязвимостях. Далее мы рассмотрим все современные заголовки, каждый — по отдельности. Это в теоретической части статьи.
В практической части мы реализуем простое Express-приложение, развернем его на Heroku и оценим безопасность с помощью WebPageTest и Security Headers . Также, учитывая большую популярность сервисов для генерации статических сайтов, мы настроим и развернем приложение с аналогичным функционалом на Netlify .
Исходный код приложений находится здесь.
Демо Heroku-приложения можно посмотреть здесь, а Netlify-приложения — здесь.
Основными источниками истины при подготовке настоящей статьи для меня послужили следующие ресурсы:
- Security headers quick reference — Google Developers
- OWASP Secure Headers Project
- Web security — MDN
Заголовки безопасности
Все заголовки условно можно разделить на три группы.
Заголовки для сайтов, на которых обрабатываются чувствительные (sensitive) данные пользователей
- Content Security Policy (CSP) ;
- Trusted Types .
Заголовки для всех сайтов
- X-Content-Type-Options ;
- X-Frame-Options ;
- Cross-Origin Resource Policy (CORP) ;
- Cross-Origin Opener Policy (COOP) ;
- HTTP Strict Transport Security (HSTS) .
Заголовки для сайтов с продвинутыми возможностями
Под продвинутыми возможностями в данном случае понимается возможность использования ресурсов сайта другими источниками (origins) или возможность встраивания или внедрения (embedding) сайта в другие приложения. Первое относится к сервисам вроде CDN (Content Delivery Network — сеть доставки и дистрибуции содержимого), второе к сервисам вроде песочниц — специально выделенные (изолированные) среды для выполнения кода. Под источником понимается протокол, хост, домен и порт.
- Cross-Origin Resource Sharing (CORS) ;
- Cross-Origin Embedder Policy (COEP) .
Угрозы безопасности, существующие в вебе
Защита сайта от внедрения кода (injection vulnerabilities)
Угрозы, связанные с возможностью внедрения кода, возникают, когда непроверенные данные, обрабатываемые приложением, могут оказывать влияние на поведение приложения. В частности, это может привести к выполнению скриптов, управляемых атакующим (принадлежащих ему). Наиболее распространенным видом атаки, связанной с внедрением кода, является межсайтовый скриптинг (Cross-Site Scripting, XSS; к слову, сокращение XSS было выбрано во избежание путаницы с CSS ) в различных формах, включая отраженные или непостоянные XSS (reflected XSS), хранимые или постоянные XSS (stored XSS), XSS , основанные на DOM (DOM XSS) и т.д.
XSS может предоставить атакующему полный доступ к пользовательским данным, которые обрабатываются приложением, и к другой информации в пределах источника.
Традиционными способами защиты от XSS являются: автоматическое экранирование шаблонов HTML с помощью специальных инструментов, отказ от использования небезопасных JavaScript API (например, eval() или innerHTML ), хранение данных пользователей в другом источнике и обезвреживание или обеззараживание (sanitizing) данных, поступающих от пользователей, например, через заполнение ими полей формы.
- используйте CSP для определения того, какие скрипты могут выполняться в вашем приложении;
- используйте Trusted Types для обезвреживания данных, передаваемых в небезопасные API ;
- используйте X-Content-Type-Options для предотвращения неправильной интерпретации браузером MIME-типов загружаемых ресурсов.
Изоляция сайта
Открытость веба позволяет сайтам взаимодействовать друг с другом способами, которые могут привести к нарушениям безопасности. Это включает в себя отправку «неожиданных» запросов на аутентификацию или загрузку данных из приложения в документ атакующего, что позволяет последнему читать или даже модифицировать эти данные.
Наиболее распространенными уязвимостями, связанными с общей доступностью приложения, являются кликджекинг (clickjacking), межсайтовая подделка запросов (Cross-Site Request Forgery, XSRF), межсайтовое добавление или включение скриптов (Cross-Site Script Inclusion, XSSI) и различные утечки информации между источниками.
- используйте X-Frame-Options для предотвращения встраивания вашего документа в другие приложения;
- используйте CORP для предотвращения возможности использования ресурсов вашего сайта другими источниками;
- используйте COOP для защиты окон (windows) вашего приложения от взаимодействия с другими приложениями;
- используйте CORS для управления доступом к ресурсам вашего сайта из других источников.
Безопасность сайтов со сложным функционалом
Spectre делает любые данные, загруженные в одну и ту же группу контекста просмотра (browsing context group), потенциально общедоступными, несмотря на правило ограничения домена. Браузеры ограничивают возможности, которые могут привести к нарушению безопасности с помощью среды выполнения кода под названием «межсайтовая изоляция» (Cross-Origin Isolation). Это, в частности, позволяет безопасно использовать такие мощные возможности, как SharedArrayBuffer .
- используйте COEP совместно с COOP для обеспечения межсайтовой изоляции вашего приложения.
Шифрование исходящего трафика
Недостаточное шифрование передаваемых данных может привести к тому, что атакующий, в случае перехвата этих данных, получит информацию о взаимодействии пользователей с приложением.
Неэффективное шифрование может быть обусловлено следующим:
- использование HTTP вместо HTTPS ;
- смешанный контент (когда одни ресурсы загружаются по HTTPS , а другие — по HTTP );
- куки без атрибута Secure или соответствующего префикса (также имеет смысл определять настройку HttpOnly );
- слабая политика CORS .
- используйте HSTS для обслуживания всего контента вашего приложения через HTTPS .
Перейдем к рассмотрению заголовков.
Content Security Policy (CSP)
XSS — это атака, когда уязвимость, существующая на сайте, позволяет атакующему внедрять и выполнять свои скрипты. CSP предоставляет дополнительный слой для отражения таких атак посредством ограничения скриптов, которые могут выполняться на странице.
Инженеры из Google рекомендуют использовать строгий режим CSP . Это можно сделать одним из двух способов:
- если HTML-страницы рендерятся на сервере, следует использовать основанный на случайном значении (nonce-based) CSP ;
- если разметка является статической или доставляется из кеша, например, в случае, когда приложение является одностраничным ( SPA ), следует использовать основанный на хеше (hash-based) CSP .
Пример использования nonce-based CSP:
Content-Security-Policy: script-src 'nonce-' 'strict-dynamic' https: 'unsafe-inline'; object-src 'none'; base-uri 'none';
Использование CSP
Обратите внимание: CSP является дополнительной защитой от XSS-атак, основная защита состоит в обезвреживании данных, вводимых пользователем.
1. Nonce-based CSP
nonce — это случайное число, которое используется только один раз. Если у вас нет возможности генерировать такое число для каждого ответа, тогда лучше использовать hash-based CSP.
Генерируем nonce на сервере для скрипта в ответ на каждый запрос и устанавливаем следующий заголовок:
Content-Security-Policy: script-src 'nonce-' 'strict-dynamic' https: 'unsafe-inline'; object-src 'none'; base-uri 'none';
Затем в разметке устанавливаем каждому тегу script атрибут nonce со значением строки :
Хорошим примером использования nonce-based CSP является сервис Google Фото .
Content-Security-Policy: script-src 'sha256-' 'sha256-' 'strict-dynamic' https: 'unsafe-inline'; object-src 'none'; base-uri 'none';
В данном случае можно использовать только встроенные скрипты, поскольку большинство браузеров в настоящее время не поддерживает хеширование внешних скриптов.
CSP Evaluator — отличный инструмент для оценки CSP .
- https: — это резервный вариант для Firefox , а unsafe-inline — для очень старых браузеров;
- директива frame-ancestors защищает сайт от кликджекинга, запрещая другим сайтам использовать контент вашего приложения. X-Frame-Options является более простым решением, но frame-ancestors позволяет выполнять тонкую настройку разрешенных источников;
- CSP можно использовать для обеспечения загрузки всех ресурсов по HTTPS . Это не слишком актуально, поскольку в настоящее время большинство браузеров блокирует смешанный контент;
- CSP можно использовать в режиме только для чтения (report-only mode);
- CSP может быть установлен в разметке как мета-тег.
В рассматриваемом заголовке можно использовать следующие директивы:
Директива | Описание |
---|---|
base-uri | Определяет базовый URI для относительных |
default-src | Определяет политику загрузки ресурсов всех типов при отсутствии специальной директивы (политику по умолчанию) |
script-src | Определяет скрипты, которые могут выполняться на странице |
object-src | Определяет, откуда могут загружаться ресурсы — плагины |
style-src | Определяет стили, которые могут применяться на странице |
img-src | Определяет, откуда могут загружаться изображения |
media-src | Определяет, откуда могут загружаться аудио и видеофайлы |
child-src | Определяет, откуда могут загружаться фреймы |
frame-ancestors | Определяет, где (в каких источниках) ресурс может загружаться во фреймы |
font-src | Определяет, откуда могут загружаться шрифты |
connect-src | Определяет разрешенные URI |
manifest-src | Определяет, откуда могут загружаться файлы манифеста |
form-action | Определяет, какие URI могут использоваться для отправки форм (в атрибуте action ) |
sandbox | Определяет политику песочницы (sandbox policy) HTML , которую агент пользователя применяет к защищенному ресурсу |
script-nonce | Определяет, что для выполнения скрипта требуется наличие уникального значения |
plugin-types | Определяет набор плагинов, которые могут вызываться защищенным ресурсом посредством ограничения типов встраиваемых ресурсов |
reflected-xss | Используется для активации/деактивации эвристических методов браузера для фильтрации или блокировки отраженных XSS-атак |
block-all-mixed-content | Запрещает загрузку смешанного контента |
upgrade-insecure-requests | Определяет, что небезопасные ресурсы (загружаемые по HTTP ) должны загружаться по HTTPS |
report-to | Определяет группу (указанную в заголовке Report-To ), в которую отправляются отчеты о нарушениях политики |
Возможные значения директив для нестрогого режима CSP :
- ‘self’ — ресурсы могут загружаться только из данного источника;
- ‘none’ — запрет на загрузку ресурсов;
- * — ресурсы могут загружаться из любого источника;
- example.com — ресурсы могут загружаться только из example.com .
Content-Security-Policy: default-src 'self'; img-src *; media-src media1.com media2.com; script-src example.com
В данном случае изображения могут быть загружены из любого источника, другие медиафайлы — только с media1.com и media2.com (исключая их поддомены), скрипты — только с example.com .
Trusted Types
XSS , основанный на DOM — это атака, когда вредоносный код передается в приемник, который поддерживает динамическое выполнение кода, такой как eval() или innerHTML .
Trusted Types предоставляет инструменты для создания, модификации и поддержки приложений, полностью защищенных от DOM XSS . Этот режим может быть включен через CSP . Он делает JavaScript-код безопасным по умолчанию посредством ограничения значений, принимаемых небезопасными API , специальным объектом — Trusted Type .
Для создания таких объектов можно определить политики, которые проверяют соблюдение правил безопасности (таких как экранирование и обезвреживание) перед записью данных в DOM . Затем эти политики помещаются в код, который может представлять интерес для DOM XSS .
Включаем Trusted Types для опасных приемников DOM :
Content-Security-Policy: require-trusted-types-for 'script'
В настоящее время единственным доступным значением директивы require-trusted-types-for является script .
Разумеется, Trusted Types можно комбинировать с другими директивами CSP :
Content-Security-Policy: script-src 'nonce-' 'strict-dynamic' https: 'unsafe-inline'; object-src 'none'; base-uri 'none'; require-trusted-types-for 'script';
C помощью директивы trusted-types можно ограничить пространство имен для политик Trusted Types , например, trusted-types myPolicy .
// проверяем поддержку if (window.trustedTypes && trustedTypes.createPolicy) < // создаем политику const policy = trustedTypes.createPolicy('escapePolicy', < createHTML: (str) =>str.replace(/\/g, '>') >) >
// будет выброшено исключение el.innerHTML = 'some string' // ок const escaped = policy.createHTML('') el.innerHTML = escaped // '<img src=x onerror=alert(1)>'
Директива require-trusted-types-for ‘script’ делает использование доверенного типа обязательным. Любая попытка использовать строку в небезопасном API завершится ошибкой.
Подробнее о Trusted Types можно почитать здесь.
X-Content-Type-Options
Когда вредоносный HTML-документ обслуживается вашим доменом (например, когда изображение, загружаемое в сервис хранения фотографий, содержит валидную разметку), некоторые браузеры могут посчитать его активным документом и разрешить ему выполнять скрипты в контексте приложения.
X-Content-Type-Options: nosniff заставляет браузер проверять корректность MIME-типа в заголовке полученного ответа Content-Type . Рекомендуется устанавливать такой заголовок для всех загружаемых ресурсов.
X-Content-Type-Options: nosniff Content-Type: text/html; charset=utf-8
X-Frame-Options
Если вредоносный сайт будет иметь возможность встраивать ваше приложение как iframe , это может предоставить атакующему возможность вызывать непреднамеренные действия пользователей через кликджекинг. В некоторых случаях это также позволяет атакующему изучать содержимое документа.
X-Frame-Options является индикатором того, должен ли ваш сайт рендериться в , , или .
Для того, чтобы разрешить встраивание только определенных страниц сайта, используется директива frame-ancestors заголовка CSP .
Полностью запрещаем внедрение:
X-Frame-Options: DENY
Разрешаем создание фреймов только на собственном сайте:
X-Frame-Options: SAMEORIGIN
Обратите внимание: по умолчанию все документы являются встраиваемыми.
Cross-Origin-Resource-Policy (CORP)
Атакующий может внедрить ресурсы вашего сайта в свое приложение с целью получения информации о вашем сайте.
CORP определяет, какие сайты могут внедрять ресурсы вашего приложения. Данный заголовок принимает 1 из 3 возможных значений: same-origin , same-site и cross-origin .
Для сервисов вроде CDN рекомендуется использовать значение cross-origin , если для них не определен соответствующий заголовок CORS .
Cross-Origin-Resource-Policy: cross-origin
same-origin разрешает внедрение ресурсов страницами, принадлежащими к одному источнику. Данное значение применяется в отношении чувствительной информации о пользователях или ответов от API , которые рассчитаны на использование в пределах данного источника.
Обратите внимание: ресурсы все равно будут доступны для загрузки, поскольку CORP ограничивает только внедрение этих ресурсов в другие источники.
Cross-Origin-Resource-Policy: same-origin
same-site предназначен для ресурсов, которые используются не только доменом (как в случае с same-origin ), но и его поддоменами.
Cross-Origin-Resource-Policy: same-site
Cross-Origin-Opener-Policy (COOP)
Если сайт атакующего может открывать другой сайт в поп-апе (всплывающем окне), то у атакующего появляется возможность для поиска межсайтовых источников утечки информации. В некоторых случаях это также позволяет реализовать атаку с использованием побочных каналов, описанную в Spectre .
Заголовок Cross-Origin-Opener-Policy позволяет запретить открытие сайта с помощью метода window.open() или ссылки target=»_blank» без rel=»noopener» . Как результат, у того, кто попытается открыть сайт такими способами, не будет ссылки на сайт, и он не сможет с ним взаимодействовать.
Значение same-origin рассматриваемого заголовка позволяет полностью запретить открытие сайта в других источниках.
Cross-Origin-Opener-Policy: same-origin
Значение same-origin-allow-popups также защищает документ от открытия в поп-апах других источников, но позволяет приложению взаимодействовать с собственными попапами.
Cross-Origin-Opener-Policy: same-origin-allow-popups
unsafe-none является значением по умолчанию, оно разрешает открытие сайта в виде поп-апа в других источниках.
Cross-Origin-Opener-Policy: unsafe-none
Мы можем получать отчеты от COOP :
Cross-Origin-Opener-Policy: same-origin; report-to="coop"
COOP также поддерживает режим report-only , позволяющий получать отчеты о нарушениях без их блокировки.
Cross-Origin-Opener-Policy-Report-Only: same-origin; report-to="coop"
Cross-Origin Resource Sharing (CORS)
CORS — это не заголовок, а механизм, используемый браузером для предоставления доступа к ресурсам приложения.
По умолчанию браузеры используют политику одного источника или общего происхождения, которая запрещает доступ к таким ресурсам из других источников. Например, при загрузке изображения из другого источника, даже несмотря на его отображение на странице, JavaScript-код не будет иметь к нему доступа. Провайдер ресурса может предоставить такой доступ через настройку CORS с помощью двух заголовков:
Access-Control-Allow-Origin: https://example.com Access-Control-Allow-Credentials: true
Использование CORS
Начнем с того, что существует два типа HTTP-запросов. В зависимости от деталей запроса он может быть классифицирован как простой или сложный (запрос, требующий отправки предварительного запроса).
Критериями простого запроса является следующее:
- методом запроса является GET , HEAD или POST ;
- кастомными заголовками могут быть только Accept , Accept-Language , Content-Language и Content-Type ;
- значением заголовка Content-Type может быть только application/x-www-form-urlencoded , multipart/form-data или text/plain .
Все остальные запросы считаются сложными.
В данном случае браузер отправляет запрос к другому источнику с заголовком Origin , значением которого является источник запроса:
Get / HTTP/1.1 Origin: https://example.com
Access-Control-Allow-Origin: https://example.com Access-Control-Allow-Credentials: true
- Access-Control-Allow-Origin: https://example.com означает, что https://example.com имеет доступ к содержимому ответа. Если значением данного заголовка является * , ресурсы будут доступны любому сайту. В этом случае полномочия (credentials) не требуются;
- Access-Control-Allow-Credentials: true означает, что запрос на получение ресурсов должен содержать полномочия (куки). При отсутствии полномочий в запросе, даже при наличии источника в заголовке Access-Control-Allow-Origin , запрос будет отклонен.
Перед сложным запросом выполняется предварительный. Он выполняется методом OPTIONS для определения того, может ли быть отправлен основной запрос:
OPTIONS / HTTP/1.1 Origin: https://example.com Access-Control-Request-Method: POST Access-Control-Request-Headers: X-PINGOTHER, Content-Type
- Access-Control-Request-Method: POST — последующий запрос будет отправлен методом POST ;
- Access-Control-Request-Headers: X-PINGOTHER, Content-Type — последующий запрос будет отправлен с заголовками X-PINGOTHER и Content-Type .
Access-Control-Allow-Origin: https://example.com Access-Control-Allow-Credentials: true Access-Control-Allow-Methods: POST, GET, OPTIONS Access-Control-Allow-Headers: X-PINGOTHER, Content-Type Access-Control-Max-Age: 86400
- Access-Control-Allow-Methods: POST, GET, OPTIONS — последующий запрос может выполняться указанными методами;
- Access-Control-Allow-Headers: X-PINGOTHER, Content-Type — последующий запрос может содержать указанные заголовки;
- Access-Control-Max-Age: 86400 — результат сложного запроса будет записан в кеш и будет там храниться на протяжении 86400 секунд.
Cross-Origin-Embedder-Policy (COEP)
Для предотвращения кражи ресурсов из других источников с помощью атак, описанных в Spectre , такие возможности, как SharedArrayBuffer , performance.measureUserAgentSpecificMemory() или JS Self Profiling API , по умолчанию отключены.
Cross-Origin-Embedder-Policy: require-corp запрещает документам и воркерам (workers) загружать изображения, скрипты, стили, фреймы и другие ресурсы до тех пор, пока доступ к ним не разрешен с помощью заголовков CORS или CORP . COEP может использоваться совместно с COOP для настройки межсайтовой изоляции документа.
На данный момент require-corp является единственным доступным значением рассматриваемого заголовка, кроме unsafe-none , которое является значением по умолчанию.
Полная межсайтовая изоляция приложения
Cross-Origin-Embedder-Policy: require-corp Cross-Origin-Opener-Policy: same-origin
Изоляция с отчетами о блокировках
Cross-Origin-Embedder-Policy: require-corp; report-to="coep"
Cross-Origin-Embedder-Policy-Report-Only: require-corp; report-to="coep"
HTTP Strict Transport Security (HSTS)
Данные, передаваемые по HTTP , не шифруются, что делает их доступными для перехватчиков на уровне сети.
Заголовок Strict-Transport-Security запрещает использование HTTP . При наличии данного заголовка браузер будет использовать HTTPS без перенаправления на HTTP (при отсутствии ресурса по HTTPS ) в течение указанного времени ( max-age ).
Strict-Transport-Security: max-age=31536000
- max-age — время в секундах, в течение которого браузер должен «помнить», что сайт доступен только по HTTPS ;
- includeSubDomains — распространяет политику на поддомены.
Другие заголовки
Referrer-Policy
Заголовок Referrer-Policy определяет содержание информации о реферере, указываемой в заголовке Referer . Заголовок Referer содержит адрес запроса, например, адрес предыдущей страницы, или адрес загруженного изображения, или другого ресурса. Он используется для аналитики, логирования, оптимизации кеша и т.д. Однако он также может использоваться для слежения или кражи информации, выполнения побочных эффектов, приводящих к утечке чувствительных пользовательских данных и т.д.
Referrer-Policy: no-referrer
Значение | Описание |
---|---|
no-referrer | Заголовок Referer не включается в запрос |
no-referrer-when-downgrade | Значение по умолчанию. Реферер указывается при выполнении запроса между HTTPS и HTTPS , но не указывается при выполнении запроса между HTTPS и HTTP |
origin | Указывается только источник запроса (например, реферером документа https://example.com/page.html будет https://example.com ) |
origin-when-cross-origin | При выполнении запроса в пределах одного источника указывается полный URL , иначе указывается только источник (как в предыдущем примере) |
same-origin | При выполнении запроса в пределах одного источника указывается источник, в противном случае, реферер не указывается |
strict-origin | Похоже на no-referrer-when-downgrade , но указывается только источник |
strict-origin-when-cross-origin | Сочетание strict-origin и origin-when-cross-origin |
unsafe-url | Всегда указывается полный URL |
Обратите внимание: данный заголовок не поддерживается мобильным Safari .
Clear-Site-Data
Заголовок Clear-Site-Data запускает очистку хранящихся в браузере данных (куки, хранилище, кеш), связанных с источником. Это предоставляет разработчикам контроль над данными, локально хранящимися в браузере пользователя. Данный заголовок может использоваться, например, в процессе выхода пользователя из приложения (logout) для очистки данных, хранящихся на стороне клиента.
Clear-Site-Data: "*"
Значение | Описание |
---|---|
«cache» | Сообщает браузеру, что сервер хочет очистить локально кешированные данные для источника ответа на запрос |
«cookies» | Сообщает браузеру, что сервер хочет удалить все куки для источника. Данные для аутентификации также будут очищены. Это влияет как на сам домен, так и на его поддомены |
«storage» | Сообщает браузеру, что сервер хочет очистить все хранилища браузера ( localStorage , sessionStorage , IndexedDB , регистрация сервис-воркеров — для каждого зарегистрированного СВ вызывается метод unregister() , AppCache , WebSQL , данные FileSystem API , данные плагинов) |
«executionContexts» | Сообщает браузеру, что сервер хочет перезагрузить все контексты браузера (в настоящее время почти не поддерживается) |
«*» | Сообщает браузеру, что сервер хочет удалить все данные |
Обратите внимание: данный заголовок не поддерживается Safari .
Permissions-Policy
Данный заголовок является заменой заголовка Feature-Policy и предназначен для управления доступом к некоторым продвинутым возможностям.
Permissions-Policy: camera=(), fullscreen=*, geolocation=(self "https://example.com" "https://another.example.com")
В данном случае мы полностью запрещаем доступ к камере (видеовходу) устройства, разрешаем доступ к методу requestFullScreen() (для включения полноэкранного режима воспроизведения видео) для всех, а к информации о местонахождении устройства — только для источников example.com и another.example.com .
Директива | Описание |
---|---|
accelerometer | Управляет тем, может ли текущий документ собирать информацию об акселерации (проекции кажущегося ускорения) устройства с помощью интерфейса Accelerometer |
ambient-light-sensor | Управляет тем, может ли текущий документ собирать информацию о количестве света в окружающей устройство среде с помощью интерфейса AmbientLightSensor |
autoplay | Управляет тем, может ли текущий документ автоматически воспроизводить медиа, запрошенное через интерфейс HTMLMediaElement |
battery | Определяет возможность использования Battery Status API |
camera | Определяет возможность использования видеовхода устройства |
display-capture | Определяет возможность захвата экрана с помощью метода getDisplayMedia() |
document-domain | Определяет возможность установки document.domain |
encrypted-media | Определяет возможность использования Encrypted Media Extensions API (EME) |
execution-while-not-rendered | Определяет возможность выполнения задач во фреймах без их рендеринга (например, когда они скрыты или их свойство diplay имеет значение none ) |
execution-while-out-of-viewport | Определяет возможность выполнения задач во фреймах, находящихся за пределами области просмотра |
fullscreen | Определяет возможность использования метода requestFullScreen() |
geolocation | Определяет возможность использования Geolocation API |
gyroscope | Управляет тем, может ли текущий документ собирать информацию об ориентации устройства с помощью Gyroscope API |
layout-animations | Определяет возможность показа анимации |
legacy-image-formats | Определяет возможность отображения изображений устаревших форматов |
magnetometer | Управляет тем, может ли текущий документ собирать информацию об ориентации устройства с помощью Magnetometer API |
microphone | Определяет возможность использования аудиовхода устройства |
midi | Определяет возможность использования Web MIDI API |
navigation-override | Определяет возможность управления пространственной навигацией (spatial navigation) механизмами, разработанными автором приложения |
oversized-images | Определяет возможность загрузки и отображения больших изображений |
payment | Определяет возможность использования Payment Request API |
picture-in-picture | Определяет возможность воспроизведения видео в режиме «картинка в картинке» |
publickey-credentials-get | Определяет возможность использования Web Authentication API для извлечения публичных ключей, например, через navigator.credentials.get() |
sync-xhr | Определяет возможность использования WebUSB API |
vr | Определяет возможность использования WebVR API |
wake-lock | Определяет возможность использования Wake Lock API для запрета переключения устройства в режим сохранения энергии |
screen-wake-lock | Определяет возможность использования Screen Wake Lock API для запрета блокировки экрана устройства |
web-share | Определяет возможность использования Web Share API для передачи текста, ссылок, изображений и другого контента |
xr-spatial-tracking | Определяет возможность использования WebXR Device API для взаимодействия с сессией WebXR |
- =() — полный запрет;
- =* — полный доступ;
- (self «https://example.com») — предоставление разрешения только указанному источнику.
Спецификация рассматриваемого заголовка находится в статусе рабочего черновика, поэтому его поддержка оставляет желать лучшего:
Перейдем к практической части.
Разработка Express-приложения
Создаем директорию для проекта, переходим в нее и инициализируем проект:
mkdir secure-app cd !$ yarn init -yp # или npm init -y
Формируем структуру проекта:
- public - favicon.png - index.html - style.css - script.js - index.js - .gitignore - .
Иконку можно найти здесь.
Набросаем какой-нибудь незамысловатый код.
В public/index.html мы подключаем иконку, стили, скрипт, Google-шрифты, Bootstrap и Boostrap Icons через CDN , создаем элементы для заголовка, даты, времени и кнопок:
Secure App Secure App
Сегодня
Сейчас
Добавляем стили в public/style.css
* < margin: 0; padding: 0; box-sizing: border-box; font-family: 'Montserrat', sans-serif; >body < min-height: 100vh; display: grid; place-content: center; text-align: center; >h1 < margin: 0.5em 0; text-transform: uppercase; font-size: 3rem; >p < font-size: 1.15rem; >.buttons < margin: 0.5em 0; display: flex; flex-direction: column; align-items: center; gap: 0.5em; >button < cursor: pointer; >pre
В public/script.js мы делаем следующее:
- определяем политику доверенных типов;
- создаем утилиты для получения ссылки на DOM-элемент, форматирования даты и времени и регистрации обработчика (по умолчанию одноразового и запускающего колбэк при возникновении события click );
- получаем ссылки на DOM-элементы;
- определяем настройки для форматирования даты и времени;
- добавляем дату и время в качестве текстового содержимого соответствующих элементов;
- определяем колбэки для обработчиков: для остановки таймера, добавления HTML-шаблона с потенциально вредоносным кодом и получения HTTP-заголовков;
- регистрируем обработчики.
// политика доверенных типов let policy if (window.trustedTypes && trustedTypes.createPolicy) < policy = trustedTypes.createPolicy('escapePolicy', < createHTML: (str) =>str.replace(/\/g, '>') >) > // утилиты // для получения ссылки на DOM-элемент const getEl = (selector, parent = document) => parent.querySelector(selector) // для форматирования даты и времени const getDate = (options, locale = 'ru-RU', date = Date.now()) => new Intl.DateTimeFormat(locale, options).format(date) // для регистрации обработчика (по умолчанию одноразового и запускающего колбэк при возникновении события `click`) const on = (el, cb, event = 'click', options = < once: true >) => el.addEventListener(event, cb, options) // DOM-элементы const containerEl = getEl('.container') const dateEl = getEl('.date', containerEl) const timeEl = getEl('.time', containerEl) const stopBtnEl = getEl('.btn-stop', containerEl) const addBtnEl = getEl('.btn-add', containerEl) const getBtnEl = getEl('.btn-get', containerEl) // настройки для даты const dateOptions = < weekday: 'long', day: 'numeric', month: 'long', year: 'numeric' >// настройки для времени const timeOptions = < hour: 'numeric', minute: 'numeric', second: 'numeric' >// добавляем текущую дату в качестве текстового содержимого соответствующего элемента dateEl.textContent = getDate(dateOptions) // добавляем текущее время в качестве текстового содержимого соответствующего элемента каждую секунду const timerId = setInterval(() => < timeEl.textContent = getDate(timeOptions) >, 1000) // колбэки для обработчиков (в каждом колбэке происходит удаление соответствующей кнопки) // для остановки таймера const stopTimer = () => < clearInterval(timerId) stopBtnEl.remove() >// для добавления HTML-шаблона с потенциально вредоносным кодом const addTemplate = () => < const evilTemplate = `` // при попытке вставить необезвреженный шаблон будет выброшено исключение // Uncaught TypeError: Failed to execute 'insertAdjacentHTML' on 'Element': This document requires 'TrustedHTML' assignment. containerEl.insertAdjacentHTML('beforeend', policy.createHTML(evilTemplate)) addBtnEl.remove() > // для получения HTTP-заголовков const getHeaders = () => < const req = new XMLHttpRequest() req.open('GET', location, false) req.send(null) const headers = req.getAllResponseHeaders() const preEl = document.createElement('pre') preEl.textContent = headers containerEl.append(preEl) getBtnEl.remove() >// регистрируем обработчики on(stopBtnEl, stopTimer) on(addBtnEl, addTemplate) on(getBtnEl, getHeaders)
yarn add express
yarn add -D nodemon open-cli
- express — Node.js-фреймворк, упрощающий разработку сервера;
- nodemon — утилита для запуска сервера для разработки и его автоматического перезапуска при обновлении соответствующего файла;
- open-cli — утилита для автоматического открытия вкладки браузера по указанному адресу.
Определяем в package.json команды для запуска серверов:
"scripts": < "dev": "open-cli http://localhost:3000 && nodemon index.js", "start": "node index.js" >
Приступаем к реализации сервера.
Справедливости ради следует отметить, что в экосистеме Node.js имеется специальная утилита для установки HTTP-заголовков, связанных с безопасностью веб-приложений — Helmet . Шпаргалку по работе с этой утилитой вы найдете здесь.
Также существует специальная утилита для работы с CORS — Cors . Шпаргалку по работе с этой утилитой вы найдете здесь.
Большинство заголовков можно определить сразу:
// предотвращаем `MIME sniffing` 'X-Content-Type-Options': 'nosniff', // для старых браузеров, плохо поддерживающих `CSP` 'X-Frame-Options': 'DENY', 'X-XSS-Protection': '1; mode=block', // по умолчанию браузеры блокируют CORS-запросы // дополнительные CORS-заголовки 'Cross-Origin-Resource-Policy': 'same-site', 'Cross-Origin-Opener-Policy': 'same-origin-allow-popups', 'Cross-Origin-Embedder-Policy': 'require-corp', // запрещаем включать информацию о реферере в заголовок `Referer` 'Referrer-Policy': 'no-referrer', // инструктируем браузер использовать `HTTPS` вместо `HTTP` // 31536000 секунд — это 365 дней 'Strict-Transport-Security': 'max-age=31536000; includeSubDomains'
Также добавим заголовок Expect-CT :
// 86400 секунд — это 1 сутки 'Expect-CT': 'enforce, max-age=86400'
Блокируем доступ к камере, микрофону, информации о местонахождении и Payment Request API :
'Permissions-Policy': 'camera=(), microphone=(), geolocation=(), payment=()'
Директивы для CSP :
'Content-Security-Policy': ` // запрещаем загрузку плагинов object-src 'none'; // разрешаем выполнение только собственных скриптов script-src 'self'; // разрешаем загрузку только собственных изображений img-src 'self'; // разрешаем открытие приложения только в собственных фреймах frame-ancestors 'self'; // включаем политику доверенных типов для скриптов require-trusted-types-for 'script'; // блокируем смешанный контент block-all-mixed-content; // инструктируем браузер использовать `HTTPS` для ресурсов, загружаемых по `HTTP` upgrade-insecure-requests `
Обратите внимание: все директивы должны быть указаны в одну строку без переносов. Мы не определяем директивы для стилей и шрифтов, поскольку они загружаются из других источников.
Также обратите внимание, что мы не используем nonce для скриптов, поскольку мы не рендерим разметку на стороне сервера, но я приведу соответствующий код.
const express = require('express') // утилита для генерации уникальных значений // const crypto = require('crypto') // создаем экземпляр Express-приложения const app = express() // посредник для генерации `nonce` /* const getNonce = (_, res, next) => < res.locals.cspNonce = crypto.randomBytes(16).toString('hex') next() >*/ // посредник для установки заголовков // 31536000 — 365 дней // 86400 — 1 сутки const setSecurityHeaders = (_, res, next) => < res.set(< 'X-Content-Type-Options': 'nosniff', 'X-Frame-Options': 'DENY', 'X-XSS-Protection': '1; mode=block', 'Cross-Origin-Resource-Policy': 'same-site', 'Cross-Origin-Opener-Policy': 'same-origin-allow-popups', 'Cross-Origin-Embedder-Policy': 'require-corp', 'Referrer-Policy': 'no-referrer', 'Strict-Transport-Security': 'max-age=31536000; includeSubDomains', 'Expect-CT': 'enforce, max-age=86400', 'Content-Security-Policy': `object-src 'none'; script-src 'self'; img-src 'self'; frame-ancestors 'self'; require-trusted-types-for 'script'; block-all-mixed-content; upgrade-insecure-requests`, 'Permissions-Policy': 'camera=(), microphone=(), geolocation=(), payment=()' >) next() > // удаляем заголовок `X-Powered-By` app.disable('x-powered-by') // подключаем посредник для генерации `nonce` // app.use(getNonce) // подключаем посредник для установки заголовков app.use(setSecurityHeaders) // определяем директорию со статическими файлами app.use(express.static('public')) // определяем порт const PORT = process.env.PORT || 3000 // запускам сервер app.listen(PORT, () => < console.log('Сервер готов') >)
Выполняем команду yarn dev или npm run dev (разумеется, на вашей машине должен быть установлен Node.js ). Данная команда запускает сервер для разработки и открывает вкладку браузера по адресу http://localhost:3000 .
Отлично! Теперь развернем приложение на Heroku и проверим его безопасность с помощью Security Headers и WebPageTest .
Деплой Express-приложения на Heroku
Создаем аккаунт на Heroku .
yarn global add heroku # или npm i -g heroku
heroku -v
Находясь в корневой директории проекта, инициализируем Git-репозиторий (разумеется, на вашей машине должен быть установлен git ), добавляем и фиксируем изменения (не забудьте добавить node_modules в .gitignore ):
git init git add . git commit -m "Create secure app"
Создаем удаленный репозиторий на Heroku :
# авторизация heroku login # создание репо heroku create # подключение к репо git remote -v
git push heroku master
Инструкцию по развертыванию приложения на Heroku можно найти здесь.
После выполнения этой команды, в терминале появится URL вашего приложения, развернутого на Heroku , например, https://tranquil-meadow-01695.herokuapp.com/ .
Перейдите по указанному адресу и проверьте работоспособность приложения.
Заходим на Security Headers , вставляем URL приложения в поле enter address here и нажимаем на кнопку Scan :
Получаем рейтинг приложения:
В Supported By читаем Вау, отличная оценка. .
Заходим на WebPageTest , вставляем URL приложения в поле Enter a website URL. и нажимаем на кнопку Start Test -> :
Получаем результаты оценки приложения (нас интересует первая оценка — Security score ):
Похоже, мы все сделали правильно. Круто!
Деплой приложения на Netlify
Переносим файлы favicon.png , index.html , script.js и style.css из папки public в отдельную директорию, например, netlify .
Для настройки сервера Netlify используется файл netlify.toml . Создаем данный файл в директории проекта. Нас интересует только раздел [[headers]] :
[[headers]] for = "/*" [headers.values] X-Content-Type-Options = "nosniff" X-Frame-Options = "DENY" X-XSS-Protection = "1; mode=block" Cross-Origin-Resource-Policy = "same-site" Cross-Origin-Opener-Policy = "same-origin-allow-popups" Cross-Origin-Embedder-Policy = "require-corp" Referrer-Policy = "no-referrer" Strict-Transport-Security = "max-age=31536000; includeSubDomains" Expect-CT = "enforce, max-age=86400" Content-Security-Policy = "object-src 'none'; script-src 'self'; img-src 'self'; frame-ancestors 'self'; require-trusted-types-for 'script'; block-all-mixed-content; upgrade-insecure-requests" Permissions-Policy = "camera=(), microphone=(), geolocation=(), payment=()"
- for = «/*» означает для всех запросов;
- [header.values] — заголовки и их значения (просто переносим их из Express-сервера с учетом особенностей синтаксиса).
yarn global add netlify-cli # или npm i -g netlify-cli
netlify -v
netlify login
Можно запустить сервер для разработки (это необязательно):
netlify dev
Данная команда запускает приложение и открывает вкладку браузера по адресу http://localhost:8888 .
Разворачиваем приложение в тестовом режиме:
netlify deploy
Выбираем Create & configure a new site , свою команду (например, Igor Agapov’s team ), оставляем Site name пустым и выбираем директорию со сборкой приложения (у нас такой директории нет, поэтому оставляем значение по умолчанию — . ):
Получаем URL черновика веб-сайта ( Website Draft URL ), например, https://60f3e6013d0afb2ce71a5623—infallible-pasteur-d015e7.netlify.app . Можно перейти по указанному адресу и проверить работоспособность приложения.
Разворачиваем приложение в продакшен-режиме:
netlify deploy -p
- -p или —prod означает производственный режим.
Получаем URL приложения ( Website URL ), например, https://infallible-pasteur-d015e7.netlify.app/ . Опять же, можно перейти по указанному адресу и проверить работоспособность приложения.
Инструкцию по развертыванию приложения на Netlify можно найти здесь.
Возвращаемся на Security Headers и WebPageTest и проверяем, насколько безопасным является наше Netlify-приложение:
Кажется, у нас все получилось!
Заключение
Подведем итоги. Мы с вами вкратце изучили все HTTP-заголовки, связанные с безопасностью веб-приложений, разработали серверное и бессерверное приложения с аналогичным функционалом и получили лучшие оценки безопасности для данных приложений на Security Headers и WebPageTest . По-моему, очень неплохо для одной статьи.
Надеюсь, что вы не зря потратили время. Благодарю за внимание и хорошего дня!
Why does ASP.NET framework add the ‘X-Powered-By:ASP.NET’ HTTP Header in responses?
I am just curious to know if there is a specific reason why the .Net Framework adds the ‘X-Powered-By:ASP.NET’ Http Header in its responses? Do other web servers (Apache, httpd) do the same thing? EDIT: I know that it can be changed. I want to know if there is a reason to keep it or leave it as it is?
Punit Vora
asked Aug 17, 2009 at 14:42
Punit Vora Punit Vora
5,106 4 4 gold badges 35 35 silver badges 44 44 bronze badges
7 Answers 7
I know that PHP does this. I guess there is no real purpose, other than marketing and making it easier for script kiddies to find suitable victims. For PHP it’s better to disable the flag entirely since it shows the PHP version and therefore makes the server more vulnerable to attacks.
Edit: Who knows, it might also lead to better search results on bing. 😉
answered Aug 17, 2009 at 14:48
Adrian Grigore Adrian Grigore
33.1k 36 36 gold badges 130 130 silver badges 210 210 bronze badges
It is a default custom header when using IIS. It is a setting in IIS, you can change it if you wish.
- Click on the HTTP Headers tab
- You can edit or remove the header in the Custom HTTP Headers box.
answered Aug 17, 2009 at 14:46
17.4k 3 3 gold badges 37 37 silver badges 39 39 bronze badges
It is probably there so that sites like Netcraft can pull together statistics for the number of servers running IIS and ASP.NET. This used to be considered an important thing when .NET was released. By stating that n number of sites started using ASP.NET Microsoft could provide metrics for companies that only adopt technology based on the number of other users out there.
I don’t believe there is a strong technical reason for having it since a PHP app could imitate an ASP.NET application, by setting the same header in Apache. I could imagine some naive client applications like FrontPage 2003, or SharePoint Designer might use headers like this to validate that they are indeed connecting to an ASP.NET enabled site but that is speculation on my part.