Код-ревью Middle Аутентификация и права спрашивают часто обновлено
Код-ревью эндпоинта входа и проверки токена
Вопрос
Найдите уязвимости в коде входа и проверки API-ключа.
import hashlib
import jwt
from fastapi import FastAPI, HTTPException, Header
app = FastAPI()
SECRET = 'secret123'
@app.post('/login')
def login(email: str, password: str):
user = db.find_user(email)
if user is None:
raise HTTPException(404, 'User not found')
if user.password != hashlib.md5(password.encode()).hexdigest():
raise HTTPException(401, 'Wrong password')
token = jwt.encode({'sub': user.id, 'role': user.role}, SECRET)
return {'token': token}
@app.get('/admin/report')
def report(x_api_key: str = Header()):
if x_api_key == ADMIN_KEY:
return build_report()
raise HTTPException(403)
def check(token: str):
return jwt.decode(token, SECRET, options={'verify_signature': False})
Коротко
Исправления: секрет из окружения; пароль через argon2.PasswordHasher().verify(user.passwordhash, password) с общим ответом «неверный email или пароль» и ограничением попыток; токен с exp, iat и типом: Проверка всегда с подписью и списком алгоритмов: jwt.decode(token, key, algorithms=["HS256"]).
Что должен найти ревьюер
- Безопасностьстрока 6Слабый секрет прямо в коде; секрет из переменных окружения, длинный и случайный
- Безопасностьстрока 13Разные ответы для «нет пользователя» и «неверный пароль» позволяют перебирать email; нужен общий ответ 401
- Безопасностьстрока 14MD5 без соли для паролей: быстро перебирается; нужен Argon2 или bcrypt (и ограничение попыток)
- Безопасностьстрока 16JWT без
exp: токен бессрочный и неотзываемый; добавить короткий срок и refresh - Безопасностьстрока 22Сравнение секрета через
==подвержено атаке по времени;hmac.compare_digest - Безопасностьстрока 28Проверка подписи отключена: любой может подделать токен с ролью admin
Разбор
Исправления: секрет из окружения; пароль через argon2.PasswordHasher().verify(user.password_hash, password) с общим ответом «неверный email или пароль» и ограничением попыток; токен с exp, iat и типом:
now = int(time.time())
token = jwt.encode({'sub': str(user.id), 'exp': now + 900, 'iat': now, 'type': 'access'},
settings.jwt_secret, algorithm='HS256')
Проверка всегда с подписью и списком алгоритмов: jwt.decode(token, key, algorithms=["HS256"]). API-ключ сравнивается через hmac.compare_digest, а лучше хранится в базе как хеш. Роль в токене удобна, но после смены роли старые токены ещё живут до истечения exp.
Ответьте сами, а не прочитайте
В тренажёре вопрос попадёт в повторения и вернётся, когда начнёт забываться. Плюс билеты с зачётом: 20 вопросов, не больше 2 ошибок.