6 дыр в безопасности инвесторского дашборда — нашёл за час, закрыл за вечер
27 марта 2026 года я наткнулся на гайд по безопасности для AI-проектов. Думал, бегло пролистаю и закрою. Вместо этого открыл свой код и нашёл шесть уязвимостей. В продакшене. На боевом сервере, куда 11 инвесторов заходят каждый день смотреть доходность своих вилл.
Это не история про взлом и катастрофу. Это история про то, как не случился взлом — и что именно я нашёл, когда впервые посмотрел на свой код глазами безопасника, а не разработчика.
У меня дашборд для инвесторов: каждый из 11 партнёров заходит и видит доходность своих объектов, историю бронирований, прогнозы на месяц. Там реальные финансовые данные. Реальные чужие деньги. И до того вечера — шесть дыр, через которые эти данные теоретически можно было получить без приглашения.
Как я вообще решил проверить
Когда строишь продукт быстро — а мой дашборд был написан за несколько сессий, параллельно с управлением виллами, настройкой ботов и ещё десятком задач — безопасность не в приоритете. Ты думаешь: работает, инвесторы довольны, разберёмся потом.
«Потом» в моём случае наступило через несколько месяцев, когда я наткнулся на чеклист безопасности для веб-приложений. Не специализированный, а базовый — для тех, кто не профессиональный безопасник, но хочет не облажаться.
Открыл список и начал методично проверять свой код пункт за пунктом. Первый пункт: «Хранятся ли секреты (пароли, ключи) в коде или в переменных окружения?»
Открыл файл конфигурации сервера. Пароль от базы данных лежал прямо там. В виде строки. В репозитории.
Уязвимость 1: пароли в коде
Это самая классическая ошибка, о которой все знают и почти все совершают на ранних этапах. Пароль от PostgreSQL, секрет сессии, ключ к внешнему API — всё это было прямо в коде.
Последствия: если бы кто-то получил доступ к файлам сервера (через другую уязвимость, через скомпрометированный сервис, через случайный git push в публичный репозиторий) — он автоматически получал бы полный доступ к базе данных.
Решение: вынести все секреты в файл `.env`, добавить его в `.gitignore`, и читать через переменные окружения. Это стандарт, который занимает час работы и радикально снижает риск утечки.
DB_PASSWORD = "my_real_password_123" прямо в config.pyПосле:
DB_PASSWORD = os.environ.get("DB_PASSWORD") + секрет в .env файле на сервере
Уязвимость 2: CORS без ограничений
CORS (Cross-Origin Resource Sharing) — это механизм, который определяет, каким сайтам разрешено делать запросы к вашему API. Если настроить CORS на «разрешить всем» — это значит, что любой сайт в интернете может делать запросы к вашему серверу от имени вошедшего пользователя.
В моём случае CORS стоял на `*` — то есть разрешить всем. Практически это означало: если инвестор зашёл в мой дашборд, а потом открыл вредоносный сайт — тот сайт мог в фоне делать запросы к моему API с сессионным токеном инвестора и читать его данные.
Решение: прописать конкретный список разрешённых источников.
CORS_ORIGINS = ["*"]После:
CORS_ORIGINS = ["https://dashboard.solarpropertybali.com", "https://solarpropertybali.com"]
Уязвимость 3: нет ограничения на попытки входа
Rate limiting на авторизацию — это механизм, который блокирует IP после N неудачных попыток войти. Без него можно перебирать пароли сколько угодно времени. При современных скоростях это вопрос часов или дней, а не лет.
В моём дашборде такого ограничения не было. Теоретически, зная email инвестора (который несложно узнать через социальные сети или открытые источники), можно было запустить перебор паролей по словарю.
Решение: добавить счётчик неудачных попыток по IP, после 4 быстрых попыток — блокировка на 15 минут, с нарастанием. Заодно настроил логирование: каждая неудачная попытка входа пишется в отдельный файл, а если за час больше 10 попыток с одного IP — мне прилетает алерт в Telegram.
Уязвимость 4: нет заголовков безопасности
HTTP-заголовки безопасности — это инструкции браузеру, как он должен обрабатывать страницу. Без них браузер использует настройки по умолчанию, которые часто слишком лояльны.
Мой сервер не отправлял ни одного из стандартных заголовков безопасности. Это открывало ряд классических атак: XSS через инъекцию скриптов, clickjacking через встраивание страницы в iframe, MIME-sniffing, при котором браузер пытается угадать тип файла и может выполнить код там, где не ожидалось.
Решение: добавить набор стандартных заголовков в конфигурацию nginx и приложения. Это занимает час, но закрывает целый класс атак.
X-Content-Type-Options: nosniffX-Frame-Options: DENYX-XSS-Protection: 1; mode=blockStrict-Transport-Security: max-age=31536000Content-Security-Policy: default-src 'self'
Уязвимость 5: изоляция данных между инвесторами
Это была самая неприятная находка. У меня 11 инвесторов, у каждого свои виллы. Дашборд должен показывать каждому только его объекты.
В коде это было реализовано на уровне фронтенда: запрос шёл за всеми данными, а потом JavaScript фильтровал по investor_id текущего пользователя. Это называется «безопасность на стороне клиента» — и это антипаттерн.
Что это означало на практике: любой инвестор, который знает базовый JavaScript, мог открыть DevTools браузера, посмотреть сетевые запросы и увидеть, что API возвращает данные по всем инвесторам сразу. А если немного подправить запрос, убрав фильтрацию, — он получал финансовые данные всех остальных.
Решение: перенести фильтрацию на сторону сервера. API теперь принимает ID авторизованного пользователя из сессии (а не из параметров запроса, которые можно подменить) и возвращает только его данные. Неважно, что пользователь пишет в запросе — он физически не получит чужие данные.
Уязвимость 6: длина и сложность сессионных токенов
Шестая уязвимость была менее критичной, но в связке с отсутствием rate limiting (уязвимость 3) могла бы стать проблемой. Сессионные токены были короче рекомендованной длины и генерировались не через криптографически стойкий генератор случайных чисел.
Это теоретически позволяло угадать действующий токен методом перебора — хотя это требовало бы большого числа запросов, которые rate limiting теперь заблокирует.
Решение: переключить генерацию токенов на `secrets.token_hex(32)` в Python — стандартная библиотека, 64 символа из криптографически стойкого генератора. Занимает пять минут.
Итог одного вечера
Шесть уязвимостей, найденных за час по чеклисту. Все закрыты за вечер. Сумарное время работы — около пяти часов с учётом тестирования каждого исправления.
- Пароли вынесены в переменные окружения
- CORS ограничен списком доверенных доменов
- После 4 быстрых попыток входа — автоблокировка IP
- Неудачные логины пишутся в лог, алерт при аномалии
- Добавлены стандартные HTTP-заголовки безопасности
- Изоляция данных перенесена на сторону сервера
- Сессионные токены пересозданы через криптостойкий генератор
Это не был проект на месяц. Это была вечерняя сессия с четким чеклистом и намерением закрыть всё, что найду.
Почему безопасность откладывается
Я понимаю, почему это происходит — сам через это прошёл. Когда строишь продукт быстро, у тебя два состояния: «работает» и «не работает». Безопасность незаметна в обоих случаях. Если её нет — ничего не ломается сразу. Если она есть — пользователь не видит разницы.
Это создаёт ложное ощущение, что с безопасностью «всё нормально», потому что ничего плохого не случилось. На самом деле это просто означает, что атаки ещё не было.
Триггером стал внешний гайд — чужой взгляд на то, что стоит проверить. У меня не было «очевидного» повода смотреть на безопасность, потому что система работала и инвесторы не жаловались. Но стоило взять чеклист и пройти по нему — картина стала другой.
Когда особенно важно это проверить
Не у каждого продукта одинаковый профиль риска. Но есть несколько признаков, при которых безопасность стоит проверить немедленно:
- Чужие финансовые данные. Если у вас дашборд, личный кабинет или отчёт, где клиент видит деньги — своё или доходность инвестиций.
- Несколько пользователей с разными правами. Если у вас не одна учётная запись для всех, а разные роли с разным доступом.
- API, которое публично доступно. Если любой может отправить запрос на ваш сервер — нужны и аутентификация, и rate limiting.
- Данные, которые стоит скрыть от конкурентов. Цены, клиентская база, финансовая модель.
Если у вас хотя бы один из этих пунктов — возьмите любой публичный чеклист безопасности для веб-приложений и пройдите по нему. Не нужно быть специалистом по безопасности. Нужна пара часов и готовность закрыть то, что найдёшь.
Что изменилось в процессе
После этого случая я добавил одно правило в свой процесс разработки: прежде чем отдать доступ к любому продукту с пользовательскими данными — пройти базовый чеклист безопасности. Не «когда будет время», а до первого реального пользователя.
Это изменение в привычке, а не в технологиях. У меня дашборд для инвесторов был готов за один день — это хорошо. Но следующий час после запуска должен быть посвящён проверке, а не следующему фичу.
Автоматизация бизнеса, о которой я много пишу, требует доверия — инвесторов, клиентов, партнёров. Это доверие легко сломать одним инцидентом. И намного дешевле потратить вечер на чеклист, чем потом объяснять, как чужие данные оказались доступны посторонним людям.
Безопасность — это не про «потом настроим». Особенно когда на дашборде чужие деньги.
— Секреты (пароли, ключи) в переменных окружения, не в коде
— CORS ограничен конкретными доменами
— Rate limiting на авторизацию (4-5 попыток → временная блокировка)
— Логирование неудачных попыток входа + алерт при аномалии
— HTTP-заголовки безопасности (X-Frame-Options, HSTS, CSP)
— Изоляция данных между пользователями на стороне сервера
— Сессионные токены через криптостойкий генератор, достаточной длины
Практическая сторона: как я искал уязвимости
Хочу отдельно сказать про процесс, потому что он был простым и воспроизводимым.
Я не запускал специализированные сканеры или инструменты пентестинга. Я просто взял гайд, открыл код и начал задавать вопросы: «Где у меня хранятся пароли? Что происходит, если подменить параметр запроса? Что выдаёт сервер, если я попробую войти сто раз подряд?»
Большинство уязвимостей находятся именно так — через систематические вопросы, а не через сложные инструменты. Сложные инструменты нужны для сложных атак. Базовые ошибки видны невооружённым глазом, если знаешь, куда смотреть.
Я использовал Claude, чтобы ускорить проверку: описывал текущую реализацию и спрашивал, какие риски он видит. Это хорошо работает как второй взгляд — ИИ хорошо знает типичные паттерны уязвимостей и может указать на то, что ты пропустил, потому что сам же писал этот код и привык к нему.
В моей системе Альтрон часть проверок теперь автоматизирована: раз в неделю агент сверяет конфигурацию сервера с чеклистом и отправляет мне отчёт. Не вместо ручной проверки, а в дополнение к ней — чтобы ни одна случайная правка не открыла дыру, которую я успел закрыть.
Безопасность строится не из одной сессии раз в полгода. Она строится из привычек: проверять при запуске, проверять при изменениях, получать алерт при аномалиях. Именно так выглядит «безопасно» на практике — не как крепость, а как регулярный осмотр того, что уже построено.