Слышу часто: "У нас есть SecurityLab, проверяем код вручную". Потом выясняется, что проверяет его один человек в спешке, перед релизом, и половину уязвимостей пропускает. Потому что человек устаёт. Машина — нет.
Давайте разберёмся, что такое ошибка безопасности в коде, почему её так сложно найти вручную, и как автоматизировать эту проверку так, чтобы уязвимость не дошла до production.
Что считается ошибкой безопасности в коде
Ошибка безопасности — это фрагмент кода, который позволяет злоумышленнику выполнить несанкционированные действия. Получить доступ к данным, запустить свой код, обойти аутентификацию, стереть базу. Всё это.
Вот типичные примеры:
SQL injection. Ты берёшь пользовательский ввод и подставляешь его прямо в SQL-запрос.
# Опасно!
user_id = request.args.get('id')
query = f"SELECT * FROM users WHERE id = {user_id}"
result = db.execute(query)
Пользователь вводит вместо ID что-то вроде 1 OR 1=1 — и получает все записи из таблицы. Не круто.
XSS (Cross-Site Scripting). Ты выводишь в HTML данные от пользователя без экранирования.
<!-- Опасно! -->
<div>Привет, {{ user_input }}</div>
Если user_input содержит <script>alert('hacked')</script>, этот скрипт выполнится в браузере.
Hardcoded credentials. Пароли и API-ключи прямо в исходном коде.
API_KEY = "sk-1234567890abcdef"
DB_PASSWORD = "admin123"
Репозиторий утекает на GitHub — ключи у всех.
Path traversal. Пользователь может получить доступ к файлам вне предполагаемой директории.
# Опасно!
filename = request.args.get('file')
with open(f'/uploads/{filename}', 'r') as f:
return f.read()
Ввод ../../etc/passwd — и у тебя проблемы.
CSRF (Cross-Site Request Forgery). Запрос выполняется от имени пользователя без его ведома.
# Опасно! Нет проверки CSRF токена
@app.post('/transfer-money')
def transfer_money():
amount = request.form.get('amount')
# Переводим деньги
Это самые частые. Есть ещё куча других — но 80% уязвимостей в production укладываются в эти категории.
SAST и DAST: как машина ловит ошибки безопасности
Есть два подхода к автоматизированному поиску уязвимостей.
SAST (Static Application Security Testing) анализирует код без его запуска. Инструмент смотрит на исходник и ищет паттерны опасного кода. Быстро, дёшево, ловит много ошибок на этапе разработки.
Работает вот так: ты пишешь код, делаешь коммит, SAST-инструмент парсит AST (Abstract Syntax Tree) и проверяет каждый узел против базы правил. Если нашёл опасный паттерн — выдаёт warning.
Примеры SAST-инструментов: SonarQube, Checkmarx, Semgrep, Bandit (для Python).
# Bandit для Python
bandit -r ./src/
# Найдёт hardcoded credentials, SQL injections и другое
DAST (Dynamic Application Security Testing) — это когда ты запускаешь приложение и пытаешься его сломать. Фаззинг, перебор параметров, попытки обхода аутентификации — всё это.
DAST медленнее SAST, но ловит уязвимости, которые статический анализ может пропустить. Например, логику в runtime.
Примеры: Burp Suite, OWASP ZAP, Acunetix.
По-хорошему, нужны оба. SAST — в процессе разработки (в CI/CD pipeline), DAST — перед продакшеном.
Как уязвимости попадают в production
Сценарий, который я видел на трёх разных проектах:
- Разработчик пишет код. Не думает о безопасности — просто решает задачу.
- Code review. Коллега смотрит код, проверяет логику, синтаксис. Про безопасность обычно забывает (это не его обязанность, вроде).
- Тесты проходят. CI/CD зелёный. Merge в master.
- Неделю спустя — найдена уязвимость. Или хуже — обнаружили после взлома.
Почему так? Потому что:
Человек устаёт. Проверять безопасность — скучно. Нужно помнить 100500 правил, шаблонов, OWASP Top 10. Мозг отключается.
Нет стандартов. В одной команде знают про CSRF, в другой — нет. Всё зависит от опыта.
Это дорого. Нанять Security Engineer, который будет смотреть каждый MR? Не потянут маленькие компании.
Поэтому автоматизация — не опция. Это необходимость.
Как устроена автоматическая проверка в CI/CD
Ты добавляешь SAST-инструмент в pipeline, и он работает на каждый коммит. Вот пример с GitHub Actions и Semgrep:
name: Security Scan
on: [push, pull_request]
jobs:
semgrep:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: returntocorp/semgrep-action@v1
with:
config: >-
p/security-audit
p/owasp-top-ten
Или с GitLab CI:
security_scan:
stage: test
script:
- pip install bandit
- bandit -r ./src/ -f json -o bandit-report.json
artifacts:
reports:
sast: bandit-report.json
На каждый MR/PR — автоматическая проверка. Если нашлась уязвимость — блокируем merge до исправления.
Звучит просто? Но есть подводные камни.
Много false positives. Инструмент может пожаловаться на безопасный код. Приходится писать исключения, настраивать правила.
Нужно выбрать инструмент. SonarQube — мощный, но дорогой. Semgrep — проще, но менее гибкий. Bandit для Python, ESLint-плагины для JavaScript.
Разработчики начинают игнорировать предупреждения. Если каждый день видишь 50 warnings, перестаёшь обращать внимание.
Примеры реальных уязвимостей и как их ловить
Давайте я покажу несколько реальных случаев из своего опыта. И как их автоматически ловить.
Пример 1: SQL injection в параметре поиска
Код:
def search_users(query):
sql = f"SELECT * FROM users WHERE name LIKE '%{query}%'"
return db.execute(sql)
Пользователь вводит: %' OR '1'='1
Результат: получает все пользователей вместо результатов поиска.
Как ловить:
# Правильно — параметризованный запрос
def search_users(query):
sql = "SELECT * FROM users WHERE name LIKE ?"
return db.execute(sql, (f"%{query}%",))
SAST-инструмент (Bandit, Semgrep) найдёт f-string в SQL-запросе и выдаст warning. Semgrep правило:
rules:
- id: sql-injection
patterns:
- pattern-either:
- pattern: |
$SQL = f"...{...}..."
db.execute($SQL)
message: "SQL injection risk"
severity: ERROR
Пример 2: Hardcoded credentials
class DatabaseConfig:
HOST = "db.example.com"
USER = "admin"
PASSWORD = "SuperSecret123!"
Это попадёт в git history. Даже если потом удалишь — останется в истории.
Как ловить:
Bandit находит это автоматически:
bandit -r ./
# Hardcoded SQL password: "SuperSecret123!"
Или используй git hooks:
# .git/hooks/pre-commit
git-secrets --scan
Лучше вообще хранить credentials в переменных окружения или vault:
import os
PASSWORD = os.getenv('DB_PASSWORD')
Пример 3: XSS в Django шаблоне
<!-- Опасно! -->
<h1>{{ user_comment }}</h1>
Если user_comment = "<script>alert('xss')</script>", скрипт выполнится.
Правильно:
<!-- Django автоматически экранирует -->
<h1>{{ user_comment|escape }}</h1>
<!-- Или просто использовать {{ user_comment }} — Django экранирует по умолчанию -->
Но нужно следить, чтобы не использовал |safe:
<!-- Опасно! -->
<h1>{{ user_comment|safe }}</h1>
SAST найдёт это правило. Semgrep:
rules:
- id: django-xss
pattern: "{{ ... |safe }}"
message: "Potential XSS vulnerability"
severity: WARNING
Пример 4: Path traversal в загрузке файлов
@app.post('/upload')
def upload_file():
filename = request.files['file'].filename
file.save(f'/uploads/{filename}')
Пользователь загружает файл с именем ../../etc/passwd — и перезаписывает важный файл.
Правильно:
import os
from werkzeug.utils import secure_filename
@app.post('/upload')
def upload_file():
filename = secure_filename(request.files['file'].filename)
# Проверяем, что filename не содержит ..
if '..' in filename or filename.startswith('/'):
return "Invalid filename", 400
file.save(os.path.join('/uploads', filename))
secure_filename удалит все опасные символы. Но лучше быть явным и проверить.
Как внедрить автоматическую проверку безопасности
Шаг 1: Выбери инструмент. Для Python — Bandit или Semgrep. Для JavaScript — ESLint с плагинами безопасности. Для Java — SpotBugs. Для Go — gosec.
Шаг 2: Добавь в CI/CD. Выше я показал примеры для GitHub Actions и GitLab CI.
Шаг 3: Настрой правила. Отключи false positives, добавь свои правила для специфики твоего проекта.
Шаг 4: Блокируй мерж при критических уязвимостях.
# GitHub Actions
- name: Check SAST results
if: failure()
run: exit 1
Шаг 5: Обучи команду. Покажи разработчикам, как читать отчёты, как исправлять уязвимости.
Почему автоматизация лучше, чем ручная проверка
По моему опыту, ручная проверка ловит максимум 60% уязвимостей. Потому что:
- Человек забывает правила
- Устаёт к концу дня
- Зависит от опыта конкретного человека
- Занимает время
Автоматизация:
- Ловит 90%+ известных паттернов уязвимостей
- Работает 24/7 без перерывов
- Одинаковая проверка для всех
- Экономит время разработчикам
Да, будут false positives. Но это лучше, чем пропустить реальную уязвимость.
На одном проекте мы внедрили Semgrep в pipeline. В первую неделю нашли 7 уязвимостей, которые раньше пропускали. Одна из них была SQL injection в админ-панели. Спасло проект от потенциального взлома.
Если ты ищешь решение, которое не требует настройки кучи инструментов — попробуй Distiq. Это AI-бот для code review, который встаёт прямо в твой GitLab или GitHub. Анализирует каждый MR/PR, ловит уязвимости, баги, проблемы с производительностью. Интегрируется за 2 минуты. Серверы в России, данные не уходят за рубеж.
