В веб-приложении на Flask встречается код: user = request.args.get('user'); query = "SELECT * FROM users WHERE name = '" + user + "'"; results = db.execute(query) — выявите уязвимости (SQL-инъекции, XSS, CSRF и т.п.), предложите конкретные исправления и обсудите подходы к хранению паролей и управлению сессиями
Код user = request.args.get′user′'user'′user′
query = "SELECT * FROM users WHERE name = '" + user + "'" results = db.executequeryqueryquery
содержит несколько серьёзных проблем безопасности. Ниже — что именно уязвимо, почему, и конкретные исправления + рекомендации по хранению паролей и управлению сессиями.
1) Уязвимости в исходном коде
SQL‑инъекция. Формирование SQL через конкатенацию строки с участием пользовательского ввода даёт возможность выполнить произвольный SQL (например: user = "x' OR '1'='1";).XSS reflected/storedreflected/storedreflected/stored. Если значение user затем выводится в HTML без экранирования — возможна межсайтовая скриптовая атака.CSRF еслиэтотGET/POSTиспользуетсядляизменениясостояниябеззащитыесли этот GET/POST используется для изменения состояния без защитыеслиэтотGET/POSTиспользуетсядляизменениясостояниябеззащиты. Хотя пример показывает только SELECT — общая практика использования небезопасных эндпоинтов и отсутствие CSRF‑защиты рискована.Утечки информации / логирование. Если логируете raw-запросы, можно слить секреты.Неправильное хранение паролей еслипароливообщехранятсяввидеplaintextвusersесли пароли вообще хранятся в виде plain text в usersеслипароливообщехранятсяввидеplaintextвusers — очень опасно.Неправильная конфигурация сессий незащищённыеcookies,отсутствиеHttpOnly/Secure/SameSiteнезащищённые cookies, отсутствие HttpOnly/Secure/SameSiteнезащищённыеcookies,отсутствиеHttpOnly/Secure/SameSite.
2) Конкретные исправления попорядкупо порядкупопорядку
A. Защита от SQL‑инъекций — использовать параметризованные запросы / ORM
Psycopg2 / psycopg2‑binary PostgresPostgresPostgres: cur.execute("SELECT * FROM users WHERE name = %s", (user,))sqlite3: cur.execute("SELECT * FROM users WHERE name = ?", (user,))SQLAlchemy рекомендуетсявFlaskрекомендуется в FlaskрекомендуетсявFlask: ORM
from sqlalchemy import text results = db.session.execute(text("SELECT * FROM users WHERE name = :n"), {"n": user})
Важно: никогда не вставляйте user напрямую в строку SQL.
B. Экранирование и защита от XSS
При рендере HTML используйте шаблонизатор с авто‑эскейпингом Jinja2вFlaskэскейпитпоумолчаниюJinja2 в Flask эскейпит по умолчаниюJinja2вFlaskэскейпитпоумолчанию. Пример: return render_template("profile.html", user=user)Если вам нужно вывести HTML, убедитесь, что вы доверяете содержимому ииспользуйтеMarkup/escapeосознаннои используйте Markup/escape осознанноииспользуйтеMarkup/escapeосознанно.Дополнительно — заголовки CSP Content‑Security‑PolicyContent‑Security‑PolicyContent‑Security‑Policy и правильный набор заголовков безопасности X−Frame−Options,X−Content−Type−OptionsX-Frame-Options, X-Content-Type-OptionsX−Frame−Options,X−Content−Type−Options.
C. CSRF
Для форм/POST/изменяющих запросов используйте CSRF‑токены Flask−WTFилиflaskwtf.csrf.CSRFProtectFlask-WTF или flask_wtf.csrf.CSRFProtectFlask−WTFилиflaskwtf.csrf.CSRFProtect. from flask_wtf.csrf import CSRFProtect csrf = CSRFProtectappappappДля API обычно используют токены в заголовке авторизации BearerBearerBearer и CORS/SameSite политики вместо cookie‑CSRF.
D. Валидация и нормализация ввода
По возможности применяйте allow‑list: длина, допустимые символы например,именапользователей—тольколатиница/цифры/символынапример, имена пользователей — только латиница/цифры/символынапример,именапользователей—тольколатиница/цифры/символы.Для поиска по LIKE используйте параметризованные запросы и экранирование wildcard‑символов.
3) Пример безопасного кода Flask+SQLAlchemy+CSRFFlask + SQLAlchemy + CSRFFlask+SQLAlchemy+CSRF
Пример минимальной реализации получения пользователя: from flask import Flask, request, render_template from flask_wtf.csrf import CSRFProtect from models import User # SQLAlchemy модель
@app.route′/user′'/user'′/user′
def user_profile: name = request.args.get′user′,′′,type=str'user', '', type=str′user′,′′,type=str
Валидация: ограничить длину и набор символовif not name or lennamenamename > 100: abort400400400
user = User.query.filter_byname=namename=namename=name.first # параметризовано return render_template′profile.html′,user=user'profile.html', user=user′profile.html′,user=user # Jinja2 эскейпит автоматически
Argon2 рекомендуетсярекомендуетсярекомендуется, bcrypt, scrypt. PBKDF2 возможно, но Argon2 предпочтительнее на 2024+.
Используйте проверенные библиотеки:
argon2-cffi: from argon2 import PasswordHasherpasslib оборачиваетbcrypt/argon2идаётудобствооборачивает bcrypt/argon2 и даёт удобствооборачиваетbcrypt/argon2идаётудобствоwerkzeug.security.generate_password_hash используетPBKDF2поумолчанию;приемлемо,ноArgon2/Bcryptлучшеиспользует PBKDF2 по умолчанию; приемлемо, но Argon2/Bcrypt лучшеиспользуетPBKDF2поумолчанию;приемлемо,ноArgon2/Bcryptлучше
Пример argon2argon2argon2: from argon2 import PasswordHasher ph = PasswordHashertimecost=2,memorycost=65536,parallelism=4time_cost=2, memory_cost=65536, parallelism=4timecost=2,memorycost=65536,parallelism=4 # настраивайте под вашу infra hash = ph.hashplainpasswordplain_passwordplainpassword
Используйте уникальную соль для каждой записи библиотекисамигенерируютсольбиблиотеки сами генерируют сольбиблиотекисамигенерируютсоль.Храните только hash ипараметры,еслибиблиотеканевключаетихи параметры, если библиотека не включает ихипараметры,еслибиблиотеканевключаетих.Используйте constant-time сравнение при проверке библиотекиделаютэтобиблиотеки делают этобиблиотекиделаютэто.Планируйте миграцию параметров увеличениеcostувеличение costувеличениеcost — храните метаданные для каждого хеша или используйте passlib, который умеет ре-хешировать при входе.
5) Управление сессиями
Конфигурация cookie: SESSION_COOKIE_SECURE = True толькоHTTPSтолько HTTPSтолькоHTTPSSESSION_COOKIE_HTTPONLY = True недоступноJSне доступно JSнедоступноJSSESSION_COOKIE_SAMESITE = 'Lax' или 'Strict' предпочтительноLaxдлябольшинствапредпочтительно Lax для большинствапредпочтительноLaxдлябольшинстваFlask по умолчанию хранит сессии в client‑signed cookies. Для большей безопасности храните сессии на сервере Redis/DBRedis/DBRedis/DB через Flask-Session: from flask_session import Session app.config′SESSIONTYPE′'SESSION_TYPE'′SESSIONTYPE′ = 'redis' SessionappappappЗащита от фиксации сессий: Генерируйте новый идентификатор сессии при логине session.clear()изаполнениеновойсессииsession.clear() и заполнение новой сессииsession.clear()изаполнениеновойсессии.На логаут обязательно удаляйте сессию на сервере и cookie.Ограничение времени жизни: app.permanent_session_lifetime = timedeltahours=1hours=1hours=1Используйте короткие тайм‑ауты для чувствительных операций и опцию "запомнить меня" с долгосрочной токенизацией, а не долговременными сессионными куки.Аутентификация: Используйте Flask-Login для управления логином/логаутом, и храните только идентификатор пользователя в сессии.Для API — используйте JWT аккуратно реплэй/отзывпроблемныреплэй/отзыв проблемныреплэй/отзывпроблемны или токены доступа + refresh токены, хранимые в httpOnly cookie и с проверкой на сервере.
6) Дополнительные рекомендации
HTTPS всегда.Ограничение частоты ratelimitingrate limitingratelimiting и защита от брутфорса Flask−Limiter,блокировки,задержкиFlask-Limiter, блокировки, задержкиFlask−Limiter,блокировки,задержки.Логирование не содержит секретов нелогируйтепароли,токены,полныезапросыссекр.параметрамине логируйте пароли, токены, полные запросы с секр. параметраминелогируйтепароли,токены,полныезапросыссекр.параметрами.Мониторинг, IDS/IPS и тестирование DAST,SAST,регулярныеpentestDAST, SAST, регулярные pentestDAST,SAST,регулярныеpentest.Минимизация прав БД: приложение использует пользователя БД с минимальными правами SELECT/INSERT/UPDATEтолькопонеобходимостиSELECT/INSERT/UPDATE только по необходимостиSELECT/INSERT/UPDATEтолькопонеобходимости.Принцип наименьших привилегий для секретов и сервисов.
Кратко — что нужно сделать прямо сейчас
Заменить конкатенацию SQL на параметризованные запросы / ORM.Экранировать/валидировать пользовательский ввод и эскейпить вывод в шаблонах Jinja2поумолчаниюJinja2 по умолчаниюJinja2поумолчанию.Подключить CSRF‑защиту для форм/изменяющих запросов.Перейти на безопасное хранение паролей Argon2/bcryptчерезбиблиотекиArgon2/bcrypt через библиотекиArgon2/bcryptчерезбиблиотеки.Пересмотреть конфигурацию сессий Secure,HttpOnly,SameSiteSecure, HttpOnly, SameSiteSecure,HttpOnly,SameSite, и при необходимости перейти на серверные сессии + регенерация сессии при логине.
Если хотите, могу:
Привести готовый пример приложения Flask с SQLAlchemy, Argon2 и Flask‑Login, где все эти рекомендации реализованы,Или показать конкретные примеры под вашу СУБД Postgres/SQLite/MySQLPostgres/SQLite/MySQLPostgres/SQLite/MySQL.
Код
user = request.args.get′user′'user'′user′ query = "SELECT * FROM users WHERE name = '" + user + "'"
results = db.executequeryqueryquery
содержит несколько серьёзных проблем безопасности. Ниже — что именно уязвимо, почему, и конкретные исправления + рекомендации по хранению паролей и управлению сессиями.
1) Уязвимости в исходном коде
SQL‑инъекция. Формирование SQL через конкатенацию строки с участием пользовательского ввода даёт возможность выполнить произвольный SQL (например: user = "x' OR '1'='1";).XSS reflected/storedreflected/storedreflected/stored. Если значение user затем выводится в HTML без экранирования — возможна межсайтовая скриптовая атака.CSRF еслиэтотGET/POSTиспользуетсядляизменениясостояниябеззащитыесли этот GET/POST используется для изменения состояния без защитыеслиэтотGET/POSTиспользуетсядляизменениясостояниябеззащиты. Хотя пример показывает только SELECT — общая практика использования небезопасных эндпоинтов и отсутствие CSRF‑защиты рискована.Утечки информации / логирование. Если логируете raw-запросы, можно слить секреты.Неправильное хранение паролей еслипароливообщехранятсяввидеplaintextвusersесли пароли вообще хранятся в виде plain text в usersеслипароливообщехранятсяввидеplaintextвusers — очень опасно.Неправильная конфигурация сессий незащищённыеcookies,отсутствиеHttpOnly/Secure/SameSiteнезащищённые cookies, отсутствие HttpOnly/Secure/SameSiteнезащищённыеcookies,отсутствиеHttpOnly/Secure/SameSite.2) Конкретные исправления попорядкупо порядкупопорядку A. Защита от SQL‑инъекций — использовать параметризованные запросы / ORM
Psycopg2 / psycopg2‑binary PostgresPostgresPostgres:cur.execute("SELECT * FROM users WHERE name = %s", (user,))sqlite3:
cur.execute("SELECT * FROM users WHERE name = ?", (user,))SQLAlchemy рекомендуетсявFlaskрекомендуется в FlaskрекомендуетсявFlask:
ORM
user_obj = User.query.filter_byname=username=username=user.first
или text + параметрыfrom sqlalchemy import text
results = db.session.execute(text("SELECT * FROM users WHERE name = :n"), {"n": user})
Важно: никогда не вставляйте user напрямую в строку SQL.
B. Экранирование и защита от XSS
При рендере HTML используйте шаблонизатор с авто‑эскейпингом Jinja2вFlaskэскейпитпоумолчаниюJinja2 в Flask эскейпит по умолчаниюJinja2вFlaskэскейпитпоумолчанию. Пример:return render_template("profile.html", user=user)Если вам нужно вывести HTML, убедитесь, что вы доверяете содержимому ииспользуйтеMarkup/escapeосознаннои используйте Markup/escape осознанноииспользуйтеMarkup/escapeосознанно.Дополнительно — заголовки CSP Content‑Security‑PolicyContent‑Security‑PolicyContent‑Security‑Policy и правильный набор заголовков безопасности X−Frame−Options,X−Content−Type−OptionsX-Frame-Options, X-Content-Type-OptionsX−Frame−Options,X−Content−Type−Options.
C. CSRF
Для форм/POST/изменяющих запросов используйте CSRF‑токены Flask−WTFилиflaskwtf.csrf.CSRFProtectFlask-WTF или flask_wtf.csrf.CSRFProtectFlask−WTFилиflaskw tf.csrf.CSRFProtect.from flask_wtf.csrf import CSRFProtect
csrf = CSRFProtectappappappДля API обычно используют токены в заголовке авторизации BearerBearerBearer и CORS/SameSite политики вместо cookie‑CSRF.
D. Валидация и нормализация ввода
По возможности применяйте allow‑list: длина, допустимые символы например,именапользователей—тольколатиница/цифры/символынапример, имена пользователей — только латиница/цифры/символынапример,именапользователей—тольколатиница/цифры/символы.Для поиска по LIKE используйте параметризованные запросы и экранирование wildcard‑символов.3) Пример безопасного кода Flask+SQLAlchemy+CSRFFlask + SQLAlchemy + CSRFFlask+SQLAlchemy+CSRF Пример минимальной реализации получения пользователя:
from flask import Flask, request, render_template
from flask_wtf.csrf import CSRFProtect
from models import User # SQLAlchemy модель
app = Flask<strong>name</strong><strong>name</strong><strong>name</strong> csrf = CSRFProtectappappapp
@app.route′/user′'/user'′/user′ def user_profile:
Валидация: ограничить длину и набор символовif not name or lennamenamename > 100:name = request.args.get′user′,′′,type=str'user', '', type=str′user′,′′,type=str
abort400400400 user = User.query.filter_byname=namename=namename=name.first # параметризовано
return render_template′profile.html′,user=user'profile.html', user=user′profile.html′,user=user # Jinja2 эскейпит автоматически
4) Хранение паролей — лучшие практики
Никогда не храните пароли в открытом виде.
Используйте адаптивные KDF keyderivationfunctionkey derivation functionkeyderivationfunction:
Argon2 рекомендуетсярекомендуетсярекомендуется, bcrypt, scrypt. PBKDF2 возможно, но Argon2 предпочтительнее на 2024+.Используйте проверенные библиотеки:
argon2-cffi: from argon2 import PasswordHasherpasslib оборачиваетbcrypt/argon2идаётудобствооборачивает bcrypt/argon2 и даёт удобствооборачиваетbcrypt/argon2идаётудобствоwerkzeug.security.generate_password_hash используетPBKDF2поумолчанию;приемлемо,ноArgon2/Bcryptлучшеиспользует PBKDF2 по умолчанию; приемлемо, но Argon2/Bcrypt лучшеиспользуетPBKDF2поумолчанию;приемлемо,ноArgon2/BcryptлучшеПример argon2argon2argon2:
проверка:from argon2 import PasswordHasher
ph = PasswordHashertimecost=2,memorycost=65536,parallelism=4time_cost=2, memory_cost=65536, parallelism=4timec ost=2,memoryc ost=65536,parallelism=4 # настраивайте под вашу infra
hash = ph.hashplainpasswordplain_passwordplainp assword
try:
неверный парольph.verifyhash,providedpasswordhash, provided_passwordhash,providedp assword except VerifyMismatchError:
Важные замечания:
Используйте уникальную соль для каждой записи библиотекисамигенерируютсольбиблиотеки сами генерируют сольбиблиотекисамигенерируютсоль.Храните только hash ипараметры,еслибиблиотеканевключаетихи параметры, если библиотека не включает ихипараметры,еслибиблиотеканевключаетих.Используйте constant-time сравнение при проверке библиотекиделаютэтобиблиотеки делают этобиблиотекиделаютэто.Планируйте миграцию параметров увеличениеcostувеличение costувеличениеcost — храните метаданные для каждого хеша или используйте passlib, который умеет ре-хешировать при входе.5) Управление сессиями
Конфигурация cookie:SESSION_COOKIE_SECURE = True толькоHTTPSтолько HTTPSтолькоHTTPSSESSION_COOKIE_HTTPONLY = True недоступноJSне доступно JSнедоступноJSSESSION_COOKIE_SAMESITE = 'Lax' или 'Strict' предпочтительноLaxдлябольшинствапредпочтительно Lax для большинствапредпочтительноLaxдлябольшинстваFlask по умолчанию хранит сессии в client‑signed cookies. Для большей безопасности храните сессии на сервере Redis/DBRedis/DBRedis/DB через Flask-Session:
from flask_session import Session
app.config′SESSIONTYPE′'SESSION_TYPE'′SESSIONT YPE′ = 'redis'
SessionappappappЗащита от фиксации сессий:
Генерируйте новый идентификатор сессии при логине session.clear()изаполнениеновойсессииsession.clear() и заполнение новой сессииsession.clear()изаполнениеновойсессии.На логаут обязательно удаляйте сессию на сервере и cookie.Ограничение времени жизни:
app.permanent_session_lifetime = timedeltahours=1hours=1hours=1Используйте короткие тайм‑ауты для чувствительных операций и опцию "запомнить меня" с долгосрочной токенизацией, а не долговременными сессионными куки.Аутентификация:
Используйте Flask-Login для управления логином/логаутом, и храните только идентификатор пользователя в сессии.Для API — используйте JWT аккуратно реплэй/отзывпроблемныреплэй/отзыв проблемныреплэй/отзывпроблемны или токены доступа + refresh токены, хранимые в httpOnly cookie и с проверкой на сервере.
6) Дополнительные рекомендации
HTTPS всегда.Ограничение частоты ratelimitingrate limitingratelimiting и защита от брутфорса Flask−Limiter,блокировки,задержкиFlask-Limiter, блокировки, задержкиFlask−Limiter,блокировки,задержки.Логирование не содержит секретов нелогируйтепароли,токены,полныезапросыссекр.параметрамине логируйте пароли, токены, полные запросы с секр. параметраминелогируйтепароли,токены,полныезапросыссекр.параметрами.Мониторинг, IDS/IPS и тестирование DAST,SAST,регулярныеpentestDAST, SAST, регулярные pentestDAST,SAST,регулярныеpentest.Минимизация прав БД: приложение использует пользователя БД с минимальными правами SELECT/INSERT/UPDATEтолькопонеобходимостиSELECT/INSERT/UPDATE только по необходимостиSELECT/INSERT/UPDATEтолькопонеобходимости.Принцип наименьших привилегий для секретов и сервисов.Кратко — что нужно сделать прямо сейчас
Заменить конкатенацию SQL на параметризованные запросы / ORM.Экранировать/валидировать пользовательский ввод и эскейпить вывод в шаблонах Jinja2поумолчаниюJinja2 по умолчаниюJinja2поумолчанию.Подключить CSRF‑защиту для форм/изменяющих запросов.Перейти на безопасное хранение паролей Argon2/bcryptчерезбиблиотекиArgon2/bcrypt через библиотекиArgon2/bcryptчерезбиблиотеки.Пересмотреть конфигурацию сессий Secure,HttpOnly,SameSiteSecure, HttpOnly, SameSiteSecure,HttpOnly,SameSite, и при необходимости перейти на серверные сессии + регенерация сессии при логине.Если хотите, могу:
Привести готовый пример приложения Flask с SQLAlchemy, Argon2 и Flask‑Login, где все эти рекомендации реализованы,Или показать конкретные примеры под вашу СУБД Postgres/SQLite/MySQLPostgres/SQLite/MySQLPostgres/SQLite/MySQL.