С примерами Python и элементарным анализом паролей

Знать или не знать?
Если вы работаете в сфере технологий, даже если вы не специалист по безопасности как таковой, имеет смысл ознакомиться хотя бы с некоторыми основными векторами атак. Таким образом, мы можем активно способствовать защите наших коллег и данных от хакеров. В этой статье я предоставлю вам некоторую информацию и фрагменты кода, описывающие «легендарную» атаку грубой силы, упомянутую в заголовке. Надеюсь, вы найдете какую-то ценность в этом письме. Если нет, то тоже нормально. Не стесняйтесь перейти к нижней части страницы для ссылки на мой код на GitHub.

Что такое атака по словарю?
Во время атаки по словарю хакер незаконно пытается получить доступ к системе, пытаясь войти в систему с помощью пароля или парольной фразы. Здесь он или она попытается проникнуть в систему, используя общие пароли, имена, дни рождения и т. д. Хотя такие атаки требуют определенных усилий со стороны хакера, вознаграждение за успех может быть огромным. Например, преступник может украсть личные данные и ценности или захватить систему пользователя для злоумышленной деятельности. Требования к строгому паролю могут быть хорошей защитой. Вот пример требования к надежному паролю:

  1. Не менее 12 символов
  2. Сочетание строчных и прописных букв
  3. Включение хотя бы одного специального символа, например, ! @ # ?
  4. Включение хотя бы одного числа

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

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

Взгляните на функцию Python ниже. (полный код можно найти внизу статьи). Каждое слово в RockYou.txt считывается, а затем хэшируется в sha256. Хэширование используется для обеспечения целостности паролей пользователей. Затем хэшированное слово сопоставляется с хешем фактического пароля для имитации атаки.

# variable declaration in DictionaryAttack Class
self.password = password
self.password_hash = hashlib.sha256(password.encode()).hexdigest()
# Return values that indicate if attempt was success or not
self.success = "The password was found: "
self.fail = "The password could not be cracked "
def crack_password(self):
# open file and parse the contents
          with open( r"path to rockyou.txt", "r", 
                   encoding="latin-1") as file:
                   words = file.read().split()
# hash every word and then try to see if it matches the passwords  
# hashed version. If it does match, return success and the password. # Otherwise return failure message.
                for word in words:
                word_hash=hashlib.sha256(word.encode()).hexdigest()
                if word_hash == self.password_hash:
                    return self.success + self.password
            else:
                return self.fail

Ну, это ужасно медленно. Как сделать это быстрее?
Теперь представьте, что RockYou.txt содержит 8,4 миллиарда записей паролей. Открытие и цикл по нему, как в предыдущем примере, займет много времени. Одним из быстрых способов ускорить код является использование Pandas Dataframes. Основная логика остается прежней, мы хэшировали слова, а затем сравнивали их с хешированными паролями. Однако мы используем предварительное вычисление хэшей. Тогда циклы больше не требуются, и Pandas сделает всю тяжелую работу.
Сначала данные считываются с помощью API pandas.

path = path to rockyou.txt
§ reading the data from the txt file
df = pd.read_csv(
            path,
            delimiter="\n",
            header=None,
            names=["Words"],
            encoding="ISO-8859-1",
        )

Этот фрейм данных (df) теперь имеет ровно один столбец Words. Теперь мы создаем хэши как второй столбец.

# function to hash the words in the pandas dataframe to sha256
def sha_encode(word):
            return hashlib.sha256(word.encode()).hexdigest()
df["Hash"] = df["Words"].astype(str).apply(sha_encode)

Теперь решение можно просто получить с помощью .loc следующим образом:

def crack_password(self, password):
# simulating the hashed password
password_hash = hashlib.sha256(password.encode()).hexdigest()
# See if hashed word matches the hashed password
solution = self.df.loc[self.df["Hash"] == password_hash]
# if solution is empty, the attack failed otherwise return password
if solution.empty:
            return self.fail
        else:
            return self.success + password

Полный код содержит таймер, с которым можно сравнить каждый подход. Действительно, вы обнаружите, что версия Pandas значительно быстрее. Злоумышленник, скорее всего, попытается максимально оптимизировать код, чтобы увеличить его скорость. Хотя можно возразить, что этот простой код не имеет больших возможностей для дальнейших улучшений.

Что делать, если первоначальная атака не увенчалась успехом?
Что ж, придумывать новые пароли для проверки гораздо сложнее и утомительнее. Таким образом, атака по словарю, вероятно, является скорее первой попыткой удачи. Однако хакер может анализировать утекшие пароли, такие как файл RockYou, и искать закономерности. Затем, после обнаружения шаблона, возможно расширение паролей на основе правил. Рассмотрим этот пример сопоставления шаблонов с использованием простого регулярного выражения:

# how many passwords in total ?
count_passwords = len(df)
# out of all the passwords with special characters, how many contain # the exclamation ! mark ?
count_password_with_exclamation_mark = df[df.Passwords.str.contains('!', na=False)].count()
percentage = count_password_with_exclamation_mark / count_passwords

Получается, что из 14.343.476 только 0.008% содержат знак !. Интересно, однако, что большинство людей ставят ! в конце своего пароля, как если бы писали текст. Обычный пароль действительно hello .
Просто добавив ! в последнюю позицию пароля, шансы на успех увеличиваются. Предыдущий пример hello! логически является интересной новой попыткой. Конечно, шаблоны могут быть намного сложнее, чем в предыдущем примере. Давайте посмотрим, как вы расширите фрейм данных Pandas с помощью вновь сгенерированных паролей:

# Let's see first if there are any weak passwords
def identify_weak_passwords(self, df):
# pattern to match a strong password (no minimum amount of 
# characters defined for simplicity)
        strong_password_regex = '(?=.*\d)(?=.*[!@#$%^&*]+)(?![.\n](?=.*[A-Z])(?=.*[a-z]).*$'
# negate the pattern with the ~ sign. to get a boolean expression
        df["is_Weak_Password"] = ~df.Passwords.str.contains(strong_password_regex, na=False)
        
        return df
 # now generate stronger passwords out of the weak passwords  
    def generate_additional_passwords(self, df):
        
        expression_one = df.loc[df['is_Weak_Password'] == True].Passwords + "!"
        expression_two = df.loc[df['is_Weak_Password'] == True].Passwords + str(random.randint(1940, 2022))
expressions = [expression_one, expression_two]
for expression in expressions:
            result_df = pd.concat((df["Passwords"], expression), axis=0)

Простой, но эффективный. Однако есть уловка с сопоставлением шаблонов регулярных выражений. Вычисление регулярных выражений (регулярных выражений) может быть заведомо медленным. Мы сопоставляем строки, что является медленным процессом. Кроме того, оптимизация регулярных выражений требует от программиста большого опыта.

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

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

^(?=.*[0-9])(?=.*[a-z])(?=.*[A-Z])(?=.*[@#$%^&+=])(?=\S+$).{8,}$
^                 # beginning of the string
(?=.*[0-9])       # a digit must occur at least once
(?=.*[a-z])       # a lower case letter must occur at least once
(?=.*[A-Z])       # an upper case letter must occur at least once
(?=.*[@#$%^&+=])  # a special character must occur at least once
(?=\S+$)          # no whitespace allowed in the entire string
.{12,}            # anything, at least twelve places 
$                 # end-of-string

Вывод:
Вы здесь! Прохладный ! Как и обещал здесь ссылка на GitHub для кода. Данные для экспериментов вы можете найти здесь. Рассмотрите возможность подписаться на меня для большего количества контента :)