Веб как разговор двух сторон
Любое веб-приложение — это диалог двух участников. Клиент (браузер, мобильное приложение, скрипт, другой сервис) формулирует запрос, сервер его обрабатывает и возвращает ответ. Между ними — протокол HTTP: набор правил, как именно оформить вопрос и как понять ответ. Важно сразу усвоить: сервер не видит "пользователя" — он видит только байты, которые пришли по сети. Кто их прислал и можно ли им верить, сервер сам по себе не знает.
Аналогия: сервер — это окошко выдачи на складе. Сотрудник за окошком не видит человека целиком, он видит только бланк-заявку, который ему просунули. Если в бланке написано "выдать товар со стеллажа №7", сотрудник пойдёт к стеллажу №7 — даже если по правилам этому клиенту туда нельзя. Вся защита держится на том, проверяет ли сотрудник бланк, или слепо его исполняет.
Из чего состоит HTTP-запрос
Запрос состоит из нескольких частей, и каждая — потенциальный канал данных от клиента:
- Строка запроса: метод (
GET,POST,PUT,DELETE), путь и query-параметры — напримерGET /search?q=книга. - Заголовки (headers):
Host,User-Agent,Cookie,Authorization,Content-Typeи десятки других. - Тело (body): данные форм, JSON, загружаемые файлы — всё, что не помещается в строку.
Ответ сервера зеркально устроен: строка статуса (200 OK, 404 Not Found, 500), заголовки (Set-Cookie, Content-Type, заголовки безопасности) и тело — HTML, JSON, картинка.
Stateless и роль состояния
HTTP по природе stateless: каждый запрос самодостаточен, сервер не помнит предыдущий. Чтобы узнать вас между запросами, приложение выдаёт сессионную куку или токен — и именно поэтому куки и заголовки авторизации становятся лакомой целью.
Типичная ошибка: считать, что раз UI скрыл кнопку или поле, то и соответствующего действия на сервере не существует. UI — это лишь удобная обёртка; реальный контракт — HTTP-запрос, и его можно отправить вручную в обход любого интерфейса.
Мини-итог: веб — это обмен HTTP-сообщениями между не доверяющими друг другу сторонами; каждая часть запроса (метод, путь, query, заголовки, куки, тело) — это данные, которые приходят извне и которым по умолчанию верить нельзя.
Почему уязвимости вообще существуют
Если свести к одному предложению причину подавляющего большинства веб-уязвимостей, она звучит так: приложение доверяет данным, которые пришли извне, и обращается с ними как со своими собственными командами или фактами. Инъекции, обход доступа, XSS, SSRF, небезопасная десериализация — все они вырастают из одного корня: недоверенный ввод смешивается с доверенной логикой без разделения границы.
Аналогия: представьте редактора, который верстает газету и зачем-то ставит в номер любую записку, просунутую под дверь, как официальную статью. Пока записки безобидны — всё хорошо. Но однажды кто-то подсунет записку "уволить главреда", и она выйдет в печать как редакционное решение. Проблема не в злой записке — проблема в том, что редактор не отделяет свой текст от чужого.
Что значит "недоверенный ввод"
Недоверенным считается любой источник, который контролируется не вами:
- значения форм и query-параметров;
- заголовки запроса, включая
Host,Referer,User-Agent; - содержимое и имена загружаемых файлов;
- тело JSON/XML, в том числе вложенные поля;
- данные из внешних API и даже из собственной БД, если туда раньше попал чужой ввод (это называют "вторичной" или stored-инъекцией).
Ключевая ловушка — думать, что клиентская валидация что-то защищает. JavaScript в браузере проверяет ввод для удобства пользователя, но злоумышленник просто не выполняет ваш JS: он шлёт запрос напрямую. Любая проверка, которая существует только на клиенте, для безопасности не существует вовсе.
Симптом против первопричины
Важно различать симптом и причину. "На странице сработал чужой скрипт" — симптом; первопричина — "ввод попал в HTML без кодирования". "Из БД утекли чужие заказы" — симптом; причина — "сервер не проверил, что объект принадлежит этому пользователю". Лечить надо причину: чинить симптом точечно (заблокировать одну нагрузку) бесполезно — найдётся следующая.
Типичная ошибка: фильтровать "плохие слова" (чёрные списки вроде запрета <script>). Чёрные списки всегда обходятся вариациями кодировки и регистра; защищает не запрет конкретных строк, а правильная обработка данных в точке использования — параметризация, кодирование вывода, проверка прав.
Мини-итог: первопричина веб-уязвимостей — смешение недоверенного ввода с доверенной логикой; недоверенным является всё, что приходит по сети, а единственная надёжная защита строится на сервере, в точке, где данные используются.
Что такое поверхность атаки
Поверхность атаки — это совокупность всех точек, через которые внешние данные могут попасть в приложение и повлиять на его поведение. Чем больше таких точек и чем хуже они контролируются, тем выше риск. Задача тестировщика на первом этапе — не атаковать, а составить карту: где приложение принимает ввод, что с этим вводом потом происходит и кому он доверяет.
Аналогия: поверхность атаки — это все двери, окна, вентиляция и почтовый ящик здания. Грабитель ищет не самую крепкую дверь, а самое слабое окно. Поэтому инвентаризация всех входов важнее, чем укрепление одного парадного входа.
Основные каналы ввода
- Формы — логин, регистрация, поиск, профиль, оплата. Классический источник инъекций и XSS.
- Query-параметры — всё после
?в URL; часто используются для идентификаторов объектов (привет, обход доступа) и для отражённого XSS. - HTTP-заголовки —
Host(Host header injection),Referer,User-Agent, кастомныеX--заголовки; их легко забыть провалидировать, потому что в форме их не видно. - Куки — сессии, настройки, иногда сериализованные объекты; подделка или подмена куки бьёт по авторизации.
- API-эндпоинты — REST/GraphQL принимают JSON и часто скрывают больше полей, чем показывает UI (массовое присваивание, mass assignment).
- Загрузка файлов — имя файла, MIME-тип, содержимое, путь сохранения; ведёт к веб-шеллам, path traversal, XXE при разборе документов.
Не только вход, но и движение данных
Поверхность атаки — это не только входы, но и маршруты: куда ввод течёт дальше. Один и тот же параметр опасен по-разному в зависимости от назначения — в SQL-запросе это инъекция, в HTML это XSS, в имени файла это traversal, в URL для внутреннего запроса это SSRF. Поэтому в карте важно фиксировать пары "источник → приёмник" (source → sink).
Типичная ошибка: проверять только то, что видно в браузере. Скрытые поля, недокументированные параметры, заголовки и "служебные" API-методы — полноценная часть поверхности и часто наименее защищённая, потому что разработчик о них при защите не вспомнил.
Мини-итог: поверхность атаки — это все точки входа недоверенных данных (формы, query, заголовки, куки, API, загрузки) и маршруты их движения к опасным приёмникам; грамотный анализ начинается с инвентаризации этих точек, а не с эксплойта.
Пользователь (или атакующий) формирует запрос: вводит данные в форму, меняет URL, подделывает заголовок. На этом шаге данные полностью под контролем клиента — доверять им нельзя.