I have to validate the Content-Type header value before passing it to an HTTP request.
Is there a specific list for all the possible values of Content-Type ?
Otherwise, is there a way to validate the content type before using it in an HTTP request?
Содержание
4 Answers 4
The most common type are:
In the Extended BNF notation of RFC 822, a Content-Type header field value is defined as follows:
Content-Type := type "/" subtype *[";" parameter]
type := "application" / "audio" / "image" / "message" / "multipart" / "text" / "video" / x-token
parameter := attribute "=" value
value := token / quoted-string
tspecials := "(" / ")" / " " / "@" ; Must be in / "," / ";" / ":" / "" / ; quoted-string, / "/" / "[" / "]" / "?" / "." ; to use within / "=" ; parameter values
And a list of known MIME types that can follow it (or, as Joe remarks, the IANA source).
As you can see the list is way too big for you to validate against all of them. What you can do is validate against the general format and the type attribute to make sure that is correct (the set of options is small) and just assume that what follows it is correct (and of course catch any exceptions you might encounter when you put it to actual use).
Also note the comment above:
If another primary type is to be used for any reason, it must be given a name starting with "X-" to indicate its non-standard status and to avoid any potential conflict with a future official name.
You’ll notice that a lot of HTTP requests/responses include an X- header of some sort which are self defined, keep this in mind when validating the types.
По сути метод HEAD — это то же самое, что и метод GET, за исключением: в ответ на запрос методом head сервер возвращает только заголовки из самого содержания. Т.е. если мы хотим посмотреть, какие заголовки сервер вернет при запросе данной страницы, а сама страница не нужна, используем метод HEAD. Заголовки, которые возвращаются сервером, мы можем менять (кроме заголовка Server, Date). Можем допослать те заголовки, которые сервер не посылает (он не знает, что их нужно послать). Может также поменять значение заголовка.
Header: location и refresh
Все заголовки посылаются функцией header (имя заголовка: значение). ОБЯЗАТЕЛЬНО все заголовки посылаются до любого вывода. Примеры заголовков:
Когда браузер видит заголовок Location, он все бросает и идет выполнять инструкцию. Представим ситуацию: на сайте форма для отправки данных методом post. Пользователь заполнил форму, нажал отправить, данные ушли. Затем опять нажал F5 или Refresh (обновить страницу), данные снова ушли. В итоге в базе получается несколько одинаковых записей (или на форуме несколько одинаковых сообщений). Но если использовать Location, повторная отправка данных не произойдет. Желательно после использования Location остановить выполнение кода с помощью exit, т.е. после этого заголовка код дальше не выполняется, страница перезапрашивается. И получается, что php вхолостую будет отрабатывать код (хотя он уже получил новую страницу через location для обработки).
Header: content-type
Всегда браузер не отслеживает, что мы запрашиваем. Например, запросили файл exe.php. Браузеру ‘до лампочки’, что это за файл, браузер смотрит в заголовок content-type. Если браузеру отдать файл .gif, а в заголовке написать, что это .jpeg, он поверит, что этот файл .jpeg, и попытается его отобразить как jpeg => ничего не получится. Примеры:
На каждом сервере написано, какому типу файлов соответствует какое расширение (html => text/html). Сервер, какое расширение получил через заголовок content-type, таким образом и вывел файл. Он никогда не заглядывает во внутрь файла, не анализирует его. Сервер никогда не передает кодировку этого файла. А браузер, получая данный файл без кодировки, пытается угадать, какая это кодировка. Поэтому задавать кодировку нужно обязательно в заголовке. Есть некоторые админы серверов, которые принудительно выставляют кодировку (типа народ.ру). В основном это бесплатный хостинг. Выставлять кодировку мы можем в мета-теге, но приоритетнее будет то, что приходит с сервера:
Данный мета-тег вообще не нужен. Все нормальные сервера посылают нужную информацию через заголовок header:content-type. Когда в браузер попадает запрашиваемая страница, он может ее не только вывести на экран перед нами, но и перенаправить в любое другое место. Например, браузер может запустить любую программу и отдать этот код программе на выполнение; или создать файл и направить исполняемый код страницы в этот файл. Это можно сделать при помощи следующего кода:
При выполнении кода браузер предложит открыть/сохранить файл. При этом файла example.txt не существует. Т.е. не все, что мы скачиваем, существует физически.
Header: Cache-Control и Expires
Десяток лет назад скорости интернета были маленькие, трафик дорогой, все работало медленно. И придумали: зачем каждый раз файл брать с сервера, если он меняется раз в месяц или раз в год; хорошо бы его сохранить у клиента, и браузер его каждый бы раз показывал. А поскольку данный файл перезапрашивать нужно (вдруг он изменился), то сохранять файл у клиента какое-то время. И придумали заголовки cache-control для кэширования, expire — срок, на сколько кэшировать файл.
Со временем появились новостные сайты, информация на которых обновляется несколько раз в день. И начали запрещать кэширование. А браузер по-умолчанию, кэширует весь контент. И началась война с кэшированием. Понятно, что в этом деле нужен обдуманный подход. Есть вещи, которые не надо кэшировать (например, новостная лента), а есть те, которые лучше кэшировать (например, стилевые файлы, ява-скрипты, картинки).
Для запрета кэширования есть много заблуждений. В той же википедии советуют:
Заголовок Expires говорит об актуальности, насколько эта страница актуальна. Заголовок Last-Modified – когда статья была изменена. ПРИМЕР: Браузер закэшировал страницу, а актуальная дата стоит на вчера, браузер взял да и удалил страницу из кэша. Поисковый робот проиндексировал нашу страницу; думает, когда зайти в следующий раз. Смотрит на актуальность, а там — вчерашняя дата. Робот заходит завтра, смотрит актуальность страницы, а там снова — вчера. Робот ‘офигевает’ (авт.). И сайт теряет позиции в поисковой выдаче. Поэтому всегда используйте в данном заголовке текущую дату.
Браузер, получив весь этот код выше, закэширует все, что ему нужно. В данном коде юзер вредит себе дважды, особенно с точки зрения поисковиков. В третьей строке используется cache-controle:no-cache – браузера этот код никак не касается. Это не запрет кэширования. По сути этот код означает: ты можешь закэшировать, но перед тем, как поднять из кэша, ты должен узнать, не обновилась ли эта страница там. Помните, кэшируются не только страницы, но и запросы.
В реальности запрет на кэширование можно записать так:
Как разрешить кэширование:
Header: Set-Cookie
Файлы cookies можно послать и заголовком.
Когда мы заходим в электронную почту, вводим логин-пароль, выскакивает сообщение: запомнить меня на этом компьютере. Нам летят cookies (логин и пароль). Пароль должен быть зашифрован. И в своей базе данных, когда мы регистрируем пользователя, пароли надо шифровать. Для этого есть много методов. Самый популярный алгоритм — это MD5.
В php для этого алгоритма есть функция — md5 (string). Любой алгоритм шифрования подразумевает зашифровку и расшифровку пароля. Поэтому точнее — это хэш-функция. Т.е. это односторонее шифрование: зашифровать можно, а расшифровать нельзя. Передавая строку в md5, мы получим абракадабру:
Одна и та же строка имеет один и тот же хэш. В этом случае, если мы посылаем cookies, мы посылаем в виде пароля этот хэш.
Многие сайты хранят в базе данных пароли в открытом виде. Взломали базу, все пароли ушли. Можно проверить это. Когда регистрируетесь на сайте, там есть ссылка — забыл свой пароль. Если придет письмо, и там будет пароль в открытом виде, значит он открыто лежит в базе данных.
Но даже если пароль зашифрован в md5, его можно взломать с помощью радужных таблиц. Об этом в другой статье.
Заголовки отправленные вашим браузером
При обращении к этой странице ваш браузер отправил следующие http-заголовки:
Заголовки отправляемые сервером
В соответствии со спецификацией http, этот протокол также поддерживает передачу служебной информации от сервера к браузеру, оформленной в виде специальных заголовков.
Таким образом, http headers — это средство общения сервера с удаленным клиентом. Каждый заголовок обычно состоит из одиночной линии ascii текста с именем и значением. Сами заголовки никак не отображаются в окне броузера, но зачастую могут сильно изменить отображение сопутствующего документа.
Механизм отправки http заголовков в php.
Механизм отправки заголовков в php представлен функцией header(). Особенность протокола http заключается в том, что заголовок должен быть отправлен до посылки других данных, поэтому функция должна быть вызвана в самом начале документа и должна выглядеть следующим образом:
header("http заголовок"[, replace]);
Необязательный параметр replace может принимать значения (true или false) и указывает на то, должен ли быть заменен предыдущий заголовок подобного типа, либо добавить данный заголовок к уже существующему.
В отношении функции header() часто применяется функция headers_sent(), которая в качестве результата возвращает true в случае успешной отправки заголовка и false в обратном случае.
Рассмотрим наиболее используемые http заголовки.
Cache-control
Заголовок управления кешированием страниц. Вообще, данная функция является одной из самых распространенных в использовании заголовков.
-
Данный заголовок может быть использован со следующими значениями:
- no-cache — Запрет кеширования. Используется в часто обновляемых страницах и страницах с динамическим содержанием. Его действие подобно meta тегу "pragma: no-cache".
- public — Разрешение кеширования страницы как локальным клиентом, так и прокси-сервером.
- private — Разрешение кеширования только локальным клиентом.
- max-age — Разрешение использования кешированного документа в течение заданного времени в секундах.
- no-store — Cтраница содержит приватные данные, сохранять в кэше нельзя!
Совсем жесткий запрет кеширования на всех этапах:
Expires
Устанавливает дату и время, после которого документ считается устаревшим. Дата должна указываться в следующем формате (на английском языке):
День недели (сокр.) число (2 цифры) Месяц (сокр.) год часы:минуты:секунды gmt
Например, fri, 09 jan 2002 12:00:00 gmt
Текущее время в этом формате возвращает функция gmdate() в следующем виде:
Возможно использование данного http заголовка для запрета кеширования. Для этого необходимо указать прошедшую дату. Иногда можно встретить и такую комбинацию Expires: now
Last-modified
Указывает дату последнего изменения документа. Дата должна задаваться в том же формате, что и в случае с заголовком expires. Данный заголовок можно не использовать для динамических страниц, так как многие серверы (например, apache) для таких страниц сами выставляют дату модификации.
При запросе это значение передаётся клиентом в специальном заголовке запроса: If-Modified-Since. Обработчик запроса может проверить, изменился ли объект, и если нет — вернуть ответ с пустым телом и кодом ответа 304 Not Modified. Само содержимое страницы не передаётся, и клиент будет использовать то содержимое, которое хранится у него в кэше.
Возможно сделать страницу всегда обновленной:
Позднее, если браузер хочет определить актуальность компонента, он передает заголовок If-None-Match для передачи ETag’а обратно на сервер. Если ETag’и совпадают, ответ от сервера приходит со статус-кодом 304, уменьшая таким образом объем передачи на 12195 байт:
Включить ETag для Apache можно, например, следующей директивой:
Открючить ETag для Apache:
Location
Полезный заголовок, который перенаправляет броузер на указанный адрес. Его действие сравнимо с meta тегом refresh:
Например, этот заголовок может быть использован так:
Content-type
Content-length
Status
Content-Encoding
Range
Разрешить кросс-доменные запросы
X-XSS-Protection
Атака XSS (межсайтовый скриптинг) это тип атаки, при котором вредоносный код может быть внедрён в атакуемую страницу.
Например вот так:
Такой тип атаки легко обнаружить и браузер вполне может с этим справиться: если в исходном коде содержится часть запроса, то это может оказаться угрозой.
И заголовок X-XSS-Protection управляет этим поведением браузера.
- фильтр выключен
- 1 фильтр включен. Если атака обнаружена, то браузер удалит вредоносный код.
- 1; mode=block. Фильтр включен, но если атака обнаружится, страница не будет загружена браузером.
- 1; report=http://domain/url. фильтр включен и браузер очистит страницу от вредоносного кода, при этом сообщив о попытке атаки. Тут используется функция Chromium для отправки отчёта о нарушении политика защиты контента (CSP) на определённый адрес.
Создадим веб сервер-песочницу на node.js, чтобы посмотреть как это работает.
Буду использовать Google Chrome 55.
Без заголовка
Ничего не произойдёт, браузер успешно заблокирует атаку. Chrome, по умолчанию, блокирует угрозу и сообщает об этом в консоли.
X-XSS-Protection: 0
X-XSS-Protection: 1
Страница была очищена из-за явного указания заголовка.
X-XSS-Protection: 1; mode=block
В этом случае атака будет предотвращена путём блокирования загрузки страницы.
X-XSS-Protection: 1; report=http://localhost:1234/report
Атака предотвращена и сообщение об этом отправлено по соответствующему адресу.
X-Frame-Options
При помощи данного заголовка можно защититься от так называемого Кликджекинга [Clickjacking].
Представьте, что у злоумышленника есть канал на YouTube и ему хочется больше подписчиков.
Он может создать страницу с кнопкой «Не нажимать», что будет значить, что все на неё обязательно нажмут. Но поверх кнопки находится абсолютно прозрачный iframe и в этом фрейме прячется страница канала с кнопкой подписки. Поэтому при нажатии на кнопку, на самом деле пользователь подписывается на канал, если конечно, он был залогинен в YouTube.
Сперва нужно установить расширение для игнорирования данного заголовка.
Создадим простую страницу.
Как можно заметить, я разместил фрейм с подпиской прям над кнопкой (z-index: 1) и поэтому если попытаться на неё нажать, то на самом деле нажмётся фрейм. В этом примере фрейм не полностью прозрачен, но это исправляется значением opacity: 0.
На практике, такое не сработает, потому что у YouTube задан нужный заголовок, но смысл угрозы, надеюсь, понятен.
Для предотвращения страницы быть использованной во фрейме нужно использовать заголовок X-Frame-Options.
- deny не загружать страницу вообще.
- sameorigin не загружать, если источник не совпадает.
- allow-from: ДОМЕН можно указать домен, с которого страница может быть загружена во фрейме.
Нам понадобится веб сервер для демонстрации
Без заголовка
Все смогут встроить наш сайт по адресу localhost:1234 во фрейм.
X-Frame-Options: deny
Страницу вообще нельзя использовать во фрейме.
X-Frame-Options: sameorigin
Только страницы с одинаковым источником смогут встраивать во фрейм. Источники совпадают, если домен, порт и протокол одинаковые.
X-Frame-Options: allow-from localhost:4321
Похоже, что Chrome игнорирует такую опцию, т.к. существует заголовок Content-Security-Policy (о ней будет рассказано ниже). Не работает это и в Microsoft Edge.
X-Content-Type-Options
Данный заголовок предотвращает атаки с подменой типов MIME (`) >) app.listen(1234)
Без заголовка
Хоть script.txt и является текстовым файлом с типом text/plain, он будет запущен как скрипт.
X-Content-Type-Options: nosniff
На этот раз типы не совпадают и файл не будет исполнен.
Content-Security-Policy
Это относительно молодой заголовок и помогает уменьшить риски атаки XSS в современных браузерах путём указания в заголовке какие именно ресурсы могут подргружаться на странице.
Например, можно попросить браузер не исполнять inline-скрпиты и загружать файлы только с одного домена. Inline-скрпиты могут выглядеть не только как , но и как
Посмотрим как это работает.
Без заголовка
Это работает так, как вы и ожидали
Content-Security-Policy: default-src ‘none’
default-src применяет правило для всех ресурсов (картинки, скрипты, фреймы и т.д.), значение ‘none’ блокирует всё. Ниже продемонстрировано что происходит и ошибки, показываемые в браузере.
Chrome отказался запускать любые скрипты. В таком случае не получится даже загрузить favicon.ico.
Content-Security-Policy: default-src ‘self’
Теперь можно использовать ресурсы с одного источника, но по прежнему нельзя запускать внешние и inline-скрипты.
Content-Security-Policy: default-src ‘self’; script-src ‘self’ ‘unsafe-inline’
На этот раз мы разрешили исполнение и inline-скриптов. Обратите внимание, что XSS атака в запросе тоже была заблокирована. Но этого не произойдёт, если одновременно поставить и unsafe-inline, и X-XSS-Protection: 0.
Другие значения
На сайте content-security-policy.com красиво показаны множество примеров.
- default-src ‘self’ разрешит ресурсы только с одного источника
- script-src ‘self’ www.google-analytics.com ajax.googleapis.com разрешит Google Analytics, Google AJAX CDN и ресурсы с одного источника.
- default-src ‘none’; script-src ‘self’; connect-src ‘self’; img-src ‘self’; style-src ‘self’; разрешит изображения, скрипты, AJAX и CSS с одного источника и запретит загрузгу любых других ресурсов. Для большинства сайтов это хорошая начальная настройка.
Я этого не проверял, но я думаю, что следующие заголовки эквиваленты:
- frame-ancestors ‘none’ и X-Frame-Options: deny
- frame-ancestors ‘self’ и X-Frame-Options: sameorigin
- frame-ancestors localhost:4321 и X-Frame-Options: allow-from localhost:4321
- script-src ‘self’ без ‘unsafe-inline’ и X-XSS-Protection: 1
Если взглянуть на заголовки facebook.com или twitter.com, то можно заметить, что эти сайты используют много CSP.
Strict-Transport-Security
HTTP Strict Transport Security (HSTS) это механизм политики безопасности, который позволяет защитить сайт от попытки небезопасного соединения.
Допустим, что мы хотим подключиться к facebook.com. Если не набрать перед запросом https://, то протокол, по умолчанию, будет выбран HTTP и поэтому запрос будет выглядеть как http://facebook.com.
После этого мы будем перенаправлены на защищённую версию Facebook.
Если подключиться к публичной WiFi точке, которая принадлежит злоумышленнику, то запрос может быть перехвачен и вместо facebook.com злоумышленник может подставить похожую страницу, чтобы узнать логин и пароль.
Чтобы обезопаситься от такой атаки, можно использовать вышеупомянутый заголовок, который скажет клиенту в следующий раз использовать https-версию сайта.
Если пользователь был залогинен в Facebook дома, а потом попытался открыть его из небезопасной точки доступа, то ему ничего не угрожает, т.к. браузеры запоминают этот заголовок.
Но что будет, если подключиться в небезопасной сети первый раз? В этом случае защититься не получится.
Но у браузеров есть козырь и на этот случай. В них есть предопределённый список доменов, для которых следует использовать только HTTPS.
Можно отправить свой домен по этому адресу. Там также можно узнать правильно ли используется заголовок.
- max-age=15552000 время, в секундах, которое браузер должен помнить о заголовке.
- includeSubDomains Если указать это опциональное значение, то заголовок распространяется и на все поддомены.
- preload если владелец сайта хочет, чтобы домен попал в предопределённый список, поддерживаемый Chrome (и используемый Firefox и Safari).
А если потребуется переключиться на HTTP перед сроком истечения max-age или если установлен preload? Можно поставить значение max-age=0 и тогда правило перехода на https версию работать перестанет.
Public-Key-Pins
HTTP Public Key Pinning (HPKP) это механизм политики безопасности, который позволяет HTTPS сайтам защититься от использования злоумышленниками поддельных или обманных сертификатов.
- pin-sha256=" " в кавычках находится закодированный с помощью Base64 отпечаток Subject Public Key Information (SPKI). Можно указать несколько пинов для различных открытых ключей. Некоторые браузеры в будущем могут использовать и другие алгоритмы хеширования, помимо SHA-256.
- max-age= время, в секундах, которое браузер запоминает что для доступа к сайту нужно использовать только перечисленные ключи.
- includeSubDomains если указать этот необязательный параметр, то заголовок действует и на все поддомены.
- report-uri=" " если указать URL, то при ошибке проверки ключа, соответствующее сообщение отправится по указанному адресу.
Вместо заголовка Public-Key-Pins можно использовать Public-Key-Pins-Report-Only, в таком случае будут отправляться только сообщения об ошибках совпадения ключей, но браузер всё равно будет загружать страницу.
Так делает Facebook:
Зачем это нужно? Не достаточно ли доверенных центров сертификации (CA)?
Злоумышленник может создать свой сертификат для facebook.com и путём обмана заставить пользователя добавить его в своё хранилище доверенных сертификатов, либо он может быть администратором.
Попробуем создать сертификат для facebook.
И сделать его доверенным в локальной системе.
А теперь запустим веб сервер, использующий этот сертификат.
Переключимся на сервер
Посмотрим что получилось
Отлично. curl подтверждает сертификат.
Так как я уже заходил на Facebook и Google Chrome видел его заголовки, то он должен сообщить об атаке но разрешить страницу, так?
Неа. Ключи не проверялись из-за локального корневого сертификата [Public-key pinning bypassed]. Это интересно…
Тот же результат. Думаю это фича.
Но в любом случае, если не добавлять эти сертификаты в локальное хранилище, открыть сайты не получится, потому что опции продолжить небезопасное соединение в Chrome или добавить исключение в Firefox не будет.
Content-Encoding: br
Данные сжаты при помощи Brotli.
Алгоритм обещает лучшее сжатие чем gzip и сравнимую скорость разархивирования. Поддерживается Google Chrome.
Разумеется, для него есть модуль в node.js.
Исходный размер: 700 Кб
Brotli: 204 Кб
Gzip: 241 Кб
Timing-Allow-Origin
С помощью Resource Timing API можно узнать сколько времени заняла обработка ресурсов на странице.
Поскольку информация о времени загрузки может быть использована чтобы определить посещал ли пользователь страницу до этого (обращая внимание на то, что ресурсы могут кэшироваться), стандарт считается уязвимым, если давать такую информацию любым хостам.
Похоже, если не указать Timing-Allow-Origin, то получить детальную информацию о времени операций (поиска домена, например) можно только для ресурсов с одним источником.
Использовать можно так:
- Timing-Allow-Origin: *
- Timing-Allow-Origin: http://foo.com http://bar.com
Alt-Svc
Альтернативные Сервисы [Alternative Services] позволяют ресурсам находиться в различных частях сети и доступ к ним можно получить с помощью разных конфигураций протокола.
Такой используется в Google:
Это означает, что браузер, если захочет, может использовать QUIC, это HTTP над UDP, через порт 443 следующие 30 дней (ma = 2592000 секунд, или 720 часов, т.е 30 дней). Понятия не имею что означает параметр v, версия?
Ниже несколько P3P заголовков, которые я встречал:
- P3P: CP=«This is not a P3P policy! See support.google.com/accounts/answer/151657?hl=en for more info.»
- P3P: CP=«Facebook does not have a P3P policy. Learn why here: fb.me/p3p»
Некоторые браузеры требуют, чтобы cookies третьих лиц поддерживали протокол P3P для обозначения мер конфиденциальности.
Организация, основавшая P3P, Консорциум Всемирной паутины (W3C), приостановила работу над протоколом несколько лет назад из-за того, что современные браузеры не до конца поддерживают протокол. В результате, P3P устарел и не включает в себя технологии, которые сейчас используются в сети, поэтому большинство сайтов не поддерживают P3P.
Я не стал слишком углубляться, но видимо заголовок нужен для IE8 чтобы принимать cookies третьих лиц.
Например, если в IE настройка приватности высокая, то все cookies с сайтов, у которых нет компактной политики конфиденциальности, будут блокированы, но те у которых есть заголовки похожие на вышеупомянутые, заблокированы не будут.