Одной из рутинных задач, с которыми операторы регулярно сталкиваются в большинстве случаев, является интеллектуальный анализ данных. Хотя точно то, что ищут операторы, зависит от среды, существует одна общая цель, которая всегда интересует всех: пароли.
После погружения в машинное обучение с состязательной точки зрения я начал обращать внимание на любые наступательные проблемы безопасности, которые можно было бы усилить с помощью ML. В одном из моих предыдущих постов я упомянул различие между состязательным машинным обучением и наступательным машинным обучением — «Уилл Пирс определяет состязательное машинное обучение как поддисциплину который специально атакует алгоритмы машинного обучения и Оскорбительное машинное обучение как Применение машинного обучения к наступательным проблемам безопасности, и я согласен с этими определениями. Мои предыдущие посты касались введения в состязательное машинное обучение, где мы создавали состязательные образцы, чтобы обойти целевые существующие модели. Наступательное машинное обучение включает в себя такие вещи, как обнаружение песочницы, дополнение к атакам с подбором пароля или улучшение целевого фишинга. В этом посте мы попытаемся разобраться с распознаванием паролей.
Очевидно, мы не первые, кто об этом подумал. Два года назад Том Калло из Hunnic Cyber опубликовал пост под названием Поиск паролей с помощью машинного обучения в Active Directory» и связанный с ним инструмент под названием SharpML. Их подход был интересным, о чем свидетельствует их графика:
Хотя в сообщении говорится: Мы также отдельно [sic] выпустим для вас нескомпилированную модель машинного обучения и правила, которые будут аналогичным образом размещены на Github.» на момент написания. этого сообщения, которое еще не произошло. Не зная архитектуры модели, показателей производительности или точно того, как она была обучена (хотя мое предположение, основанное на репозитории, заключается в том, что она использовала 10 тысяч утекших паролей), я хотел воссоздать модель распознавания паролей. и предоставить как можно больше подробностей. Я также хотел избежать компонента проверки пароля в реальном времени, поэтому мне не нужно было беспокоиться о транспортировке и выполнении модели на произвольном хосте в целевой среде.
[EDIT 22.06.20] Том Калло связался с LinkedIn и сообщил, что были некоторые проблемы с выпуском их конкретной модели, но он все еще планирует это сделать. Он также упомянул, что модель «не обучалась на 10 000 паролей, а фактически логически основывалась на ключевых показателях эффективности, относящихся к собственному руководству Microsoft по паролям», и что скоро у него должна появиться новая классная работа!
Полное примечание заранее: эта модель не идеальна, и я буду объяснять ее недостатки по ходу дела. На самом деле есть публикация StackExchange о некоторых из этих трудностей, в том числе тема, о которой мы собираемся поговорить здесь: «…сложная часть заключается в том, чтобы выяснить, как получить приличные обучающие данные в достаточном объеме. ”
Блокнот Jupyter и модель Dockerized теперь общедоступны в репозитории DeepPass GitHub.
Проблема
О многих вещах в машинном обучении легче сказать, чем сделать. Когда я начал изучать эту дисциплину, таинство машинного обучения было похоже на то, когда я начал изучать наступательную безопасность: я думал, что все возможно. Теперь я все еще верю, что все возможно, но некоторые вещи определенно более возможны, чем другие.
Так какую именно проблему мы хотим здесь решить?
Наши операторы загружают большее количество документов по большинству заданий, будь то через агента или через различные сайты, такие как Sharepoint. Чтение большого объема текста в поисках определенных ключевых слов или вещей, похожих на пароли, занимает много времени, и машинное обучение могло бы помочь с (обратите внимание, я не не говорю "решить" ;)
Я собираюсь пропустить здесь тему автоматического приема и сосредоточусь на том, как создать абстрактный API, в который мы можем отправить произвольный документ и получить обратно кандидаты паролей. С точки зрения архитектуры это распространенный сценарий развертывания моделей машинного обучения, и я расскажу о реализации Dockerized в следующем разделе.
Наш API должен:
- Принимать документы с различными типами данных (.docx, .pdf, .pptx и т. д.)
- Извлечение слов открытого текста (часто называемых токенами в жаргоне машинного обучения) из каждого типа документа.
- Пропустите извлеченный текст через какую-либо модель машинного обучения.
- Возвратите «кандидатов» паролей из результатов модели вместе с небольшим количеством окружающего контекста.
Прежде чем мы начнем строить и тренировать, еще раз слон в комнате, упомянутый в сообщении StackExchange: как нам получить хорошие данные для обучения?
Проблема данных
Когда мы вручную сортируем документы как люди, мы используем богатый опыт и внутреннее распознавание образов. Любой, кто выполнял наступательные операции, может просмотреть любое количество документов и легко определить, присутствует ли пароль. Рассмотрим следующие предложения:
- Для входа используйте следующий пароль: Password123!
- Мы изменили ваши учетные данные на «ChangeMePlz?». Пожалуйста, измените их в какой-то момент в будущем.
- Учетные данные для базы данных: user:Qwerty123456.
Очевидно, пароль в документе почти всегда окружает большое количество пикантного контекста, который содержит большую красную надпись «ПАРОЛЬ!». отметьте нас при сортировке документов. Однако, если мы хотим обучить прогностическую модель для этого конкретного набора задач, нам нужны помеченные данные и их достаточное количество, если мы не просто выполняем базовый поиск в стиле регулярных выражений. Это означает, что нам нужно большое количество документов, подобных тому, что мы могли бы встретить в поле, где мы знаем, что в подмножестве есть пароли, и мы знаем, что подмножество вообще не имеет паролей. Мы не сохраняем данные клиентов после отчетности, и я не знаю ни одного общедоступного корпуса документов, удовлетворяющего этим требованиям.
В машинном обучении мало что разочаровывает больше, чем вера в то, что вы можете решить проблему, если у вас просто есть правильный (несуществующий) набор данных.
Таким образом, мой подход был идеей следующего лучшего, но не идеального подхода: я сосредоточился на том, чтобы отличить пароли от «обычных» слов. Повторюсь: я знаю, что это не идеально, но это был лучший подход, который я мог придумать, учитывая ограничения. Относительная редкость паролей в тексте документа также создает проблему дисбаланса классов, о решении которой мы поговорим в следующих разделах. На английском языке: обучение модели обнаружению относительно редких событий сопряжено с уникальным набором задач!
Для входного набора данных мне понадобилось большое количество паролей и непаролей. Для паролей я скачал дамп паролей настоящего человека с CrackStation. Я случайным образом выбрал 2 000 000 паролей длиной от 7 до 32 символов, чтобы они функционировали как половина набора данных с известным паролем.
Затем я запустил большое количество дорков Google, чтобы найти технические/связанные с ИТ документы различных форматов, в конечном итоге собрав около 1300 общедоступных доброкачественных документов. Хотя в этих документах могут содержаться пароли, обычный текст значительно превосходит любые возможные выбросы. Я прогнал эти документы с помощью Apache Tika, известного набора инструментов, который …обнаруживает и извлекает метаданные и текст из более чем тысячи различных типов файлов (таких как PPT, XLS и PDF). также буду использовать Tika позже в разделе обслуживания моделей. Я выбрал 2 000 000 случайных слов / токенов из этого набора для половины набора данных с известными словами.
Модель
В моих предыдущих сообщениях я подробно описал использование ряда различных архитектур моделей для нашего табличного запутанного набора данных PowerShell AST. Я также упомянул, что деревья с градиентным усилением имеют тенденцию превосходить нейронные сети на табличных данных. Для текста определенные типы нейронных сетей обычно превосходят другие подходы, такие как наивный байесовский подход или подход N-Gram, хотя иногда это зависит от объема имеющихся у вас данных. В этом случае, поскольку мы выполняем токенизацию символов отдельных слов, а не всего документа, я экспериментировал с различными нейронными сетями. Это заняло приличное количество времени из-за набора данных в 4 000 000 слов, поэтому я не мог экспериментировать так много, как хотелось бы. Я уверен, что существует либо другая архитектура, либо другой настроенный подход, который работает лучше, чем то, что у меня есть здесь.
Я попробовал несколько архитектур, от многоуровневых LSTM до множества одномерных ConvNet (включая многоканальные варианты). Наилучшей полученной архитектурой была сеть с двунаправленной долговременной кратковременной памятью (LSTM) на 200 узлов с ручным слоем встраивания и небольшим отсевом:
Если это вы после прочтения последних нескольких абзацев, я попытаюсь добавить немного контекста:
Я никак не могу полностью объяснить внедрение токенов для нейронных сетей или двунаправленную архитектуру LSTM в этом посте, и я сомневаюсь, что многие хотят, чтобы я это сделал. Поэтому я предоставлю краткое объяснение высокого уровня и несколько ссылок на случай, если вам интересно.
Текст — это последовательные данные. В предложении важна последовательность слов, а для слова (т. е. пароля), как здесь, важна последовательность символов. Мы не можем обрабатывать последовательности так же, как мы обрабатывали наши табличные/матричные данные, нам нужен другой тип модели. Вот тут-то и появляются LSTM.
LSTM — это особый тип архитектуры глубокого обучения, называемый рекуррентной нейронной сетью (RNN), который был разработан для обработки различных типов данных последовательности. Нейроны в RNN имеют петлю, которая позволяет им «запоминать» информацию о данных в последовательности. LSTM — это усовершенствованная архитектура RNN, которая оказалась очень эффективной для текстовых данных и была де-факто текстовой архитектурой до того, как несколько лет назад на смену пришли преобразователи. Я специально использовал двунаправленный LSTM, который обучает один LSTM, работающий вперед, и один, работающий назад, на входных последовательностях. Это помогает нам получить как можно больше сведений о шаблоне пароля.
- Подробное объяснение сетей долговременной кратковременной памяти (LSTM)
- Дополнительная информация о двунаправленных LSTM
- Дополнительная информация о встраиваниях слов
- Подробнее об отсеве
Примечание для ботаников: «Почему вы не использовали трансформаторы??!» Ну, я подумал об этом и немного поиграл, но почти все примеры трансформаторов, которые я нашел, подходят с последовательностями слов, а не последовательностями символов. Я также не совсем уверен, что нейронное внимание — лучший инструмент для последовательностей символов длиной в слово. Тем не менее, позвольте мне повторить свое обычное заявление «я не эксперт и не слишком хорошо умею считать» и что я полностью открыт для тех, кто говорит, что показывает мне, что я не прав!
Для обучения я также использовал технику под названием Ранняя остановка, чтобы предотвратить переобучение. Глубокое обучение включает в себя несколько эпох, когда весь набор данных обучается в пакетах, которые объединяют все данные в модель один раз за эпоху. Ранняя остановка используется для отслеживания определенных показателей производительности модели во время обучения и останавливает обучение, если производительность не улучшается после определенного количества эпох. Это помогает сохранить безудержное переоснащение.
Так как же показала себя модель?
На самом деле довольно хорошо, с точностью почти 99%! Тем не менее, это все еще большая проблема из-за соотношения редких событий (например, слово в документе является паролем) благодаря нашему старому другу ошибке базовой скорости. Давайте рассмотрим упрощенный пример, чтобы понять, почему.
Допустим, мы загружаем 100 документов, каждый из которых содержит в среднем 5000 слов, и в 10 из этих документов есть один пароль. Это означает, что из 500 000 слов-кандидатов, которые мы собираемся обработать, только 10 являются паролями. Что дает нам точность в 99%?
499 990 непаролей * точность 99% = 494990 истинно отрицательных непаролей + 5000 ложноположительных паролей
10 паролей * точность 99% = 0 ложноотрицательных непаролей, 10 истинно положительных паролей
Мы вероятно пометим все пароли, но у нас также будет 5000 ложных срабатываний! Это большая проблема, похожая на проблему ложных срабатываний, с которой сталкиваются аналитики обнаружения. Когда истинное срабатывание является относительно редким событием в большом наборе данных, например, пароль в документе или реальное вторжение на хост в большой сети, даже очень точные модели будут давать большое количество ложных срабатываний.
Часто при обучении модели на редких событиях сам набор данных несбалансирован. То есть набор данных будет отражать распределение данных в реальном мире, например частоту паролей, которую я описал ранее. Существуют различные методы обработки несбалансированных наборов данных, такие как повышающая/понижающая выборка образцов классов, увеличение данных и т. д. Однако в нашем случае из-за того, как мы сгенерировали набор данных, он идеально сбалансирован.
Чтобы справиться с проблемой ложных срабатываний, у меня было две идеи, с которыми я экспериментировал. Первым было изменение порога принятия решения о вероятности для выходных данных модели. Наша модель с единственным сигмовидным выходным нейроном выводит не жесткую метку «пароль» или «не пароль», а скорее вероятность того, что входное слово является паролем. В большинстве случаев мы используем порог 0,5 для определения меток, где ›=0,5 означает, что ввод является паролем. Однако мы можем настроить это вручную, возвращая только те результаты, которые имеют более высокую, скажем, 0,9 вероятность того, что они являются паролем. Одним из преимуществ здесь является то, что мы можем настроить порог несколько раз после обучения модели.
Другой вариант — взвешивать каждый класс по-разному при обучении модели. Это изменяет функцию потерь, чтобы более строго наказывать неправильную классификацию для определенного класса, что может изменить соотношение ложных положительных или ложных отрицательных результатов в зависимости от того, как вещи взвешиваются. Недостатком здесь является то, что после того, как мы обучим модель на взвешенных классах, это нельзя изменить, как настроить порог. Однако это в конечном итоге превзошло пороговый вариант в этом конкретном случае, хотя мы могли бы объединить подходы, если бы захотели. В последней модели, которую я обучал, использовались весовые коэффициенты классов 0,9/0,1, которые более жестко наказывали ложные срабатывания. Это подробно описано в записной книжке ./notebooks/password_model_bilstm.ipynb в репозитории DeepPass.
Для окончательной оценки я использовал наиболее эффективную модель ранней остановки в тестовом наборе, чтобы получить приведенные ниже показатели производительности. Если вы не знакомы с матрицей путаницы или наборами проверки/тестирования, ознакомьтесь с моим вводным постом о машинном обучении.
точность: 0,98997
точность: 0,99629
вспомнить: 0,98358
Оценка Ф1. : 0,98990
Это определенно более управляемо: 723 ложных срабатывания из 400 000 сбалансированных входных слов. Ложноположительные и ложноотрицательные неразрывно связаны, и уменьшение одного увеличивает другое. Использование взвешивания классов для уменьшения количества ложных срабатываний здесь означает, что количество ложных срабатываний или потенциальных паролей, которые мы пропустим, возрастет! Однако я чувствовал, что этот компромисс был необходим для того, чтобы подход можно было использовать. Я также получил еще больше сочувствия к защитникам, которым приходится каждый день сортировать поток предупреждений :)
Развертывание модели
Хорошо, у нас есть отличная модель Keras LSTM, которая, кажется, неплохо справляется с нашей задачей. Как мы на самом деле используем эту вещь?
В соответствии с нашей целью разработки API я решил использовать Docker для упаковки всего вместе. К счастью для нас, есть официально поддерживаемый Apache Tika Docker Image, который мы можем использовать для воспроизведения шага извлечения текста, а у TensorFlow (базовая ткань для используемой нами платформы глубокого обучения Keras) есть замечательный проект под названием TensorFlow Serving. TensorFlow Serving — это упрощенный подход к обслуживанию функциональности модели после обучения, а также имеет приятный образ Docker.
Последним шагом является создание небольшого приложения для обработки запросов ввода и вызова всех частей. Кроме того, я хотел предоставить возможность предоставлять пользовательские термины регулярных выражений для поиска по тексту каждого документа, а также предоставлять несколько слов окружающего контекста для каждого результата. Весь этот код должным образом докеризован и находится в репозитории DeepPass. Вот как выглядит крошечное веб-приложение для проверки концепции (знаю, дерьмовое):
И вот пример результата:
Очевидно, что всю систему можно использовать как прямой API по адресу https://localhost:5000/api/passwords.
C:\Users\harmj0y\Documents\GitHub\DeepPass›curl -F file=@test_doc.docx https://localhost:5000/api/passwords
[{"file_name": "test_doc.docx", "model_password_candidates": [{"left_context": ["for", "the", "production", "server", "is:"], "password": " P@ssword123!», «right_context»: [«Пожалуйста», «не», «говорить», «кому», «на»]}, {«left_context»: [«тот», «тот», «другой» , «пароль», «это»], «пароль»: «LiverPool1», «right_context»: [«.», «Это», «это», «наш», «резервный».]}], «regex_password_candidates» : [{«left_context»: [«для», «это», «производство», «сервер», «является:»], «пароль»: «P@ssword123!», «right_context»: [«Пожалуйста», «не», «говорить», «кому», «на»]}], «custom_regex_matches»: null}]
Напоминаем, что форматы файлов — это те, которые поддерживает Apache Tika. Кроме того, ранее я упомянул порог вероятности принятия решения — его можно настроить в app.py файле в строке 221, если вы хотите еще больше уменьшить количество ложных срабатываний.
Заключение
Это мое первое применение «наступательного» машинного обучения, и это был для меня интересный проект. Я надеюсь, что это было интересно другим, и что модель будет полезной, даже если она не идеальна. Я также надеюсь, что это может вдохновить других на идеи относительно обнаружения паролей во время интеллектуального анализа данных. Эти усилия определенно еще раз убедили меня в том, что получение качественных размеченных данных обычно является самой сложной частью любого проекта машинного обучения.
Если вы хотите переобучить модель с другим набором данных, более подходящим для ваших операций, записная книжка Jupyter в репозитории DeepPass должна показать вам, как это сделать. Просто создайте новый dataset.csv со столбцами слово, is_password, переобучите модель, сохраните модель и подставьте модель в ./models/. > раздел проекта Docker.