Перейти к содержимому

Burp Suite и multipart-запросы: настройка Repeater, который ломает тело

Burp Suite и multipart-запросы: настройка Repeater, который ломает тело
Время чтения: 14 мин.

На пентесте формы загрузки документов перехватываю multipart-запрос в Burp Proxy, отправляю в Repeater, меняю filename с report.docx на shell.php — жму Send. Сервер возвращает 400 Bad Request. Не потому что серверная валидация отловила подмену — Content-Length (заголовок, который говорит серверу точный размер тела запроса в байтах) перестал совпадать с реальным размером тела после правки. Repeater не пересчитал его автоматически. И так — раз за разом: Burp Suite корректно перехватывает multipart/form-data, но при ручном редактировании тела в Repeater структура запроса рассыпается. Разберём по шагам: где именно ломается, какие настройки включить, какие расширения поставить и как довести модификацию multipart-запроса до рабочего состояния.

Как устроен multipart/form-data и почему Repeater его портит

Прежде чем чинить — нужно понять, что ломается. multipart/form-data — формат передачи данных в HTTP-запросе, когда в одном POST отправляются несколько частей: текстовые поля формы, файлы, метаданные. Браузер переключается на него вместо стандартного application/x-www-form-urlencoded, когда в форме есть <input type="file"> — бинарные данные (картинки, PDF, архивы) в URL-кодированном формате не передать. По документации PortSwigger, multipart/form-data предпочтительнее при отправке больших объёмов бинарных данных.

Типичный multipart-запрос выглядит так (структура на основе реального перехвата):

POST /upload HTTP/1.1
Host: target.example
Content-Type: multipart/form-data; boundary=----WebKitFormBoundary7MA4
Content-Length: 237

------WebKitFormBoundary7MA4
Content-Disposition: form-data; name="file"; filename="photo.png"
Content-Type: image/png

<бинарное содержимое файла>
------WebKitFormBoundary7MA4--

Три элемента, на которых держится вся конструкция:

Boundary (разделитель) — уникальная строка, указанная в заголовке Content-Type после boundary=. Она разделяет части запроса. Каждая часть начинается с -- плюс значение boundary, а завершающая строка заканчивается на -- плюс boundary плюс --. Если boundary в теле не совпадает с тем, что указан в заголовке — сервер не разберёт запрос и вернёт ошибку.

Content-Disposition — заголовок внутри каждой части multipart-тела, где прописаны имя поля формы (name="file") и имя файла (filename="photo.png"). Именно здесь пентестер чаще всего вносит правки: подменяет расширение, добавляет null-байт в имя, меняет имя поля. Каждая такая правка сдвигает длину тела.

Content-Length — заголовок HTTP-запроса, указывающий серверу размер тела в байтах. Если после правки Content-Disposition имя файла стало короче или длиннее, а Content-Length остался прежним — сервер получит обрезанное тело или попытается прочитать лишние байты. Результат: 400 Bad Request, 413 Payload Too Large или молчаливый обрыв соединения.

Repeater (инструмент Burp Suite для ручной отправки и модификации отдельных HTTP-запросов) показывает тело в текстовом виде. Когда вы меняете строку в Raw-вкладке, Burp может не пересчитать Content-Length — зависит от настроек. Для обычных POST-запросов с application/x-www-form-urlencoded это редко мешает — они короткие. Но multipart-запрос с файлом — это сотни или тысячи байт, где расхождение в три символа ломает всё.

Вторая проблема: при копировании multipart-запроса из Proxy в Repeater Burp иногда нормализует переводы строк. Стандарт HTTP требует \r\n (CR LF — два байта: 0D 0A в hex) как разделитель строк. Некоторые операции в текстовом редакторе заменяют \r\n на \n (один байт). На каждой строке multipart-тела теряется по байту — Content-Length уезжает.

Зачем вообще вникать в эту кухню? Понимание multipart на уровне байтов пригождается далеко за пределами тестирования загрузки файлов. Техника HTTP request smuggling через multipart-границы эксплуатирует именно расхождения в парсинге boundary между фронтенд-прокси и бэкенд-сервером. Ручной разбор HTTP-запроса — навык, который работает везде.

Настройка Burp Suite для перехвата multipart-запросов

Update Content-Length — чекбокс, который решает большую часть проблем

В настройках Repeater есть опция «Update Content-Length» — автоматический пересчёт Content-Length перед отправкой запроса. В большинстве версий Burp Suite она включена по умолчанию, но если конфигурацию меняли — может оказаться выключена.

Где искать: откройте вкладку Repeater, нажмите на иконку настроек (шестерёнка) в верхней части панели. Убедитесь, что пункт «Update Content-Length» активен. В старых версиях Burp этот пункт находился в меню Repeater → Options.

Что происходит при включённой настройке: после каждого изменения тела Burp пересчитывает Content-Length перед отправкой. Вы меняете filename="report.docx" (11 символов) на filename="shell.php" (9 символов) — длина тела уменьшилась на 2 байта. Burp обновит заголовок, запрос уйдёт корректно.

Как убедиться, что работает: после правки тела отправьте запрос. Если сервер вернул осмысленный ответ — 200, 403, 415 Unsupported Media Type, что угодно кроме 400 с невнятным сообщением — Content-Length корректен. Если по-прежнему 400 — проблема в другом: boundary, кодировка, бинарные данные.

Настройка доступна и в Community, и в Professional. В Kali Linux Burp предустановлен в версии Community.

Scope и фильтры Proxy для multipart-трафика

При пентесте загрузки файлов multipart-запросы генерируют много трафика. Чтобы Proxy History не превратился в свалку, настройте scope — набор доменов, с которыми вы работаете.

Откройте Target → Scope settings, добавьте домен тестируемого приложения в Include in scope. Затем в Proxy → Options включите фильтр «Only intercept requests in scope». Без scope Burp записывает весь трафик браузера — фоновые запросы к CDN, аналитике, трекерам. Один загруженный файл в 5 МБ засоряет HTTP History и замедляет интерфейс.

Отдельный момент — MIME-фильтры в Proxy → Options → Intercept Client Requests. По умолчанию Burp может пропускать запросы с определёнными Content-Type. Убедитесь, что multipart/form-data не попал в список исключений — иначе запросы загрузки файлов пролетят мимо Intercept, и вы их просто не увидите.

После настройки в HTTP History должны отображаться только запросы к целевому домену, multipart-запросы появляются в списке и доступны для отправки в Repeater.

Модификация multipart запросов в Repeater без поломки структуры

Ручное редактирование multipart body в Raw-режиме

Самый частый сценарий: вы перехватили запрос загрузки файла, отправили в Repeater через правый клик → Send to Repeater и хотите проверить, пропустит ли сервер файл с другим расширением или Content-Type.

Переключитесь на Raw-вкладку в панели редактора сообщений (вкладки Pretty, Raw, Hex, Render — их описание есть в документации PortSwigger по Message Editor). В Raw вы видите запрос «как есть», без форматирования. Для multipart это принципиально: Pretty-вкладка может перестроить отступы и скрыть boundary-разделители.

Что безопасно менять в Raw:

Имя файла в Content-Disposition. Например: filename="photo.png" → filename="shell.php.png". Меняйте прямо в текстовом редакторе. Если Update Content-Length включён — Burp пересчитает длину.

Content-Type части (не путать с заголовком запроса) — это заголовок внутри boundary-блока: Content-Type: image/png → Content-Type: application/x-php. MIME type — тип содержимого, по которому сервер определяет формат файла (image/png, application/pdf и т.д.). Серверная валидация часто проверяет именно его. Подмена — один из базовых тестов при пентесте загрузки файлов.

Содержимое файла. Если вместо бинарного файла хотите отправить текстовый payload (например, PHP-код для проверки серверного исполнения) — замените бинарные данные текстом прямо в Raw.

А вот что менять без подготовки опасно:

Boundary в теле. Если вы случайно задели строку boundary — удалили дефис, добавили пробел, сдвинули символ — тело перестанет парситься. Boundary в теле должен точно совпадать с тем, что указан в заголовке Content-Type. Строки вроде ----WebKitFormBoundaryiA6aJgbokzo7IFSh длинные и случайные — задеть легко.

Переводы строк между частями. Каждая часть multipart-тела отделяется пустой строкой (два \r\n подряд после заголовков части, перед содержимым). Удалите один перевод строки — сервер склеит заголовок части с содержимым файла.

Перед правкой продублируйте вкладку Repeater (правый клик на вкладке → Duplicate tab). Так у вас останется эталонный запрос для сравнения. Я делаю это на автомате — спасало не раз.

Hex-вкладка Burp Suite для точечных правок multipart

Hex-вкладка в редакторе сообщений Burp (описана в документации PortSwigger по Message Editor) показывает тело в шестнадцатеричном формате: 16 байт на строку, рядом — ASCII-представление. Здесь доступны операции, невозможные в текстовом режиме.

Вставка null-байта. Null-байт (00) в имени файла — классический приём обхода серверной валидации: shell.php%00.png. В Raw-вкладке null-байт вставить нельзя (непечатаемый символ), а в Hex — просто байт 00. Через контекстное меню (правый клик) → Insert byte вставляете 00 в нужную позицию.

Проверка переводов строк. Убедитесь, что на границах частей стоят пары 0D 0A (CR LF), а не одиночные 0A (LF). Видите только 0A — вставьте 0D перед каждым 0A на границах.

Подсчёт байт. Посчитайте количество байт от начала тела (после пустой строки за заголовками запроса) до последнего байта и сравните с Content-Length. Числа расходятся — причина ошибки 400 найдена.

Вставка magic bytes. Серверная валидация иногда проверяет первые байты файла (magic bytes, они же сигнатура формата). PNG начинается с 89 50 4E 47 0D 0A 1A 0A, JPEG — с FF D8 FF. Через Hex-вкладку можно добавить magic bytes PNG перед PHP-кодом и посмотреть, обманет ли это валидацию.

Кнопка \n на панели текстового редактора переключает отображение непечатаемых символов — CR, LF, null-байты становятся видны прямо в Raw-вкладке. Удобно для быстрой диагностики без переключения в Hex.

Расширения Burp Suite для работы с multipart

BApp Store (магазин расширений Burp Suite, доступен из интерфейса: Extender → BApp Store) содержит расширения, которые упрощают работу с multipart.

Content Type Converter конвертирует тело запроса между форматами: application/x-www-form-urlencoded ↔ multipart/form-data ↔ application/json ↔ application/xml. Зачем это нужно?

Допустим, сервер принимает application/json, но вы хотите проверить, обработает ли он тот же запрос в формате multipart — некоторые фреймворки парсят оба формата, а валидация может различаться. Или вы нашли SQL-инъекцию в JSON-запросе и хотите перепроверить через multipart — WAF (Web Application Firewall, межсетевой экран уровня приложений) иногда фильтрует один формат, но пропускает другой. В Repeater: правый клик на теле → Extensions → Content Type Converter → выбираете целевой формат. Расширение перестроит тело и обновит заголовок Content-Type.

Hackvertor — расширение PortSwigger для кодирования и трансформации данных внутри запроса. Использует теги вида <@base64>payload<@/base64>, которые Burp обрабатывает перед отправкой. Для multipart полезен в нескольких сценариях: генерация уникального boundary на каждый запрос (помогает обходить WAF-правила, привязанные к конкретному boundary), кодирование payload в имени файла (URL-encoding, double-encoding) без ручного подсчёта длины, автоматическое обновление Content-Length через тег <@auto_content_length>.

Оба расширения бесплатны и работают в Burp Community.

Стоит упомянуть AutoRepeater от NCC Group — но с оговоркой. По его GitHub-трекеру, расширение не полностью поддерживает парсинг multipart-форм в правилах замены. Разработчики рекомендуют использовать режим «replace string» вместо «param replacement» для multipart-запросов. Обходной путь, пока полноценный парсинг не реализован.

Пентест загрузки файлов через Burp: от перехвата до обхода валидации

Тестирование формы загрузки файлов — одна из ключевых задач при пентесте веб-приложений. Зачем атакующему форма загрузки? Через неё можно залить на сервер вредоносный файл — от web shell (в базе MITRE ATT&CK это техника T1505.003; ATT&CK — открытая база тактик и техник атак, где T-коды обозначают конкретные техники; T1505.003 относится к тактике Persistence — закрепление в системе через вредоносный скрипт на сервере) до скриптов для выкачивания данных.

По документации PortSwigger, в реальных приложениях почти всегда стоит защита от произвольной загрузки — но она часто обходится через манипуляцию Content-Type, расширения файла или его содержимого. MITRE ATT&CK классифицирует эксплуатацию таких форм как Exploit Public-Facing Application (T1190, тактика Initial Access). На практике: загрузка PHP-шелла через форму аватара = initial access и persistence в одном шаге.

Пошаговый сценарий модификации multipart-запроса в Burp для проверки валидации.

Что понадобится: Burp Suite Community или Professional; браузер с настроенным прокси (встроенный браузер Burp или FoxyProxy); целевое приложение с формой загрузки файлов; разрешение на тестирование.

Шаг 1. Перехват легитимного запроса. Откройте форму загрузки в браузере, загрузите обычный файл — например, test.png. В Burp → Proxy → HTTP History найдите POST-запрос с Content-Type: multipart/form-data. Запроса нет? Проверьте scope, состояние Intercept и отсутствие multipart/form-data в списке исключений MIME-фильтров. В HTTP History должен появиться запрос с boundary-разделителями и содержимым файла в теле.

Шаг 2. Отправка в Repeater и базовая проверка. Правый клик на запросе → Send to Repeater. Перейдите во вкладку Repeater. Убедитесь, что Update Content-Length включён. Нажмите Send без изменений. Сравните ответ с тем, что получал браузер: должен быть тот же статус (200 OK или redirect). Ответ отличается — проблема в сессии: истёкшая cookie или одноразовый CSRF-токен.

Шаг 3. Подмена Content-Type части. В Raw-вкладке найдите строку Content-Type: image/png внутри boundary-блока (не заголовок запроса!). Замените на Content-Type: application/x-php. Нажмите Send. Сервер принял файл — валидация MIME отсутствует или неэффективна. Ошибка «Invalid file type» — сервер проверяет Content-Type части. Теперь вы знаете, проверяется ли MIME.

Шаг 4. Подмена расширения файла. Верните Content-Type на image/png, но измените filename="test.png" на filename="test.php.png" или filename="test.php%00.png". Для варианта с null-байтом переключитесь в Hex-вкладку и вставьте байт 00 между php и .png. Нажмите Send. Сервер принял — проверьте, доступен ли загруженный файл по URL и исполняется ли как скрипт. Если да — это уязвимость.

Шаг 5. Подмена содержимого. Замените бинарное содержимое PNG на текст <?php echo "test"; ?>. Оставьте Content-Type: image/png и filename="test.png". Нажмите Send. Сервер проверяет magic bytes — отклонит файл. Тогда через Hex-вкладку добавьте PNG-сигнатуру (89 50 4E 47 0D 0A 1A 0A) перед PHP-кодом и повторите. Так вы определяете уровень валидации — только Content-Type, расширение, magic bytes или всё вместе.

Этот сценарий покрывает проверки из OWASP A05:2021 — Security Misconfiguration (некорректная конфигурация обработки загрузок) и A03:2021 — Injection (исполнение загруженного кода на сервере).

Диагностика ошибок Repeater при работе с multipart

Когда multipart-запрос в Repeater ведёт себя не так, как в браузере — проверяйте по порядку.

400 Bad Request после правки тела. Первое действие: сравните Content-Length в заголовке с реальным размером тела. Переключитесь в Hex и посчитайте байты от начала тела (после пустой строки за заголовками) до последнего байта. Числа не совпадают — включите Update Content-Length и отправьте снова. Совпадают — проблема в boundary или кодировке.

Boundary не совпадает с заголовком. Сравните значение boundary в Content-Type: multipart/form-data; boundary=XXXX со строками-разделителями в теле. В теле каждый разделитель начинается с --XXXX (два дополнительных дефиса перед значением boundary). Частая ошибка: при редактировании тела случайно задеть строку boundary — удалить дефис, добавить пробел.

Тело обрезается сервером. Content-Length может быть меньше реального размера тела. Или Burp нормализовал переводы строк: \r\n → \n. В Hex-вкладке проверьте: на границах строк multipart-тела должны стоять пары 0D 0A, а не одиночные 0A. Видите только 0A — вставьте 0D вручную перед каждым.

Сервер отвечает иначе, чем браузеру. Причина часто не в multipart, а в сессии: истёкшая cookie, одноразовый CSRF-токен (нужен свежий из Proxy при каждом повторном запросе), проверка Referer или Origin. Сравните заголовки Repeater-запроса с оригинальным из HTTP History через модуль Comparer (правый клик → Send to Comparer на оба запроса).

Burp Suite не парсит multipart в Inspector. Inspector (панель справа в редакторе сообщений с доступом к параметрам, cookies, заголовкам) не всегда корректно разбирает multipart-запросы — особенно если boundary содержит нестандартные символы или тело включает бинарные данные. В таких случаях работайте через Raw и Hex. Inspector хорош для application/x-www-form-urlencoded, для multipart он вторичен.

Разобраться с multipart/form-data на уровне байтов стоит хотя бы один раз — не ради теории, а чтобы не тратить час на отладку того, что чинится за минуту. Большинство проблем с multipart в Repeater сводятся к трём вещам: Content-Length не пересчитан, boundary не совпадает с заголовком, переводы строк нормализованы. Первое решается одним чекбоксом. Второе и третье — Hex-вкладкой и внимательностью.

Замечаю закономерность: начинающие пентестеры часто обвиняют Burp в «некорректной работе с multipart», хотя проблема в непонимании протокола. HTTP-запрос — последовательность байтов. Инструмент передаёт ровно то, что вы ему дали. Поменяли пять символов в теле и не обновили Content-Length — это не баг Repeater. Настройка Update Content-Length существует для автоматизации, но полагаться на неё слепо тоже не стоит: при работе с бинарными данными через Hex она может не сработать, потому что вы правите байты напрямую, минуя текстовый редактор.

Навык ручной работы с HTTP на уровне отдельных байтов — это то, что отделяет пентестера, умеющего нажимать кнопки в Burp, от того, кто понимает, что происходит между клиентом и сервером. Инструменты обновляются каждый год, появляются расширения, меняется UI — но 0D 0A на границе строки не менялся с 1999 года. Если хочется закрыть этот фундамент последовательно, а не собирать по кускам из статей — на IB Basics в Codeby как раз начинают с ручного разбора протокола и инструментария, без «запомните терминологию».

Эту тему и смежные навыки разбирают на практике в курсе «Тестирование веб-приложений на проникновение (WAPT)» Codeby Academy.