Information Security
September 2014 15

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

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

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

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

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

  1. Описание данных
  2. Невалидные пароли и не-пароли
  3. Распределение длины паролей
  4. Распределение надёжности паролей
  5. Словарная атака
  6. Топ паролей
  7. Выборка Gmail
  8. Выборка Rambler
  9. Анализ открытых источников
  10. Заключение


Описание данных


Данные из всех трех баз представляют собой набор пар адрес-пароль, разделенные двоеточием. Никаких других «мета-данных» недоступно. Однако данные достаточно зашумленные т.е. в них присутствуют строки не являющимися ни адресами почты, ни допустимыми паролями.



Если мы исследуем особенности данных, то сможем выдвинуть (или опровергнуть) гипотезу о том, в результате какого процесса пароли могли быть получены.

Невалидные пароли и не-пароли


Самые простой критерий невалидности пароля — несоответствие длины пароля требованиям почтовых сервисов.



Полученные данные говорят, что пароли из выборки не могли быть получены в результате «внутренней» утечки, так как несколько тысяч паролей не являются валидными паролями в принципе из-за ограничений на длину пароля в шесть символов (а для современных паролей gmail в восемь символов).

Рассмотрим эти аномально длинные (более 60) и короткие пароли (менее 6) в деталях.

Примеры

Длинные пароли представляют собой куски HTML-кода, один из репрезентативных примеров:



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

Краткая выборка слишком коротких паролей:



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

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

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

Распределение длины паролей


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



Распределение надёжности паролей


Для того, чтобы проверить эту гипотезу, рассмотрим простую метрику надежности пароля основанную на
стандарте 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)
}


Тогда распределение надёжности имеет вид:



Как видно из графика большинство паролей попадают в категорию не-надежные. В качестве примера рассмотрим пароли нулевой надёжности, так как скорее всего это ещё один репрезентативный пример невалидных паролей.

Пароли нулевой надёжности



Как видно из примеров выше, данные пароли не являются валидными (и с точки зрения человека выглядят скорее ошибкой ввода, чем действительным паролем), так как почтовые сервисы не дают зарегистрировать ящик, если считают пароль слишком простым, например, повторением одного и того же символа шесть раз. А значит, что возможно ещё больший пласт паролей не является валидным согласно современным требованиям.

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

Словарная атака


В качестве дополнительного аргумента проведем следующий эксперимент: возьмём выборку релевантных словарей паролей из общего доступа, проведем атаку на доступные пароли по этим словарям и оценим какой процент паролей содержится в этой выборке словарей (автор буквально не уходил дальше первых трёх ссылок гугла по запросу [password dictionary]).



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

Топ паролей


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



Выборка Gmail


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

Задача: проверить валидность (т.е. что пароль действительно подходит) паролей. Действие: по небольшой выборке из ~150-200 попробовать получить доступ к ящикам. Из всей выборки в принципе валидными являются ~2-3% (через несколько часов появления данных в открытом доступе), и фактически все являются деактивированными на момент проверки. Реально действующими являлись менее 1% ящиков и те заброшены владельцами по крайней мере в течение года.

Выборка Rambler


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



Что интересно, среди них довольно большой процент адресов рамблера.

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

<юмор></юмор>

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

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



Что является еще одним подтверждением гипотезы о фишинге и кумулятивной природе базы.

Анализ открытых источников


Вернемся к рассмотрению открытых источников. Активный поиск по паролям-логинам, привел нас к ряду раздач с геймерских форумов:



Оказывается, что часть списка уже в какой-то форме гуляла по сети.

Таким образом данные позволяют отвергнуть гипотезу о единственном источнике данных таком как «внутренняя утечка».

Основная часть используемого кода:
Анализ данных и визуализация
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



Заключение


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

С точки зрения пользователя данное событие не несет существенной опасности и скорее выглядит попыткой создания инфоповода.

UPD. Еще одним свидетельством того, что слитые данные — компиляция различный источников является наличие подборки gmail аккаунтов с "+" фичей в базе, когда адрес имеет вид имя+домен\слово at gmail.com (за напоминание про эту фичу спасибо geka)
Топ-10 доменов из выборки (весь список тут)
176 xtube
132 daz
88 filedropper
66 daz3d
64 eharmony
63 friendster
62 savage
57 spam
54 bioware
52 savage2

11 paygr
11 comicbookdb

Про paygr: пользователь gkond писал, что
Нашел свой е-мейл в дампе. Пароль, которой рядом с ним указан автоматически сгенерировал мне сервис paygr.com, в далеком мае 2011 года.

при этом «paygr» встречается 11 раз в списке "+" gmail. Возможно их база также была скомпрометирована.

Но самое важное, что comicbookdb признали, что их база была действительна украдена вместе с паролями (за ссылку спасибо EnterSandman): www.comicbookdb.com/hacked.php
+129
135.3k 205
Comments 106
Top of the day