Добрый день, коллеги. Хочу рассказать о полученном мной интересном опыте. Может быть кому-то пригодится.

В современном мире пароли используются повсеместно. На корпоративном компьютере, на личном телефоне и планшете, в почте и т.д. И казалось бы всем уже неоднократно объяснялось, что пароль должен быть стойким. Показывались рекомендации, что пароль не должен содержать личные данные, словарные слова, простые комбинации и т.д. Но тем не менее, еще множество людей продолжают использовать простые пароли. Что является не только нарушением требований безопасности, но представляет серьезную опасность как личным так и корпоративным данным.

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

В связи с тем, что у нас домен построен на базе Microsoft Windows, для решения этой задачи было решено сравнивать хеши паролей пользователей домена с хешами словаря. В целом такой подход можно применить к хешам из любых систем. Изменятся только методы получения хешей пользователей.

Хеши паролей пользователей в домене Windows можно получить из файла ntds.dit. Но в штатном режиме доступ к нему запрещен системой. С помощью Google была найдена возможность получить копию файла ntds.dit (содержащий в том числе и логины/хеши паролей пользователей домена) стандартными средствами контроллера домена.

Для получения копии ntds.dit используются возможности утилиты ntdsutil.exe. С ее помощью необходимо сделать снимок системы (Volume Shadow Service) и получить копию файла SYSTEM (в нем содержится ключ для извлечения хешей из базы ntds.dit).
Для создания копии выполняем следующие команды:

C:\>ntdsutil
ntdsutil: activate instance ntds
ntdsutil: ifm
ifm: create full c:\audit
ifm: quit
ntdsutil: quit

В результате работы появляется каталог С:\Audit. Внутри каталога присутствуют две папки: Active Directory и registry. Соответственно в первой лежит копия ntds.dit, а во второй копии веток реестра SYSTEM и SECURITY. Данные папки можно скопировать на другой компьютер или оставить на контроллере домена.

Далее необходимо извлечь из ntds.dit логины и пароли пользователей домена. Для этого воспользуемся маленькой утилитой ntds_decrypt. Взять ее можно по адресу ntds_decode.zip. Качаем архив, распаковываем. Получаем два файла. Собственно исполняемый файл и readme с описанием опций.

ntds_decode -s FILE -d FILE -m -i

    -s <FILE> : SYSTEM registry hive
    -d <FILE> : Active Directory database
    -m             : Machines (omitted by default)
    -i                : Inactive, Locked or Disabled accounts (omitted by default)

Для использования утилиты необходимо запустить командную строку с правами «Администратор». На файле cmd.exe кликаем правой кнопкой мыши и выбираем пункт «Запуск от имени Администратора». В командной строке запускаем утилиту:

ntds_decode -s C:\Audit\registry\SYSTEM -d "C:\Audit\Active Directory\ntds.dit"

Здесь мы запускаем утилиту с основными параметрами (пути до файлов ntds.dit и SYSTEM), т.к. нам не нужны заблокированные или отключенные учетные записи (опция -i), и учетные записи компьютеров (опция -m).

В результате, получаем файл hashes.txt. Формат файла аналогичен формату pwdump и принимается большинством программ брутфорсеров (типа L0phtCrack). Формат файла такой:

<username>:<rid>:<lm hash>:<ntlm hash>:<description>:<home directory>

Собственно в файле hashes.txt нам интересны в первую очередь поля «username» и «ntlm hash».

Хорошо, мы получили исходные данные. Теперь нам нужен словарь. Я взял один из словарей в Интернете на 9 миллионов слов размером 92 Мб. Однако хотелось бы немного его расширить типовыми шаблонами. Например, добавить в конце пару цифр, поменять регистр букв и т.д. Для этой операции замечательно подошел старый добрый John the Ripper. В его функционале есть возможность произвести мутацию словаря по определенным правилам. Я решил не ограничивать возможности JtR и запустил его в стандартном варианте со всеми возможными мутациями.

john --wordlist=9mil.txt --rules dict.txt

После некоторого времени у меня получился файл dict.txt содержащий примерно 131 миллион слов. Теперь для слов нужно получить NTLM хеши. Т.к. сравнивать мы хотели именно хеш с хешем. Для расчета NTLM хешей словаря воспользуемся набором утилит HashManager. Взять его можно по адресу HashManager. Кстати в его составе также есть утилиты для мутации словарей. Но нам понадобится замечательная утилита GenerateHashList. Она генерирует хэши для всех паролей в исходном файле.

В распакованном архиве переходим в папку Bonus — GenerateHashList. И запускаем в командной строке bat файл с параметрами:

generate.bat NTLM dict.txt

Через некоторое (продолжительное ) время получим файл dictionary.txt, содержащий хеши словаря. Объем файла примерно 4.3 Гб. Это очень большой файл. И в идеале нужно уже переходить к использованию SQL, но не хотелось, т.к. это не подходило под требования по возможности использовать подручные средства не требующие установки сложного ПО.

Тогда, для начала, решено было использовать возможности Windows. А именно, утилиту FINDSTR. Данная утилита позволяет искать строку в файле и является обновленной версией утилиты FIND. Воспользовавшись Google было найдено решение, которое считывало хеш из файла hashes.txt и искало его в файле dictioanry.txt. Собственно, вот команда:

(for /f "usebackq tokens=1,4 delims==:" %%i in ("hashes.txt") do FINDSTR /I /B "%%j" Dictionary_sort.txt && Echo %%i>>"audit.txt" && Echo %%i %%j>>"audit_full.txt")

Результатом работы утилиты будет два файла:

— audit.txt, содержащий только логины пользователей, чьи хеши паролей были найдены в словаре
— audit_full.txt содержащий кроме логина еще и сам хеш. Это на тот случай если у пользователя возникнут сомнения в том, что хеш был найден в словаре.

Имеющийся файл hashes.txt содержал примерно 20 000 строк. Запустив утилиту find я обнаружил, что поиск одного хеша в словаре занимает около 40 секунд на моем ноутбуке Lenovo X220. Прикинув количество имеющихся строк и среднее время поиска получилось, что на поиск всех хешей уйдет в районе 10 дней. Правда результат можно видеть и в процессе. Так как найденные хеши сразу попадают в файл audit.txt. На более мощном компьютере скорость будет повыше, но не на много. Линейный поиск достаточно трудоемкий. И время поиска напрямую зависит от объема словаря. В общем — это рабочий вариант, но неудобный.

Тогда было решено написать свой скрипт поиска. Я давно интересовался Python и совсем недавно, просматривая в Youtube лекции Гарварда CS50, вспомнил про алгоритм бинарного поиска и решил попробовать реализовать его для поиска хешей в словаре. Ну что, поехали!

Для начала нужно отсортировать словарь. Это необходимо для осуществления бинарного поиска и кроме этого позволит еще больше обеспечить сохранность пароля. Так как после сортировки нельзя будет однозначно сопоставить хеш словаря со словом. Исходя из поставленной цели, использовать подручные средства, воспользуемся утилитой SORT из состава Windows. В командной строке запускаем команду:

sort dictionary.txt > dictionary_sort.txt

Получили отсортированный словарь хешей. Теперь сам скрипт. Я не специалист в Python, я только учусь, поэтому с помощью Google и разных нецензурных слов собрал скрипт и заставил его работать. Естественно скрипт не оптимален и страшно выглядит, но работает. В качестве параметров ему передается путь к файлу hashes.txt, путь к файлу словаря, и путь для записи результата (audit.txt, audit_full.txt).

Формат запуска:

PassAudit.exe -i c:\audit\hashes.txt -d c:\audit\dictionary_sort.txt -o c:\audit\

Вот сам скрипт:

import argparse
#Создаем поля для парсера
parser = argparse.ArgumentParser(description='This script testing passwords against dictioanry attack. '
                                             'It takes hashes from file and compares to hashes in dictionary.'
                                             'Accounts with weak password outputs into the file audit.txt '
                                             'and audit_full.txt. You can not get clear password from hash.'
                                             'To get hashes from domain controller you should execute next commands'
                                             'at command prompt on domain controller: '
                                             'cd c:\\ -> ntdsutil "activate instance ntds" ifm "create full c:\\pentest" quit quit'
                                             ' -> cd c:\\password\\ -> ntds_decode -s c:\\pentest\\registry\\SYSTEM '
                                             '-d \"c:\\pentest\\Active Directory\\ntds.dit\"  '
                                             'After that you will get file hashes.txt. ntds_decode.exe you can get here: '
                                             'http://www.insecurety.net/downloads/pwdtools/ntds_decode.zip '
                                             'Dictionary hashes file must be sorted and formated one hash per line. '
                                             'You can make it from any dictionary with John the Ripper and Hash manager '
                                             'or any other programs. Copyright Handy761. 2016')
parser.add_argument('-i', '--input', help='Full path to hashes file', required=True)
parser.add_argument('-d', '--dictionary', help='Full path to dictionary file', required=True)
parser.add_argument('-o', '--output', help='Path to output files', required=True)
args = parser.parse_args()
#Открываем файлы hashes.txt и словарь
f0 = open(args.input)
f = open(args.dictionary)
#Формируем пути для открытия файлов результатов
result_file_path0 = args.output + '\\' + 'Audit.txt'
#Открываем файл результата Audit
r0 = open(result_file_path0, "w")
result_file_path1 = args.output + '\\' + 'Audit_full.txt'
#Открываем файл результата Audit_full
r = open(result_file_path1, "w")
#Создаем переменную для хеша пользователя
pass_hash = ''
#Запускаем цикл поиска
while True:
#Устанавливаем указатель на конец файла
    f.seek(0, 2)
#Задаем начальный указатель в словаре
    begin = 0
#Задаем конечный указатель в словаре
    end = f.tell()
#Т.к. указатели на байты, а у нас строки то будем конвертировать байты в строки. Задаем номер начальной строки
    lines_begin = 0
#Задаем номер конечной строки. Т.к. длина строки 32 байта плюс перевод строки делим на 34
    lines_end = end / 34
#Считываем из фала Hashes.txt строку и проверяем на конец файла
    pass_line = f0.readline()
    if ("" == pass_line):
        print("file finished")
        break
#Парсим строку на элементы. Разделитель :
    pass_line_parse = pass_line.split(":")
#Выбираем поле ntlm_hash и прибавляем к нему символ перевода строки. Т.к. в словаре строки оканчиваются переводом строки.
    pass_hash = pass_line_parse[3] + '\n'
#Создаем переменную для указателя на середину диапазона поиска
    point1 = 100
#Пока указатель на середину диапазона больше 1, будем искать.
    while (point1 > 1):
#Вычисляем середину диапазона
        point1 = (lines_end - lines_begin) // 2
#Если у нас указатель на начало диапазона строк больше нуля, то полученное значение середины диапазона прибавляем к этому указателю и умножаем на 34 для получения смешения в байтах
        if lines_begin > 0:
            point = (lines_begin + point1) * 34
        else:
            point = point1 * 34
#Устанавливаем указатель в файле словаря на середину диапазона
        f.seek(point, 0)
#Считываем строку из словаря
        line_key = f.readline()
#Сравниваем значение из словаря со значением из файла hashes.txt и если совпало, то выводим в файлы и на экран. Иначе определяем, где искомое значение (выше или ниже), чтобы понять какую половину отбросить и соответственно сдвинуть указатели на начало и конец диапазона. После этого начинаем сначала.
        if pass_hash == line_key:
            print(pass_line_parse[0], line_key)
            r.write(pass_line_parse[0])
            r.write(' ')
            r.write(line_key)
            r0.write(pass_line_parse[0])
            r0.write('\n')
            break
        elif pass_hash > line_key:
            begin = f.tell()
            lines_end = end / 34
            lines_begin = begin / 34
        else:
            end = f.tell()
            lines_end = end / 34
            lines_begin = begin / 34
#По окончанию закрываем все файлы
f.close()
f0.close()
r.close()
r0.close()

Для удобства я скомпилировал скрипт в exe-файл. После запуска, на том же самом наборе данных, время перебора составило порядка 3 минут.

Все перечисленные здесь манипуляции по получению хешей паролей пользователей домена и проверке их по словарю достаточно неплохо автоматизируются в скрипт. Генерация хешей словаря операция разовая и производится по необходимости.

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

Возможно все это можно было сделать проще, быстрее и красивее, но в любом случае это был интересный опыт.
Поделиться с друзьями
-->

Комментарии (27)


  1. magnitudo
    21.09.2016 14:27

    Раскройте секрет — сколько пользователей у вас отличилось словарными паролями?


    1. Handy761
      21.09.2016 14:36
      +1

      107 учеток из примерно 23000.


  1. AVX
    21.09.2016 15:04

    Толку от этого всего мало — у 90% могут быть пароли типа Аа123456. Вроде и не словарное, но весьма слабый пароль.


    1. Handy761
      21.09.2016 15:39

      Словарь может быть разным. В моем случае это не просто словарь состоящий из слов, а словарь состоящий в том числе из паролей утекших на разных ресурсах. Типа LinkedIn. И там такие пароли в большинстве своем присутствуют.


  1. amarao
    21.09.2016 15:27
    +2

    Всё то же самое, но с помощью hashcat'а на GPU, с базами тыренных паролей и базовыми пермутациями. Я уверен, что половина паролей окажется сломанными, не смотря на визуально безопасные вида «dribTuj0O».


    1. Handy761
      21.09.2016 15:41

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


      1. amarao
        21.09.2016 19:36
        +1

        После того, как пароль пробрутили, конфиденциальность уже не актуальна.


        1. Handy761
          21.09.2016 21:35

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


          1. Deosis
            22.09.2016 07:35

            В хабе https://habrahabr.ru/hub/infosecurity/ ссылка на внешний ресурс и совет запустить программу с правами администратора.
            Вы на самом деле подобрали пароль, и пользователь «может оспорить какие-либо свои действия в будущем, мотивируя тем, что его пароль был известен аудитору», и уже вам придется доказывать, что вам неизвестен пароль. Во время генерации хешей достаточно в конец строки вписать исходное слово.


          1. amarao
            22.09.2016 17:37

            Если мне аудитор скажет, что у меня небезопасный пароль, потому что по его хеш смогла пробрутфорсить программа, то даже без предъявления пароля аудитором я смогу утверждать, что аудитор знал про это и пробрутфорсил мой пароль (на своей домашней машине) и воспользовался им. Что это можно сделать мне аудитор только что сам подтвердил.

            Не вижу разницы.


      1. Kusado
        22.09.2016 21:59
        +1

        hashcat может на выходе показывать сломанные хеши без уточнения собственно пароля.
        Прогнал hashcat по своим хешам — волосы дыбом. 35% паролей за три часа на старой видеокарточке…


  1. RuCosinus
    21.09.2016 17:01

    Я прошу прощения, а куда все ссылки из текста подевались?
    Вы их вырезали по каким-либо причинам или забыли вставить?


  1. Handy761
    21.09.2016 17:02

    Мой косяк. Спасибо. Исправил.


  1. Sleuthhound
    22.09.2016 05:27

    Сколько не пытался заставить юзеров делать сложные пароли, даже с помощью политик с периодической сменой, все равно всегда одна история — «Сложный пароль и он записан на бумажку, а бумажка под клавиатурой или куда хуже на мониторе» и это в 99% случаев, всегда.
    Как только разрешаешь пароли попроще и не длиннее 8 символов, и без перегибов типа !J~6%bsH&Ns_ то все встает на свои места, пароли хоть и простые (типа 6TsH7jns), зато они в головах юзеров, а не на бумажке под клавиатурой.


  1. Izzet
    22.09.2016 08:58

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

    Для Windows систем, знание самого пароля в большинстве случаев не обязательно. Зная хэш, можно запустить программу из под пользователя, посмотреть почту, подключиться по RDP (в некоторых случаях) и т.д.
    Pass-the-hash


    1. Handy761
      22.09.2016 09:16

      Согласен. Метод не идеален. Будем дорабатывать. Но в моем случае это лучше, чем ничего.


  1. videns
    22.09.2016 09:13
    +1

    Имхо, правильнее не брутить пароли, а не давать ставить такие простые пароли. В случае с AD есть не очень дорогой софт, который позволяет задавать расширенные политики проверки пароля. Ставится на контроллер домена и реализует нативную механику AD:
    https://msdn.microsoft.com/en-us/library/windows/desktop/ms721882(v=vs.85).aspx
    Надеюсь, не сочтут за рекламу:
    http://anixis.com/products/ppe/password_policy_rules.htm


    1. Handy761
      22.09.2016 09:20

      Интересный продукт. Но покупка ПО у нас не так быстра, как хотелось. Поэтому пока выкручиваемся подручными инструментами.


      1. zirf
        22.09.2016 13:36

        В том то и беда, ИБ-политика должна быть комплексной в духе ISO 2700x. Просто массу контор знаю, где нет регламента по HR (прием, переход, увольнение) с четкой прописью завести/сменить права/удалить. А с паролями стандарт — сложные — записывают, хотя у домена MS Windows по умолчанию политика довольно жесткая по длине(8), символам(A-Z,a-z,0-9, спецсимволы.) и сроку действия(42 дня). Первое, что приходится делать — расслаблять ее до приемлемого уровня. Менеджменту сверхумный персонал в телемаркетинге, к примеру, не нужен, больно дорого.


  1. varnav
    22.09.2016 13:05
    +1

    Новые рекомендации по парольным политикам от Microsoft:

    http://research.microsoft.com/pubs/265143/Microsoft_Password_Guidance.pdf


  1. DM-Demius
    22.09.2016 13:53

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

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


    1. zirf
      27.09.2016 08:49

      По лицу — встроено в ОС (Windows 10 точно), вопрос насколько это надежно. Дорого — нет, нужна копеечная вэб-камера.


  1. Handy761
    22.09.2016 13:53
    +1

    Сейчас много контор предлагают OTP на базе смартфонов. Типа Google Authenticator.


    1. DM-Demius
      23.09.2016 07:55

      Да но это зависимость от конторы, тем более если например эта контора сделает всё, запустит, но не даст права вносить самому изменения в систему и не расскажет как всё это работает. То тогда будет всё завязано на эту контору, а это уже в таком вопросе как доступ к критическим данным будет однозначно давать сильную проблему безопасности.


      1. Handy761
        23.09.2016 10:32

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


  1. ov7a
    23.09.2016 17:26

    Правильно ли я понял, что хэши несоленые? Неужели никак нельзя настроить, чтобы они солились?


    1. Handy761
      26.09.2016 12:10

      В Windows насколько я знаю, хеши несоленые. И по моему с этим ничего сделать нельзя. По крайней мере я не знаю.