Соображения безопасности
Веб-приложения обычно сталкиваются со множеством проблем безопасности, и очень сложно всё сделать правильно. Flask пытается решить некоторые из этих проблем за вас, но есть ещё несколько, о которых нужно позаботиться самостоятельно.
Межсайтовый скриптинг (XSS)
Межсайтовый скриптинг — это концепция внедрения произвольного HTML (а вместе с ним и JavaScript) в контекст веб-сайта. Для решения этой проблемы разработчики должны правильно экранировать текст, чтобы он не содержал произвольные HTML-теги. Дополнительную информацию об этом можно найти в статье Википедии о Межсайтовом скриптинге.
Flask настраивает Jinja2 на автоматическое экранирование всех значений, если явно не указано иное. Это должно исключить все проблемы XSS, вызванные в шаблонах, но всё же есть другие места, где нужно быть осторожным:
- генерация HTML без помощи Jinja2
- вызов
Markupдля данных, отправленных пользователем - вывод HTML из загруженных файлов, никогда этого не делайте, используйте заголовок
Content-Disposition: attachmentдля предотвращения этой проблемы. - вывод текстовых файлов из загруженных файлов. Некоторые браузеры используют угадывание типа содержимого, основываясь на первых байтах, поэтому пользователи могут обмануть браузер, чтобы он выполнил HTML.
Ещё одна очень важная вещь — это атрибуты без кавычек. Хотя Jinja2 может защитить вас от проблем XSS, экранируя HTML, есть одна вещь, от которой он не защищает: XSS с помощью внедрения атрибутов. Чтобы противостоять этому возможному вектору атаки, всегда заключайте ваши атрибуты в двойные или одинарные кавычки при использовании Jinja-выражений в них:
<input value="{{ value }}">
Почему это необходимо? Потому что, если вы этого не сделаете, злоумышленник может легко внедрить пользовательские обработчики JavaScript. Например, злоумышленник может внедрить такой фрагмент HTML+JavaScript:
onmouseover=alert(document.cookie)
Когда пользователь затем перемещает курсор мыши над полем ввода, cookie будет представлен пользователю в окне предупреждения. Но вместо того, чтобы показать cookie пользователю, опытный злоумышленник может также выполнить любой другой код JavaScript. В сочетании с инъекциями CSS злоумышленник может даже заставить элемент заполнить всю страницу, так что пользователю достаточно навести курсор мыши на любую часть страницы, чтобы вызвать атаку.
Существует один класс проблем XSS, от которых экранирование Jinja не защищает. Атрибут href тега a может содержать javascript: URI, который браузер выполнит при щелчке, если он не защищён должным образом.
<a href="{{ value }}">click here</a>
<a href="javascript:alert('unsafe');">click here</a>
Для предотвращения этого вам нужно установить заголовок ответа Политика безопасности содержимого (CSP).
Межсайтовая подделка запроса (CSRF)
Ещё одна большая проблема — CSRF. Это очень сложная тема, и я не буду подробно её описывать здесь, а только упомяну, что это такое и как теоретически её предотвратить.
Если ваша информация об аутентификации хранится в cookie, у вас есть неявное управление состоянием. Состояние «быть авторизованным» контролируется cookie, и этот cookie отправляется с каждым запросом на страницу. К сожалению, это включает запросы, инициированные сторонними сайтами. Если вы не учтёте это, некоторые люди могут обмануть пользователей вашего приложения с помощью социальной инженерии, чтобы выполнить действия без их ведома.
Допустим, у вас есть определённый URL, который, при отправке POST запросов, удаляет профиль пользователя (скажем, http://example.com/user/delete). Если теперь злоумышленник создаёт страницу, которая отправляет POST-запрос на эту страницу с помощью JavaScript, ему нужно лишь обмануть некоторых пользователей, чтобы они загрузили эту страницу, и их профили будут удалены.
Представьте, что вы запускаете Facebook с миллионами одновременных пользователей, и кто-то отправляет ссылки на изображения маленьких котят. Когда пользователи переходят по этой ссылке, их профили удаляются, пока они смотрят изображения пушистых котов.
Как вы можете предотвратить это? В основном для каждого запроса, который изменяет содержимое на сервере, вам нужно использовать одноразовый токен и сохранить его в cookie и также передать его в данных формы. После получения данных на сервере снова вам нужно сравнить два токена и убедиться, что они равны.
Почему Flask не делает это за вас? Идеальное место для этого — фреймворк валидации форм, которого нет в Flask.
Безопасность JSON
В Flask 0.10 и ниже jsonify() не сериализовал массивы верхнего уровня в JSON. Это было связано с уязвимостью безопасности в ECMAScript 4.
ECMAScript 5 закрыл эту уязвимость, поэтому уязвимы только очень старые браузеры. У всех этих браузеров есть другие более серьёзные уязвимости, поэтому это поведение было изменено, и jsonify() теперь поддерживает сериализацию массивов.
Заголовки безопасности
Браузеры распознают различные заголовки ответа для управления безопасностью. Мы рекомендуем просмотреть каждый из заголовков ниже для использования в вашем приложении. Расширение Flask-Talisman можно использовать для управления HTTPS и заголовками безопасности за вас.
HTTP Strict Transport Security (HSTS)
Сообщает браузеру преобразовать все запросы HTTP в HTTPS, предотвращая атаки типа «человек посередине» (MITM).
response.headers['Strict-Transport-Security'] = 'max-age=31536000; includeSubDomains'
Политика безопасности содержимого (CSP)
Указывает браузеру, откуда он может загружать различные типы ресурсов. Этот заголовок следует использовать, когда это возможно, но требует некоторых усилий для определения правильной политики для вашего сайта. Очень строгая политика будет:
response.headers['Content-Security-Policy'] = "default-src 'self'"
- https://csp.withgoogle.com/docs/index.html
- https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy
X-Content-Type-Options
Принуждает браузер соблюдать тип содержимого ответа вместо попытки его определить, что может быть использовано для создания атаки межсайтового скриптинга (XSS).
response.headers['X-Content-Type-Options'] = 'nosniff'
X-Frame-Options
Предотвращает сторонние сайты от встраивания вашего сайта в iframe. Это предотвращает класс атак, где щелчки в внешнем фрейме могут быть невидимо переведены в щелчки по элементам вашей страницы. Это также известно как «кликинг джек».
response.headers['X-Frame-Options'] = 'SAMEORIGIN'
X-XSS-Protection
Браузер попытается предотвратить отраженные атаки XSS, не загружая страницу, если запрос содержит что-то, что похоже на JavaScript, а ответ содержит те же данные.
response.headers['X-XSS-Protection'] = '1; mode=block'
Параметры Set-Cookie
-
Secureограничивает cookie только HTTPS-трафиком. -
HttpOnlyзащищает содержимое cookie от считывания с помощью JavaScript. -
SameSiteограничивает, как cookie отправляются с запросами со сторонних сайтов. Может быть установлено в'Lax'(рекомендуется) или'Strict'.Laxпредотвращает отправку cookie с предрасположенными к CSRF запросами со сторонних сайтов, таких как отправка формы.Strictпредотвращает отправку cookie со всеми внешними запросами, включая переходы по обычным ссылкам.
app.config.update(
SESSION_COOKIE_SECURE=True,
SESSION_COOKIE_HTTPONLY=True,
SESSION_COOKIE_SAMESITE='Lax',
)
response.set_cookie('username', 'flask', secure=True, httponly=True, samesite='Lax')
Указание параметров Expires или Max-Age удалит cookie по истечении указанного времени или текущего времени плюс срок годности соответственно. Если ни один из параметров не указан, cookie будет удалён при закрытии браузера.
# cookie expires after 10 minutes
response.set_cookie('snakes', '3', max_age=600)
Для cookie сеанса, если session.permanent установлен, то используется PERMANENT_SESSION_LIFETIME для установки срока действия. Стандартное реализация cookie Flask проверяет, что криптографическая подпись не старше этого значения. Уменьшение этого значения может помочь смягчить атаки с повторным использованием, когда перехваченные cookie могут быть отправлены позже.
app.config.update(
PERMANENT_SESSION_LIFETIME=600
)
@app.route('/login', methods=['POST'])
def login():
...
session.clear()
session['user_id'] = user.id
session.permanent = True
...
Используйте itsdangerous.TimedSerializer для подписи и проверки других значений cookie (или любых значений, которые требуют защищённых подписей).
- https://developer.mozilla.org/en-US/docs/Web/HTTP/Cookies
- https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie
HTTP Public Key Pinning (HPKP)
Это говорит браузеру аутентифицироваться с сервером, используя только определённый ключ сертификата, чтобы предотвратить атаки типа «человек посередине».
Предупреждение
Будьте осторожны при включении этого, так как его очень трудно отменить, если вы неправильно настроили или обновили свой ключ.
© 2007–2020 Pallets
Licensed under the BSD 3-clause License.
https://flask.palletsprojects.com/en/1.0.x/security/