Client-Side Fundamental
  • Добро пожаловать
  • Глава 1 - Начало работы с XSS
    • Браузерная модель безопасности
    • Знакомимся с уязвимостью XSS
    • Более глубокое понимание XSS
    • Опасный псевдопротокол javascript
  • Глава 2 - Защита и Обход для XSS
    • Первая линия обороны от XSS - Sanitization
    • Вторая линия обороны от XSS - CSP (Content Security Policy)
    • Третья линия обороны против XSS - сокращение области воздействия
    • Последние методы защиты от XSS - Trusted Types и встроенный Sanitizer API
    • Обход защитных мер - Обычные способы обхода CSP
    • Обход защитных мер - Mutation XSS
    • Самая опасная XSS - Universal XSS
  • Глава 3 - Атаки без JavaScript
    • Кто сказал, что для атаки обязательно выполнять JavaScript?
    • Prototype Pollution - Эксплуатация цепочки прототипов
    • Может ли HTML влиять на JavaScript - Введение в DOM clobbering
    • Template Injection in Frontend - CSTI
    • CSS Injection - Атака с использованием только CSS (Часть 1)
    • CSS Injection - Атака с использованием только CSS (Часть 2)
    • Можно ли атаковать, используя только HTML
  • Глава 4 - Межсайтовые атаки
    • Same-origin Policy и Same-Site
    • Введение в Cross-Origin Resource Sharing (CORS)
    • Проблемы Cross-Origin безопасности
    • Cross-Site Request Forgery (CSRF)
    • Спаситель от CSRF - Same-site cookie
    • От same-site до главного site
    • Интересная и практичная Cookie Bomb
  • Глава 5 - Другие интересные темы
    • То, что вы видите, это не то, что вы получаете - Clickjacking
    • Эксплуатация MIME Sniffing
    • Атаки на цепочку поставок во фронтенде - Attacking Downstream from Upstream
    • Атаки на веб-фронтенд в Web3
    • Самая интересная атака на побочные каналы фронтенда - XSLeaks (Часть 1)
    • Самая интересная атака на побочные каналы фронтенда - XSLeaks (Часть 2)
Powered by GitBook
On this page
  • MIME Sniffing 101
  • Exploiting MIME Sniffing for Attacks
  • Content Types that can execute JavaScript
  • Content Types that can be loaded as scripts
  • Заключение
  1. Глава 5 - Другие интересные темы

Эксплуатация MIME Sniffing

PreviousТо, что вы видите, это не то, что вы получаете - ClickjackingNextАтаки на цепочку поставок во фронтенде - Attacking Downstream from Upstream

Last updated 8 months ago

В каждом запросе обычно есть заголовок ответа, называемый Content-Type, который сообщает браузеру MIME-тип ответа, такой как text/html или application/json.

Но что, если заголовка Content-Type нет? Браузер попытается определить тип на основе содержимого файла. Даже если заголовок Content-Type присутствует, браузер все равно может интерпретировать его как другой тип.

Это поведение, при котором MIME-тип выводится из содержимого файла, называется MIME sniffing. Давайте вместе исследуем эту функцию!

MIME Sniffing 101

Мы можем легко отправить ответ без заголовка Content-Type, используя Express:

const express = require('express');
const app = express();
app.get('/', (req, res) => {
  res.write('<h1>hello</h1>')
  res.end()
});
app.listen(5555, () => {
  console.log('Server is running on port 5555');
});

Если вы откроете эту веб-страницу в браузере, вы увидите, что текст "hello" становится больше и жирным, что указывает на то, что браузер отображает ответ как веб-страницу:

Теперь давайте посмотрим на второй пример, где мы изменяем <h1> на <h2>:

const express = require('express');const app = express();app.get('/', (req, res) => {  res.write('<h2>hello</h2>')  res.end()});app.listen(5555, () => {  console.log('Server is ruconst express = require('express');
const app = express();

app.get('/', (req, res) => {
  res.write('<h2>hello</h2>');
  res.end();
});

app.listen(5555, () => {
  console.log('Server is running on port 5555');
});
nning on port 5555');});

Почему это вдруг отображается как простой текст?

Теперь в третьем примере у нас есть тот же <h1>, но с дополнительным текстом:

const express = require('express');
const app = express();
app.get('/', (req, res) => {
  res.write('Hello, <h1>world</h1>');
  res.end();
});
app.listen(5555, () => {
  console.log('Server is running on port 5555');
});

Снова он отображается как простой текст вместо HTML.

Вы можете подумать, что механизм MIME sniffing в браузере — это загадка, как черный ящик, о том, как он работает. К счастью, мы тестируем в Chrome, и Chromium имеет открытый исходный код.

// Detecting mime types is a tricky business because we need to balance
// compatibility concerns with security issues.  Here is a survey of how other
// browsers behave and then a description of how we intend to behave.  
// 
// HTML payload, no Content-Type header:
// * IE 7: Render as HTML
// * Firefox 2: Render as HTML
// * Safari 3: Render as HTML
// * Opera 9: Render as HTML
// 
// Here the choice seems clear:
// => Chrome: Render as HTML
// 
// HTML payload, Content-Type: "text/plain":
// * IE 7: Render as HTML
// * Firefox 2: Render as text
// * Safari 3: Render as text (Note: Safari will Render as HTML if the URL
//                                   has an HTML extension)
// * Opera 9: Render as text
// 
// Here we choose to follow the majority (and break some compatibility with IE).
// Many folks dislike IE's behavior here.
// => Chrome: Render as text
// 
// We generalize this as follows.  If the Content-Type header is text/plain
// we won't detect dangerous mime types (those that can execute script).
// 
// HTML payload, Content-Type: "application/octet-stream":
// * IE 7: Render as HTML
// * Firefox 2: Download as application/octet-stream
// * Safari 3: Render as HTML
// * Opera 9: Render as HTML
// 
// We follow Firefox.
// => Chrome: Download as application/octet-stream
// One factor in this decision is that IIS 4 and 5 will send
// application/octet-stream for .xhtml files (because they don't recognize
// the extension).  We did some experiments and it looks like this doesn't occur
// very often on the web.  We choose the more secure option.

Итак, что заставляет его считать полезную нагрузку "HTML payload"? Это проверяется дальше в коде:

// Our HTML sniffer differs slightly from Mozilla.  For example, Mozilla will
// decide that a document that begins "<!DOCTYPE SOAP-ENV:Envelope PUBLIC " is
// HTML, but we will not.
#define MAGIC_HTML_TAG(tag) \
  MAGIC_STRING("text/html", "<" tag)
static const MagicNumber kSniffableTags[] = {
  // XML processing directive.  Although this is not an HTML mime type, we sniff
  // for this in the HTML phase because text/xml is just as powerful as HTML and
  // we want to leverage our white space skipping technology.
  MAGIC_NUMBER("text/xml", "<?xml"),
  // Mozilla
  // DOCTYPEs
  MAGIC_HTML_TAG("!DOCTYPE html"),
  // HTML5 spec
  // Sniffable tags, ordered by how often they occur in sniffable documents.
  MAGIC_HTML_TAG("script"),
  // HTML5 spec, Mozilla
  MAGIC_HTML_TAG("html"),
  // HTML5 spec, Mozilla
  MAGIC_HTML_TAG("!--"),
  MAGIC_HTML_TAG("head"),
  // HTML5 spec, Mozilla
  MAGIC_HTML_TAG("iframe"),
  // Mozilla
  MAGIC_HTML_TAG("h1"),
  // Mozilla
  MAGIC_HTML_TAG("div"),
  // Mozilla
  MAGIC_HTML_TAG("font"),
  // Mozilla
  MAGIC_HTML_TAG("table"),
  // Mozilla
  MAGIC_HTML_TAG("a"),
  // Mozilla
  MAGIC_HTML_TAG("style"),
  // Mozilla
  MAGIC_HTML_TAG("title"),
  // Mozilla
  MAGIC_HTML_TAG("b"),
  // Mozilla
  MAGIC_HTML_TAG("body"),
  // Mozilla
  MAGIC_HTML_TAG("br"),
  MAGIC_HTML_TAG("p"),
  // Mozilla
};
// ...
// Returns true and sets result if the content appears to be HTML.
// Clears have_enough_content if more data could possibly change the result.
static bool SniffForHTML(base::StringPiece content,
                         bool*

Он проверяет, соответствует ли строка в начале ответа, после удаления пробелов, перечисленным выше шаблонам HTML. Общие начала HTML, такие как <!DOCTYPE html и <html, включены в этот список. Это также объясняет, почему только пример <h1>hello</h1> отображается как HTML в наших предыдущих тестах.

Согласно исходному коду, похоже, что Chromium не учитывает расширение файла или другие факторы в URL. Он полностью полагается на содержимое файла. Давайте проведем еще один тест, чтобы это подтвердить:

const express = require('express');
const app = express();
app.get('/test.html', (req, res) => {
  res.write('abcde<h1>test</h1>');
  res.end();
});
app.listen(5555, () => {
  console.log('Server is running on port 5555');
});

Я не буду включать изображение здесь, но независимо от того, что URL — это test.html, результат все равно отображается как простой текст. А как насчет других браузеров? Давайте попробуем открыть его в Firefox:

Мы можем увидеть, что Firefox действительно отображает его как HTML! Следовательно, мы можем сделать вывод, что Firefox учитывает расширение файла в URL при выполнении MIME sniffing.

Exploiting MIME Sniffing for Attacks

Из нашего предыдущего исследования мы подтвердили факт: если у ответа нет установленного Content-Type и мы можем контролировать содержимое, мы можем использовать MIME sniffing, чтобы заставить браузер интерпретировать файл как веб-страницу.

Например, предположим, что есть функция загрузки изображений, которая проверяет только расширение файла, но не содержимое. Мы можем загрузить файл с именем a.png, но содержимое будет <script>alert(1)</script>. Если сервер не добавляет автоматически Content-Type при обслуживании этого изображения, это становится уязвимостью XSS.

Однако большинство серверов в настоящее время автоматически добавляют Content-Type. Возможно ли это все еще?

Да, мы можем объединить это с другими незначительными проблемами.

Apache HTTP Server — это широко используемый сервер, и знаменитый стек LAMP (Linux + Apache + MySQL + PHP) использует этот сервер.

Apache HTTP Server имеет своеобразное поведение: если имя файла содержит только точку (.), он не выведет Content-Type. Например, a.png автоматически определит MIME-тип на основе расширения файла и выведет image/png, но если имя файла будет ..png, он не выведет Content-Type.

Таким образом, если бэкенд использует Apache HTTP Server для обработки загрузок файлов, мы можем загрузить, казалось бы, валидное изображение с расширением ..png, но при открытии в браузере оно будет отображаться как веб-страница, становясь уязвимостью XSS.

Согласно Apache HTTP Server, это ожидаемое поведение, и вы можете обратиться к @YNizry для получения дополнительных деталей.

Content Types that can execute JavaScript

Кроме HTML-файлов, какие еще типы файлов могут выполнять JavaScript?

Из списка мы видим, что кроме самых распространенных типов контента HTML, XML и SVG есть и другие, которые могут выполнять JavaScript.

Особый интерес представляют файлы SVG, потому что многие веб-сайты имеют функциональность загрузки изображений, и SVG считается форматом изображения. Поэтому некоторые веб-сайты разрешают загрузку SVG. Однако из этого исследования мы можем узнать, что разрешение загрузки SVG эквивалентно разрешению загрузки HTML, поскольку SVG может выполнять JavaScript!

Content Types that can be loaded as scripts

Возьмем следующий фрагмент кода в качестве примера:

<script src="URL"></script>

Вы когда-нибудь задумывались, какой должен быть тип контента URL, чтобы браузер загрузил его как скрипт?

Например, если это image/png, это не сработает, и вы увидите следующее сообщение об ошибке в браузере:

Refused to execute script from 'http://localhost:5555/js' because its MIME type ('image/png') is not executable.

Наиболее распространенный тип, text/javascript, очевидно, работает нормально. Но есть ли другие?

Из десяти перечисленных ниже типов контента только два из них не сработают. Можете угадать, какие два?

  1. application/zip

  2. application/json

  3. application/octet-stream

  4. text/csv

  5. text/html

  6. text/json

  7. text/plain

  8. huli/blog

  9. video/mp4

  10. font/woff2

//  Support every script type mentioned in the spec, as it notes that "User
//  agents must recognize all JavaScript MIME types." See
//  https://html.spec.whatwg.org/#javascript-mime-type.
const char* const kSupportedJavascriptTypes[] = {
    "application/ecmascript",
    "application/javascript",
    "application/x-ecmascript",
    "application/x-javascript",
    "text/ecmascript",
    "text/javascript",
    "text/javascript1.0",
    "text/javascript1.1",
    "text/javascript1.2",
    "text/javascript1.3",
    "text/javascript1.4",
    "text/javascript1.5",
    "text/jscript",
    "text/livescript",
    "text/x-ecmascript",
    "text/x-javascript",
};

Все вышеперечисленные — это действительные MIME-типы JavaScript, и вы можете увидеть множество типов из прошлого, таких как jscript или livescript.

Кроме действительных MIME-типов JavaScript, согласно спецификации, есть только четыре типа, которые не сработают:

  1. audio/*

  2. image/*

  3. video/*

  4. text/csv

Кроме этих, все остальные типы действительны. Таким образом, среди упомянутых выше вариантов только text/csv и video/mp4 не сработают, в то время как остальные будут работать! Да, даже text/html и application/json будут работать, и даже xss/blog будет работать.

Если вы хотите ужесточить этот механизм и разрешить загрузку только MIME-типов JavaScript, вы можете добавить заголовок в ответ: X-Content-Type-Options: nosniff. После добавления этого заголовка ни один из 10 ранее упомянутых примеров не будет работать, и вы увидите следующее сообщение об ошибке при загрузке:

Refused to execute script from 'http://localhost:5555/js' because its MIME type ('text/plain') is not executable, and strict MIME type checking is enabled.

strict MIME type это функция, включаемая добавлением этого заголовка.

То же самое касается таблиц стилей. После включения этой функции только MIME-тип text/css будет признан действительным, и все остальные приведут к ошибке.

Итак, что произойдет, если вы продолжите включать эту функцию? Это создаст дополнительный риск безопасности.

Предположим, вы случайно нашли уязвимость XSS на веб-сайте. Однако проблема в том, что CSP веб-сайта — это script-src 'self';, что означает, что он не позволяет загружать внешние скрипты, и встроенные скрипты также заблокированы. Как вы можете обойти CSP в этом случае?

Если веб-сайт предоставляет функцию загрузки файлов, которая принимает файлы, отличные от изображений, видео и CSV-файлов, и не проверяет содержимое, предположим, что он принимает только ZIP-файлы, тогда вы можете загрузить файл, содержащий код JavaScript.

Таким образом, вы можете использовать <script src="/uploads/files/my.zip"></script>, чтобы загрузить скрипт и успешно обойти CSP. Причина, по которой это работает, заключается в поведении, упомянутом ранее — пока MIME-тип не является одним из тех немногих типов, его можно загрузить как скрипт.

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

Заключение

В этой статье мы рассмотрели множество интересных аспектов, связанных с MIME-типами, а также изучили много исходного кода Chromium. Лично я считаю, что исходный код Chromium написан очень понятно, включает комментарии и ссылки на спецификации, поэтому вам не нужно отдельно искать спецификации. Это как убить двух зайцев одним выстрелом. Наконец, мы поняли цель и функцию заголовка X-Content-Type-Options: nosniff. Я уверен, что многие люди видели этот заголовок раньше, но не знали, для чего он нужен, и теперь вы это знаете.

Дополнительные ссылки:

Код, отвечающий за MIME sniffing в Chromium, можно найти в файле . В начале кода объясняется, как это работает:

В исследовании, проведенном BlackFan в 2020 году: предоставлен исчерпывающий список: .

Например, febin сообщил о уязвимости в программном обеспечении с открытым исходным кодом Mantis Bug Tracker в 2022 году: . Эта уязвимость возникла потому, что пользователи могли загружать файлы при создании нового репорта, и разрешенный формат файла был SVG. Поэтому файл SVG мог быть загружен, и когда другие пользователи открывали его, скрытый код внутри выполнялся.

Ответ будет раскрыт позже, но сначала давайте рассмотрим "действительные MIME-типы JavaScript", указанные в исходном коде Chromium: :

net/base/mime_sniffer.cc
Content-Type Research
Content-Type that can be used for XSS
CVE-2022-33910: Stored XSS via SVG file upload
/third_party/blink/common/mime_util/mime_util.cc
UIUCTF 2022 Write-up
What do you know about script type?