惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

L
LangChain Blog
The GitHub Blog
The GitHub Blog
Recent Announcements
Recent Announcements
MyScale Blog
MyScale Blog
P
Proofpoint News Feed
S
Security @ Cisco Blogs
N
News and Events Feed by Topic
H
Hacker News: Front Page
Attack and Defense Labs
Attack and Defense Labs
S
Secure Thoughts
Microsoft Security Blog
Microsoft Security Blog
N
Netflix TechBlog - Medium
U
Unit 42
Stack Overflow Blog
Stack Overflow Blog
T
Threat Research - Cisco Blogs
Google Online Security Blog
Google Online Security Blog
Spread Privacy
Spread Privacy
Threat Intelligence Blog | Flashpoint
Threat Intelligence Blog | Flashpoint
Exploit-DB.com RSS Feed
Exploit-DB.com RSS Feed
L
LINUX DO - 热门话题
T
Tenable Blog
博客园 - 叶小钗
D
DataBreaches.Net
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
博客园_首页
人人都是产品经理
人人都是产品经理
aimingoo的专栏
aimingoo的专栏
C
Check Point Blog
博客园 - 三生石上(FineUI控件)
量子位
P
Proofpoint News Feed
H
Help Net Security
Blog — PlanetScale
Blog — PlanetScale
宝玉的分享
宝玉的分享
Recorded Future
Recorded Future
The Register - Security
The Register - Security
F
Fortinet All Blogs
Engineering at Meta
Engineering at Meta
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
Last Week in AI
Last Week in AI
S
Schneier on Security
V
Vulnerabilities – Threatpost
雷峰网
雷峰网
Microsoft Azure Blog
Microsoft Azure Blog
G
GRAHAM CLULEY
G
Google Developers Blog
月光博客
月光博客
V
V2EX
T
Troy Hunt's Blog
A
Arctic Wolf

Все публикации подряд на Хабре

Ловим музу за клавиатуру: как айтишнику стать автором Что умеет Midjourney в 2026? Мой немного грустный разбор этого шикарного инструмента Никто не любит писать тесты, но ИИ может исправить это IPv8 выглядит как мечта. Поэтому почти наверняка не взлетит Производители вернули в продажу материнки с DDR3. Что происходит? Управление агентом с телефона через Telegram теперь в KodaCode От координации к лидерству: как меняется роль руководителя разработки Я сделала родителям бизнес вместо пенсии: зарабатываем 70 тысяч, мама не даёт продать В три раза быстрее приемка товара и оптимизация трудозатрат на 73%: как «РСТ-Инвент» помог Gulliver Group ИИ-шечный мир победил? О влиянии искусственного интеллекта на игропром Кремль снижает давление на Телеграмм пока Европа строит интернет по паспорту Как CEO, CTO и CIO за 8 часов собрали ИИ-директора, который умеет держать позицию под давлением Как (не) потерять домен за выходные Вместо 8 разных VPS: как я организовал практику студентам на одном сервере Почему твой Open Source проект не замечают? R&D: искусство управления неопределенностью в разработке AI-дефляция: вакансий для разработчиков больше, а рост зарплат — худший за 15 лет Мы отдали управление роботами OpenClaw. Что из этого вышло Галактический ID: система идентификации для всех форм разумной жизни Шесть основ бизнес-анализа: начинаем с вопроса «Кто в игре?» Код-ревью, в котором дело не в коде Данные переехали. Команда — нет Системной подход к сдаче OSWE в 2025 Почему комната управления реактором покрашена в цвет морской пены 4 YAML-файла вместо PySpark: как аналитикам строить пайплайны без разработчиков LLM-агент для поиска свободных доменов: автоматизируем подбор Когда, зачем и как правильно начинать новую сессию в Claude Code? Как я заставил нейросеть писать макросы для FreeCAD Анатомия ИИ‑агента для подбора персонала. От тысячи резюме к топ‑10 за минуты Опыт разработчика как экономика внимания Автономность как точка невозврата: кто будет субъектом в цифровом будущем Обучение ИИ в «диких» условиях: как рутинные действия превращаются в датасеты Как измерить LLM для задач кибербеза: обзор открытых бенчмарков Где хранить код? Сравнение GitHub, GitLab и Bitbucket Математика объясняет, почему нормальное распределение встречается повсюду Почему ваш FinOps не работает: 12 тезисов от практиков Как подписать проектную документацию УКЭП с использованием бесплатных лицензий Pilot Адаптивное администрирование Sigla Vision Я грузил уран в бочки, а потом 20 лет строил ИТ в атомной отрасли Чем позвонить с Эвереста? История и обзор спутниковой связи. Часть 2 Как языковая модель помогает контролировать качество инструктажей по охране труда в металлургии Как не передать на desktop свой IP в РКН Анатомия SAP Privileges: как устроено управление правами в macOS MoneyDev: Сказка про три главных слова Обновлённый токенизатор видео K-VAE 2.0 от Сбера Как сделать диспетчеризацию дома на 1284 квартиры почти бесплатно Как мы разогнали железную дорогу Мы дали агентам рутину. Теперь надо решить — что делать с освободившимся временем Токсичный контент, промпт-хакинг и защита ИИ — всё о Guardrails для LLM Умный город начинается с точного взгляда: как «Фалькон Тех» меняет пространство к лучшему Навайбкодил приложение для анализа графов Почему Дюну так интересно читать? Упрощаем работу с рутиной или как стать Гендальфом Белым Деконструкция Go: CPU, RAM и что там происходит. Go Assembler база. Часть 1.1 Какие профессии исчезнут из-за ИИ, а какие появятся? И что с этим делать Как мы построили IT-отдел, где хочется расти: архитектурные встречи, прозрачные метрики и книжные подарки Rufler: Делаем из Claude Code автономный рой через один YAML-конфиг Sing-box и белый список приложений Как построить надёжный обмен сообщениями в микросервисах: лучшие практики для enterprise OpenAI строит MLM-пирамиду, а McKinsey и Accenture помогают ей в этом Дом, который не построил Фишер (Часть 2) «Сверхзвуковой математик» против «Вдумчивого логиста»: битва алгоритмов 3D-упаковки Мультимодальные модели – грубый и дорогой инструмент Разговоры ничего не стоят. Код тоже Проверки физических лиц: с кого начнет ФНС Топ-10 бесплатных нейросетей для создания видео в 2026 году Первые слои кода: как наши решения сегодня определяют архитектуру ИИ на десятилетия Разработка нового статического анализатора: PVS-Studio JavaScript Поиск уязвимостей ПО: базовый минимум или роскошный максимум Почему оценка персонала не работает как инструмент управления Как мы разработали ИИ-ассистента и сократили рутину продуктовой команды на 50% Как я ушел из найма, нажарил косточек и продал на маркетплейсах на 168 млн в год Когда 1С:ERP уже внедрена, а нормального производственного плана всё ещё нет Как я сделал Claude мультимодальным, подключив к нему Qwen Omni Как приглашение на вакансию мечты превращается в атаку Infrastructure as Code: философия и лучшие практики IaC Тестируем Yandex Code Assistant на задаче, в которой нужно хранить секреты nxs-universal-chart v3.0: новое поколение универсального Helm-чарта Callback Injection: Техника, которая отправила Microsoft Defender в глухой нокаут «Все идеи на стол»: митап как способ вывести проект из тупика Сегодня я узнал нечто новое о GPU благодаря багу в своей игре Как заставить LLM ̶ ̶г̶а̶л̶л̶ю̶ ̶ эволюционировать Карта событий как фундамент аналитики: практический кейс для E-commerce Что выбрать для AI: x86, ARM или RISC-V? Дайджест железа за март Роль соматических мутаций в развитии аутоиммунных заболеваний: путь к избирательной терапии Mythos от Anthropic — тревожный сигнал для всех, а не только для банков Guardrails для LLM на Java: как приручить промпт‑инъекции и токсичные ответы Green-VLA: как мы собрали VLA-модель для реального антропоморфного робота и не потеряли обобщение Финансовая гонка вооружений: почему умные люди добровольно в ней участвуют Эра ИИ-агентов наступила: выбираем лучшего цифрового сотрудника # Практический опыт внедрения WinCC Redundancy на производственном предприятии Сделал MVP за 3 дня, а потом неделю прикручивал оплату. Оно того стоило? Физика против Маска: почему Starship V3 может оказаться ещё одной катастрофой Нефть Венесуэлы: крупнейшие запасы в мире, но не крупнейшая нефтяная держава JPA 4. Переосмысление Hibernate Почему зеркальная фотокамера Nikon D5 десятилетней давности идеально подошла для миссии «Артемида-2» Проект «Уровень-Спутник» или как мы сделали платформу для гидрологов «Замедлиться, чтобы ускориться»: почему ИИ повышает цену ошибок в требованиях и архитектуре Как с нуля поднять трафик IT-компании на 1657% при бюджете 55 тыс. и выжить Pixel-perfect Downsampling — идеальная отрисовка 50 миллионов точек без потерь
Сделаем Python безопасным… снова
Andrey_Biryu · 2026-05-07 · via Все публикации подряд на Хабре

Уровень сложностиСредний

Время на прочтение33 мин

Охват и читатели770

Туториал

Все мы любим Python за то, что он дает нам свободу: динамическую типизацию, кроссплатформенность, огромное количество библиотек и многое другое. Но зачастую эта свобода становится кошмаром для security‑инженеров и архитекторов, когда речь заходит о высоконагруженных системах с серьезными требованиями к безопасности. В этой статье мы поговорим о том, как перехватить выполнение Python‑кода, запретить опасные вызовы и построить систему контрактов без изменения исходников.

Плохая динамика

Но сначала давайте немного «поучимся плохому» и рассмотрим несколько примеров, демонстрирующих проблемы динамического выполнения:

Начнем с наиболее известного примера — выполнения произвольного кода.

user_input = "import os; os.system('rm -rf /')"
eval(user_input)  

Как видно, здесь eval() выполняет содержимое user_input без проверки. Никогда не делайте так!

Далее, рассмотрим подмену методов в рантайме.

import requests
requests.get = lambda args, *kwargs: print("Перехвачено!")

Здесь мы подменяем стандартный метод requests.get на собственную lambda‑функцию. Это затрагивает все импорты requests в нашем приложении и любых сторонних библиотеках, которые его используют. Другие части программы, ожидающие настоящий ответ от сервера, внезапно начнут получать None (потому что print возвращает None).

И, наконец, еще одна достаточно распространенная ошибка — это несанкционированный доступ к файловой системе.

def read_sensitive_data():
    with open('/etc/passwd', 'r') as f:
        return f.read()

В домашних скриптах так делать может и можно, но в продуктиве точно не стоит.

Что мы можем предложить для борьбы с этим? Первое, что приходит в голову это старый, добрый статический анализ. Однако, от таких ошибок данный инструмент не очень защитит. Дело в том, что он проверяет типы, но не контролирует, что произойдёт во время выполнения. И для решения этой проблемы нужны более глубокие механизмы.

Для примера давайте рассмотрим несколько стандартных инструментов и их недостатки. Так, mypy проверяет типы аргументов и возвращаемые значения. При этом утилита не проверяет безопасность вызовов и возможные побочные эффекты. Инструмент pylint проверяет стиль кода и потенциальные баги. В свою очередь, сканер bandit ищет известные уязвимости (hardcoded secrets, eval).

Хочу свой анализатор

Но что делать, если нам не хватает тех возможностей, которые предлагают готовые инструменты? В таком случае мы можем разработать собственный анализатор, например, на основе модуля ast.

Этот модуль позволяет анализировать структуру кода без его выполнения. Вот небольшой пример.

import ast
import sys

class SecurityAnalyzer(ast.NodeVisitor):

    """Анализатор безопасности, который запрещает опасные паттерны"""

    def init(self):
        self.violations = []
        self.forbidden_functions = {'eval', 'exec', 'compile', '__import__'}
        self.forbidden_attrs = {'os.system', 'subprocess.Popen', 'os.remove'}

    def visit_Call(self, node):

        # Запрет на вызов eval/exec

        if isinstance(node.func, ast.Name):
            if node.func.id in self.forbidden_functions:
                self.violations.append({
                    'line': node.lineno,
                    'message': f"Запрещён вызов {node.func.id}()",
                    'severity': 'CRITICAL'
                })

        # Запрет на опасные методы
        elif isinstance(node.func, ast.Attribute):
            attr_path = self._get_attr_path(node.func)
            if attr_path in self.forbidden_attrs:
                self.violations.append({
                    'line': node.lineno,
                    'message': f"Запрещён вызов {attr_path}",
                    'severity': 'HIGH'
                })

        self.generic_visit(node)

    def visit_Import(self, node):

        # Проверка импортов

        for alias in node.names:
            if alias.name in ['os', 'subprocess', 'socket', 'ctypes']:
                self.violations.append({
                    'line': node.lineno,
                    'message': f"Опасный импорт: {alias.name}",
                    'severity': 'MEDIUM'
                })

    def visit_ImportFrom(self, node):
        self.visit_Import(node)

    def getattr_path(self, attr_node):

        """Рекурсивное восстановление полного имени атрибута (a.b.c)"""

        if isinstance(attr_node, ast.Attribute):
            return f"{self._get_attr_path(attr_node.value)}.{attr_node.attr}"
        elif isinstance(attr_node, ast.Name):
            return attr_node.id
        return ""

    def analyze_file(self, filename):
        with open(filename, 'r') as f:
            tree = ast.parse(f.read(), filename=filename)
        self.visit(tree)
        return self.violations

# Использование

if name == "__main__":
    analyzer = SecurityAnalyzer()
    violations = analyzer.analyze_file("target_script.py")

    for v in violations:
        print(f"[{v['severity']}] Line {v['line']}: {v['message']}")

    if violations:
        sys.exit(1)  # Блокируем выполнение

Давайте посмотрим, что делает этот код. Переданный на анализ файл target_script.py читается и преобразуется в AST — древовидную структуру, где каждый узел представляет элемент кода (функция, вызов, импорт и так далее). Затем выполняется обход дерева с помощью анализатора SecurityAnalyzer, который вызывает соответствующие методы (visit_Call, visit_Import и так далее) для каждого узла.

В зависимости от того, какие несоответствия удается найти, им присваиваются уровни критичности. Так, запрещённым функциям, таким как eval, exec и др., будет присвоен CRITICAL, опасным методам (os.system и др.) — HIGH, а опасному импорту (os, subprocess и др.) — MEDIUM. Все найденные проблемы сохраняются в self.violations и печатаются в формате [УРОВЕНЬ] Line X: Сообщение. Если есть нарушения, скрипт завершается с кодом 1 (ошибка).

Так, для приведённого в качестве примера плохого кода фрагмента:

import os

eval("print('Hello')")
os.system("rm -rf /")

Мы получим следующий отчет:

[MEDIUM] Line 1: Опасный импорт: os

[CRITICAL] Line 2: Запрещён вызов eval()

[HIGH] Line 3: Запрещён вызов os.system

Таким образом, мы можем выполнить анализ исходного кода самостоятельно. Однако одного статического анализа недостаточно, так как код может генерироваться динамически, загружаться из плагинов и так далее. Тут нам на помощь приходят перехватчики выполнения. Так функция settrace позволяет установить обработчик, который вызывается при каждом событии выполнения.

import sys
import dis

from typing import Optional, Any, Dict, Set
from functools import wraps

class ExecutionMonitor:

    """

    Мониторинг выполнения кода с защитой от опасных операций.

    Исправленная версия с корректной работой всех компонентов.

    """

    def init(self):
        self.calls_count: int = 0
        self.max_depth: int = 1000
        self.is_tracing: bool = False
        self.forbidden_functions: Set[str] = {'eval', 'exec', '__import__', 'compile'}
        self.forbidden_modules: Set[str] = {'os', 'subprocess', 'socket', 'sys', 'shutil'}

    def trace_calls(self, frame, event: str, arg: Any) -> Optional[callable]:

        """

        Обработчик трассировки. Возвращает себя для продолжения мониторинга.

        """

        if event == 'call':
            # Защита от рекурсивного зацикливания трассировщика
            if self.is_tracing:
                return self.trace_calls
           
            self.is_tracing = True
           
            try:
                # Проверка глубины рекурсии
                self.calls_count += 1
                if self.calls_count > self.max_depth:
                    raise RecursionError(
                        f"Превышена максимальная глубина вызовов ({self.max_depth})"
                    )

                

                # Проверяем имя вызываемой функции
                func_name = frame.f_code.co_name
                
                # Проверка опасных функций
                if func_name in self.forbidden_functions:
                    raise RuntimeError(
                        f"Запрещён вызов опасной функции: {func_name}()"
                    )
               
                # Проверка опасных модулей (если импортируется функция из них)
                if func_name == '__import__':
                    # В контексте импорта можно проверить аргументы

                    local_vars = frame.f_locals
                    if 'name' in local_vars and local_vars['name'] in self.forbidden_modules:
                        raise RuntimeError(
                            f"Запрещён импорт модуля: {local_vars['name']}"
                        )
                
                # Логирование вызова (опционально)
                # print(f"[CALL] {func_name} в {frame.f_code.co_filename}:{frame.f_lineno}")
                
            finally:
                self.is_tracing = False
             
        elif event == 'line':
            # Логирование строк (можно отключить для производительности)
            # print(f"[LINE] {frame.f_lineno} в {frame.f_code.co_filename}")
            pass

        elif event == 'return':
            self.is_tracing = True

            try:
                self.calls_count -= 1

            finally:
                self.is_tracing = False

        elif event == 'exception':
            # Обработка исключений
            exc_type, exc_value, exc_traceback = arg
            print(f"[EXCEPTION] {exc_type.__name__}: {exc_value}")
        return self.trace_calls

    def analyze_bytecode(self, code_obj) -> Dict[str, list]:

        """

        Анализ байткода на наличие опасных инструкций (без активной блокировки).

        Полезно для статического анализа перед выполнением.

        """

        warnings = []

        try:
            instructions = dis.get_instructions(code_obj)
           
            for instr in instructions:
                # Проверка опасных инструкций
                if instr.opname == 'IMPORT_NAME':
                    if instr.argrepr and any(
                        mod in instr.argrepr.lower() 
                        for mod in ['os', 'subprocess', 'socket']
                    ):

                        warnings.append({
                            'type': 'dangerous_import',
                            'instruction': instr.opname,
                            'details': f'Импорт: {instr.argrepr}',
                            'line': instr.starts_line
                        })

                elif instr.opname in ('CALL_FUNCTION', 'CALL_FUNCTION_KW'):

                    # В байткоде сложно определить имя вызываемой функции

                    # Можно анализировать предыдущие инструкции LOAD_GLOBAL/LOAD_ATTR
                    pass

                elif instr.opname == 'LOAD_GLOBAL':
                    if instr.argrepr in self.forbidden_functions:
                        warnings.append({
                            'type': 'dangerous_function',
                            'instruction': instr.opname,
                            'details': f'Использование: {instr.argrepr}()',
                            'line': instr.starts_line
                        })

        except Exception as e:
            warnings.append({
                'type': 'analysis_error',
                'details': str(e)
            }) 

        return {'warnings': warnings, 'is_safe': len(warnings) == 0}

    @staticmethod
    def safe_executor(code: str, globals_dict: Optional[dict] = None):

        """

        Безопасное выполнение кода в изолированном окружении.

        """

        # Создаём чистое пространство имён
        safe_globals = {

            '__builtins__': {

                'print': print,

                'len': len,

                'range': range,

                'str': str,

                'int': int,

                'float': float,

                'bool': bool,

                'list': list,

                'dict': dict,

                'tuple': tuple,

                'set': set,

                # Исключаем eval, exec, compile, import
            }
        }

        if globals_dict:
            safe_globals.update(globals_dict)

        # Создаём монитор и включаем трассировку
        monitor = ExecutionMonitor()
        old_trace = sys.gettrace()
        sys.settrace(monitor.trace_calls)

        

        try:
            # Выполняем код
            exec(code, safe_globals)
        except (RuntimeError, RecursionError) as e:
            print(f"[БЕЗОПАСНОСТЬ] Блокировано: {e}")
        except Exception as e:
            print(f"[ОШИБКА] Ошибка выполнения: {e}")
        finally:

            # Восстанавливаем старый трассировщик
            sys.settrace(old_trace)

class SandboxedFunction:

    """

    Декоратор для безопасного выполнения функций с мониторингом.

    """

    def init(self, max_depth: int = 500):
        self.max_depth = max_depth
        self.monitor = ExecutionMonitor()
        self.monitor.max_depth = max_depth

    

    def call(self, func):
        @wraps(func)
        def wrapper(*args, **kwargs):
            old_trace = sys.gettrace()
            sys.settrace(self.monitor.trace_calls)

            try:
                return func(*args, **kwargs)

            finally:
                sys.settrace(old_trace)
        return wrapper

 

 

# Примеры использования

if name == "__main__":
    print("=== Пример 1: Безопасное выполнение ===")
    safe_code = """

print("Это безопасный код")

x = 10 + 20

print(f"Результат: {x}")

"""

    ExecutionMonitor.safe_executor(safe_code)

    

    print("\n=== Пример 2: Блокировка eval ===")
    dangerous_code = """

print("Попытка использовать eval...")

eval("print('Взлом!')")

"""

    ExecutionMonitor.safe_executor(dangerous_code)

    

    print("\n=== Пример 3: Статический анализ байткода ===")
    test_code = """

import os
import json

 

def bad_function():

    eval("print('test')")
    return os.system('ls')

"""

    monitor = ExecutionMonitor()

    # Компилируем код в объект кода

    code_obj = compile(test_code, '<string>', 'exec')

    analysis = monitor.analyze_bytecode(code_obj)
    print(f"Код безопасен: {analysis['is_safe']}")

    for warning in analysis['warnings']:
        print(f"  Предупреждение: {warning['details']}")

    print("\n=== Пример 4: Декоратор для отдельной функции ===")

    @SandboxedFunction(max_depth=100)
    def my_secure_function():
        print("Эта функция защищена")

        # Следующая строка вызовет исключение
        # eval("print('hack')")

    my_secure_function()

Этот код реализует систему мониторинга и ограничения выполнения Python‑кода для создания изолированной среды. Его основной целью является ограничить выполнение потенциально опасного кода, запретив опасные функции (eval, exec, compile, import), модули (os, subprocess, socket, sys, shutil) и слишком глубокую рекурсию (защита от переполнения стека).

Мониторинг системных вызовов

Audit hook — самый мощный механизм для production‑систем. Он перехватывает все опасные операции на уровне интерпретатора, например импорт модулей, динамическую компиляцию кода, взаимодействие с файловой системой, сетевые запросы и другие потенциально значимые действия. Также он позволяет собирать информацию о внутренних или иначе необнаруживаемых действиях Python или библиотек, написанных на Python.

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

import sys
import os

class AuditLogger:

    """Логирование всех системных вызовов"""
    
    def init(self, blocked_operations=None):
        self.blocked = blocked_operations or [
            'os.system', 'subprocess.Popen', 'open', 'exec', 'import'
        ]
        self.audit_log = []

    def audit_hook(self, event, args):
        """Обработчик аудит-событий"""
     
        # Логируем событие

        log_entry = {
            'event': event,
            'args': str(args)[:100],  # Ограничиваем длину
            'timestamp': import('time').time()
        }

        self.audit_log.append(log_entry)

        # Проверяем, нужно ли заблокировать
        for blocked in self.blocked:
            if blocked in event:
                print(f" БЛОКИРОВКА: {event} с аргументами {args}")
                raise PermissionError(f"Операция {event} запрещена политикой безопасности")
        print(f" {event}: {args}")

    def install(self):
        sys.addaudithook(self.audit_hook)

    def get_report(self):
        return self.audit_log

# Установка аудит-хука

audit = AuditLogger(blocked_operations=['open', 'os.system'])
audit.install()

# Пример 1: Попытка открыть файл

try:
    f = open('/etc/passwd', 'r')
except PermissionError as e:
    print(f"Заблокировано: {e}")

# Пример 2: Попытка выполнить системную команду

try:
    os.system('whoami')
except PermissionError as e:
    print(f"Заблокировано: {e}")

# Пример 3: Безопасная операция

x = 1 + 2
print(f"Результат: {x}")

# Вывод отчёта

print("\n=== ОТЧЁТ АУДИТА ===")
for entry in audit.get_report():
    print(f"{entry['event']}: {entry['args'][:50]}")

Результат работы данного примера будет следующее:

Для примера 1:

БЛОКИРОВКА: open с аргументами ('/etc/passwd', 'r')

Заблокировано: Операция open запрещена политикой безопасности

Для примера 2:

БЛОКИРОВКА: os.system с аргументами ('whoami',)

Заблокировано: Операция os.system запрещена политикой безопасности

=== ОТЧЁТ АУДИТА ===

open: ('/etc/passwd', 'r')

os.system: ('whoami',)

builtins.id: (1,)

Модификация байткода

И наконец, высший пилотаж — изменение кода до его выполнения.

import dis
import types

def inspect_bytecode(func):
    """Декомпилируем функцию в байткод"""
    print(f"=== БАЙТКОД {func.__name__} ===")
    dis.dis(func)

    # Получаем сырой байткод
    code = func.__code__
    print(f"\nИнструкции: {code.co_code}")
    print(f"Константы: {code.co_consts}")
    print(f"Имена переменных: {code.co_names}")
    print(f"Локальные переменные: {code.co_varnames}")

def example(a, b):
    x = a + b
    return x * 2

inspect_bytecode(example)

Этот код дизассемблирует функцию Python в байткод и показывает её внутреннее устройство. Здесь мы сначала импортируем модули: dis (преобразует байткод в человеко‑читаемый вид) и types (для работы с типами). Далее функция inspect_bytecode(func) проводит инспекцию байткода. В результате мы получаем байткод в понятном формате: номера инструкций, сами инструкции и их аргументы.

=== БАЙТКОД example ===

  2           0 LOAD_FAST                0 (a)

              2 LOAD_FAST                1 (b)

              4 BINARY_ADD

              6 STORE_FAST               2 (x)

 

  3           8 LOAD_FAST                2 (x)

             10 LOAD_CONST               1 (2)

             12 BINARY_MULTIPLY

             14 RETURN_VALUE

 

Инструкции: b'\x97\x00\x97\x01\x17\x00}\x02\x97\x02d\x01\x14\x00S\x00'

Константы: (None, 2)

Имена переменных: ()

Локальные переменные: ('a', 'b', 'x')

 

В завершении давайте рассмотрим статический анализатор безопасности Python‑кода, который исследует байткод функций для обнаружения потенциально опасных паттернов. Мы будем автоматически обнаруживать подозрительный код путём анализа байткода без его выполнения, что позволяет оценить риски до запуска программы.

import types
from typing import List, Dict, Any
 
class SecurityBytecodeInspector:

    """Анализирует байткод на наличие опасных паттернов"""
    
    DANGEROUS_PATTERNS = {
        'IMPORT_NAME': 'импорт модулей (возможно опасных)',
        'LOAD_GLOBAL': 'использование глобальных функций',
        'CALL_FUNCTION': 'вызов функций',
        'LOAD_ATTR': 'доступ к атрибутам (возможно, вызов методов)',
    }
   
    HIGHLY_SUSPICIOUS = {
        'eval': 'динамическое выполнение кода',
        'exec': 'динамическое выполнение кода',
        '__import__': 'динамический импорт',
        'open': 'чтение/запись файлов',
        'compile': 'компиляция кода',
        'globals': 'доступ к глобальным переменным',
        'locals': 'доступ к локальным переменным',
        '__builtins__': 'доступ к встроенным функциям',
    }

    
    def init(self):
        self.findings = []
    
    def analyze_function(self, func) -> Dict[str, Any]:
        """Анализ функции на безопасность"""

        self.findings = []
        
        print(f"\n АНАЛИЗ БЕЗОПАСНОСТИ: {func.__name__}")
        print("="*50)
        
        # Получаем байткод
        code = func.__code__
        
        # Анализируем константы
        self._check_constants(code.co_consts)
        
        # Анализируем имена
        self._check_names(code.co_names)
       

        # Детальный разбор инструкций
        self._analyze_instructions(func)
        
        return {
            'function': func.__name__,
            'is_safe': len(self.findings) == 0,
            'findings': self.findings,
            'constants': code.co_consts,
            'global_names': code.co_names,
            'local_names': code.co_varnames
        }

    

    def checkconstants(self, constants):
        """Проверка констант на опасные строки"""
        for const in constants:
            if isinstance(const, str):
                # Проверка на подозрительные строки
                suspicious = ['__import__', 'eval', 'exec', 'system', 'subprocess']

                for sus in suspicious:
                    if sus in const:
                        self.findings.append({
                            'severity': 'HIGH',
                            'type': 'SUSPICIOUS_STRING',
                            'detail': f'Найдена подозрительная строка: "{const}"',
                            'suggestion': 'Возможно, код пытается выполнить динамические операции'

                        })

    

    def checknames(self, names):
        """Проверка используемых имён"""
        for name in names:
            if name in self.HIGHLY_SUSPICIOUS:
                self.findings.append({
                    'severity': 'CRITICAL',
                    'type': 'DANGEROUS_FUNCTION',
                    'detail': f'Обнаружена опасная функция: {name}()',
                    'suggestion': self.HIGHLY_SUSPICIOUS[name]
                })
    
    def analyzeinstructions(self, func):
        """Детальный анализ инструкций байткода"""
        print("\n📋 ПОИСК ОПАСНЫХ ИНСТРУКЦИЙ:")
       

        for instr in dis.get_instructions(func):

            # Проверяем инструкции импорта

            if instr.opname == 'IMPORT_NAME':
                self.findings.append({
                    'severity': 'MEDIUM',
                    'type': 'IMPORT',
                    'detail': f'Импорт модуля: {instr.argrepr}',
                    'line': instr.starts_line
                })
                print(f"  Найден импорт: {instr.argrepr} (строка {instr.starts_line})")

            

            # Проверяем загрузку глобальных переменных
            elif instr.opname == 'LOAD_GLOBAL' and instr.argrepr in self.HIGHLY_SUSPICIOUS:
                self.findings.append({
                    'severity': 'HIGH',
                    'type': 'SUSPICIOUS_GLOBAL',
                    'detail': f'Загрузка опасной глобальной функции: {instr.argrepr}',
                    'line': instr.starts_line
                })
                print(f"  КРИТИЧНО: {instr.argrepr}() (строка {instr.starts_line})")
            
            # Проверка доступа к атрибутам (возможно, методы объектов)
            elif instr.opname == 'LOAD_ATTR':
                self.findings.append({
                    'severity': 'LOW',
                    'type': 'ATTRIBUTE_ACCESS',
                    'detail': f'Доступ к атрибуту: {instr.argrepr}',
                    'line': instr.starts_line
                })
    
    def generate_report(self) -> str:
        """Генерация отчёта о безопасности"""
        if not self.findings:
            return " Код безопасен для выполнения"
        
        report = [" ОБНАРУЖЕНЫ УЯЗВИМОСТИ:\n"]
        
        critical = [f for f in self.findings if f['severity'] == 'CRITICAL']

        high = [f for f in self.findings if f['severity'] == 'HIGH']
        medium = [f for f in self.findings if f['severity'] == 'MEDIUM']
        
        if critical:
            report.append(f" КРИТИЧЕСКИЕ ({len(critical)}):")
            for finding in critical:
                report.append(f"   - {finding['detail']}")
                report.append(f"     → {finding['suggestion']}")

        if high:

            report.append(f"\n ВЫСОКИЙ РИСК ({len(high)}):")
            for finding in high:
                report.append(f"   - {finding['detail']}")
      
        if medium:

            report.append(f"\n СРЕДНИЙ РИСК ({len(medium)}):")

            for finding in medium:
                report.append(f"   - {finding['detail']}")
        
        return "\n".join(report)


# ===== ПРИМЕРЫ ИСПОЛЬЗОВАНИЯ =====

 

def safe_calculation(x, y):

    """Безопасная функция - только математика"""
    return (x + y) * 2

 

def dangerous_file_reader():

    """Опасная функция - читает файлы"""
    f = open('/etc/passwd', 'r')
    content = f.read()
    f.close()
    return content

 

def malicious_code_executor():

    """Зловредный код - динамическое выполнение"""
    user_input = "__import__('os').system('rm -rf /')"
    eval(user_input)

 

def stealthy_attacker():

    """Скрытый зловред - через константы"""
    # Прячем опасный код в константе
    dangerous_code = "eval('print(\"HACKED\")')"
    # Позже может быть выполнен
    return dangerous_code

 

# Запуск анализа

inspector = SecurityBytecodeInspector()

 

# 1. Анализ безопасной функции
print(" ТЕСТ 1: Безопасная функция")
result = inspector.analyze_function(safe_calculation)
print(inspector.generate_report())

 

# 2. Анализ опасной функции

print("\n" + "="*60)
print(" ТЕСТ 2: Опасная функция (чтение файлов)")
result = inspector.analyze_function(dangerous_file_reader)
print(inspector.generate_report())

 

# 3. Анализ вредоносной функции

print("\n" + "="*60)
print(" ТЕСТ 3: Вредоносная функция (eval)")
result = inspector.analyze_function(malicious_code_executor)
print(inspector.generate_report())

 

# 4. Анализ скрытой угрозы

print("\n" + "="*60)
print(" ТЕСТ 4: Скрытая угроза (в константах)")
result = inspector.analyze_function(stealthy_attacker)
print(inspector.generate_report())

 

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

Опасный код с open() вернет следующее:

КРИТИЧЕСКИЕ (1):

— Обнаружена опасная функция: open()

→ чтение/запись файлов

Вредоносный код с eval()

КРИТИЧЕСКИЕ (2):

— Обнаружена опасная функция: eval()

→ динамическое выполнение кода

— Найдена подозрительная строка: «__import__('os').system('rm ‑rf /')»

В примере со скрытой угрозой:

ВЫСОКИЙ РИСК (1):

— Найдена подозрительная строка: «eval('print(\»HACKED\«)')»

Заключение

Мы рассмотрели четыре уровня контроля над выполнением Python‑кода: статический анализ, sys.settrace() — для построчного мониторинга, sys.addaudithook() — для перехвата системных операций и анализ байткода — для внедрения контрактов и проверок

Здесь стоит отметить, что ни один из методов не даёт 100% гарантии в чистом Python. Для реальных high‑security проектов комбинируйте эти техники с изоляцией на уровне ОС (контейнеры, seccomp, AppArmor).

Python удобен не потому, что «простой», а потому что позволяет быстро добраться до сложных вещей: рантайма, байткода, AST, системных хуков и архитектурных решений вокруг безопасности. Но чтобы уверенно работать с такими механизмами в продакшене, уже недостаточно знать синтаксис и популярные библиотеки.

На курсе «Python‑разработчик. Продвинутый уровень» разбираем Python глубже: от внутреннего устройства языка и асинхронности до проектирования надежных backend‑сервисов, работы с производительностью и инженерных практик, которые нужны разработчику уровня middle+.

📌 Перед стартом можно пройти бесплатное тестирование: оно поможет оценить текущий уровень, понять, насколько программа подходит под ваши задачи, и увидеть, какие темы стоит подтянуть до обучения.[Оценить свой уровень]