$ sudo teach|
    $ sudo teach|
    IT school
  • Telegram
  • Партнёрам
  • Все курсы
$ sudo teach IT
OOO "SALEPROFIT"Контакты и реквизитыIT-Park Logo

Школа

  • Блог
  • Проверить сертификат

Сотрудничество

  • Стать учителем
  • Партнёрская программа
  • О проекте

Право

  • Оферта
  • Политика конфиденциальности

© 2023–2026 $ sudo teach IT™. All Rights Reserved. Public user contributions licensed under CC BY-SA 4.0 license with attribution required
TelegramGitHubYouTube
ГлавнаяБлогБезопасный Python для продакшена: опасные практики и как их заменять

Безопасный Python для продакшена: опасные практики и как их заменять

$ sudo teach IT
·22 июля 2026 г.·10 мин·49
Безопасный Python для продакшена: опасные практики и как их заменять

Разберём типовые источники проблем: инъекции, небезопасные десериализации, неверная обработка секретов и ошибок. Дадим практические шаблоны защиты.

Содержание
Опасные практики №1: инъекции и небезопасная работа с вводомSQL-инъекции: когда строковая сборка запросов становится катастрофойNoSQL-инъекции: «JSON как строка» и экзотика запросовCommand injection: запуск через shell и подстановка строкSSRF: когда ваш сервер ходит туда, куда ему сказалиОпасные практики №2: небезопасная десериализацияpickle: удобство, которое иногда превращается в RCEJSON и «вольные» структуры: где прячется логическая уязвимостьОпасные практики №3: секреты — как их «случайно» раскрываютСекреты в коде, логах и ошибкахПеременные окружения: не подмена «управления секретами»Шифрование «для галочки» и неверные режимыОпасные практики №4: обработка ошибок и раскрытие информацииВозвращать пользователю исключения — значит подсветить внутренностиНеуправляемые коды статуса и «инфо-сигналы»Практические шаблоны защиты: от input validation до secure-by-default1) Центральная валидация входа2) Таймауты и лимиты: безопасность как устойчивость3) Безопасные политики логирования4) Минимальные привилегии и «безопасные дефолты»Безопасный код на Python: конкретные примеры замены опасного на безопасноеУбираем eval/exec: даже если «только для своих»Убираем небезопасные потоки данных в шаблоныКак проверять безопасность: практики, которые дают эффект1) Профиль угроз (threat model) — хотя бы минимальный2) SAST/линтеры и правила для уязвимостей3) Динамическое тестирование

Python часто выбирают за скорость разработки и удобную экосистему. Но безопасность — это не «добавляется позже»: она требует дисциплины на уровне архитектуры, обработки ввода, секретов, логирования и ошибок. В продакшене проблемы редко возникают из‑за одного «злого» бага. Чаще это цепочка: небезопасная практика → отсутствие защитного контроля → утечка данных → неверная реакция системы (иногда ещё и раскрытие внутренностей).

Ниже разберём наиболее типичные источники уязвимостей для Python‑приложений и дадим практические шаблоны замены опасных подходов на более безопасные. Материал ориентирован на тех, кто пишет backend (API, веб‑сервисы, очереди) и уже сталкивался с инцидентами или понимает, что «на тестах работало».


Опасные практики №1: инъекции и небезопасная работа с вводом

SQL-инъекции: когда строковая сборка запросов становится катастрофой

Классический сценарий в Python:

  • разработчик собирает SQL как строку;
  • в запрос попадает пользовательский ввод;
  • база интерпретирует это как часть команды.

Пример опасного кода (особенно в связке с f-string):

code
# ОПАСНО
user_id = request.args["user_id"]
sql = f"SELECT * FROM users WHERE id = {user_id}"
cursor.execute(sql)

Как заменить:

  1. Использовать параметризованные запросы (placeholder).
  2. Валидировать типы и диапазоны до передачи в БД.
  3. Ограничивать права учетной записи БД (минимальные привилегии).

Пример безопасного варианта для psycopg (аналогично для большинства драйверов):

code
# БЕЗОПАСНО
user_id = int(request.args["user_id"])  # валидация типа
sql = "SELECT id, email FROM users WHERE id = %s"
cursor.execute(sql, (user_id,))

Нюанс: параметризация работает для значений, но не для частей синтаксиса (например, нельзя безопасно подставить имя таблицы через параметр). В таких случаях используют whitelist.

code
# Безопасно выбирать имя таблицы через whitelist
table = request.args.get("table")
allowed = {"users", "orders"}
if table not in allowed:
    raise ValueError("Unknown table")

sql = f"SELECT * FROM {table} WHERE id = %s"  # таблица только из whitelist
cursor.execute(sql, (user_id,))

NoSQL-инъекции: «JSON как строка» и экзотика запросов

В MongoDB или похожих системах инъекции могут быть менее очевидными: если вы «склеиваете» фильтр из входных данных без контроля структуры, злоумышленник может влиять на операторную часть ($where, $gt, $ne, и т. п.).

Плохой паттерн:

code
# ОПАСНО: пользователь может передать структуру-операторы
filter_doc = json.loads(request.body)  # например, {"email": {"$ne": null}}
collection.find(filter_doc)

Замена:

  • валидировать ожидаемую схему;
  • извлекать только конкретные поля;
  • запрещать операторные ключи ($...);
  • предпочтительно — явно строить фильтр из безопасных значений.
code
# БЕЗОПАСНО: строим фильтр из явных полей
payload = json.loads(request.body)

email = payload.get("email")
if not isinstance(email, str) or len(email) > 200:
    raise ValueError("Invalid email")

# Никаких операторов из payload
result = collection.find({"email": email})

Command injection: запуск через shell и подстановка строк

Если приложение формирует команду и запускает её через shell (shell=True или аналог), любая строка из входа потенциально превращается в вектор атаки.

Опасно:

code
# ОПАСНО
cmd = f"convert {name} /tmp/out.png"
subprocess.run(cmd, shell=True, check=True)

Безопасно:

  • не использовать shell;
  • передавать аргументы как список;
  • валидировать имя файла (допустимые символы, расширение);
  • избегать интерпретации метасимволов.
code
# БЕЗОПАСНО
from pathlib import Path
import re
import subprocess

name = request.args["name"]  # например, "photo.jpg"
if not re.fullmatch(r"[a-zA-Z0-9._-]{1,100}\.jpg", name):
    raise ValueError("Invalid filename")

src = Path("/data/in") / name
subprocess.run(["convert", str(src), "/tmp/out.png"], check=True)

SSRF: когда ваш сервер ходит туда, куда ему сказали

SSRF (Server-Side Request Forgery) возникает, если приложение делает HTTP-запросы на URL, полученный от пользователя, без проверки. Это может дать доступ к внутренним сервисам (localhost, 169.254.169.254 — metadata у облаков), а иногда к файловым схемам или внутренним сетям.

Безопасный подход:

  • разрешать только whitelisted домены или схемы;
  • запрещать IP‑диапазоны из приватных/локальных;
  • валидировать DNS (IP резолвить и проверять результат);
  • ограничивать таймауты и размер ответа.

Схема проверки IP с учётом приватности (пример):

code
import ipaddress
from urllib.parse import urlparse

def is_private_or_reserved(host: str) -> bool:
    # Простейший вариант: если host — IP
    try:
        ip = ipaddress.ip_address(host)
        return ip.is_private or ip.is_loopback or ip.is_link_local or ip.is_reserved or ip.is_multicast
    except ValueError:
        return False

def validate_url(url: str) -> None:
    parsed = urlparse(url)
    if parsed.scheme not in {"http", "https"}:
        raise ValueError("Unsupported scheme")

    host = parsed.hostname
    if not host:
        raise ValueError("No host")

    if is_private_or_reserved(host):
        raise ValueError("Private/reserved host is not allowed")

Важно: если домен резолвится в несколько IP, недостаточно проверять только строку. Надёжнее — резолвить и проверять каждый IP. А ещё лучше — whitelist доменов, если бизнес‑логика позволяет.


Опасные практики №2: небезопасная десериализация

pickle: удобство, которое иногда превращается в RCE

pickle в Python не является форматом данных в безопасном смысле. Это по сути механизм восстановления объектов, который может исполнять код в процессе загрузки. Если атакующий контролирует байты, полученные на входе pickle.loads, вы получаете удалённое выполнение кода (RCE).

Опасно:

code
# ОПАСНО: payload контролируется атакующим
import pickle

data = request.body
obj = pickle.loads(data)  # RCE при crafted payload

Замена:

  • никогда не делать pickle.loads над данными из сети/очередей без строгой песочницы;
  • использовать безопасные форматы: JSON, MessagePack (с осторожностью), protobuf;
  • если нужно хранить сложные структуры — проектировать схему и валидировать её.

Безопасный пример на JSON:

code
import json
from dataclasses import dataclass

@dataclass
class User:
    id: int
    email: str

def parse_user(payload: bytes) -> User:
    obj = json.loads(payload.decode("utf-8"))
    if not isinstance(obj, dict):
        raise ValueError("Invalid payload")
    user_id = obj.get("id")
    email = obj.get("email")
    if not isinstance(user_id, int) or not isinstance(email, str):
        raise ValueError("Invalid fields")
    return User(id=user_id, email=email)

JSON и «вольные» структуры: где прячется логическая уязвимость

JSON — безопаснее, но не значит «безопасно всегда». Если вы:

  • доверяете структуре целиком;
  • затем передаёте в опасные места (например, в SQL как часть запроса);
  • или используете динамические атрибуты/eval/exec;

то уязвимость никуда не исчезает, просто меняет форму.

Шаблон защиты:

  • валидировать схему входа;
  • использовать типы;
  • отбрасывать неизвестные поля;
  • нормализовать значения (например, email приводить к нижнему регистру, номера — к диапазонам).

На практике это делают через pydantic или dataclasses + ручную валидацию.


Опасные практики №3: секреты — как их «случайно» раскрывают

Секреты в коде, логах и ошибках

Самые частые утечки:

  • ключи и токены в репозитории;
  • секреты в конфигурации без защиты;
  • логирование заголовков/тел входящих запросов;
  • возврат деталей исключения пользователю (traceback в API).

Ключевая мысль: секреты нужно проектировать как данные, которые нельзя случайно вывести наружу. Это означает:

  • централизованное управление секретами (секрет-хранилище/переменные окружения);
  • фильтрацию логов;
  • аккуратную обработку исключений;
  • разделение сред (dev/stage/prod).

Практический шаг для логирования: маскирование. Например, токены в заголовке Authorization:

code
def mask_headers(headers: dict) -> dict:
    masked = dict(headers)
    auth = masked.get("Authorization")
    if auth:
        masked["Authorization"] = auth[:10] + "...(masked)"
    return masked

А чтобы не возвращать traceback наружу:

  • в API отвечать нейтральным сообщением;
  • логировать подробности на стороне сервера;
  • не включать секреты в сообщения исключений.

Переменные окружения: не подмена «управления секретами»

Переменные окружения удобны, но их легко утечь через:

  • ошибочные dumps;
  • диагностику в контейнерах;
  • ошибки в CI/CD.

Минимальный набор зрелости:

  • секреты хранятся в системе управления секретами (cloud secrets manager / vault);
  • в репозиторий — никогда;
  • в логи — никогда;
  • доступ по принципу минимальных привилегий.

Шифрование «для галочки» и неверные режимы

Если вы шифруете данные сами, распространены ошибки:

  • слабый алгоритм или режим без аутентификации;
  • хранение ключей рядом с данными;
  • отсутствие ротации ключей.

Если бизнес требует шифрования:

  • используйте стандартные решения и библиотеки;
  • предпочтите схемы с аутентифицированным шифрованием (AEAD);
  • делайте ротацию ключей и версионирование;
  • фиксируйте threat model: от чего именно защищаемся (утечка БД? перехват трафика? компрометация хоста?).

Опасные практики №4: обработка ошибок и раскрытие информации

Возвращать пользователю исключения — значит подсветить внутренности

Надёжное правило: пользователю — только то, что нужно для корректного использования API, без деталей инфраструктуры.

Плохой паттерн:

  • API возвращает traceback;
  • 500 содержит сообщение «relation does not exist: public.users_password_hash».

Замена:

  • единый middleware/обработчик ошибок;
  • сопоставление кодов ошибок бизнес‑уровня;
  • подробности — только в серверных логах.

Пример упрощённого подхода для FastAPI:

code
from fastapi import FastAPI, Request
from fastapi.responses import JSONResponse
import traceback

app = FastAPI()

@app.middleware("http")
async def error_handling_middleware(request: Request, call_next):
    try:
        return await call_next(request)
    except Exception:
        # Логи с деталями — на сервере
        traceback.print_exc()
        return JSONResponse(
            status_code=500,
            content={"detail": "Internal server error"}
        )

В продакшене print_exc() обычно заменяют на структурированный логгер (и следят, чтобы там не было секретов).

Неуправляемые коды статуса и «инфо-сигналы»

Разные ответы на одинаковые по смыслу запросы могут раскрывать:

  • существование пользователя (account enumeration);
  • структуру ресурсов;
  • особенности авторизации.

Решение:

  • одинаковые ответы для «не найдено» и «нет прав», если так задумана модель безопасности;
  • использовать 401 vs 403 осмысленно;
  • применять rate limiting и защиту от перебора.

Практические шаблоны защиты: от input validation до secure-by-default

Ниже набор «скелетов», которые стоит держать под рукой.

1) Центральная валидация входа

Золотая практика: «невалидное — отбрасываем рано». Желательно на уровне:

  • схемы запроса (Pydantic / Marshmallow);
  • лимитов размеров тела;
  • ограничений по скорости (rate limiting);
  • таймаутов на сетевые операции.

Пример на pydantic:

code
from pydantic import BaseModel, EmailStr, Field

class LoginRequest(BaseModel):
    email: EmailStr
    password: str = Field(min_length=8, max_length=200)

# Внутри endpoint-логики вы получите уже валидированные поля

EmailStr не магия от всех проблем, но снижает риск логических ошибок и «грязного» ввода.

2) Таймауты и лимиты: безопасность как устойчивость

SSRF, медленные запросы и «зависания» — всё это часто заканчивается DoS. Типовые меры:

  • таймауты на HTTP‑клиентах;
  • лимит размера тела запросов;
  • ограничение количества одновременных задач;
  • backpressure на очередях.

На практике: всегда задавайте timeout и не используйте бесконечные ожидания.

3) Безопасные политики логирования

Минимум:

  • не логировать секреты;
  • маскировать токены/пароли;
  • не логировать целиком тело запроса, если оно потенциально содержит личные данные;
  • хранить логи с правами, ограничивающими доступ.

Пример маскирования в middleware (концептуально):

code
SENSITIVE_FIELDS = {"password", "token", "authorization"}

def mask_payload(payload: dict) -> dict:
    masked = {}
    for k, v in payload.items():
        if k.lower() in SENSITIVE_FIELDS:
            masked[k] = "***"
        else:
            masked[k] = v
    return masked

4) Минимальные привилегии и «безопасные дефолты»

Учетная запись БД:

  • только нужные операции (обычно без прав на DDL);
  • отдельные БД/схемы для окружений.

Файловая система:

  • контейнеры без лишних томов;
  • принцип «запись только куда нужно».

Сеть:

  • egress ограничивать (где возможно);
  • закрывать внутренние порты, если они не нужны наружу.

Безопасный код на Python: конкретные примеры замены опасного на безопасное

Убираем eval/exec: даже если «только для своих»

Плохой вариант:

code
# ОПАСНО
expr = request.query_params["expr"]
result = eval(expr)

Замена зависит от цели. Если выражения должны быть ограниченными, создайте DSL с парсером или используйте безопасный вариант интерпретатора (но наивные решения всё равно опасны).

Если задача — расчёт математических формул из ограниченного множества, лучше заранее определить допустимые операции и использовать парсер (например, ast с whitelist узлов).

Пример концепции через AST (упрощённо):

code
import ast
import operator as op

ALLOWED = {
    ast.Add: op.add,
    ast.Sub: op.sub,
    ast.Mult: op.mul,
    ast.Div: op.truediv,
}

def safe_eval_math(expr: str) -> float:
    node = ast.parse(expr, mode="eval")

    def walk(n):
        if isinstance(n, ast.Constant) and isinstance(n.value, (int, float)):
            return float(n.value)
        if isinstance(n, ast.BinOp) and type(n.op) in ALLOWED:
            return ALLOWED[type(n.op)](walk(n.left), walk(n.right))
        raise ValueError("Expression not allowed")

    return walk(node.body)

Это не «универсальный безопасный eval», а демонстрация подхода: вы контролируете допустимые конструкции.

Убираем небезопасные потоки данных в шаблоны

Если приложение формирует HTML, всегда учитывайте контекст. В Python‑фреймворках (Jinja2 и др.) используйте autoescape и избегайте принудительной вставки «сырого» HTML.


Как проверять безопасность: практики, которые дают эффект

1) Профиль угроз (threat model) — хотя бы минимальный

Перед «хакинг-генератором» вопросов стоит ответить:

  • какие входы контролирует атакующий (HTTP, очереди, файлы)?
  • что критично украсть/изменить?
  • какие границы доверия (dev/prod, internal/external)?
  • какой ущерб при компрометации?

Это помогает выбрать правильные приоритеты: например, SSRF и секреты чаще бьют в прод раньше, чем «очень редкая RCE через pickle».

2) SAST/линтеры и правила для уязвимостей

Используйте:

  • линтеры с security-правилами;
  • шаблоны кода в review (запрещать pickle.loads от входа, запретить shell=True с пользовательскими строками);
  • тесты для валидации и отказоустойчивости.

3) Динамическое тестирование

DAST/пентесты помогают поймать то

Войдите, чтобы поставить лайк и оставить комментарий.

Автор

$ sudo teach IT

Продолжите обучение

Все курсы
Python – для начинающих!

Python – для начинающих!

С нуля до профессионального уровня. Подходит для всех. Учитесь каждый день и овладейте самым популярным языком программирования.

Перейти к курсу
Приложения для iPhone и Apple Watch на SwiftUI

Приложения для iPhone и Apple Watch на SwiftUI

Разработка приложений для iPhone и Apple Watch на SwiftUI: навигация, SwiftData, виджеты, часы, выпуск. Нужен Mac с Xcode 27, сами устройства не нужны.

Перейти к курсу
Ботостроение Telegram

Ботостроение Telegram

Лёгкий, быстрый и доступный способ познакомиться с миром ботостроения в Telegram. Видео, конспекты, практика и помощь – всё у нас на курсе.

Перейти к курсу
Приложения для macOS на SwiftUI

Приложения для macOS на SwiftUI

Разработка приложений для Mac на SwiftUI: окна и меню, Liquid Glass, SwiftData, сеть, выпуск. Нужен Mac с macOS 27 и Xcode 27.

Перейти к курсу

Другие статьи

Python для аналитики на практике: как строить признаки и проверять их воспроизводимость
python

Python для аналитики на практике: как строить признаки и проверять их воспроизводимость

Поймём, где чаще всего ломается воспроизводимость (версии данных, фильтры, дрейф) и как оформить пайплайн признаков так, чтобы результаты совпадали между запусками и окружениями.

17 сентября 2026 г.
1040
Системный дебаг памяти и производительности в Python: где искать утечки и как проверять гипотезы
системный

Системный дебаг памяти и производительности в Python: где искать утечки и как проверять гипотезы

Пошагово разберём типовые причины роста памяти (кэширование, циклические ссылки, удержание объектов в замыканиях, неверная работа с ресурсами) и как подтверждать догадки трейсами, профилировщиками и метриками. В конце — чек-лист расследования для задач из

15 сентября 2026 г.
1030
Создание Telegram бота в 2026 легко и просто! Полный курсы!
создание

Создание Telegram бота в 2026 легко и просто! Полный курсы!

19 июня 2026 г.
1421
Как выбрать, куда идти в IT: веб, боты, данные — без хаоса
выбрать

Как выбрать, куда идти в IT: веб, боты, данные — без хаоса

Разберём самые понятные направления для новичка (веб, боты, аналитика/данные) и чем они отличаются по шагам обучения. В конце вы получите простой план, как проверить, что вам подходит, и какой стартовый путь выбрать.

5 октября 2026 г.
580
Путь от README до демо: как собрать портфолио разработчика, чтобы его хотели смотреть
путь

Путь от README до демо: как собрать портфолио разработчика, чтобы его хотели смотреть

Разберём структуру репозитория, как упаковать проект в понятную историю (проблема → решение → запуск → метрики качества) и какие артефакты повышают доверие: скринкасты, архитектурная схема, чек-лист продакшн-готовности.

17 сентября 2026 г.
1070
Как понять, какой язык программирования учить первым: простой тест и выбор
понять

Как понять, какой язык программирования учить первым: простой тест и выбор

Помогу новичку выбрать первый язык без стресса: объясню варианты по целям (веб, боты, анализ данных), что учить в первую очередь и как не потратить месяцы впустую. В конце — краткий план старта на ближайшие недели.

2 октября 2026 г.
620

Комментарии

Станьте первым в обсуждении

Поделитесь мнением о статье или задайте вопрос автору

Содержание

Опасные практики №1: инъекции и небезопасная работа с вводомSQL-инъекции: когда строковая сборка запросов становится катастрофойNoSQL-инъекции: «JSON как строка» и экзотика запросовCommand injection: запуск через shell и подстановка строкSSRF: когда ваш сервер ходит туда, куда ему сказалиОпасные практики №2: небезопасная десериализацияpickle: удобство, которое иногда превращается в RCEJSON и «вольные» структуры: где прячется логическая уязвимостьОпасные практики №3: секреты — как их «случайно» раскрываютСекреты в коде, логах и ошибкахПеременные окружения: не подмена «управления секретами»Шифрование «для галочки» и неверные режимыОпасные практики №4: обработка ошибок и раскрытие информацииВозвращать пользователю исключения — значит подсветить внутренностиНеуправляемые коды статуса и «инфо-сигналы»Практические шаблоны защиты: от input validation до secure-by-default1) Центральная валидация входа2) Таймауты и лимиты: безопасность как устойчивость3) Безопасные политики логирования4) Минимальные привилегии и «безопасные дефолты»Безопасный код на Python: конкретные примеры замены опасного на безопасноеУбираем eval/exec: даже если «только для своих»Убираем небезопасные потоки данных в шаблоныКак проверять безопасность: практики, которые дают эффект1) Профиль угроз (threat model) — хотя бы минимальный2) SAST/линтеры и правила для уязвимостей3) Динамическое тестирование