- Узнавать категории OWASP Top 10 и объяснять каждую на примере
- Исправлять уязвимый SQL-код с помощью параметризованного запроса
- Предотвращать XSS экранированием вывода
- Выбирать защиту от CSRF и основные заголовки безопасности
Эльвин написал свой первый сайт: страница входа, комментарии, профиль. Для честных пользователей всё работает идеально. Веб-безопасность задаёт другой вопрос: что будет, если пользователь пришлёт что-то неожиданное? Ответы на этот вопрос годами собирает в свой список сообщество OWASP.
OWASP Top 10
| Код | Категория | Пример |
|---|---|---|
| A01 | Нарушение контроля доступа | смена номера заказа в URL открывает чужой заказ |
| A02 | Криптографические ошибки | чувствительные данные передаются или хранятся без шифрования |
| A03 | Внедрение (инъекции) | текст пользователя выполняется как SQL или команда; сюда же относится XSS |
| A04 | Небезопасный дизайн | восстановление пароля держится только на вопросе «любимый цвет» |
| A05 | Ошибки конфигурации | стандартные пароли администратора, подробные тексты ошибок для пользователей |
| A06 | Уязвимые и устаревшие компоненты | версия библиотеки с известной уязвимостью |
| A07 | Ошибки идентификации и аутентификации | нет ограничения на число попыток входа |
| A08 | Нарушения целостности ПО и данных | обновления ставятся без проверки подписи |
| A09 | Недостатки журналирования и мониторинга | атака месяцами остаётся незамеченной |
| A10 | Подделка запросов на стороне сервера (SSRF) | сервер обращается по URL от пользователя к внутреннему сервису |
Инъекции: когда данные становятся кодом
Когда SQL-запрос «склеивают» с текстом пользователя как строку, база данных не может отличить данные от команд. Специально подобранный ввод с кавычкой может досрочно закрыть строку и дописать своё условие — например, всегда истинное. Итог — вход без пароля или утечка всей таблицы. Лекарство — параметризованный запрос: текст SQL и значения передаются отдельно, и значение никогда не читается как код.
def find_user(conn, username):
query = "SELECT id, email FROM users WHERE username = '" + username + "'"
return conn.execute(query).fetchall()def find_user(conn, username):
query = 'SELECT id, email FROM users WHERE username = ?'
return conn.execute(query, (username,)).fetchall()? — это заполнитель: драйвер передаёт значение отдельно, поэтому кавычки в значении не могут изменить структуру запроса.import sqlite3
conn = sqlite3.connect(':memory:')
conn.execute('CREATE TABLE users (id INTEGER, username TEXT)')
conn.executemany('INSERT INTO users VALUES (?, ?)', [(1, 'aysel'), (2, "o'neil")])
name = "o'neil" # a real surname with an apostrophe
try:
conn.execute("SELECT id FROM users WHERE username = '" + name + "'").fetchall()
except sqlite3.OperationalError as error:
print('String building failed:', error)
rows = conn.execute('SELECT id FROM users WHERE username = ?', (name,)).fetchall()
print('Parameterised query:', rows)▸ Ожидаемый результат
String building failed: near "neil": syntax error Parameterised query: [(2,)]
XSS: чужой скрипт в браузере
Межсайтовый скриптинг (XSS) возникает, когда текст пользователя вставляется в страницу без экранирования и браузеры других посетителей выполняют его как код. Такой скрипт может украсть сессионную cookie или совершать действия от имени жертвы. Защита: экранировать вывод с учётом контекста (шаблоны с автоэкранированием), заголовок Content-Security-Policy и cookie с флагом HttpOnly.
import html
comment = "<script>alert('hi')</script> Nice lesson!"
print('Raw: ', comment)
print('Escaped:', html.escape(comment))▸ Ожидаемый результат
Raw: <script>alert('hi')</script> Nice lesson!
Escaped: <script>alert('hi')</script> Nice lesson!CSRF и ошибки аутентификации
CSRF (межсайтовая подделка запроса): пользователь вошёл в интернет-банк, затем открыл другой сайт, а тот тайно отправил в банк форму. Браузер автоматически прикладывает cookie банка, и запрос выполняется от имени жертвы. Защита: случайный анти-CSRF-токен в каждой форме с проверкой на сервере, cookie с SameSite=Lax или Strict, проверка заголовка Origin и повторное подтверждение важных действий.
Ошибки аутентификации — это отсутствие ограничения на число попыток (именно так работает массовая проверка утёкших паролей), разрешение слабых паролей, неизменный идентификатор сессии после входа и незавершённая сессия при выходе. Лекарство: ограничение частоты запросов, MFA, cookie с Secure и HttpOnly, новая сессия после входа.
Заголовки безопасности
| Заголовок | Что делает |
|---|---|
| Content-Security-Policy | задаёт, откуда можно загружать скрипты, стили и изображения, — второй барьер против XSS |
| Strict-Transport-Security | велит браузеру открывать сайт только по HTTPS (HSTS) |
| X-Content-Type-Options: nosniff | запрещает браузеру «угадывать» тип файла |
| X-Frame-Options / frame-ancestors | не даёт встроить сайт в скрытый фрейм на чужой странице (кликджекинг) |
| Set-Cookie: Secure; HttpOnly; SameSite | cookie передаётся только по HTTPS, недоступна скриптам и не прикладывается к запросам с чужих сайтов |
Напиши escapeHtml(text): функция должна заменять &, <, >, " и ' соответственно на &, <, >, " и '. Внимание: & нужно заменять первым.
function escapeHtml(text) {
// replace & first, then < > " '
return text;
}
console.log(escapeHtml('Tom & Jerry'));
console.log(escapeHtml('<b>bold</b>'));
console.log(escapeHtml(`<img src="x" alt='y'>`));
console.log(escapeHtml('<'));▸ Ожидаемый результат
Tom & Jerry <b>bold</b> <img src="x" alt='y'> &lt;
Главное
- OWASP Top 10 — список самых критичных рисков веб-приложений; на первом месте нарушение контроля доступа.
- Единственное надёжное средство от SQL-инъекций — параметризованные запросы.
- Против XSS вывод экранируют по контексту, а CSP создаёт второй барьер.
- От CSRF защищают анти-CSRF-токены и cookie с SameSite.
- Заголовки безопасности (CSP, HSTS, nosniff, frame-ancestors) — дешёвая, но сильная защита.
Проверь себя
Вопросов: 10. Каждый правильный ответ приносит XP.
order=1001 на order=1002 и видит чужой заказ. Какая это категория?