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"
`encodeURI` кодирует весь URL, но оставляет `:`, `/`, `?`, `#` - структурные символы. `encodeURIComponent` кодирует всё - используй его для отдельных параметров.

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. Экранировать вывод        < → &lt;  > → &gt;  " → &quot;
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) обходят защиту.

Не экранируй при записи в БД - экранируй при выводе. Потому что контекст вывода разный: HTML, URL, JS, CSS требуют разного экранирования.

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
CORS не мешает curl или Postman - они не браузеры. CORS защищает пользователя: его браузер не отправит запрос к API банка с сайта атакующего.

HTTPS - минимальная гигиена

Без HTTPS всё, что передаётся между браузером и сервером, видно любому в сети: пароли, cookie, данные форм. В 2026 году HTTP без S - это как отправлять пароль на открытке.

Что даёт HTTPS:
- Шифрование - данные нечитаемы для посредников
- Аутентификация - сертификат подтверждает, что это тот самый сервер
- Целостность - данные не подменены по пути

Мини-задание

  • Попробуй encodeURIComponent() в консоли браузера с разными спецсимволами
  • Открой любой сайт → DevTools → Application → Cookies и посмотри флаги (HttpOnly, Secure, SameSite)
  • Найди заголовок Content-Security-Policy в ответах Network (если есть)
  • Попробуй вставить <b>жирный</b> в поле ввода и посмотри - экранируется ли при выводе
  • Проверь, есть ли CORS-заголовки в ответах API твоего сайта

Зарегистрируйтесь бесплатно, чтобы пройти квиз, решить задание с автопроверкой и вести прогресс.