Основы HTTP для кибербезопасности
В этой статье я хочу разобрать ключевые основы сетевых технологий, которые считаю базой для работы в сфере кибербезопасности более подробно и с примерами. Также стоит уточнить что статья получилась довольно обширной, но я старался писать только ключевые и важные моменты.
Что ж, начнём с основ: сегодня большинство приложений, которыми мы пользуемся, постоянно взаимодействуют с интернетом - как веб-, так и мобильные. Большинство этих коммуникаций идёт через веб-запросы по протоколу HTTP (HyperText Transfer Protocol) - прикладному протоколу для доступа к ресурсам Всемирной паутины. Термин «гипертекст» означает текст, содержащий ссылки на другие ресурсы и легко интерпретируемый программами-клиентами.
Архитектура HTTP построена на модели клиент-сервер: клиент инициирует запрос, сервер обрабатывает его и возвращает ресурс. HTTP-коммуникация всегда включает эти две стороны: клиент запрашивает ресурс, сервер его отдаёт. Порт по умолчанию - 80, но конфигурация веб-сервера может задать любой другой.
Структура URL
URL предоставляет значительно больше возможностей, чем простое указание домена. Рассмотрим его компоненты:
Компонент |
Пример |
Назначение |
|---|---|---|
|
|
Протокол взаимодействия с сервером. Завершается разделителем |
|
|
Учётные данные для HTTP-аутентификации в формате |
|
|
Адрес сервера - доменное имя (FQDN) или IP-адрес |
|
|
Порт подключения. По умолчанию: |
|
|
Путь к ресурсу на сервере. При отсутствии запрашивается индексный файл (обычно |
|
|
Параметры запроса в формате |
|
|
Якорь для навигации внутри страницы. Обрабатывается браузером, на сервер не передаётся |
Обязательные компоненты - схема и хост. Остальные элементы опциональны.
Жизненный цикл HTTP-запроса
На схеме ниже показан обобщённый поток HTTP-запроса. Когда пользователь впервые вводит URL в браузере, тот обращается к DNS-серверу (системе доменных имён), чтобы разрешить домен и получить соответствующий IP-адрес.
Например, для домена example.com DNS может вернуть IP-адрес 198.51.100.42. DNS-сервер находит нужный IP и возвращает его клиенту. Любое доменное имя должно пройти такую процедуру разрешения, иначе сервер просто не узнает, куда отправлять ответ.

При обращении к веб-ресурсу происходит следующая последовательность (добавлены детали кэшей, TCP/TLS и HTTP):
Браузер отправляет DNS-запрос для разрешения доменного имени. Сначала проверяются локальный кэш браузера и ОС, затем файл
/etc/hosts, потом системный резолвер. Ответ кэшируется на время TTL. Вместо UDP/53 всё чаще применяется DoH/DoT (DNS поверх HTTPS/TLS).Получив IP, клиент устанавливает TCP-соединение с сервером (порт
80для HTTP или443для HTTPS): три шагаSYN->SYN-ACK->ACK.Если используется HTTPS, сразу после TCP идёт TLS-рукопожатие: клиент указывает домен через SNI, договаривается о протоколе (ALPN:
http/1.1,h2,h3) и проверяет сертификат сервера.Браузер отправляет HTTP-запрос (
GET / HTTP/1.1) с ключевыми заголовками:Host,User-Agent,Accept,Accept-Encoding,Cookie,Cache-Control. Для HTTPS эти данные уже передаются внутри зашифрованного канала.Ответ может прийти от ближайшего узла на пути: CDN/прокси/балансировщик или origin-сервер. Сервер возвращает статус-код (2xx/3xx/4xx/5xx), тело (например,
index.html), заголовки кэширования (Cache-Control,ETag,Last-Modified), редиректы,Set-Cookieи сжатие (Content-Encoding: gzip/br).Браузер обрабатывает ответ: рендерит HTML, загружает дополнительные ресурсы (CSS/JS/изображения/шрифты), может открывать несколько TCP/TLS-соединений или использовать мультиплексирование HTTP/2/3. Кэшированные ответы могут быть переиспользованы или валидированы (304 Not Modified).
Примечание о DNS: браузер сначала проверяет локальный файл /etc/hosts на наличие записи для домена. Это позволяет вручную задавать DNS-разрешение, добавляя строки в формате IP домен.
HTTPS: защищаем канал
HTTP удобен для наглядных примеров и отладки, но у него есть критичный минус: весь трафик передаётся в открытом виде. Любой, кто окажется между клиентом и сервером (публичный Wi-Fi, скомпрометированный роутер, злонамеренный провайдер), может устроить MITM-атаку (Man-in-the-Middle), подсмотреть логины, пароли, токены сессий и содержимое запросов. В лёгком варианте это заканчивается трекингом и сбором статистики, в тяжёлом - угоном аккаунтов и подменой ответов сервера. Именно эту проблему и решает переход на HTTPS.

С приходом HTTPS (по сути тот же HTTP, но поверх TLS) полезная нагрузка перестаёт быть читаемой: тело запросов и ответов шифруется сессионным ключом, и перехватчик видит лишь поток зашифрованных байтов, а не логины, пароли или JSON. В сниффере это выглядит как «каша» из символов в окне анализа потока - без ключей расшифровать такой трафик невозможно:

Один из очевидных признаков защищённого канала - префикс https:// в адресной строке и иконка замка (или другого индикатора безопасности) рядом с доменом. В большинстве современных браузеров по клику на эту иконку открывается всплывающее окно со сведениями о сертификате, уровне шифрования и разрешениях сайта; если с сертификатом что-то не так, здесь же появятся предупреждения.
Небольшой нюанс: даже при HTTPS раскрывается домен, если DNS-запросы идут без шифрования. В боевых сетях лучше включить DoH/DoT или завернуть трафик в VPN, чтобы не светить, что именно вы смотрите.
Поток HTTPS

Если вбить http:// для сайта, который на самом деле ожидает HTTPS, браузер сначала делает обычный HTTP-запрос на порт 80. Сервер отвечает редиректом 301 Moved Permanently (иногда 308 Permanent Redirect) на тот же ресурс, но уже по https:// и порту 443. После этого начинается TLS-рукопожатие: клиент отправляет Client Hello с поддерживаемыми версиями и наборами шифров, сервер отвечает Server Hello и сертификатом, стороны обмениваются ключами и проверяют валидность цепочки. Лишь после успешного завершения рукопожатия поверх этого зашифрованного канала начинает работать обычный HTTP - уже в виде HTTPS.
Злоумышленник теоретически может попытаться провести атаку понижения и подменить HTTPS на HTTP через свой прокси, заставляя клиента остаться на незащищённой схеме. Но современные браузеры и серверы стараются этому противодействовать: HSTS, preload-списки, отключение старых версий TLS и жёсткие политики шифров сильно усложняют реализацию таких атак.
Анатомия HTTP-запроса и ответа
Чтобы понимать, что именно уходит и приходит по сети, удобно разобрать сырые пакеты. Вот пример запроса GET:

Первая строка состоит из трёх полей, разделённых пробелами:
Поле |
Пример |
Что значит |
|---|---|---|
Метод |
|
Глагол, описывающий действие (получить, отправить, удалить…). |
Путь |
|
Конкретный ресурс; сюда же попадает query вида |
Версия |
|
Версия протокола, которую ожидает сервер. |
Дальше идут пары «заголовок: значение» (Host, User-Agent, Cookie, Accept и десятки других). Заголовки закрываются пустой строкой, после которой может идти тело запроса (форма, JSON, файл).
Если при брутфорсе или тестах что-то идёт не так, сервер часто отвечает 400 Bad Request или вообще молчит - и не всегда понятно, в чём причина. По моему опыту, одна из самых частых ошибок - отсутствие пустой строки (\r\n\r\n) между заголовками и телом запроса. Без неё сервер не может понять, где заканчиваются метаданные и начинаются данные. Ещё частые проблемы: лишние пробелы в заголовках, неправильная кодировка спецсимволов в URL или битый Content-Length. Если запрос молча отваливается - первым делом проверяю структуру в hex-редакторе или через curl -v, чтобы увидеть, что реально уходит на сервер.
HTTP-ответ строится аналогично:
Первая строка - версия протокола и статус (200 OK, 404 Not Found и т.п.). Затем идут заголовки (Date, Server, Set-Cookie, Content-Type, длина тела). После пустой строки - само тело: HTML, JSON, изображение, PDF или любой другой контент, на который настроен сервер.
Небольшая деталь про версии: HTTP/1.x - это читаемый текст, поля разделяются \r\n. HTTP/2 уже бинарный и по проводам выглядит иначе, хотя логика запрос/ответ сохраняется.
Для пентестера это важно: в Wireshark HTTP/2 и HTTP/3 нельзя просто прочитать как текст - нужен декодер. Зато Burp Suite и mitmproxy автоматически разбирают бинарный формат и показывают запросы в привычном виде. Ещё один нюанс - мультиплексирование: в HTTP/2 несколько запросов идут по одному соединению параллельно, что усложняет анализ тайминга и race condition атак. HTTP/3 работает поверх QUIC (UDP), что добавляет свои особенности: соединение сложнее перехватить и проксировать.
HTTP-методы и коды состояния
Метод в первой строке запроса говорит серверу, что именно мы хотим сделать, а код в ответе показывает, как сервер обработал попытку. В curl -v метод видно сразу (GET / HTTP/1.1), а в браузерных DevTools это колонка Method.
Методы запросов
Самые часто встречающиеся:
Метод |
Что делает |
На что смотреть при тесте |
|---|---|---|
|
Получить ресурс. Параметры в query ( |
IDOR через подмену ID в параметрах, утечка данных в URL и логах, обход авторизации. |
|
Отправить данные в теле: текст, JSON, файлы. Формы, логины, загрузки. |
Инъекции (SQL, XSS), отсутствие CSRF-токена, mass assignment, загрузка shell'ов. |
|
Только заголовки без тела. Разведка перед скачиванием. |
Утечка информации в заголовках, обход проверок размера контента. |
|
Создать или заменить ресурс целиком. Идемпотентен. |
Произвольная запись файлов, перезапись конфигов, загрузка без авторизации. |
|
Удалить ресурс. Идемпотентен. |
IDOR на удаление чужих данных, DoS через массовое удаление. |
|
Какие методы поддерживает сервер. Возвращает CORS и |
Разведка доступных методов, неправильная CORS-конфигурация. |
|
Частичные изменения ресурса. Не идемпотентен. |
Mass assignment, изменение чужих данных, privilege escalation. |
Методы зависят от конфигурации приложения. В REST-API чаще всего встречается четвёрка GET/POST/PUT/DELETE, но на тестах я всегда пробую OPTIONS, чтобы быстро понять, что вообще разрешено.
Коды ответа
Классы HTTP-кодов показывают общий результат:
Класс |
Что означает |
|---|---|
|
Информируют о ходе обработки. |
|
Запрос успешно выполнен. |
|
Клиента просят перейти по другому адресу. |
|
Проблема на стороне запроса (не тот URL, формат, права). |
|
Сервер сам не смог обработать запрос. |
Примеры по классам:
Код |
Комментарий |
|---|---|
|
Запрос выполнен успешно, тело обычно содержит ресурс. |
|
Временный редирект, браузер перейдёт на новый URL. |
|
Неверный запрос: например, не хватает перевода строки. |
|
Нет доступа; иногда причина в WAF, блокирующем подозрительный запрос. |
|
Ресурс отсутствует. |
|
Внутренняя ошибка сервера - стоит исследовать подробнее. |
Когда я получаю код 403/401, сразу сверяю, нет ли жёсткой привязки к заголовкам или методу. Иногда достаточно сменить GET на POST или добавить X-Forwarded-For, и ресурс внезапно открывается.
Несколько кодов особенно интересны при тестировании:
401vs403- если сервер отдаёт401на несуществующий ресурс и403на существующий, это позволяет энумерировать скрытые эндпоинты.500 Internal Server Error- часто сопровождается stack trace или отладочной информацией. Смотри тело ответа: там могут быть пути к файлам, имена таблиц, версии фреймворков.302/301на страницу логина - выдаёт защищённые эндпоинты. Если/adminредиректит на/login, значит/adminсуществует.405 Method Not Allowed- сервер говорит, что метод не поддерживается, но сам эндпоинт есть. Пробуй другие методы.429 Too Many Requests- rate limiting. Полезно знать порог для брутфорса и обхода.
cURL: быстрая разведка из терминала
Для ручных проверок и автоматизации удобно использовать cURL (client URL) - CLI-утилиту и библиотеку, которая помимо HTTP понимает кучу других протоколов. Её плюсы для пентестера: скриптуемость, контроль над заголовками и возможность быстро сравнить ответы без полноценного браузера. Плюс через ключи легко подложить куки/токены или прятаться за прокси, мгновенно сверяя различия в ответах.
Базовый запрос:
curl example.com
Загрузка страницы или файла с сохранением имени удалённого ресурса:
curl -O example.com/index.html
Тихий режим, чтобы не видеть прогресс-бар и служебные сообщения:
curl -s -O example.com/index.html
Пара опций, которые часто выручают:
-i- включить заголовки ответа;-v- подробный разбор запроса/ответа (удобно для отладки);-o <file>- явно указать имя выходного файла;-u user:pass- передать простую HTTP-аутентификацию.
Когда нужно видеть весь протокол, добавляйте -v:
curl example.com -v
Вывод покажет, что именно отправил клиент (GET / HTTP/1.1, Host, User-Agent) и что вернул сервер (строка статуса, заголовки, тело). Если приходит 401 Unauthorized или редирект, это заметно сразу. Тройной verbose (-vvv) дополнительно раскрывает TLS-детали и мелкие нюансы соединения.
Для полного списка ключей можно вызвать curl --help all или почитать man curl. Даже краткое знакомство с ними заметно ускоряет рутину при проверке веб-ресурсов.
HTTPS и cURL
При работе по HTTPS curl сам проводит TLS-рукопожатие и проверяет сертификаты. Если цепочка подписи битая или используется самоподписанный сертификат, утилита оборвёт соединение - это нормальная защита от MITM:
user@linux:~$ curl https://example.com
curl: (60) SSL certificate problem: Invalid certificate chain
More details here: https://curl.haxx.se/docs/sslcerts.html
...SNIP...
Браузеры ведут себя так же и ругаются на недоверенные сертификаты. В учебных лабах или при тестировании локального сервиса можно временно игнорировать проверку флагом -k:
user@linux:~$ curl -k https://www.example.com
<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML 2.0//EN">
<html><head>
...SNIP...
DevTools: быстрый аудит из браузера
Практически в каждом современном браузере есть инструменты разработчика. В Chrome/Firefox их можно открыть CTRL+SHIFT+I или F12, и для сетевых задач нам нужна вкладка Network.
При обновлении страницы там появится список запросов: статус ответа, метод, домен, путь и размер. Если страница тянет сотни ресурсов, помогает поле Filter - по нему легко отыскать интересующий URL или тип контента.
Кликнув по запросу, можно посмотреть вкладки Headers, Response и Cookies. В Response есть переключатель на сырой вид, чтобы увидеть исходный HTML/JSON без рендера. Это почти бесплатный аналог сниффера, который всегда под рукой.
Киллер-фича для пентестера - Copy as cURL. Правый клик на любом запросе -> Copy -> Copy as cURL - и в буфере окажется готовая команда со всеми заголовками, cookie и телом. Вставляешь в терминал, меняешь параметры, отправляешь - идеально для быстрого тестирования без настройки прокси. В Chrome также есть Copy as Fetch для воспроизведения запроса прямо в консоли браузера.
Ещё полезные трюки:
Preserve log- сохранять запросы при переходах между страницами (иначе список очищается);Disable cache- отключить кэш, чтобы видеть реальные запросы к серверу;фильтр
method:POSTилиstatus-code:500- быстро найти интересные запросы в большом потоке.
GET-запросы на практике
С теорией HTTP мы уже разобрались, но интересно посмотреть, как выглядит самый базовый сценарий - обычный переход по ссылке - если смотреть глазами пентестера или разработчика.
Что делает браузер, когда мы просто открываем страницу
Когда мы вбиваем адрес в строку браузера и жмём Enter, клиент отправляет к серверу GET-запрос: просит отдать HTML-документ, а затем по мере разбора кода страницы догружает скрипты, стили, шрифты, картинки и запросы к API - тоже чаще всего через GET или POST.
Это хорошо видно во вкладке Network: один наш клик превращается в целый «веер» запросов к разным URL. Такой взгляд особенно полезен при аудите и багбаунти: становится понятно, какие именно эндпоинты на самом деле существуют у приложения и какие параметры оно принимает.
Пример: Basic Auth на уровне веб-сервера
Иногда доступ к разделу ограничивают не формой логина на самой странице, а встроенной аутентификацией веб-сервера - HTTP Basic Auth. Браузер в таком случае показывает системное окошко «Введите логин/пароль», а проверку учётки делает уже не приложение, а, например, Apache или nginx.
Представим, что по адресу /reports/ лежит внутренняя админка и веб-сервер защищает её Basic Auth'ом. Если обратиться к ней без авторизации, мы увидим типичную картину:
user@linux:~$ curl -i https://intranet.example.com/reports/
HTTP/1.1 401 Unauthorized
Date: Mon, 01 Dec 2025 10:00:00 GMT
Server: Apache
WWW-Authenticate: Basic realm="Internal reports"
Content-Length: 16
Content-Type: text/plain; charset=utf-8
Authorization required
код
401говорит, что доступ запрещён без аутентификации;заголовок
WWW-Authenticateподсказывает клиенту, что сервер ожидает Basic Auth и как называется защищаемая область (realm).
Теперь отправим тот же запрос, но уже с логином и паролем, например security:Winter2025!:
user@linux:~$ curl -u security:Winter2025! https://intranet.example.com/reports/
<!DOCTYPE html>
<html lang="en">
<head>
...SNIP...
На этот раз доступ есть. Если добавить флаг -v, станет видно, какой заголовок на самом деле уходит к серверу:
user@linux:~$ curl -v -u security:Winter2025! https://intranet.example.com/reports/ 2>&1 | sed -n '1,15p'
> GET /reports/ HTTP/1.1
> Host: intranet.example.com
> Authorization: Basic c2VjdXJpdHk6V2ludGVyMjAyNSE=
> User-Agent: curl/8.5.0
> Accept: */*
Строка после Basic - это просто base64 от security:Winter2025!. Никакого шифрования здесь нет, поэтому Basic Auth имеет смысл только поверх HTTPS.
Тот же эффект можно получить, если задать заголовок вручную:
user@linux:~$ curl -H 'Authorization: Basic c2VjdXJpdHk6V2ludGVyMjAyNSE=' https://intranet.example.com/reports/
GET-параметры и работа с API
Теперь посмотрим на типичный сценарий с GET-параметрами. Допустим, после логина в админке есть строка поиска по отчётам. Пользователь вводит текст, а фронтенд в ответ делает запрос к API, например:
https://intranet.example.com/api/reports?query=fraud&limit=5
Всё, что идёт после ?, - это query string (строка запроса). Здесь есть два параметра:
query=fraud- текст поиска;limit=5- максимальное количество результатов.
Если открыть Network во время поиска, мы увидим этот запрос целиком и сможем воспроизвести его вручную:
user@linux:~$ curl 'https://intranet.example.com/api/reports?query=fraud&limit=5' \
-H 'Authorization: Basic c2VjdXJpdHk6V2ludGVyMjAyNSE='
Сервер вернёт JSON с результатами, и дальше с этим можно работать уже не через браузер, а как угодно: сохранять ответы, перебирать параметры, писать маленькие скрипты для массового тестирования.
POST
В прошлом разделе мы смотрели на то, как GET используется для получения страниц и выполнения простых запросов к API. Но как только нужно передать логин/пароль, загрузить файл или просто убрать чувствительные параметры из URL, в дело почти всегда вступает POST.
Ключевое отличие простое:
GETпередаёт параметры в строке запроса (?param=valueв URL);POSTскладывает данные в тело HTTP-запроса.
У этого есть несколько приятных эффектов для разработчика и безопасника:
параметры и файлы не светятся в логах веб-сервера и истории браузера как часть URL;
тело может быть бинарным (загрузка файлов), кодировать нужно только служебные разделители;
ограничение на длину URL (часто ~2000 символов) больше не мешает передавать большие структуры данных.
Ниже разберём живые сценарии: формы логина, сессионные cookie и JSON-запросы к API.
Формы логина (Login Forms)
В отличие от Basic Auth, где браузер сам рисует системное окно ввода логина/пароля и шлёт заголовок Authorization, в реальных приложениях чаще всего используются обычные HTML-формы. Пользователь вводит логин/пароль, жмёт кнопку - фронтенд отправляет POST на эндпоинт авторизации, например /login.php или /auth.
Типичный пример тела запроса, который можно увидеть во вкладке Network -> Request:
username=analyst&password=LabPass123%21
То же самое легко воспроизвести вручную через curl:
user@linux:~$ curl -X POST -d 'username=analyst&password=LabPass123!' https://portal.example.com/login.php
<!DOCTYPE html>
<html lang="en">
<head>
<title>Analytics Portal</title>
...SNIP...
Опции здесь важны:
-X POST- явно задаём метод (во многих случаяхcurlсам поймёт, что нужен POST, как только видит-d, но явное объявление полезно для читаемости);-d- данные формы в форматеkey=value&key2=value2(по умолчаниюcurlотправляет их сContent-Type: application/x-www-form-urlencoded).
Многие формы после успешного логина делают редирект, например с /login.php на /dashboard. Чтобы не ловить только ответ с кодом 302, удобно добавить -L - тогда curl сам пойдёт по цепочке редиректов:
user@linux:~$ curl -L -X POST -d 'username=analyst&password=LabPass123!' https://portal.example.com/login.php
Cookie после авторизации
Почти любое современное веб-приложение после логина выдаёт клиенту сессионную cookie. Именно по ней сервер дальше понимает, что запросы идут от авторизованного пользователя, а не от гостя.
Посмотрим на ответ того же запроса, если добавить -i, чтобы увидеть заголовки:
user@linux:~$ curl -i -X POST -d 'username=analyst&password=LabPass123!' https://portal.example.com/login.php
HTTP/1.1 302 Found
Date: Mon, 01 Dec 2025 12:00:00 GMT
Server: nginx
Set-Cookie: SESSIONID=abf1329e7d904c9fa3c4c1b8f8c21d3a; Path=/; HttpOnly; Secure
Location: /dashboard
Content-Length: 0
Здесь нас интересует строка:
Set-Cookie: SESSIONID=abf1329e7d904c9fa3c4c1b8f8c21d3a; Path=/; HttpOnly; Secure
Сервер говорит: «С этого момента все запросы с cookie SESSIONID=... считаются привязанными к залогиненной сессии». Мы можем взять это значение и использовать его в дальнейших запросах, не проходя логин каждый раз.
Передать cookie curl-у можно двумя способами:
Через -b:
user@linux:~$ curl -b 'SESSIONID=abf1329e7d904c9fa3c4c1b8f8c21d3a' https://portal.example.com/dashboard
Или через явный заголовок:
user@linux:~$ curl -H 'Cookie: SESSIONID=abf1329e7d904c9fa3c4c1b8f8c21d3a' https://portal.example.com/dashboard
В браузере то же самое можно повторить вручную: открыть DevTools, вкладку Storage / Application -> Cookies, подставить своё значение SESSIONID и обновить страницу. Если cookie валидна, вы сразу окажетесь в авторизованной части интерфейса.
Важный момент для безопасности: браузер автоматически отправляет cookie на соответствующий домен при каждом запросе. Это удобно, но создаёт угрозу CSRF (Cross-Site Request Forgery) - злоумышленник может заставить браузер жертвы отправить запрос на уязвимый сайт, и cookie подставится автоматически. Именно поэтому формы защищают CSRF-токенами, а cookie помечают флагом SameSite. Но это тема для отдельной статьи.
POST + JSON: работа с API
Формы - это классика, но всё чаще фронтенд общается с бекендом напрямую через JSON-API. В этом случае тело POST-запроса - уже не form-url-encoded, а структурированный JSON, а заголовок Content-Type меняется на application/json.
Представим, что на нашем портале есть поиск по городам, и фронтенд при каждом вводе символа отправляет запрос к /api/cities:
POST /api/cities HTTP/1.1
Host: portal.example.com
Content-Type: application/json
Cookie: SESSIONID=abf1329e7d904c9fa3c4c1b8f8c21d3a
{"q":"london","limit":5}
Такой запрос легко повторить вручную:
user@linux:~$ curl -X POST \
-H 'Content-Type: application/json' \
-H 'Cookie: SESSIONID=abf1329e7d904c9fa3c4c1b8f8c21d3a' \
-d '{"q":"london","limit":5}' \
https://portal.example.com/api/cities
["London (UK)","London (CA)","London (NZ)"]
Здесь важны три момента:
тело - корректный JSON (кавычки двойные, без лишних запятых);
Content-Type: application/jsonговорит серверу, как парсить тело;cookie по-прежнему отвечает за авторизацию: без неё backend может вернуть
401или пустой ответ.
В DevTools такой запрос можно посмотреть и скопировать как Copy -> Copy as Fetch, а затем в консоли браузера поиграться с параметрами прямо на лету:
fetch('https://portal.example.com/api/cities', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
credentials: 'include',
body: JSON.stringify({ q: 'os', limit: 10 }),
}).then(r => r.json()).then(console.log);
Заключение
Мы разобрали одни из основ сетевых протоколов - HTTP и немного затронули HTTPS. Спасибо всем, кто дочитал, и удачи в дальнейшем обучении или практике в кибербезопасности!