# HTTP method override обходит проверку по методу

Проверять запрос только по методу небезопасно, если проверяющий видит одну строку запроса. Запрос может прийти как `POST`, содержать `X-HTTP-Method-Override: DELETE`, а после переписывания метода промежуточным ПО попасть в обработчик удаления. Если экран подтверждения, правило шлюза, проверка прав или журнал аудита считает его обычным POST, проверена оболочка, а действие пропущено.

Это не трюк протокола HTTP, который понимает каждый сервер. Это соглашение на уровне приложения, поэтому о нем легко забыть. Поддержка зависит от фреймворка, порядка промежуточного ПО, маршрута и развертывания. Не нужно считать каждый POST скрытым разрушительным запросом. Нужно найти все места, где принимается переопределение, разрешать его до решения о безопасности и отклонять неоднозначность.

## Метод в запросе и фактический метод сообщают разные факты

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

RFC 9110 называет токен метода главным источником смысла запроса. POST означает обработку с учетом конкретного ресурса, а DELETE просит исходный сервер убрать связь между целевым ресурсом и его текущей функцией. RFC не стандартизирует `X-HTTP-Method-Override`. Этот заголовок входит в семейство соглашений о совместимости для клиентов и посредников, которые умели работать только с GET и POST.

Обычные программные компоненты до сих пор поддерживают это соглашение. Промежуточное ПО `method-override` в Express по умолчанию читает `X-HTTP-Method-Override` в POST. Приняв значение, оно меняет `req.method`, а прежнее сохраняет в `req.originalMethod`. В ASP.NET Core есть аналогичный компонент с тем же заголовком по умолчанию. `HiddenHttpMethodFilter` в Spring работает иначе: читает параметр формы `_method` в POST и допускает PUT, DELETE и PATCH.

Эти детали разделяют понятия, которые команды часто смешивают. «Шлюз разрешил POST» описывает транспорт. «Приложение выполнило DELETE» описывает поведение. Для подтверждения или решения о доступе нужен второй факт. Если принимающий решение компонент не может определить фактический метод, он должен отклонить признаки переопределения, а не молча классифицировать запрос по внешнему глаголу.

Поддержка во фреймворке сама по себе не доказывает уязвимость. В Express нужно установить компонент и выбрать порядок, в ASP.NET Core добавить его, в Spring включить и правильно разместить фильтр. Итог зависит от развернутой конфигурации, включая обратные прокси и компоненты отдельных маршрутов. Просмотр кода дает кандидатов, проверка состояния дает доказательство.

## Подтвержденный POST может попасть в обработчик DELETE

Туннелированная запись проходит контроль, когда компоненты по-разному определяют главное представление действия. Допустим, агент предлагает такой запрос к API проектов:

```http
POST /v1/projects/42 HTTP/1.1
Host: api.example.test
Authorization: Bearer [injected outside the agent]
X-HTTP-Method-Override: DELETE
Content-Length: 0
```

Слой подтверждения показывает «POST /v1/projects/42» и применяет правило, разрешающее POST для сеанса. Обратный прокси передает незнакомый заголовок дальше. RFC 9110 обычно требует передавать неизвестные поля, если конфигурация явно не блокирует и не преобразует их. В приложении промежуточное ПО меняет метод до маршрутизации. Маршрутизатор выбирает DELETE, и проект исчезает.

Для этой ошибки не нужна сломанная проверка прав DELETE. Приложение может правильно разрешать этой учетной записи удаление. Дефект находится раньше: человек или автоматическая проверка подтвердили существенно другое действие, потому что классификатор проигнорировал поддерживаемый ввод. Такое расхождение также затрагивает межсетевой экран приложения, ограничитель частоты, защиту CSRF, метрики и журналы доступа.

Порядок промежуточного ПО определяет, какой контроль увидит какой факт. Документация Express говорит прямо: переопределение метода должно выполняться до всех компонентов, которым нужен метод запроса. Для маршрутизации и CSRF внутри процесса это верно. Внешний шлюз, уже принявший решение по строке запроса, такой порядок не исправит.

Есть и менее заметный сбой. Пограничный журнал пишет POST, приложение пишет DELETE, а расследующий считает их разными запросами. Общий идентификатор свяжет записи, только если обе стороны его сохраняют и команда сравнивает поля методов. Единая нормализованная запись действия оставляет меньше места для ошибки.

Не определяйте последствия по одному слову DELETE. RFC 9110 уточняет, что DELETE убирает связь ресурса с его текущей функцией; уничтожение данных и освобождение хранилища зависят от приложения. Маршрут DELETE может архивировать, отключать, ставить задачу в очередь или удалять данные. Проверяйте реальный результат маршрута, а не общее определение глагола.

## Проверьте конечную точку контролируемой матрицей

Надежный тест сравнивает состояние после нативных методов, заголовков переопределения и параметров на одноразовом ресурсе. Запускайте его в тестовой среде или на специально подготовленных данных. Используйте учетную запись с теми же ограниченными правами, что у настоящего вызывающего, и создавайте ресурс заново перед каждым случаем, чтобы успешное удаление не исказило следующий результат.

Сначала отправьте контрольный POST без переопределения. Запишите статус, тело ответа и состояние ресурса. Затем отправьте нативный DELETE, чтобы подтвердить наличие маршрута и права учетной записи. После этого повторите POST для каждого соглашения. Минимальная проверка заголовка выглядит так:

```sh
BASE='https://staging.example.test'
ID='override-probe-17'

curl -sS \n  -D response.headers \n  -o response.body \n  -w 'case=x-http-method-override outer=POST status=%{http_code} bytes=%{size_download}
' \n  -X POST "$BASE/v1/projects/$ID" \n  -H 'Authorization: Bearer test-token' \n  -H 'X-HTTP-Method-Override: DELETE'

curl -sS \n  -o state.body \n  -w 'verify=read-after-request status=%{http_code} bytes=%{size_download}
' \n  -H 'Authorization: Bearer test-token' \n  "$BASE/v1/projects/$ID"
```

Форма вывода специально сделана стабильной для разбора в CI:

```text
case=x-http-method-override outer=POST status=204 bytes=0
verify=read-after-request status=404 bytes=71
```

В примере переопределение удалило ресурс, но такие статусы не универсальны. Одни API возвращают 200 с документом, другие 202 для отложенного удаления, третьи оставляют доступное представление после мягкого удаления. Определяйте успех по контракту своего приложения.

Используйте матрицу, а не случайную последовательность. Контрольный POST без переопределения должен вести себя как обычный POST. Нативный DELETE без переопределения показывает документированное удаление. Затем отправьте POST с `X-HTTP-Method-Override: DELETE`, `X-HTTP-Method: DELETE` и `X-Method-Override: DELETE`. Завершите POST с `?_method=DELETE` в строке запроса и `_method=DELETE` в форме. Каждый вариант должен соответствовать вашему явному дизайну либо завершаться без изменения состояния.

OWASP Web Security Testing Guide называет эти три заголовка и советует повторять с ними запрос, если ограниченный метод отклонен. Сравнения статусов недостаточно: различие может появиться из-за проверки, маршрутизации или посредника. После каждого запроса проверяйте ресурс, его версию и задачи в очереди.

Если весь стек под вашим контролем, снимите наблюдения на каждом участке: пограничный журнал, событие подтверждения, журнал приложения, выбранный маршрут и результат в данных. Вы ищете расхождение. Безопасным результатом будет единообразный отказ или намеренная нормализация с теми же правами и подтверждением, что у нативного DELETE.

## Статус дает подсказку, изменение состояния дает доказательство

Один HTTP-статус не показывает, выполнилось ли переопределение. Ответ 204 и исчезнувший ресурс дают сильное доказательство. Ответ 200 может быть обычным результатом POST, квитанцией об удалении или плохо оформленной ошибкой. Ответ 405 может прийти с границы до промежуточного ПО, хотя другой маршрут или тип содержимого все еще его достигает.

Стройте утверждение вокруг контрольного ресурса с уникальным идентификатором и известной начальной версией. До проверки прочитайте ресурс и сохраните метку версии или отпечаток представления. После запроса прочитайте снова и изучите запись операции. Для обновления выберите безвредное и заметное поле. Для удаления заранее выясните, будет оно физическим, мягким или отложенным.

Сравнение ответов тоже полезно. Сохраните статус, выбранные заголовки, размер и отпечаток тела. Сравните переопределение с обычным POST и нативным DELETE. Если ответ и состояние похожи на DELETE, вывод убедителен. Если ответ похож на POST, а состояние изменилось как после DELETE, слой ответа или журнала может использовать внешний метод.

Перенаправления нужно проверять отдельно. При переходе по 301, 302, 303, 307 или 308 клиент может изменить поведение, а инструменты по-разному сохраняют метод и тело. Сначала отключите переходы и запишите `Location`. Затем намеренно пройдите каждый шаг. Не смешивайте перенаправление и переопределение в одном непрозрачном результате.

Асинхронному API нужно больше времени наблюдения, а не догадки. Если ответ содержит идентификатор операции, опрашивайте ее по документированному контракту. Если постоянной ссылки нет, смотрите очередь или журнал тестовой среды. Неопределенный случай так и отмечайте, не называйте его безопасным.

Проверяйте и отсутствие побочных действий. Отклоненное переопределение не должно создавать, менять, архивировать, ставить в очередь, отправлять письмо или вызывать привилегированный вебхук. Аккуратный 4xx способен обмануть: один компонент мог уже записать эффект до отказа другого. Наблюдайте за ресурсом достаточно подробно, чтобы увидеть этот порядок.

## Варианты заголовков и неоднозначность нужно тестировать вместе

Проверка только канонического написания пропускает код совместимости и разногласия парсеров. Обычные имена: `X-HTTP-Method-Override`, `X-HTTP-Method` и `X-Method-Override`, но приложение может задать собственный способ чтения. Строка запроса и форма могут содержать `_method`, а старые интеграции иногда используют имена конкретного производителя.

Регистр имени не создает отдельное соглашение. По RFC 9110 имена полей не зависят от регистра, поэтому `x-http-method-override` и `X-HTTP-Method-Override` обозначают одно поле. Фильтр, который распознает только один вариант, работает неверно, даже если библиотека нормализует имена.

Повторяющиеся и конфликтующие значения открывают более сложную проблему. RFC 9110 разрешает объединять некоторые повторные строки через запятую и советует учитывать повторы даже для ожидаемого одиночного значения. У заголовков переопределения нет общего стандартизированного правила. Express берет первое повторение, а несколько обработчиков могут задать приоритет между разными именами.

Добавьте `x-http-method-override: DELETE` и требуйте того же результата. Отправьте два одинаковых DELETE и ожидайте документированного результата или отказа. Затем PUT и DELETE в повторах, а также разные значения в двух именах; оба конфликта нужно отклонять. Отклоняйте `PUT, DELETE`, нативный PATCH с DELETE без явного контракта и DELETE вместе с `_method=PATCH`.

Не ограничивайтесь DELETE. Проверьте PUT и PATCH, потому что система может по-разному оценивать создание, замену, частичное обновление и удаление. Неподдерживаемый токен будет отрицательным контролем. Не проверяйте TRACE и CONNECT без явного разрешения: они затрагивают другие части инфраструктуры и не улучшают доказательство.

Пробелы и регистр значения выявляют непоследовательную нормализацию. Если клиент позволяет, отправьте `delete`, ` DELETE ` и пустое значение. Нормализуйте только документированные формы, сверяйте с явным набором и отклоняйте остальное. Молчаливый выбор первого читаемого значения вернет обход после обновления прокси или библиотеки.

## Запросы агентов упрощают срабатывание слепой зоны

ИИ-агенты не создают слабое место, но делают неполную проверку опаснее. Агент может составлять произвольные заголовки, повторять примеры из документации и после неудачного DELETE попробовать туннель через POST, не понимая границы подтверждения. Человек на компактной карточке скорее заметит крупный глагол и путь.

Считайте каждую созданную агентом часть запроса недоверенным вводом действия, включая похожие на метаданные заголовки. Подстановка учетных данных вне модели снижает риск утечки секрета, но не делает операцию безобидной. Выбранные агентом путь, метод, заголовки, параметры и тело попадают в систему, которая может толковать их вместе.

Схема инструмента может уменьшить неоднозначность до создания запроса. Универсальный HTTP-инструмент со свободными заголовками дает вызывающему возможность запросить переопределение. Специализированный инструмент удаления может прямо назвать разрушительное действие и исключить такие поля. Его проще проверять, если реализация не оставляет скрытый выход для лишних заголовков.

Если универсальный инструмент необходим, разбирайте предложение до показа человеку. Парсер должен использовать тот же перечень и приоритет, что исполнитель. После подтверждения зафиксируйте нормализованное действие. Если посредник меняет заголовки позднее, включите его детерминированное преобразование в проверяемое действие либо разрешите запрос заново и запросите новое подтверждение.

Повторные попытки тоже важны. POST не становится безопасным или идемпотентным автоматически. RFC 9110 определяет его широко, и многие POST меняют данные без переопределения. Инструмент не может считать повтор безопасным только по внешнему POST и не должен считать каждый POST разрушительным. Нужны смысл маршрута и фактический метод.

Разделяйте тестовые и рабочие учетные записи. Слишком широкие права позволят всем пробам пройти и скроют различия. Используйте реальную ограниченную роль агента, подтвердите одинаковое решение для нативной и туннельной формы и запишите, что увидел человек. Ошибка доказана, когда предложение, показ, выполнение и состояние соединены в одну цепочку.

## Первый контроль должен классифицировать фактическое действие

Любое решение по методу должно приниматься после одного общего разрешающего компонента либо отклонять все переопределения. Это касается подтверждения, прав, CSRF, маршрутов, лимитов и аудита. Независимый разбор в каждом контроле гарантирует расхождения.

Представляйте результат явно:

```json
{
  "transport_method": "POST",
  "effective_method": "DELETE",
  "override_source": "header:x-http-method-override",
  "override_value": "DELETE",
  "target": "/v1/projects/42",
  "ambiguous": false
}
```

Компонент должен знать все заголовки и параметры, собрать значения и отклонить запрос с несколькими различными кандидатами. Переопределение следует принимать только от разрешенных внешних методов, обычно POST. Значение нужно сверять с намеренно поддерживаемым набором и выпускать неизменяемую запись для следующих контролей.

Проверку прав ставьте после разрешения и связывайте с маршрутом и фактическим методом. Вопрос «может ли эта учетная запись отправить POST сюда» слишком слаб, если POST служит туннелем. Спрашивайте, может ли она выполнить DELETE над этим ресурсом. Для нативной и туннельной формы применяйте одинаковые права.

Экран подтверждения должен сначала показывать последствие, а при расхождении обе формы. Надпись `DELETE /v1/projects/42 via POST override` передает действие и транспорт. Если спрятать заголовок в раскрываемой сырой записи, человеку придется искать опасность под давлением.

Если шлюз не способен повторить правила приложения, не учите его неполному набору. Отклоняйте любой известный сигнал до подтверждения или перенесите подтверждение после доверенной нормализации. Шлюз с одним заголовком перед приложением с тремя создает ложное ощущение защиты.

Отделяйте разрешение метода от бизнес-смысла. Компонент определяет DELETE, а маршрут решает, значит ли это архивирование, отзыв, отсоединение или стирание. Для действий с серьезными последствиями нужно описание маршрута. Нормализация метода обязательна, но один глагол не описывает все эффекты.

## Удаление одного заголовка не решает задачу

Удалять `X-HTTP-Method-Override` на прокси безопасно, только если сервис намеренно не поддерживает туннели. Прием популярен из-за простоты и блокирует первый пример. Он не работает, если остается другой заголовок, `_method`, прямой путь к приложению или более поздний компонент.

Руководство Microsoft по YARP называет три заголовка, сообщает об их передаче по умолчанию и предлагает преобразование для удаления, когда нужно закрыть обход. Это полезное усиление границы, но не доказательство игнорирования параметров приложением и не защита трафика в обход YARP.

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

Сначала проведите инвентаризацию. Ищите промежуточное ПО, обертки запросов, `_method`, три имени и присваивание метода. Проверьте группы маршрутов и старые модули совместимости, оставшиеся после обновлений.

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

Удаление поддержки способно сломать клиентов, поэтому его откладывают. Сначала измерьте присутствие сигналов без записи секретов и полных тел. Объявите прекращение, ясно отказывайте в тестах и одновременно удалите поддержку на сервере и правило на границе. Скрытая совместимость обходится дороже миграции оставшихся клиентов.

## В журнале нужны оба метода

Аудит должен хранить транспортный и фактический методы, источник, нормализованное значение, маршрут, учетную запись, цель, решение, ответ и результат. Без обоих методов нельзя объяснить переход POST к DELETE. Без результата виден только замысел.

Записывайте и неоднозначные отказы. Конфликт важен, даже если не дошел до обработчика. Храните имена и нормализованные токены, а не учетные данные и лишнее тело. Ограничивайте и экранируйте недопустимые значения, чтобы управляющие символы не подделали строки.

Связывайте слои идентификатором, созданным на первом доверенном участке. Внешний идентификатор нужно заменить или отделить пространством имен. Граница, разрешение, подтверждение, приложение и асинхронная задача должны нести один идентификатор.

Точно называйте поля. Общее `method` приглашает компоненты записывать разные смыслы. Используйте `transport_method` для строки и `effective_method` для действия. Осознанно сопоставляйте `originalMethod`, не считая его смысл одинаковым во всех фреймворках.

Sallyport выполняет HTTP-вызов агента, не раскрывая ему учетные данные, и записывает вызов в журнал Activity. Такое разделение не делает переопределение безопасным: до выполнения проверка все равно должна классифицировать фактический метод.

Защита от подмены важна после автономного запуска. Цепочка хешей делает редактирование заметным, но не восстанавливает отсутствующий смысл. Записывайте нормализованное действие в момент решения, а не пытайтесь позднее вывести его из журналов.

Панель должна показывать расхождения. Подсчет `transport_method != effective_method` по сервису и источнику обнаруживает неожиданный трафик совместимости. Оповещайте о новых источниках, неоднозначности и разрушительных фактических методах, если подтверждение сохранило только внешний.

## Регрессионный тест должен безопасно отказывать

Долговечное исправление требует контрактного теста по развернутому пути, который падает, если неподтвержденное переопределение меняет состояние. Модульный тест не обнаружит исчезнувшее правило прокси, новый порядок компонентов или другой вход.

Опишите ожидаемый результат каждого случая. Если туннель отключен, любой сигнал должен давать документированный 4xx без изменений. Если одно соглашение разрешено, его DELETE должен совпадать с нативным по правам, подтверждению, маршруту и аудиту. Конфликты и неизвестные токены должны отказывать до эффектов.

Включите такие утверждения:

- подтверждение называет фактический метод;
- нативная и туннельная формы получают одинаковые права;
- отклоненный ввод не меняет версию и счетчики эффектов;
- оба метода записаны под доверенным идентификатором;
- прямой доступ не обходит нормализацию.

Запускайте набор при изменениях прокси, фреймворка, аутентификации, подтверждения, маршрутов и агентских инструментов. Ночной запуск ловит дрейф, блокировка выпуска ловит намеренные изменения. Изолируйте ресурс от пользовательских данных.

Считайте новое соглашение изменением безопасности, даже если фреймворк называет его совместимостью. Нужны владелец, потребность, приоритет, внешний метод, набор фактических методов и условие удаления. Иначе одна строка настройки молча расширит словарь действий, который обязаны понимать контроли.

В чистой системе одно описание действия проходит от входа до результата. Если в сети написано POST, а приложение выполняет DELETE, разница должна быть видна до любого «да». Позже останется только восстанавливать происшествие.
