Анализ утёкших паролей Gmail, Yandex и Mail.ru

Совсем недавно в публичный доступ попали базы паролей популярных почтовых сервисов [1,2,3] и сегодня мы их проанализируем и ответим на ряд вопросов о качестве паролей и возможном источнике (или источниках). Так же мы обсудим метрики качества отдельных паролей и всей выборки.Не менее интересными являются некоторые аномалии и закономерности баз паролей, возможно, они смогут пролить свет на то, что могло служить источником данных и насколько данная выборка является опасной с точки зрения обычного пользователя.

Формально, мы рассмотрим следующие вопросы: насколько надежными являются пароли в базе и могли ли они быть собраны словарной атакой? Есть ли признаки фишинговых атак? Могла ли «утечка» данных быть единственным источником данных? Могла ли данная база быть аккумулирована в течении длительного периода или данные исключительно «свежие»?

Структура статьи

Описание данных Невалидные пароли и не-пароли Распределение длины паролей Распределение надёжности паролей Словарная атака Топ паролей Выборка Gmail Выборка Rambler Анализ открытых источников Заключение Описание данныхДанные из всех трех баз представляют собой набор пар адрес-пароль, разделенные двоеточием. Никаких других «мета-данных» недоступно. Однако данные достаточно зашумленные т.е. в них присутствуют строки не являющимися ни адресами почты, ни допустимыми паролями.056449076470443bb262159a2b266c11.pngЕсли мы исследуем особенности данных, то сможем выдвинуть (или опровергнуть) гипотезу о том, в результате какого процесса пароли могли быть получены.

Невалидные пароли и не-пароли Самые простой критерий невалидности пароля — несоответствие длины пароля требованиям почтовых сервисов.168839ff390f429fbbb492047af28fe2.pngПолученные данные говорят, что пароли из выборки не могли быть получены в результате «внутренней» утечки, так как несколько тысяч паролей не являются валидными паролями в принципе из-за ограничений на длину пароля в шесть символов (а для современных паролей gmail в восемь символов).Рассмотрим эти аномально длинные (более 60) и короткие пароли (менее 6) в деталях.

Примеры Длинные пароли представляют собой куски HTML-кода, один из репрезентативных примеров: cfc8bb6bbdf74b9d80e84d3a12bfdf04.pngПодобные примеры указывают, что одним из источников паролей мог быть фишинг. Запись в базе явно не была проверена человеком и получена автоматически, на фишинг так же указывает тот факт, что в пароле присутствует html-разметка, что довольно нетипично для кражи пароля через заражение.Краткая выборка слишком коротких паролей: 49486c1892ec4cb48d9f4bc131f8c941.png

Еще один индикатор того, что одним из источников мог быть фишинг — отсутствие логина и пароля в записях. Особенно интересно выглядит апостроф без указания пароля. Возможно, потенциальная жертва догадалась о фишинговой форме и попыталась проверить наличие SQL инъекции.

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

Для того, чтобы оценить качество всей выборки, мы удалим из неё заведомо неверные пароли длины меньше 6 и больше 60 и рассмотрим всё распределение в целом по нескольким параметрам.

Распределение длины паролей Как видно из графика ниже, большая часть паролей имеет длину в 8 или менее символов. Что может указывать на то, что существенный пласт паролей потенциально неустойчив к различному виду атак переборных атак.e97f01995c9b407ca7b085562d6505f8.pngРаспределение надёжности паролей Для того, чтобы проверить эту гипотезу, рассмотрим простую метрику надежности пароля основанную настандарте PCI.Пусть за удовлетворение одного из следующих условий пароль получает условный балл: пароль содержит не менее 7 ми символов; пароль содержит хотя бы одну строчную букву; пароль содержит хотя бы одну прописную букву; пароль содержит хотя бы одну цифру; пароль содержит хотя бы один специальный символ. Если пароль получает 4/5, то мы называем его надежным (очень надежным за 5/5), соответственно 3/5 назовем средним, а 2/5 слабым (0 или 1 балл назовем очень слабым). Код на языке R приведен ниже.Функция надежности library («Hmisc») strength <- function(password){ # must contain at least 7 characters score = 0 if (nchar(password) >= 7){ inc (score) <- 1 } # at least one digit if(grepl("[[:digit:]]", password)){ inc(score) <- 1 } # at least one lowercase letter if(grepl("[[:lower:]]", password)){ inc(score) <- 1 } # at least one uppercase letter if(grepl("[[:upper:]]", password)){ inc(score) <- 1 } # at least one special symbol if(grepl("[#!?^@*+&%]", password)){ inc(score) <- 1 } # 0-1 very weak # 2 - weak # 3 - medium # 4 - strong # 5 - very strong return(score) } Тогда распределение надёжности имеет вид:55629360d7f649e3b62c8ecb816c093b.pngКак видно из графика большинство паролей попадают в категорию не-надежные. В качестве примера рассмотрим пароли нулевой надёжности, так как скорее всего этого еще один репрезентативный пример невалидных паролей.Пароли нулевой надёжности 443c679df6cb42f090b0b4883d7ee67e.pngКак видно из примеров выше, данные пароли не являются валидными (и с точки зрения человека выглядят скорее ошибкой ввода, чем действительным паролем), так как почтовые сервисы не дают зарегистрировать ящик, если считают пароль слишком простым, например, повторением одного и того же символа шесть раз. А значит, что возможно ещё больший пласт паролей не является валидным согласно современным требованиям.Возможно, что существенная часть базы собрана в течении длительного периода времени, когда требования к паролям были мягче? Иначе довольно сложно объяснить столь большую группу паролей, не соответствующих требованиям современных почтовых систем.

Словарная атака В качестве дополнительного аргумента проведем следующий эксперимент: возьмём выборку релевантных словарей паролей из общего доступа, проведем атаку на доступные пароли по этим словарям и оценим какой процент паролей содержится в этой выборке словарей (автор буквально не уходил дальше первых трёх ссылок гугла по запросу [password dictionary]).3701f54a13d6465da08e04d15c64644b.pngИз таблицы выше видно, что существенная доля паролей содержится в словарях, что так же указывает на то, что часть паролей могла быть получена в результате словарной атаки (или какой-то модификации перебора).Топ паролей Приведем подборку наиболее популярных паролей и заметим, что большая часть сейчас не является допустимыми паролями.947674c4aa734f498a6c4cc13ea0ecd4.pngВыборка Gmail Действия и данные, описанные и полученные в данной и следующей части, были произведены и переданы другом моего друга, пожелавшего остаться неизвестным.Задача: проверить валидность (т.е. что пароль действительно подходит) паролей. Действие: по небольшой выборке из ~150–200 попробовать получить доступ к ящикам. Из всей выборки в принципе валидными являются ~2–3% (через несколько часов появления данных в открытом доступе), и фактически все являются деактивированными на момент проверки. Реально действующими являлись менее 1% ящиков и те заброшены владельцами по крайней мере в течении года.

Выборка Rambler Несложно обнаружить в сети списки «действительно валидных» адресов, составленных широким кругом заинтересованных лиц (ака кулхацкеры).fa328b306f8949c4afbc12953cab7170.pngЧто интересно, среди них довольно большой процент адресов рамблера.Rambler был предупрежден за несколько дней до публикации и был получен ответ, что необходимые меры безопасности будут приняты в ближайшее время.

<юмор>daceb5d836da4a0bb4c9b0c5fdd05c68.png

Что интересно, процент валидных паролей существенно выше и до последнего времени rambler был вне медийного поля событий и не активизировал дополнительных систем безопасности.

Это позволило неизвестному антропологу утечки оценить последние моменты жизни почтовых ящиков. Несмотря на валидность паролей, все ящики являлись заброшенными в течении долгого времени (~1–1.5 года) и заканчивались одним из подобных писем: 6f812d1c3f884bc6bbd93b808668ce6e.pngЧто является еще одним подтверждением гипотезы о фишинге и кумулятивной природе базы.

Анализ открытых источников Вернемся к рассмотрение открытых источников. Активный поиск по паролям-логинам, привел нас к ряду раздач с геймерских форумов: d09cb848073e44c2869184fc5eee5127.pngОказывается, что часть списка уже в какой-то форме гуляла по сети.Таким образом данные позволяют отвергнуть гипотезу о единственном источнике данных таком как «внутренняя утечка».

Основная часть используемого кода:

Анализ данных и визуализация source («multiplot.R») source («password_strength.R») library («ggplot2») print («loading yandex data») yandex <- read.csv("yandex.txt", header = FALSE, sep = ":", quote = "", stringsAsFactors = FALSE) print("loading mailru data") mailru <- read.csv("mail.txt", header = FALSE, sep = ":", quote = "", stringsAsFactors = FALSE) print("loading gmail data") gmail <- read.csv("gmail.txt", header = FALSE, sep = ":", quote = "", stringsAsFactors = FALSE) ##testing if data loaded correctly print("testing, if loaded correctly") print(head(yandex)) print(head(mailru)) print(head(gmail)) ##changing names names(yandex) <- c("email", "password") names(mailru) <- c("email", "password") names(gmail) <- c("email", "password") print("computing lengths of passwords and adding to the datasets") yandex$pass_length <- sapply(yandex$password, nchar) mailru$pass_length <- sapply(mailru$password, nchar) gmail$pass_length <- sapply(gmail$password, nchar) print("number of invalid passwords by length") print(nrow(yandex[yandex$pass_length < 6,])) print(nrow(yandex[yandex$pass_length > 60,])) print (nrow (mailru[mailru$pass_length < 6,])) print(nrow(mailru[mailru$pass_length > 60,])) print (nrow (gmail[gmail$pass_length < 6,])) print(nrow(gmail[gmail$pass_length > 60,])) print («removing invalid passwords by length») yandex <- subset(yandex, pass_length >= 6 & pass_length <= 60) mailru <- subset(mailru, pass_length >= 6 & pass_length <= 60) gmail <- subset(gmail , pass_length >= 6 & pass_length <= 60) #print("checking that they are removed") print(nrow(yandex[yandex$pass_length < 6,])) print(nrow(yandex[yandex$pass_length > 60,])) print (nrow (mailru[mailru$pass_length < 6,])) print(nrow(mailru[mailru$pass_length > 60,])) print (nrow (gmail[gmail$pass_length < 6,])) print(nrow(gmail[gmail$pass_length > 60,])) print («visualizing distribution of password lenghts by provider») gmailcolor <- "deepskyblue" yandexcolor <- "orangered1" mailrucolor <- "limegreen" pgmail <- ggplot(data=gmail, aes(x=pass_length)) + scale_x_discrete(limits=seq(6, 20, 1), breaks=seq(6, 20, 1), drop=TRUE) + geom_histogram(colour="black", fill=gmailcolor, aes(y=..density..)) + coord_cartesian(xlim=c(5,21.5)) + xlab(expression("Длина пароля"))+ ylab(expression("Доля"))+ggtitle("Gmail") pyandex <- ggplot(data=yandex, aes(x=pass_length)) + scale_x_discrete(limits=seq(6, 21, 1), breaks=seq(6, 21, 1), drop=TRUE) + geom_histogram(colour="black", fill=yandexcolor, aes(y=..density..)) + coord_cartesian(xlim=c(5,21.5)) + xlab(expression("Длина пароля"))+ ylab(expression("Доля"))+ggtitle("Yandex") pmailru <- ggplot(data=mailru, aes(x=pass_length)) + scale_x_discrete(limits=seq(6, 20, 1), breaks=seq(6, 20, 1), drop=TRUE) + geom_histogram(colour="black", fill=mailrucolor, aes(y=..density..)) + coord_cartesian(xlim=c(5,20.5)) + xlab(expression("Длина пароля"))+ ylab(expression("Доля"))+ggtitle("Mail.ru") multiplot(pgmail, pyandex, pmailru, cols=3)

print («computing strength of the passwords») yandex$strength <- sapply(yandex$password, strength) mailru$strength <- sapply(mailru$password, strength) gmail$strength <- sapply(gmail$password, strength) print(head(yandex)) print(head(mailru)) print(head(gmail)) scale <- scale_x_discrete(limits=c(1,2,3,4,5), breaks=c(1,2,3,4,5), drop=TRUE, labels=c("Очень\nслабый", "Слабый", "Средний", "Надежный", "Очень\nнадежный")) pgmail <- ggplot(data=gmail , aes(factor(strength))) + geom_bar(colour="black", fill=gmailcolor) + xlab(expression("Надежность"))+ coord + ylab(expression("Доля"))+ggtitle("Gmail") + scale pyandex <- ggplot(data=yandex, aes(factor(strength))) + geom_bar(colour="black", fill=yandexcolor, binwidth=0.5) + xlab(expression("Надежность"))+ coord + ylab(expression("Доля"))+ggtitle("Yandex") + scale pmailru <- ggplot(data=mailru, aes(factor(strength))) + geom_bar(colour="black", fill=mailrucolor, binwidth=0.5) + xlab(expression("Надежность"))+ coord + ylab(expression("Доля"))+ggtitle("Mail.ru") + scale multiplot(pgmail, pyandex, pmailru, cols=3) print("Zero strength passwords") print("GMAIL") print(head(gmail[gmail$strength == 0,])) print("YANDEX") print(head(yandex[yandex$strength == 0,])) print("MAILRU") print(head(mailru[mailru$strength == 0,]))

table_gmail <- sort(table(gmail$password) , TRUE) table_yandex <- sort(table(yandex$password), TRUE) table_mailru <- sort(table(mailru$password), TRUE)

print («gmail most frequent») print (head (table_gmail, 100)) print («yandex most frequent») print (head (table_yandex,100)) print («mailru most frequent») print (head (table_mailru,100))

only_pass_gmail <- gmail[ ,2] write.csv(only_pass_gmail, "only_pass_gmail", row.names = FALSE) only_pass_yandex <- yandex[,2] write.csv(only_pass_yandex, "only_pass_yandex", row.names = FALSE) only_pass_mailru <- mailru[,2] write.csv(only_pass_mailru, "only_pass_mailru", row.names = FALSE) Код эксперимента 'словарная атака' #!/bin/bash data=sample_mailru dict=saved_dict_mailru > $dict j=0 while read p; do ((j++)) echo -n $j if grep -q »^$p$» dictionary/*; then echo » in » echo $p >> $dict else echo » out » fi if ((»$j» > 10000)); then break fi done <$data Заключение Таким образом наиболее вероятной выглядит гипотеза, что данная выборка — компиляция различных источников (фишинг, заражении, словарно-переборные атаки, собрание популярных подборок) в течении длительного периода времени. Достаточная часть данных в принципе не является валидными паролями по формальным синтаксическим критериям, что также подтвердила экспериментальная проверка.С точки зрения пользователя данное событие не несет существенной опасности и скорее выглядит попыткой создания инфоповода.

© Habrahabr.ru