URL, encoding и безопасность на уровне веба
В вебе безопасность начинается с простого правила: не доверяй пользовательскому вводу. Никогда. См. также общие основы аутентификации.
URL encoding (процентное кодирование)
В URL некоторые символы имеют специальное значение: & разделяет параметры, ? начинает query-строку, # - фрагмент. Если эти символы нужны в данных - их кодируют:
Пробел → %20 (или + в query-строке)
@ → %40
& → %26
# → %23
/ → %2F
? → %3F
= → %3D
// JavaScript
encodeURIComponent('hello world & stuff')
// → "hello%20world%20%26%20stuff"
decodeURIComponent('hello%20world')
// → "hello world"
XSS (Cross-Site Scripting)
XSS - атака, при которой злоумышленник вставляет JavaScript-код через пользовательский ввод. Если сервер или клиент выводит данные без экранирования - код выполнится в браузере жертвы.
Как это работает
Пользователь вводит в поле «Имя»:
<img src=x onerror="alert(document.cookie)">
Если сервер выведет это в HTML без экранирования:
<p>Привет, <img src=x onerror="alert(document.cookie)">!</p>
Браузер выполнит код - и атакующий получит cookie жертвы.
Типы XSS
Stored XSS - вредоносный код сохранён в БД (комментарий, профиль)
и выполняется у каждого, кто видит эту страницу
Reflected XSS - код в URL-параметре, жертва кликает по ссылке
?search=<script>alert(1)</script>
DOM-based XSS - код не проходит через сервер, выполняется
через JavaScript на клиенте (innerHTML, eval)
Защита от XSS
1. Экранировать вывод < → < > → > " → "
2. Content-Security-Policy заголовок запрещает inline-скрипты
3. HttpOnly cookies JavaScript не может читать cookie сессии
4. Не использовать innerHTML вместо него - textContent или фреймворки
// Опасно: innerHTML вставляет HTML как есть
element.innerHTML = userInput;
// Безопасно: textContent экранирует автоматически
element.textContent = userInput;
React, Vue, Angular экранируют по умолчанию - но dangerouslySetInnerHTML (React) или v-html (Vue) обходят защиту.
CSRF (Cross-Site Request Forgery)
CSRF - атака, при которой злоумышленник заставляет браузер жертвы выполнить запрос от её имени.
Как это работает
Пользователь залогинен в банке. Открывает страницу атакующего, на которой:
<img src="https://bank.com/transfer?to=hacker&amount=1000" />
Браузер отправит GET-запрос к банку с cookie жертвы - и перевод выполнится.
Защита от CSRF
1. CSRF-токен - уникальный токен в форме, сервер проверяет его
2. SameSite cookie - cookie не отправляются с кросс-доменных запросов
3. Проверка Origin/Referer - сервер проверяет, откуда пришёл запрос
4. Не менять данные через GET - только POST/PUT/DELETE
<!-- CSRF-токен в форме -->
<form method="post" action="/transfer">
<input type="hidden" name="csrf_token" value="abc123xyz" />
<input name="amount" />
<button type="submit">Перевести</button>
</form>
Set-Cookie: session=abc; SameSite=Strict; HttpOnly; Secure
SameSite=Strict - cookie не отправляются при переходе с другого сайта. Lax - отправляются при навигации (GET), но не при POST.
Content Security Policy (CSP)
CSP - HTTP-заголовок, который ограничивает, какие ресурсы может загружать страница:
Content-Security-Policy: default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'
default-src 'self' - по умолчанию загружать только с того же домена
script-src 'self' - JavaScript только с нашего домена (запрет inline)
style-src 'unsafe-inline' - разрешить inline-стили (иногда необходимо)
img-src * data: - изображения откуда угодно + data URL
CSP блокирует inline-скрипты - самый частый вектор XSS.
CORS (Cross-Origin Resource Sharing)
Браузер блокирует запросы на чужие домены по умолчанию (Same-Origin Policy). CORS позволяет серверу разрешить запросы с определённых доменов:
Access-Control-Allow-Origin: https://frontend.example.com
Access-Control-Allow-Methods: GET, POST, PUT, DELETE
Access-Control-Allow-Headers: Content-Type, Authorization
Простой запрос (GET, POST с form data) - отправляется сразу
Сложный запрос (PUT, DELETE, JSON) - сначала OPTIONS preflight
sequenceDiagram
autonumber
participant B as Браузер
participant S as api.other.com
Note over B,S: "сложный" запрос (PUT/DELETE/JSON) - сначала preflight
B->>S: OPTIONS /resource<br/>Origin, Access-Control-Request-Method
S-->>B: 204 No Content<br/>Access-Control-Allow-Origin, Allow-Methods
Note over B,S: только если preflight разрешил - летит реальный запрос
B->>S: PUT /resource (JSON)
S-->>B: 200 OK
HTTPS - минимальная гигиена
Без HTTPS всё, что передаётся между браузером и сервером, видно любому в сети: пароли, cookie, данные форм. В 2026 году HTTP без S - это как отправлять пароль на открытке.
Что даёт HTTPS:
- Шифрование - данные нечитаемы для посредников
- Аутентификация - сертификат подтверждает, что это тот самый сервер
- Целостность - данные не подменены по пути
Мини-задание
- Попробуй
encodeURIComponent()в консоли браузера с разными спецсимволами - Открой любой сайт → DevTools → Application → Cookies и посмотри флаги (HttpOnly, Secure, SameSite)
- Найди заголовок Content-Security-Policy в ответах Network (если есть)
- Попробуй вставить
<b>жирный</b>в поле ввода и посмотри - экранируется ли при выводе - Проверь, есть ли CORS-заголовки в ответах API твоего сайта