почему номера паспортов всех участников интернет-голосования попали в Интернет
После завершения интернет-голосования, которое закончилось удивительно хорошо, меня и многих людей долго не покидало чувство того, что в России просто не может что-то пройти так хорошо. Сейчас можно расслабиться — реальность не подкачала и мы увидели двойное безумие: как с точки зрения архитектуры решения, так с точки зрения криптографии.
Кстати, Минкомсвязь до сих пор исключает
возможность утечки паспортных данных избирателей
Между тем распределение серий паспортов выглядит вот так:
Давайте воспроизведем события и попробуем понять как всего этого можно было избежать
Что произошло?
9 июля появляется материал Медузы
где они рассказали про архив degvoter.zip.
Как найти архив degvoter.zip?
Я нашел так. Внимательный поиск через Yandex привел меня к странице:
Там был найден текст «Https checkvoter.gosuslugi.ru degvoter.zip». Датировка на тот момент была 7.7.2020 (до публикации Медузы!), сейчас этот текст уже «переехал» на начало страницы и датировка изменилась.
Сам архив был убран с сайта госуслуги, но его копия сохранилась в web.archive.org, откуда его скачали все заинтересованные в исследовании лица в том числе и я. Чтобы понять почему так произошло рекомендую обратиться к первоисточнику — файлу
на сайте ГосУслуги.
Что внутри degvoter.exe?
Сама программа degvoter написана на C# и представляет из себя написанное «на коленке» WinForms приложение, которое работает с sqlite базой данных. Файлы в архиве датированы 2020-06-30 22:17 (30 июня 2020 года). Видно, что приложение писалось в кратчайшие сроки, ибо на Камчатке в этот момент уже было 1 июля 7:17, а тот факт, что участки открывались там в 8:00 говорит о том, что дедлайн был как никогда близок (хорошо что электронно голосовали только Москва и Нижний Новгород).
Код проверки паспорта:
Приложение как с архитектурной точки зрения, так и с криптографической — адовейший говнокод. И вот почему:
Описание просчетов архитектуры и принципа атаки на восстановление индентификаторов паспортов
В комплекте с программой находилась локальная БД в которой находилась таблица passports с двумя полями num и used. Где num было SHA256(<серия>+<номер>).
Очень часто, когда программист без соответствующего опыта подходит к вопросам криптографии, он делает кучу однотипных ошибок. С одной из таких ошибок относится применение хэш-фукнции без какой либо обвески. Идентификатор паспорта состоит из 4-ех циферной серии и 6-циферного номера [xxxx xxxxxx]. Т.е. у нас 10^10 вариантов. Номер телефона, к слову, состоит также из 10 цифр [+7(xxx)xxx-Экс экс-Экс экс]. В масштабах современного цифрового мира это не такие большие цифры. Так один Гбайт – это больше 10^9 байт, т.е. 100Гбайт хватит чтобы записать все варианты. Вполне вероятно что их банально можно перебрать. Я измерил что в однопоточном режиме современный Intel Core i5 процессор перебирает все sha256 хеши для одной серии паспорта за 5 секунд (000000-999999). И это на стандартной реализации sha256 без каких либо дополнительных ухищрений. Т.е. полный перебор всего пространства на обычном компьютере займет меньше дня. Если же учесть, что перебор можно вести в несколько потоков, то средненький процессор справится с такой задачей за несколько часов. Это является демонстрацией факта непонимания разработчиком системы принципов использования хеш-функций. Но даже правильное применение хэш-функций при такой архитектуре не спасает паспортные данные от раскрытия, если противник имеет неограниченные ресурсы. Ведь человек получивший доступ к БД может за конечное время получить идентификаторы паспортов, т.к. проверка одного паспорта должна проходить конечное время. Весь вопрос только в ресурсах (хотя если бы здесь просто применили хэширование в пару миллионов раундов, даже такое спорное архитектурное решение, как распространение БД вместе с приложением, не привело бы к такому громкому эффекту, т.к. позволило бы защититься хотя бы от журналистов). Медуза всего лишь продемонстрировала некомпетентность людей, проектировавших эту часть системы.
Давайте попробуем придумать, как сделать с одной стороны сильно лучше, с другой стороны уложиться также в одну ночь разработки.
Архитектура на коленке
Предположим, что у нас вообще нет времени и надо написать решение в течении ночи.
Очевидным требованием является то, что БД с хешами паспортов должна находиться на сервере и это обязательно должно быть клиент-серверное приложение. Сразу же возникнет вопрос, а что делать если вдруг на участке сломается Интернет? Для этих целей нужно сделать Android-версию клиентского приложения, которую нужно также дать скачать членам УИК. В местах, где нет ни интернета, ни сотовой связи на этом голосовании люди не голосовали.
Хэш в базе не должен вычисляться непосредственно из идентификатора паспорта. Это делается для того, чтобы хэши в базе нельзя было подобрать, используя существующие таблицы для перебора. Во-первых, нужно использовать стойкую-хеш функцию. Главный вопрос в том, КАК её нужно использовать. Возможных реализаций тут много, но по сути всё сводится к применению алгоритма в котором будут три параметров: тип хеш-функции, количество итераций, а также значение(я) которое нужно использовать для подмешивания к хэшу (оно будет общее для всех хешей). Конечное требование – внутри каждой итерации должен быть использована стойкая хеш-функция, а скорость вычисления хэша должна быть несколько единиц в секунду. Даже завладев БД с сервера злоумышленнику в этом случае потребовалось бы значительное время на восстановление всех данных.
Каждое из клиентских приложений будет представлять из себя просто поле ввода + Http-клиент, которые отправляет запрос на сервер.
Сервер работает только по HTTPS и только во время голосования и имеет ограничение в 1 RPS с IP. В качестве ограничителя RPS используем Redis, куда пишем в качестве ключа IP-адрес и TTL в одну секунду. Есть значение – запрос с IP не разрешен, нет значения – запрос с IP разрешен. Это даст возможность избежать перебора извне.
Написанное таким образом наше, буквально из говна и палок, решение окажется на порядок более защищенным чем текущее degvoter. При этом разница во времени написания невелика и с сам процесс написания кода может быть распараллелен на 3 человек (сервер, win-client,android-client).
Разберем возможные сценарии утечек.
У нас есть следующие точки где можно получить информацию о системе
- Исходный код серверной части
- Скомпилированные файлы серверной части
- Серверная БД
- Клиентские приложения
Клиентские приложения в этом случае не несут никакой информации, при этом к ним имеет доступ максимальное число людей и именно здесь максимальная вероятность утечек (что и произошло).
Для того чтобы восстановить информацию потребуется получить доступ к информации из точек (1,2) или (1,3). Если есть только база, то без известного метода хеширования восстановить что-то будет невозможно.
Выводы
- Каждый раз, когда нужно в каком-то виде работать с персональными данными — привлекайте архитектора
- Каждый раз, когда нужно в каком-то виде работать с персональными данными — привлекайте разработчика с опытом/образованием в сфере криптографии или информационной безопасности
Эти два простых правила помогут избежать позора, который мы наблюдали на примере с приложением degvoter, (Помните, что обычный разработчик может и не разбираться в нюансах применения хэш-функций)
Утилита для демонстрации возможности восстановления персональных данных DegvoterDecoder находится в репозитории, посвященном анализу данных голосования. По-умолчанию она настроена на 8 потоков. В случае если вы уже скачали архив degvoter.zip и вы программируете на C# – вы без труда разберетесь в принципе её работы.