Загрузка файлов
В этом разделе вы узнаете, как простые функции загрузки файлов могут использоваться как мощный вектор для ряда атак высокой критичности. Мы покажем, как обходить распространённые механизмы защиты, чтобы загрузить web shell, что позволит получить полный контроль над уязвимым веб-сервером. С учётом того, насколько часто встречаются функции загрузки, необходимо уметь правильно их тестировать.

Лабораторные задания
Если вы уже знакомы с базовыми концепциями уязвимостей загрузки файлов и хотите перейти к практике, вы можете получить доступ ко всем лабораторным заданиям по ссылке ниже.
Что такое уязвимости загрузки файлов?
Уязвимости загрузки файлов возникают, когда веб-сервер позволяет пользователям загружать файлы в свою файловую систему без достаточной проверки таких аспектов, как имя, тип, содержимое или размер. Отсутствие должного контроля может привести к тому, что даже базовая функция загрузки изображений может использоваться для загрузки произвольных и потенциально опасных файлов. Это может включать и серверные скрипты, которые позволяют удалённое выполнение кода.
В некоторых случаях сам факт загрузки файла уже достаточен, чтобы нанести ущерб. Другие атаки могут включать последующий HTTP-запрос к файлу, как правило, чтобы инициировать его выполнение сервером.
Какие последствия уязвимостей загрузки файлов?
Влияние уязвимостей загрузки файлов обычно зависит от двух ключевых факторов:
Какой аспект файла сайт проверяет некорректно — размер, тип, содержимое и т. п.
Какие ограничения накладываются на файл после успешной загрузки.
В худшем случае тип файла проверяется ненадёжно, а конфигурация сервера позволяет выполнять в виде кода определённые типы файлов (такие как .php и .jsp). В этом случае злоумышленник потенциально может загрузить файл с кодом, который действует как web shell, фактически предоставляя ему полный контроль над сервером.
Если имя файла проверяется некорректно, это может позволить злоумышленнику перезаписать критически важные файлы просто загрузив файл с тем же именем. Если сервер также уязвим к обходу директорий, то атакующий сможет загружать файлы в непредусмотренные места.
Отсутствие контроля размера файла также может позволить выполнить разновидность атаки DoS, при которой злоумышленник заполняет доступное дисковое пространство.
Как возникают уязвимости загрузки файлов?
Учитывая очевидные риски, редко встречаются сайты вообще без ограничений на типы загружаемых файлов. Чаще разработчики внедряют, как им кажется, надёжную проверку, которая либо изначально ошибочна, либо легко обходится.
Например, они могут пытаться создать список запрещенных типов файлов, но не учитывать рассогласования при разборе расширений. Как и с любым блеклистом, легко случайно упустить более редкие типы, которые всё же могут быть опасны.
В других случаях сайт может пытаться определять тип файла, проверяя свойства, которые легко подменить злоумышленнику с помощью инструментов вроде Burp Proxy или Repeater. В конечном итоге даже надёжные меры проверки могут применяться непоследовательно в сети хостов и директорий, из которых состоит сайт, что приводит к уязвимым несоответствиям.
Как веб-серверы обрабатывают запросы к статическим файлам?
Прежде чем рассматривать эксплуатацию уязвимостей загрузки, важно базово понимать, как серверы обрабатывают запросы к статическим файлам.
Исторически сайты состояли почти полностью из статических файлов, которые отдавались пользователю по запросу. В результате путь каждого запроса можно было в точности отобразить на иерархию директорий и файлов в файловой системе сервера. Сегодня сайты всё более динамические, и путь запроса часто никак напрямую не связан с файловой системой. Тем не менее веб-серверы всё ещё обслуживают некоторые статические файлы, включая таблицы стилей, изображения и т. п.
Процесс обработки этих статических файлов в основном тот же. В какой-то момент сервер парсит путь в запросе, чтобы определить расширение файла. Затем он использует это расширение для определения типа файла, обычно сравнивая его со списком заранее настроенных расширений и типов MIME. Дальнейшее поведение зависит от типа файла и конфигурации сервера.
Если тип файла неисполняемый, например изображение или статическая HTML-страница, сервер может просто отправить содержимое файла клиенту в HTTP-ответе.
Если тип файла исполняемый, например PHP-файл, и сервер настроен на выполнение файлов этого типа, он присвоит переменным значения на основе заголовков и параметров HTTP-запроса, а затем выполнит скрипт. Полученный вывод может быть отправлен клиенту в HTTP-ответе.
Если тип файла исполняемый, но сервер не настроен на выполнение файлов этого типа, он обычно вернёт ошибку. Однако в некоторых случаях содержимое файла всё же может быть отправлено клиенту как обычный текст. Подобные некорректные настройки можно использовать для утечки исходного кода и другой конфиденциальной информации. Вы можете посмотреть пример этого в материалах по раскрытию информации.
Эксплуатация неограниченной загрузки для развёртывания web shell
С точки зрения безопасности худший сценарий — когда сайт позволяет загружать серверные скрипты, такие как PHP, Java или Python, и при этом настроен выполнять их как код. Это делает тривиальным создание собственной веб-оболочки на сервере.
Если вам удаётся успешно загрузить веб-оболочку, вы фактически получаете полный контроль над сервером. Это означает, что вы можете читать и записывать произвольные файлы, извлекать конфиденциальные данные, а также использовать сервер для поворотных атак как против внутренней инфраструктуры, так и других серверов вне сети. Например, следующий однострочник на PHP может использоваться для чтения произвольных файлов из файловой системы сервера:
После загрузки запрос к этому вредоносному файлу вернёт содержимое целевого файла в ответе.
Более универсальная веб-оболочка может выглядеть так:
Этот скрипт позволяет передать произвольную системную команду через параметр запроса следующим образом:
Эксплуатация некорректной проверки загрузки файлов
В реальном мире вряд ли вы найдёте сайт вообще без защиты от атак загрузки, как в предыдущем примере. Но наличие защиты не означает, что они надёжны. Иногда вы всё равно можете эксплуатировать изъяны в этих механизмах, чтобы получить веб-оболочку и удалённое выполнение кода.
Ошибочная проверка типа файла
При отправке HTML-форм браузер обычно отправляет данные в запросе POST с типом содержимого application/x-www-form-urlencoded. Этого достаточно для передачи простого текста, вроде имени или адреса. Однако это не подходит для передачи больших объёмов бинарных данных, таких как целый файл изображения или документ PDF. В таком случае предпочтителен тип содержимого multipart/form-data.
Рассмотрим форму с полями для загрузки изображения, описания и ввода имени пользователя. Отправка такой формы может привести к запросу, похожему на этот:
Как видно, тело сообщения разделено на отдельные части для каждого поля формы. Каждая часть содержит заголовок Content-Disposition, который предоставляет базовую информацию о соответствующем поле ввода. Отдельные части также могут содержать собственный заголовок Content-Type, который сообщает серверу MIME-тип отправленных данных.
Один из способов, которым сайты пытаются проверять загрузки — сверять, что этот заголовок Content-Type соответствует ожидаемому типу MIME. Например, если сервер ожидает только изображения, он может разрешать только типы вроде image/jpeg и image/png. Проблемы возникают, когда значение этого заголовка сервер доверчиво принимает безусловно. Если не проводится дальнейшая проверка на соответствие содержимого заявленному типу MIME, эту защиту легко обойти с помощью инструментов вроде Burp Repeater.
Предотвращение выполнения файлов в директориях, доступных пользователям
Хотя явно лучше не позволять изначально загружать опасные типы файлов, второй рубеж обороны — запрещать серверу выполнять любые скрипты, которые всё же просочились.
В качестве меры предосторожности серверы обычно выполняют только те скрипты, тип MIME которых явно разрешён в конфигурации. Иначе они могут просто вернуть сообщение об ошибке или, в некоторых случаях, отдать содержимое файла как обычный текст:
Такое поведение само по себе потенциально интересно, так как может дать способ утечки исходного кода, но оно сводит на нет любую попытку создать веб-оболочку.
Подобная конфигурация часто отличается между директориями. Директория, в которую загружаются файлы пользователей, вероятнее всего имеет куда более строгие ограничения, чем другие места файловой системы, которые предполагаются недоступными конечным пользователям. Если вы найдёте способ загрузить файл в другую директорию, не предназначенную для пользовательских файлов, сервер может всё же выполнить ваш скрипт.
Также учтите, что хотя вы можете отправлять все запросы на одно и то же доменное имя, оно часто указывает на reverse proxy-сервер, например балансировщик нагрузки. Ваши запросы часто обрабатываются дополнительными серверами за кулисами, которые также могут быть настроены по-разному.
Недостаточный список запрещенных типов файлов
Один из очевидных способов не позволить пользователям загружать вредоносные скрипты — создать список запрещенных расширений файлов, таких как .php. Практика blacklist по природе порочна, так как сложно явно заблокировать каждое возможное расширение, которое может привести к выполнению кода. Подобные списки иногда обходятся использованием менее известных альтернативных расширений, которые всё ещё могут исполняться, таких как .php5, .shtml и т. п.
Переопределение конфигурации сервера
Как обсуждалось выше, серверы обычно не выполняют файлы, если их на это явно не настроили. Например, чтобы Apache выполнял PHP-файлы по запросу клиента, разработчики должны добавить следующие директивы в файл /etc/apache2/apache2.conf:
Многие серверы также позволяют разработчикам создавать специальные конфигурационные файлы внутри отдельных директорий, чтобы переопределять или дополнять одну или несколько глобальных настроек. Например, серверы Apache загрузят конфигурацию для директории из файла .htaccess, если он присутствует.
Аналогично, разработчики могут делать настройку на уровне директории на серверах IIS с помощью файла web.config. Это может включать директивы вроде следующей, которая в данном случае разрешает отдавать пользователям файлы JSON:
Веб-серверы используют такие конфигурационные файлы при их наличии, но обычно к ним нельзя обращаться через HTTP-запросы. Тем не менее изредка встречаются серверы, которые позволят вам загрузить собственный вредоносный конфигурационный файл. В этом случае, даже если нужное расширение заблокировано, вы можете обмануть сервер, сопоставив произвольное, нестандартное расширение исполняемому типу MIME.
Обфускация расширений файлов
Даже самые исчерпывающие блеклисты можно обойти классическими приёмами обфускации. Предположим, код проверки чувствителен к регистру и не распознаёт, что exploit.pHp на самом деле является .php-файлом. Если код, который затем сопоставляет расширение с типом MIME, не чувствителен к регистру, это несоответствие позволит пронести вредоносные PHP-файлы через проверку, а впоследствии сервер может их выполнить.
Похожих результатов можно добиться следующими техниками:
Укажите несколько расширений. В зависимости от алгоритма разбора имени файл
exploit.php.jpgможет интерпретироваться как PHP-файл или JPG-изображение.Добавьте завершающие символы. Некоторые компоненты обрезают или игнорируют завершающие пробелы, точки и т. п.:
exploit.php.Попробуйте использовать URL-кодирование (или двойное URL-кодирование) точек, косых и обратных слешей. Если значение не декодируется при проверке расширения, но позже декодируется на стороне сервера, это также может позволить загрузить вредоносные файлы, которые иначе были бы заблокированы:
exploit%2EphpДобавьте точки с запятой или URL-кодированные нулевые байты перед расширением. Если проверка написана на высокоуровневом языке вроде PHP или Java, а сервер обрабатывает файл низкоуровневыми функциями на C/C++, это может привести к несоответствиям в том, где считается конец имени файла:
exploit.asp;.jpgилиexploit.asp%00.jpgПопробуйте использовать многобайтные символы Unicode, которые после преобразования или нормализации могут превратиться в нулевые байты и точки. Последовательности
xC0 x2E,xC4 xAEилиxC0 xAEмогут преобразоваться вx2E, если имя файла сначала разбирается как строка UTF-8, а затем конвертируется в ASCII до использования в пути.
Другие защиты включают удаление или замену опасных расширений, чтобы не позволить файлу выполниться. Если такое преобразование не применяется рекурсивно, вы можете расположить запрещённую подстроку так, что её удаление всё равно оставит валидное расширение. Например, рассмотрим, что произойдёт, если удалить .php из следующего имени файла:
Это лишь небольшая выборка множества способов обфускации расширений.
Ошибочная проверка содержимого файла
Вместо безусловного доверия заголовку Content-Type в запросе более защищённые серверы стараются проверить, что содержимое файла действительно соответствует ожидаемому.
В случае функции загрузки изображения сервер может попытаться проверить некоторые внутренние свойства изображения, например его размеры. Если вы попытаетесь загрузить PHP-скрипт, например, у него вообще не будет размеров. Следовательно, сервер может сделать вывод, что это не изображение, и отклонить загрузку.
Аналогично, определённые типы файлов могут всегда содержать специфическую последовательность байтов в заголовке или хвосте. Их можно использовать как отпечаток или сигнатуру, чтобы определить, соответствует ли содержимое ожидаемому типу. Например, JPEG-файлы всегда начинаются байтами FF D8 FF.
Это намного более надёжный способ проверки типа, но и он не безупречен. С помощью специальных инструментов, таких как ExifTool, можно без труда создать полиглот-JPEG с вредоносным кодом в метаданных.
Эксплуатация состояния гонки при загрузке файлов
Современные фреймворки стали более устойчивыми к таким атакам. Обычно они не загружают файлы напрямую в конечное место в файловой системе. Вместо этого предпринимаются меры предосторожности: загрузка в временную, изолированную директорию, рандомизация имени, чтобы избежать перезаписи существующих файлов. Затем проводится проверка этого временного файла, и только после признания его безопасным он переносится в назначенное место.
Тем не менее разработчики иногда реализуют собственную обработку загрузок вне фреймворка. Это не только сложно сделать качественно, но и может привести к опасным состояниям гонки, которые позволяют полностью обойти даже самые надёжные проверки.
Например, некоторые сайты загружают файл напрямую в основную файловую систему, а затем удаляют его, если он не прошёл проверку. Такое поведение типично для сайтов, полагающихся на антивирусы и т. п. для проверки на вредоносный код. Это может занимать всего несколько миллисекунд, но в то короткое время, пока файл существует на сервере, злоумышленник может успеть его выполнить.
Такие уязвимости часто чрезвычайно тонкие, что затрудняет их обнаружение в ходе тестирования методом черного ящика, если только вы не найдёте способ утечки соответствующего исходного кода.
Состояния гонки при загрузке файлов по URL
Похожие состояния гонки могут возникать в функциях, позволяющих загрузить файл по предоставленному URL. В этом случае сервер должен получить файл из интернета и создать локальную копию, прежде чем сможет выполнить проверку.
Поскольку файл загружается по HTTP, разработчики не могут использовать встроенные механизмы фреймворка для безопасной проверки файлов. Вместо этого они могут вручную создать собственные процессы временного хранения и проверки файла, которые могут быть не столь безопасны.
Например, если файл загружается во временную директорию со случайным именем, в теории злоумышленнику должно быть невозможно эксплуатировать состояние гонки. Если он не знает имя директории, он не сможет запросить файл, чтобы инициировать его выполнение. С другой стороны, если случайное имя директории генерируется с использованием псевдослучайных функций вроде PHP uniqid(), его потенциально можно перебрать.
Чтобы упростить такие атаки, вы можете попытаться увеличить время обработки файла, тем самым расширив окно для перебора имени директории. Один из способов сделать это — загрузить более крупный файл. Если он обрабатывается по частям, можно воспользоваться этим, создав вредоносный файл с полезной нагрузкой в начале, а затем добавив большое количество произвольных заполняющих байтов.
Эксплуатация уязвимостей загрузки файлов без удалённого выполнения кода
В рассмотренных выше примерах нам удавалось загружать серверные скрипты для удалённого выполнения кода. Это самое серьёзное последствие небезопасной функции загрузки, но такие уязвимости можно эксплуатировать и иначе.
Загрузка вредоносных скриптов на стороне клиента
Хотя вам может быть недоступно выполнение скриптов на стороне сервера, вы всё ещё можете загружать скрипты для атак на стороне клиента. Например, если вы можете загружать HTML-файлы или SVG-изображения, вы потенциально можете использовать теги <script>, чтобы создать хранимые XSS-пэйлоады.
Если загруженный файл затем появляется на странице, которую посещают другие пользователи, их браузер выполнит скрипт при рендеринге страницы. Обратите внимание, что из-за ограничений same-origin policy такие атаки работают только в том случае, если загруженный файл отдаётся с того же источника, куда вы его загружаете.
Эксплуатация уязвимостей при разборе загруженных файлов
Если загруженный файл сохраняется и отдаётся безопасно, последней попыткой остаётся эксплуатация уязвимостей, специфичных для разбора или обработки различных форматов. Например, если вы знаете, что сервер парсит файлы, основанные на XML, такие как Microsoft Office .doc или .xls, это может быть потенциальным вектором для XXE.
Загрузка файлов с использованием PUT
Стоит отметить, что некоторые веб-серверы могут быть настроены поддерживать запросы PUT. Если соответствующие защиты не включены, это может предоставить альтернативный способ загрузить вредоносные файлы, даже когда функции загрузки нет в веб-интерфейсе.
Как предотвратить уязвимости загрузки файлов
Позволять пользователям загружать файлы — обычная практика, и она не обязательно опасна, если принять правильные меры. В целом, наиболее эффективный способ защитить собственные сайты от этих уязвимостей — реализовать все следующие практики:
Проверяйте расширение файла по списку разрешенных расширений, а не по списку запрещённых. Гораздо проще перечислить расширения, которые вы хотите разрешить, чем угадать все, которые атакующий попытается загрузить.
Убедитесь, что имя файла не содержит подстрок, которые могут интерпретироваться как директория или последовательность обхода (
../).Переименовывайте загруженные файлы, чтобы избежать коллизий, способных привести к перезаписи существующих файлов.
Не загружайте файлы в постоянную файловую систему сервера, пока они не будут полностью проверены.
По возможности используйте проверенный фреймворк для предварительной обработки загрузок, вместо попыток писать собственные механизмы проверки.
Last updated