Основы безопасности веб-приложений
Несколько частых ошибок и принципов, которые нужно знать даже в маленьком проекте.
Веб-безопасность — большая тема; здесь — только самые частые ошибки и принципы, которые нужно знать даже в маленьком проекте. Глубокое изучение остаётся будущему специализированному курсу (раздел 22.36).
SQL-инъекция
Если собрать SQL-запрос склеиванием строк с пользовательским вводом, часть введённого текста может быть воспринята как часть самого запроса, а не как данные:
# ТАК ДЕЛАТЬ НЕЛЬЗЯ
db.execute(f"SELECT * FROM tasks WHERE title = '{{user_input}}'")
db.execute("SELECT * FROM tasks WHERE title = ?", (user_input,))
sqlite3.execute() в Python не разрешает несколько SQL-инструкций за один вызов: составная атака вроде x'; DROP TABLE tasks; -- завершится ошибкой ProgrammingError. Это ограничение конкретного Python-драйвера, а не универсальная защита SQL. Оно не спасает от одноинструкционных инъекций: ввод ' OR '1'='1 меняет условие WHERE и возвращает все строки. Никогда не вставляйте недоверенный ввод в текст SQL — передавайте его только параметром.Раздел 22.22 уже показывал этот приём — параметризованный запрос,
где значение передаётся отдельно от текста SQL и никогда не смешивается с ним. Модуль
sqlite3 сам отвечает за то, чтобы значение осталось данными,
что бы в нём ни было — даже фрагмент, похожий на SQL.
XSS — межсайтовый скриптинг
Если чужой текст попадает в HTML-страницу без обработки, он может содержать
исполняемый код, который выполнится в браузере другого пользователя, — например,
<script>...</script> в названии задачи. Раздел
22.12 уже показал защиту: в автоэкранируемом HTML-контексте Jinja заменяет специальные
символы HTML соответствующими escape-последовательностями, поэтому введённый текст не
интерпретируется как разметка. Это не делает значение безопасным вставлять куда угодно —
например, внутрь атрибута <script> или URL нужна уже
другая защита, — именно поэтому фильтр |safe, который вообще
отключает экранирование, нельзя применять к вводу, который вы не полностью контролируете
сами.
CSRF — подделка межсайтового запроса
CSRF (Cross-Site Request Forgery) актуален там, где браузер автоматически прикладывает учётные данные (например, cookie сессии, раздел 22.30) к запросам, а сам запрос меняет состояние на сервере: пользователь, залогиненный на сайте, открывает другую, вредоносную страницу, а та страница теоретически может незаметно отправить от его имени запрос на ваш сайт — браузер сам приложит те же cookie. Пока в проекте главы нет входа пользователей, риск CSRF ограничен, но для приложений, где авторизация или состояние опираются на автоматически отправляемые браузером cookie, изменяющие состояние запросы должны быть защищены от CSRF соответствующим механизмом — обычно его предоставляет сам фреймворк или расширение.
Пароли
Пароли никогда не хранят как обычный текст — только в виде хеша, полученного через проверенный, специально предназначенный для паролей инструмент. Изобретать собственную схему хеширования паролей не нужно и не стоит — для этого есть готовые, тщательно проверенные библиотеки. Проект этой главы не хранит пароли вообще: раздел 22.30 уже объяснил, почему полноценный вход пользователей остался за пределами этой главы.
Секреты
SECRET_KEY приложения, пароли к базе данных, ключи внешних
API — всё это не должно попадать в публичный исходный код. Как такие значения обычно
передают через переменные окружения вместо того, чтобы записывать их прямо в файл с
кодом, — в разделе 22.34.
HTTPS — ещё раз, коротко
Раздел 22.10 уже объяснил: HTTPS защищает данные по пути между браузером и сервером, но не устраняет ошибки внутри самого приложения — SQL-инъекцию, XSS или утечку секретов HTTPS не остановит. Это разные, дополняющие друг друга уровни защиты.