Узнайте, как UUID решает проблему утечки данных через ID. Реальный пример из Второй мировой войны и код на Python для защиты вашего приложения.
Во время Второй мировой войны союзники столкнулись с вопросом: сколько танков на самом деле производит Германия? Официальные методы — шпионаж, перехват сообщений и догадки — давали оценку в 1500 танков в месяц. Но статистики, используя только серийные номера с захваченных танков и простую формулу, получили 327. После войны выяснилось, что реальное число — 342. Ошибка разведки — более 1000, ошибка статистиков — 15. Урок: последовательные номера выдают секреты. Если ваши ID идут по порядку, любой, увидев несколько, может оценить общее количество. Эта статья покажет, как избежать этой уязвимости с помощью UUID.
Представьте: у вас есть база данных с автоинкрементным первичным ключом. Пользователь регистрируется и получает ID = 4317. Через неделю он регистрируется снова — ID = 4981. Вычитаем: 664 регистрации за неделю. Ваш рост раскрыт без взлома. То же с заказами: /invoices/58 говорит, что у вас всего 58 счетов. А последовательные ID легко перебирать: если я вижу /api/orders/9000, могу попробовать /8999 — это IDOR (Insecure Direct Object Reference), уязвимость из OWASP Top 10.
# Пример: утечка через автоинкремент
user_ids = [4317, 4981] # ID двух регистраций
signups = user_ids[1] - user_ids[0] # 664
print(f"Новых пользователей за неделю: {signups}")UUID (Universally Unique Identifier) — 128-битное случайное число, например, f47ac10b-58cc-4372-a567-0e02b2c3d479. Оно не имеет порядка: нельзя определить предыдущий, следующий ID или общее количество. Задача о немецких танках бессильна против UUID. Дополнительный плюс: UUID уникален глобально без координации — два сервера могут генерировать ID одновременно без коллизий.
import uuid
# Генерация UUIDv4
user_id = uuid.uuid4()
print(f"Новый UUID: {user_id}")
# Вывод: f47ac10b-58cc-4372-a567-0e02b2c3d479UUIDv4 случайный, что плохо для B-деревьев индексов. Каждая вставка — как пьяный гость, садящийся между двумя людьми: база данных постоянно перестраивает страницы. Это write amplification и фрагментация. Для высоконагруженных таблиц — катастрофа. Решение — UUIDv7 (стандарт 2024): старшие биты содержат временную метку, младшие — случайность. ID растут упорядоченно, но остаются непредсказуемыми.
# Пример генерации UUIDv7 (требуется Python 3.12+)
import uuid
user_id = uuid.uuid7()
print(f"UUIDv7: {user_id}")
# Вывод: 018f3b1a-2c7e-7a00-8000-000000000001 (время + случайность)ULID — сортируемая текстовая кодировка с временной меткой. Snowflake ID (Twitter) — 64 бита: временная метка + ID машины + счетчик. Компактнее UUID, но слегка выдают время создания. Выбирайте осознанно.
# Пример ULID (библиотека ulid-py)
import ulid
user_id = ulid.new()
print(f"ULID: {user_id}")
# Вывод: 01ARZ3NDEKTSV4RRFFQ69G5FAVСлучайный ID — это замок, но нужен ещё охранник — проверка прав доступа. UUID защищает от перебора, но не от IDOR, если не проверять, кому принадлежит ресурс. Всегда проверяйте авторизацию.
# Flask пример: проверка авторизации
@app.route('/api/orders/<uuid:order_id>')
def get_order(order_id):
order = Order.query.get(order_id)
if order.user_id != current_user.id:
return 'Forbidden', 403
return jsonify(order.to_dict())# Пример: миграция для добавления внешнего UUID
ALTER TABLE users ADD COLUMN public_id UUID DEFAULT gen_random_uuid();
# В коде (SQLAlchemy)
import uuid
from sqlalchemy import Column, Integer, UUID
class User(Base):
__tablename__ = 'users'
id = Column(Integer, primary_key=True) # внутренний
public_id = Column(UUID, default=uuid.uuid7, unique=True) # внешнийСтатистики всё ещё существуют. И они всё ещё самодовольны. Не дайте им прочитать ваши серийные номера.
Хочешь закрепить знания на практике?
Решай задачи на Algolit — интерактивная платформа для обучения
Начать бесплатно →