Прескочи към съдържанието
Урок 01Beginner

Как работи HTTP

Заявки, отговори, методи и статус кодове — протоколът, на който е написана всяка уеб атака.

Всяко едно нещо, което ще правиш в този курс — всяка инжекция, всяка открадната сесия, всяка подправена заявка — се случва вътре в HTTP. Ако протоколът ти е мъгляв, атаките ще изглеждат като магически заклинания за наизустяване. Ако е ясен, те стават очевидни следствия от това как работи нещото.

Затова нека го изясним.

Целият протокол в един обмен

HTTP е протокол заявка/отговор, обикновено върху TCP. Клиентът изпраща блок текст, сървърът връща блок текст — и това е по същество целият договор. Ето истинска заявка, написана така, както реално тръгва по мрежата:

GET /account/settings HTTP/1.1
Host: app.example.com
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64)
Accept: text/html,application/xhtml+xml
Accept-Language: en-GB,en;q=0.9
Cookie: session=aG9sZGluZy1hLXNlc3Npb24taWQ
Referer: https://app.example.com/account
Connection: keep-alive

Четири неща за забелязване, защото и четирите се злоупотребяват по-късно:

  1. Редът на заявката — метод, път, версия на протокола. Пътят включва query string, затова ?id=7 е част от реда на заявката, не отделно поле.
  2. Заглавки (headers) — двойки ключ/стойност, по една на ред, с нечувствителни към регистъра ключове.
  3. Празен ред — разделителят между заглавки и тяло. Това е по-важно, отколкото изглежда: несъгласието къде свършват заглавките и започва тялото е цялата основа на request smuggling.
  4. Тялото — тук липсва, защото GET заявките обикновено нямат такова.

Отговорът има същата форма, но със статус ред вместо ред на заявка:

HTTP/1.1 200 OK
Content-Type: text/html; charset=utf-8
Content-Length: 1829
Set-Cookie: session=bmV3LXNlc3Npb24taWQ; HttpOnly; Secure; SameSite=Lax
Cache-Control: no-store
X-Frame-Options: DENY
 
<!doctype html><html>...

Как работи

Проследи какво става, когато напишеш app.example.com/account и натиснеш Enter.

  1. DNS резолюция. Браузърът превръща app.example.com в IP адрес. Тук не се случва нищо удостоверено, затова DNS е толкова сочна цел.
  2. TCP връзка. Трипосочно ръкостискане към порт 443.
  3. TLS ръкостискане. Разменят се и се проверяват сертификати и се договаря симетричен ключ. Оттук нататък HTTP байтовете са криптирани по пътя. Забележи тези думи внимателно: TLS защитава канала, не приложението.
  4. Заявката е изпратена. Браузърът записва текста на заявката, описан по-горе, в криптирания тунел.
  5. Сървърът я обработва. Маршрутизиране, удостоверяване, оторизация, заявки към базата, рендиране на шаблони. Това е слоят, който ще атакуваш през останалата част от курса.
  6. Отговорът е изпратен. Статус ред, заглавки, тяло.
  7. Браузърът рендира. Разбира HTML-а и после прави още заявки — за всеки стил, скрипт, изображение и шрифт, които намери. Всяка е нова HTTP заявка, носеща същите бисквитки.

Последната точка заслужава момент. Едно зареждане на страница не е една заявка; то са десетки, изстреляни автоматично, всяка удостоверена с каквито бисквитки браузърът реши да прикачи. Браузърът е изключително ревностен HTTP клиент, който ще праща удостоверени заявки от името на всяка страница, която случайно зареди. Добра половина от уеб сигурността е за обуздаване на тази ревностност.

Браузър Сървър |-- GET /account ----------------->| Cookie: session=... |<-- 200 OK (HTML) ----------------| |-- GET /app.css ----------------->| Cookie: session=... |-- GET /app.js ------------------>| Cookie: session=... |-- GET /avatar.png -------------->| Cookie: session=... |-- POST /api/telemetry ---------->| Cookie: session=...

Едно зареждане на страница, много удостоверени заявки

Методите и лъжата за тях

Ще чуеш, че GET чете, а POST пише. Приемай това като обществена конвенция, не като техническа гаранция.

МетодПредназначениеБезопасен?Идемпотентен?
GETВзема ресурсДаДа
HEADСамо заглавки, без тялоДаДа
POSTИзпраща данни, създаваНеНе
PUTЗаменя ресурс изцялоНеДа
PATCHЧастично променяНеНе
DELETEПремахва ресурсНеДа
OPTIONSПита какво е позволеноДаДа

Безопасен значи, че заявката не би трябвало да променя състоянието на сървъра. Идемпотентен значи, че изпращането ѝ пет пъти има същия ефект като изпращането ѝ веднъж.

Нищо в HTTP не налага нищо от това. GET /account/delete?confirm=true е напълно валидна заявка и приложения постоянно пускат крайни точки точно като нея. Когато го правят, атакуващият може да задейства действието само с това на страница, която посещаваш:

<img src="https://app.example.com/account/delete?confirm=true" width="1" height="1">

Защото, както току-що установихме, браузърът с радост ще изстреля тази заявка с прикачените ти бисквитки. Това е CSRF и започва с разработчик, който третира конвенцията като контрол.

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

Статус кодове, които наистина ти казват нещо

Да ги наизустиш всичките е безсмислено. Да знаеш класовете и шепата, която изтича информация, не е.

  • 2xx — проработи. 200 OK, 201 Created, 204 No Content.
  • 3xx — търси другаде. 301 постоянно, 302 и 307 временно, 304 Not Modified.
  • 4xx — ти направи нещо грешно. 400, 401, 403, 404, 429.
  • 5xx — сървърът направи нещо грешно. 500, 502, 503.

Интересните за атакуващия са двойките, които би трябвало да са неразличими, но не са.

  • 401 Unauthorized значи не си удостоверен. 403 Forbidden значи знаем кой си и пак не можеш. Крайна точка, която връща 403 вместо 404 за запис, който не е твой, току-що потвърди, че записът съществува.
  • Форма за вход, която връща различно време за отговор, дължина на тялото или съобщение за валидно потребителско име с грешна парола спрямо потребителско име, което не съществува, е оракул за изброяване на потребители. Ще построиш такъв нарочно в по-късен модул.
  • 500 със стек трейс в тялото ти казва фреймуърка, ORM-а, често пътищата до файловете на диска и понякога точната SQL заявка, която току-що счупи. Многословните грешки са клас уязвимост сами по себе си.

Защо е важно

Безсъстоятелният, текстов, вярвай-на-клиента дизайн на HTTP е първопричината за повечето от онова, което следва.

Той е безсъстоятелен. Сървърът няма памет за предходната ти заявка. Всяка заявка трябва да носи собствено доказателство за самоличност, затова съществуват бисквитките и токените — и затова открадването на такъв е равносилно на това да станеш ти. В самия HTTP няма „изход отвсякъде“, а само каквото приложението добави отгоре.

Всичко е под контрола на атакуващия. Заглавки, път, query string, тяло, бисквитки: всичко това е просто текст, който клиентът ти е избрал да прати. User-Agent, Referer, X-Forwarded-For — разработчик, който вярва на някое от тях за решение по сигурността, ти е подал ключовете. „Заявката дойде от админ панела ни, проверихме заглавката Referer“ е изречение, което обикновено предшества пробив.

Съчетава се зле с услужливи посредници. Истинският трафик минава през CDN-и, балансьори на натоварване, обратни проксита и кешове. Когато два от тях не се съгласят къде свършва едната заявка и започва следващата — защото единият зачита Content-Length, а другият Transfer-Encoding — получаваш request smuggling и заявката на един потребител се зашива към връзката на друг.

През 2017 г. Equifax загуби лични данни на около 147 милиона души. Входната точка беше уязвимост в Apache Struts, задействана от злонамерена стойност в заглавката Content-Type на иначе обикновена HTTP заявка. Не хитро криптографско разбиване. Заглавка, прочетена като команда.

Това е урокът на целия този модул: HTTP е текстов протокол, в който всеки байт е под контрола на атакуващия, а сигурност е само онова, което приложението наистина си прави труда да провери.

Провери знанията си

2 от 3 за успех

3 въпроса върху това, което току-що прочете. Веднага ще виждаш дали всеки отговор е верен, с кратко обяснение. Дай 2 верни, за да отбележиш урока като завършен.

Не си влязъл, затова този опит няма да бъде запазен. Създай акаунт за да следиш напредъка и да отключваш по-късните модули.