Эксплуатация MIME Sniffing
Last updated
Last updated
В каждом запросе обычно есть заголовок ответа, называемый Content-Type
, который сообщает браузеру MIME-тип
ответа, такой как text/html
или application/json
.
Но что, если заголовка Content-Type
нет? Браузер попытается определить тип на основе содержимого файла. Даже если заголовок Content-Type
присутствует, браузер все равно может интерпретировать его как другой тип.
Это поведение, при котором MIME-тип
выводится из содержимого файла, называется MIME sniffing
. Давайте вместе исследуем эту функцию!
Мы можем легко отправить ответ без заголовка Content-Type
, используя Express:
Если вы откроете эту веб-страницу в браузере, вы увидите, что текст "hello" становится больше и жирным, что указывает на то, что браузер отображает ответ как веб-страницу:
Теперь давайте посмотрим на второй пример, где мы изменяем <h1>
на <h2>
:
Почему это вдруг отображается как простой текст?
Теперь в третьем примере у нас есть тот же <h1>
, но с дополнительным текстом:
Снова он отображается как простой текст вместо HTML.
Вы можете подумать, что механизм MIME sniffing
в браузере — это загадка, как черный ящик, о том, как он работает. К счастью, мы тестируем в Chrome, и Chromium имеет открытый исходный код.
Итак, что заставляет его считать полезную нагрузку "HTML payload"? Это проверяется дальше в коде:
Он проверяет, соответствует ли строка в начале ответа, после удаления пробелов, перечисленным выше шаблонам HTML. Общие начала HTML, такие как <!DOCTYPE html
и <html
, включены в этот список. Это также объясняет, почему только пример <h1>hello</h1>
отображается как HTML в наших предыдущих тестах.
Согласно исходному коду, похоже, что Chromium не учитывает расширение файла или другие факторы в URL. Он полностью полагается на содержимое файла. Давайте проведем еще один тест, чтобы это подтвердить:
Я не буду включать изображение здесь, но независимо от того, что URL — это test.html, результат все равно отображается как простой текст. А как насчет других браузеров? Давайте попробуем открыть его в Firefox:
Мы можем увидеть, что Firefox действительно отображает его как HTML! Следовательно, мы можем сделать вывод, что Firefox учитывает расширение файла в URL при выполнении MIME sniffing
.
Из нашего предыдущего исследования мы подтвердили факт: если у ответа нет установленного 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 для получения дополнительных деталей.
Кроме HTML-файлов, какие еще типы файлов могут выполнять JavaScript?
Из списка мы видим, что кроме самых распространенных типов контента HTML, XML и SVG есть и другие, которые могут выполнять JavaScript.
Особый интерес представляют файлы SVG, потому что многие веб-сайты имеют функциональность загрузки изображений, и SVG считается форматом изображения. Поэтому некоторые веб-сайты разрешают загрузку SVG. Однако из этого исследования мы можем узнать, что разрешение загрузки SVG эквивалентно разрешению загрузки HTML, поскольку SVG может выполнять JavaScript!
Возьмем следующий фрагмент кода в качестве примера:
Вы когда-нибудь задумывались, какой должен быть тип контента URL, чтобы браузер загрузил его как скрипт?
Например, если это image/png
, это не сработает, и вы увидите следующее сообщение об ошибке в браузере:
Refused to execute script from 'http://localhost:5555/js' because its MIME type ('image/png') is not executable.
Наиболее распространенный тип, text/javascript
, очевидно, работает нормально. Но есть ли другие?
Из десяти перечисленных ниже типов контента только два из них не сработают. Можете угадать, какие два?
application/zip
application/json
application/octet-stream
text/csv
text/html
text/json
text/plain
huli/blog
video/mp4
font/woff2
Все вышеперечисленные — это действительные MIME-типы JavaScript, и вы можете увидеть множество типов из прошлого, таких как jscript
или livescript
.
Кроме действительных MIME-типов JavaScript, согласно спецификации, есть только четыре типа, которые не сработают:
audio/*
image/*
video/*
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: :