Как использовать Llama для поиска критичных данных в коде

D2

Администратор
Регистрация
19 Фев 2025
Сообщения
4,380
Реакции
0
В этой статье я расскажу, зачем аппсек‑инженеру следить за тем, какие данные живут у него в разрабатываемых продуктах, как извлечь из кода сервисов структуру передаваемых данных, как раскрасить поля найденных объектов по заранее заведенным правилам или как попросить локальную опенсорсную LLM-модель, чтобы она раскрашивала данные за тебя.

Ищем критичное в коде​

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

Если коротко, данные клиентов мы бережем как зеницу ока и хотели бы следить за тем, в каких микросервисах нашего продукта живут имена, адреса, телефоны, паспортные данные, финансовая информация, логины, пароли и другие авторизационные данные. Это позволит сфокусировать именно на них наши ИБ‑процессы, то есть выбрать те двадцать процентов сервисов, повышенное внимание к которым принесет нам восемьдесят процентов пользы.

Представим типичную ситуацию: с интернетом наш продукт связан через некоторый API gateway, все, что туда отдается по определенным роутам, мы можем найти в Swagger-файле, лежащем в коде этого гейтвея. Внутри между собой сервисы общаются по GRPC, и все их внешние методы лежат в proto-файлах. Сервисы, которые что‑то пишут в базу Postgres, хранят SQL-схемы в папке миграций. И чтобы знать, что где содержится, нам нужно уметь парсить Swagger, proto и схемы таблиц в каком‑то единообразном виде.

llama.png


Если, предположим, у нас добавилось новое поле «СНИЛС» в объекте пользователя, оно добавится в Swagger на гейтвее, и в proto-контракте, и в схеме базы сервиса владельца пользовательских данных. Мы, сравнив два состояния на сегодня и на вчера, увидим на дифе это новое поле.

llama-1.png


Итак, теперь мы умеем вынимать из кода сервисов Swagger и Protobuf, а сравнив состояния сервисов за вчера и за сегодня, знаем, какие поля поменялись.

Скоринг на минималках​

Опытный аппсек‑инженер может сразу заметить, что в большом продукте ежедневно происходит огромное количество изменений в сервисах. Даже если мы будем следить только за изменениями во внешних контрактах (proto, QraphQL, сваггеры и схемы БД), нас все равно может завалить десятками сообщений об этих изменениях. Нужно как‑то фильтровать поток.

Мы представляем себе архитектуру нашего продукта и знаем хотя бы на интуитивном уровне, какие данные внутри него нам нужно беречь. Попробуем составить базовый набор ключевых слов, чтобы все поля в наших контрактах и сами содержащие их контракты окрашивались как критичные, а остальные — нет. Создадим базовый набор правил, которые разметят поля в контрактах по критичным категориям.

llama-2.png


Теперь некоторые поля наших объектов обрели уровень критичности и тег, определяющий тип данных.

llama-3.png


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

Также в потоке изменений мы можем реагировать только там, где в схемах добавились или поменялись критичные поля. Зачем нам знать, что у пользователя появилось поле «Любимый фильм»? А вот если у него добавилось поле для ввода ИНН или номера паспорта — неплохо бы обратить на это внимание.

Подключаем железные мозги​

С базовыми правилами мы разобрались, но остается ощущение, что мы не уследим, если где‑нибудь в нашем продукте появится новый тип данных, для которого мы не придумали правила заранее. Уже сейчас в примерах правил мы видим, что неплохо бы было разметить и СНИЛС, и email, а в базовых правилах их нет.

Как бы нам заложить в логику оценки новых объектов базовый здравый смысл? Задача звучит просто: хочется, чтобы система сама позвала нас, если найдет что‑то подозрительное. Это звучит как задача, с которой должны хорошо справиться современные LLM. Проверим эту гипотезу с королем всех LLM-моделей, его величеством ChatGPT.

llama-4.png



llama-5.png



llama-6.png


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

Значит, нам нужна on-premise-альтернатива, и желательно open source. Тут из top-перформеров можно выделить Llama 3.2 от организации, которую нельзя называть, и Phi 3.5 от пока еще не запрещенной в России корпорации Microsoft. Чтобы протестировать качество этих инструментов, проще всего воспользоваться Huggingface — здесь нас ждет изобилие оригинальных, квантизированных и анцензурированных моделей.

llama-7.png


Опустим муки выбора среди имеющегося многообразия вариантов: для решения нашей задачи оптимальной с точки зрения размера, потребляемых ресурсов и логики оказалась квантизированная и расцензурированная Llama 3.2 на 3B (3 биллиона токенов) и Llama 3.1 на 8B.

llama-8.png


Другие варианты моделей с тем же размером либо тупили при выборе правильного набора из предложенных полей, либо ошибались с форматом результата, либо не укладывались в локальные ресурсы.

Локальное использование модели​

Теперь посмотрим, как пользоваться моделью локально. Из популярных вариантов для экосистемы Python это transformers от того же Huggingface и llama-cpp-python, которая представляет собой враппер для сишной либы llama.cpp. Первый вариант достойно показывает себя при наличии доступа к GPU, но без него на CPU дождаться результатов на стареньком Macbook Pro M2 мне так и не удалось. В итоге llama-cpp-python оказалась лучшим выбором для обычного десктопного железа и виртуализации: загрузка модели и последующие ответы занимают считаные секунды — вполне приемлемый результат для нашей не слишком rial-time задачи.

Итак, вызов модели из кода достаточно тривиален, системный промпт будет аналогичен таковому для ChatGPT.

llama-9.png


При первом запуске библиотека загружает в указанный каталог с кешем выбранную нами модель, дальше этот кеш можно использовать в офлайн‑окружении.

llama-10.png


Вот результаты для 3B-модели.

llama-12.png


И на том же запросе для 8B-модели.

llama-11.png


В младшей модели результат был получен примерно за 10 секунд, и, как мы видим, он совсем не идеален. Старшая модель думала порядка 30 секунд и нашла больше значимых для нас полей, но, допустим, пропустила codeword и добавила явно лишний для нашего контекста id.

Также на большой выборке данных адекватность ответа сильно зависит от количества полей в запросе: и та и другая модель может посчитать критичным поле в объекте с небольшим количеством полей, но затупить, если у нас на входе и выходе объекта достаточно развесистые структуры данных.

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

llama-13.png


Старшая модель в запросах по единичным полям показывает себя сравнимо с облачными аналогами, но и тратит на каждый запрос 15–20 секунд, что обходится слишком дорого для больших объектов. Нужно думать про кеши, поднятый в памяти и прогретый пул объектов моделей с распределением по нему запросов и более подходящее для задачи железо. Использование большого объема памяти и GPU дает драматический прирост в скорости, и мы получаем время реакции на старших моделях, измеряемое в единицах секунд.

Но все‑таки мы научились получать в сканере критичные объекты с достаточно общим запросом на входе.

llama-14.png



llama-15.png



Выводы​

Мы получили инструмент, который вынимает из кода объекты по нашему уже известному набору ключевых слов или по общему запросу к LLM-модели. Хотелось бы использовать этот инструмент на долгой дистанции.

Самое простое — периодически прогонять скан по доступной нам кодовой базе и смотреть на diff критичных изменений. Там, где появились новые критичные объекты, нужно разбираться в деталях.

Есть вариант чуть посложнее, когда мы запускаем discovery-скан в CI/CD с выгрузкой результата в SARIF (--output report.json --output-type sarif) и с последующей отправкой в DefectDojo. Отслеживать и триажить появление новых критичных объектов от сканера можно так же, как мы разбираемся с уязвимостями.

llama-16.png



llama-17.png


Тулзу appsec-discovery, с помощью которой выполнялось сканирование представленных на скриншотах данных, ты можешь найти на моем гитхабе.

У себя мы собираем структуру объектов вместе с оценкой их критичности и грузим в базу отдельного сервиса, дальше развлекаемся с разнообразными графиками и пирожочками с помощью Apache Superset в тандеме с Trino.

Автор Дмитрий Марюшкин
linkedin.com/in/dmarushkin/
github.com/dmarushkin/appsec-discovery
 
Сверху Снизу