После прочтения сжечь

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



    О чём речь


    Итак, secureshare.pw — сервис одноразовых ссылок, т.е. средство безопасной передачи конфиденциальных данных другому человеку. Суть в том, что секретные данные пользователя сохраняются в базе данных сервера, а пользователю даётся одноразовая ссылка, по которой доступны эти самые данные. Ссылка действительна только для одного (по умолчанию) просмотра, сразу после него данные уничтожаются. Можно не бояться того, что ссылка останется в истории сообщений вашего IM-сервера, в списке отправленных писем, в access-логах сервера, на скриншотах мониторов. К тому времени, когда злоумышленник доберётся до этой ссылки, она будет уже бессмысленна.

    Первый подход


    Базовый функционал системы очень прост. Всё, что нужно — сохранить данные из формы в базу данных и выдать юзеру ссылку, однозначно идентифицирующую эти данные. Например, добавить в get-параметр primary key. После показа данных выполнить элементарный DELETE-запрос, и готово. После первого подхода система в точности так и работала.

    Но как-то это несерьёзно, не так ли?



    Второй подход


    Ну, давайте подумаем, что тут не так. Первое, что бросается в глаза — primary key прямо в get-параметрах запроса. То есть, если перебирать все числа по очереди, можно насобирать много чужих секретов. Решение достаточно очевидно — использовать вместо числа что-то большое и неподбираемое. Скажем, sha1-хеш (в md5 нашли коллизии и его уже не рекомендуется использовать). Отлично, добавляем в БД новое поле, делаем по нему UNIQUE индекс и генерируем его случайным образом для каждого нового пользовательского секретика. Уже лучше.

    Третий подход


    Данные в БД у нас хранятся в открытом виде. Нехорошо как-то, это же секрет. Если база утечёт, все наши секреты будут доступны злоумышленнику. Что делать? Не хранить данные в плейн-тексте! Давайте их зашифруем. Выберем стойкий симметричный блочный алгоритм шифрования, а при показе данных будем расшифровывать данные на лету, благо объём сообщений обычно невелик. Теперь, если база утечёт, злоумышленнику придётся ещё попотеть, чтобы достать нужные данные. Неплохо. Но есть серьезное «но». Часто всё-таки хакер либо не имеет вообще никакого доступа к системе, либо имеет полный. В том числе и к исходникам нашего сервиса, осуществляющего расшифровку, в том числе и к ключам шифрования, а тогда все данные у него будут как на ладони — расшифровать их можно будет «одной левой». Получается, что и в шифровании особенного смысла-то нет. Что делать?



    Четвертый подход


    Каждое пользовательское сообщение шифруется случайно сгенерированным ключом. После шифрования ключ делится на две половинки, одна из которых сохраняется в БД вместе с зашифрованными данными, а вторая половинка встраивается в ссылку, которая отдаётся клиенту. В итоге получается, что расшифровать данные можно будет, только имея обе половинки ключа. Таким образом, базу можно вообще выкладывать в открытый доступ и полностью раскрыть весь алгоритм работы системы — без второй половинки ключа, которая в ссылке у клиента, данные в базе — всего лишь куча мусора. Отлично!



    Пятый и заключительный подход


    Кажется, всё неплохо. Придумываем название, регистрируем подходящий домен. Им стал secureshare.pw.
    Последняя деталь: негоже серьезные вещи по открытому HTTP-протоколу гонять. Нужен HTTPS. Первый шаг прост — не разрешаем пользоваться по HTTP и принудительно редиректим с 80 порта на 443, предварительно настроив на сервере HTTPS. Работает. Но в уголке горит ярко-красная надпись, говорящая о том, что подлинность домена не подтверждена, а Chrome выдаёт навевающий страх красный (в новой версии — жёлтый) экран с предупреждением.



    Нехорошо. Надо, чтобы нашу личность подтвердил один из корневых центров сертификации. Пара запросов в поисковике — и находим массу предложений от самых разных компаний. Я выбрал GoDaddy — не слишком сложная процедура получения и адекватные деньги. Для стартапа подойдёт ;-)
    В качестве финальных штрихов добавим пару приятных фич — количество показов, прежде чем ссылка удалится из базы (по умолчанию = 1), дата, по достижении которой ссылка самоуничтожится (кронскрипт каждый день), даже если не была просмотрена (по умолчанию неделя), а также подтверждение просмотра (защита от непреднамеренного просмотра и уничтожения данных).

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

    Ваши вопросы и предложения, как обычно, велкам. Спасибо за внимание!

    Статья написана в рамках конкурса статей студентов проекта Технопарк Mail.Ru.
    Mail.ru Group
    Building the Internet

    Comments 81

      +2
      Наверняка сервис найдёт своих пользователей, а параноики на то и параноики.
      Ещё файлик атачить можно было бы…
        +23
        Настоящий параноик таким сервисом пользоваться не станет. Где гарантия, что данные действительно удаляются после просмотра?
          +4
          Это я и имел ввиду что параноикам не прокатит, а кто-то другой наверняка заинтересуется.
            0
            Исходники на github, дать поставить у себя, потискать, попытаться взломать — успокоиться и юзать свою копию/разочароваться и дать фидбэк.
              +4
              Кто мешает выложить на гитхаб исходники, а юзать на сервере форк?
                0
                Даже если на сервере не форк, то удалится информация или нет сильно зависит от СУБД.
                Большинство СУБД не удаляют информацию (я уж молчу про затирание) по запросу DELETE, а только ставят в записи пометку, что она удалена. Таким образом обеспечивается её отсутствие в выдаче по какому-либо запросу. При наличии физического доступа к серверу все записи в БД можно будет восстановить.
            0
            Зашифруйте отправляемые данные, пароль передайте по телефону после скачивания по ссылке
              0
              Настоящий параноик передает информацию только лично. Написанную на бумаге и запечатанную в непрозрачный конверт.
                0
                Берем какую-нибудь экзотическую длину волны света (что там проходит через бумагу) и профит!
              +1
              Гарантию, конечно, обеспечить сложно. Можно предложить человеку пользоваться несколькими независимыми каналами передачи данных, одним из которых может стать secureshare. Например, если Вы передаёте реквизиты доступа к серверу по ssh, то логин можно передать в письме или смс, а хост и пароль — в одноразовой ссылке.
              0
              Файлики — одна из приоритетных задач на ближайшее время, скоро будет
                0
                файлики тоже шифровать надо
              +14
              Решение достаточно очевидно — использовать вместо числа что-то большое и неподбираемое. Скажем, sha1-хеш (в md5 нашли коллизии и его уже не рекомендуется использовать).


              Зачем люди делают такое постоянно?
              Если нужна случайная строчка — генерируешь случайную строчку.
              Чем отличается с точки зрения взлома генерация случайного числа от генерации случайного числа, а затем взятия от него md5/sha1/любого хеша? Ведь узнав алгоритм генерации хешей, ничто не мешает просто так же перебрать числа.
                0
                Вы совершенно правы, генерировать хеш или случайную строку — в данном случае всё равно. Теоретически, если генерировать строку той же длины из б0льшего алфавита (не 0-9a-f, а, скажем, A-Za-z0-9), то возможных сочетаний получается больше и вероятность случайно угадать хеш снижается. Но на практике оба этих варианта обеспечивают достаточную устойчивость к перебору.
                  +10
                  Но нет же :)
                  То есть алгоритм «использую числа по порядку в качестве куска url» от «использую хеш от того же числа в качестве куска url» не отличается с точки зрения взлома почти ничем, нам достаточно узнать что за хеш использовали и нагенерировать тех же чисел, только теперь похешировав их.
                  С алгоритмом «использую случайное число» от «использую хеш от него же» то же самое: от того, что ключ визуально стал больше, случайнее он не стал, различных хешей всё равно останется = [0, RAND_MAX)
                  Поэтому хочется именно повысить количество случайных бит, а сколько раз потом к ним какие-либо детерменированные функции применяли не играет же роли.
                    0
                    Я имел в виду не брать хеш от случайного числа, а именно генерировать случайную строку. То есть, иными словами, генерить число в 62-ричной системе (если используем алфавит A-Za-z0-9). Таким образом, если хеш, как сейчас, будет 40 байт, то различных вариантов будет 62^40. Согласитесь, это больше чем RAND_MAX :-)
                      0
                      rand вообще-то не особо случайные числа выдает, /dev/random куда лучше, да и размер не ограничен, берите сколько нужно.
                        0
                        В /dev/random с таким сервисом может и энтропия закончиться. Лучше выглядят решения с совместной генерацией случайного числа клиентом и сервером, в таком случае можно и /dev/urandom использовать.
                          0
                          Да и так можно urandom использовать. Ведь это не криптографическое применение — это число не будет ни ключом, ни вектором и никаким образом ни будет связано с исходным текстом (в отличии от хэша). И кстати, urandom тоже криптографически сильный генератор, чего не скажешь о rand.
                        +3
                        Так вы от этой случайной строки хеш берете или нет? Если берёте — то не надо, это ничего не улучшит. Если не берёте — то не называйте её хешом.
                        0
                        А если посолить?
                          +2
                          Мертвому припарки это называется. Для генерирования случайных чисел, от случайности которых зависит безопасность — надо использовать специальные криптографические генераторы. Точка.
                            0
                            Убедили, спасибо)
                        +3
                        UUID4
                          +1
                          нет, вероятность коллизии с использованием хеш-функции — больше
                      • UFO just landed and posted this here
                          +1
                          Удаление действительно нельзя считать достаточно надёжным, но примите во внимание то, что в базе хранятся зашифрованные данные. Если злоумышленнику удастся восстановить удалённые данные, то он получит лишь кусок мусора. Для расшифровки нужен ключ, а в базе хранится только его часть.
                            0
                            Если у злоумышленника нет второй половинки ключа, то и ссылку нет нужды делать одноразовой :)
                            Изначальная идея была все же что даже если злоумышленник получит со временем эту половинку, то открыть ссылку все равно не сможет. Впрочем это уже мелочи.
                              0
                              Ну тут злоумышленник получит доступ к ссылке если он украдёт вторую половинку ключа И получит доступ к базе И СУБД к тому времени ещё не перезатрёт удаленную первую половинку.
                                0
                                Я понимаю :). Просто такая атака все же реалистична, а когда речь заходит о криптографических схемах то обычно стараются избежать любых реалистичных атак, даже самых сложных.
                                0
                                Следует различать атаку на пользователя (прочитали почту, увидели ссылку, хотим прочитать конкретное сообщение) и атаку на сервис в целом (влозмали сервер, хотим прочитать все сообщения).
                                  +1
                                  Это вариант атаки на пользователя, просто он включает в себя нетривиальный шаг по получению доступа к сервису. Это нормально для рассмотрения криптографических схем. Например MITM-атаки подразумевают возможность заблаговременного встраивания в канал связи между абонентами, что тоже технически сложно реализовать, но атаками на пользователя они от этого быть не перестают.
                                    +1
                                    Я не о том. Многие не переживают о персональных атаках («кому я нужен»), но беспокоятся о массовом взломе и массовой утечке чувствительных данных. Защиты от профессиональных персональных атак практически нет, но если сервис делает невозможным (в криптографическом смысле слова — очень маловероятным) массовую утечку by design (приватного ключа на стороне сервера не хранится), то доверие к нему возрастает.
                                +1
                                удаление данных by DELETE имеет обратную сторону медали: дефрагментация пространства. Я бы вместо РСУБД базы использовал бы какое-нибудь NoSQL, например тот же маилрушный тарантул: и можем использовать первичный ключ, и персистентность, и производительность и с кроном заморачиваться не надо, можно хранимку в рамках самого хранилища написать.
                                  0
                                  Перед удалением можно написать поверх какого-нибудь мусора.
                                  Хотя, с реализованной защитой данных это, возможно, уже и лишнее.
                                +3
                                Интересные мысли, понравилось про половинки ключей.
                                  +5
                                  тогда получается вообще незачем делить случайное число на две части. Пришли данные от поьзователя, сгенерил случаное число, зашифровал данные, сложил их в базу. Клиенту отдал сгенерированное случайное число и id записи.
                                  В чем прикол хранить половину у себя? от жесткого перебора это все равно не спасет
                                    +1
                                    Прикол не в том, чтобы хранить половину у себя, а в том, чтобы не хранить ключ целиком у себя. Представьте такую ситуацию: хакеру удалось проникнуть в систему, он смог восстановить зашифрованные данные из базы. Теперь ему нужен ключ. Если бы мы отдавали весь ключ целиком клиенту, его можно было бы вытащить, к примеру, из access-логов сервера. А так хакеру потребуется приложить больше усилий для взлома.

                                    Как известно, в таких ситуациях решающим фактором является цена информации и цена усилий для её получения. Если вторую цену мы поднимем достаточно высоко, то он скорее плюнет и бросит эту дурацкую затею.
                                      +8
                                      мне кажется не суть, ну возьмет он из access логов вторую половину ключа а первую из кода, раз уж добрался. Разделение этого ключа на 2 части ничего не решает.

                                      Если уж параноить тут можно так сделать еще интереснее:
                                      анкор (хэш) урлы не уходят из браузера. Браузер при переходе вот по такой урле http://habrahabr.ru/#search_form отшлет на сервер get запрос только habrahabr.ru/

                                      Это можно заюзать таким образом. Оставляем всю вашу схему с серверным шифрованием но перед этим еще и шифруем на клиенте (в браузере перед отправкой на сервер). Ключик клиентского шифрования пока храним в браузере. После того как сервер вернул сгенеренную урлу, дописываем ключ клиентского шифрования через хэш:
                                      secureshare.pw/s/617cde716b6f4bfb34...#client_side_secret_password

                                      теперь все стало еще сложнее потому как client_side_secret_password никуда не уйдет из браузера (в логи апача, провайдера и тп)
                                        +5
                                        Поздравляю, вы только что придумали mega.co.nz
                                          0
                                          ну клиентское шифрование — чего такого ) Наверняка Mega не хранит ключик в хэш урле. Тут фишка именно в том чтобы без регисрации и паролей придумать самый секьюрный способ передачи данных через веб сервис
                                            +5
                                            Именно что хранит: https://mega.co.nz/#!MANgUBIR!VYxIijCZsebOoSAA7aowPzFyhZQo8hXj_JwCKaJQttY

                                            При нажатии на share там даже опция появляется, вставить ключик в хэш или скопировать отдельно (и тогда ссылка будет просто https://mega.co.nz/#!MANgUBIR)
                                              +4
                                              хых, ну тогда прикольно получилось ) решение напрашивалось )
                                          0
                                          Начал писать этот же комментарий. Хорошо, что глянул двумя строчками выше и заметил ваш. Вот для полноты ссылка на JS AES.
                                          0
                                          чтоб не палить данные в access логе — используем POST
                                            0
                                            смысл в том чтобы послать эту ссылку человеку.
                                              +3
                                              log_format postdata $request_body;
                                          0
                                          мне кажется не суть, ну возьмет он из access логов вторую половину ключа а первую из кода, раз уж добрался. Разделение этого ключа на 2 части ничего не решает.

                                          Суть в том, что если злоумышленник будет знать вторую часть ключа из ссылки, то он сможет посмотреть только данные доступные по ссылке, остальные данные, которые хранятся в БД он узнать не сможет.
                                            0
                                            так, а как он сможет посмотреть остальные данные если на сервере мы не храним вторую часть ключа? это же рандомный ключ для каждого документа:
                                            шифруется случайно сгенерированным ключом

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


                                            Получается, что после взлома неизвестная часть ключа содержит в два раза меньше бит, чем исходный. Насколько это устойчиво к brute-force?
                                            Может имеет смысл не делить ключ, а «удваивать» его: один в базе, другой передавать через url?
                                              +3
                                              На самом деле, имеет смысл передавать ключ целиком, не храня ничего в базе.
                                              +1
                                              Вопрос к автору: ловит ли он заходы по ссылкам сервисов передачи сообщений?
                                              Некоторые сервисы, например ВК, Фейсбук (даже gmail раньше) забирали заголовки, или весь контент с посланной через них ссылки (для удобства пользователя конечно)
                                                +2
                                                Да, специально для этого сделал подтверждение просмотра, раньше ссылка неожиданно протухала, если её передавали через ВКонтакте или подобные сервисы.
                                                  +2
                                                  т.е. в логах сервера ссылка появляется раньше чем ее открывают (для конкретных случаев). В таком варианте использовать hash (location#hash) куда правильнее. А при подтверждении уже POST-ом отдавать данные.
                                                0
                                                Как насчет guid в качестве первичного ключа использовать?

                                                Интересно, а если добавить возможность еще установить время начала действия ссылки, чтобы нельзя было раньше времени ей воспользоваться, это бы предотвратило преждевременный перехват
                                                  0
                                                  Плохо — функции создания guid тоже предсказуемы, хотя и менее, чем rand()
                                                  +1
                                                  Копировать ссылку потом неудобно. JS'ом сделайте, чтобы при нажатии на блок с ссылкой она выделялась полностью. Замените code на disabled input и добавьте такой скрипт (jQuery у вас подключен как я понял):

                                                  $('input[type="text"]').click(function() {
                                                      $(this).select();
                                                  });
                                                  
                                                    +4
                                                    При редиректе с HTTP на HTTPS отсутствует заголовок HSTS

                                                    HTTP/1.1 302 Found
                                                    Date: Wed, 22 Jan 2014 11:56:52 GMT
                                                    Server: Apache/2.2.15 (Red Hat) mod_rpaf/0.6 DAV/2 PHP/5.3.27
                                                    X-Powered-By: PHP/5.3.27
                                                    Location: https://secureshare.pw/
                                                    Content-Length: 0
                                                    Content-Type: text/html; charset=UTF-8
                                                    Connection: close
                                                      +2
                                                      Note: The Strict-Transport-Security header is ignored by the browser when your site is accessed using HTTP; this is because an attacker may intercept HTTP connections and inject the header or remove it. When your site is accessed over HTTPS with no certificate errors, the browser knows your site is HTTPS capable and will honor the Strict-Transport-Security header.
                                                      Впрочем, этот заголовок в HTTPS-версии тоже отсутствует
                                                      HTTP/1.1 200 OK
                                                      Server: nginx/0.7.65
                                                      Date: Wed, 22 Jan 2014 12:12:43 GMT
                                                      Content-Type: text/html; charset=UTF-8
                                                      Connection: keep-alive
                                                      X-Powered-By: PHP/5.3.27
                                                      Expires: Thu, 19 Nov 1981 08:52:00 GMT
                                                      Cache-Control: no-store, no-cache, must-revalidate, post-check=0, pre-check=0
                                                      Pragma: no-cache
                                                      Content-Length: 6324
                                                      
                                                      +3
                                                      Секьюрно, так до конца — нужен csrf токен в форму сабмита + anti-clickjacking хидер :)
                                                        –8
                                                        http://vk.com/app3406095 => https://secureshare.pw/s/2105e613792d1b1d938097ce7a90808a6da9f8325089e367e82d7ac3
                                                        Сократил, спасибо. Понимаю, секюрно, все дела, но для сервиса коротких ссылок слишком длинно.

                                                          +10
                                                          А это и не является сервисом коротких ссылок…
                                                          0
                                                          хорошо бы еще придумать план монетизации…
                                                          +2
                                                          Исходник можно посмотреть? Если вы делаете хеш обычной блочной функцией, а не специально предназначенным для этого HMAC, будьте осторожны с подводными камнями.
                                                          +4
                                                          оффтоп
                                                          Мой друг занимается типографикой. На досуге он адаптировал афишу этого фильма на русский язык по всем правилам оригинала. Было это пять лет назад, поэтому ни разу не реклама.

                                                          Сжечь после прочтения


                                                          А то как выполнен официальный постер — стыд и позор.
                                                            0
                                                            Хороший стиль изложения у вас, понравилось. А на сайте хочется календарик для ввода дат :)
                                                              +1
                                                              Далеко не новая и определенно не заслуживающая такого внимания тема. Уже довольно продолжительное время существует, например, Privnote, у которого ко всему прочему и шифрование осуществляется на стороне клиента, а ключ передается в качестве хеша в URL.
                                                                +2
                                                                Сделайте фичу, что ссылка удаляется только через минуту. А то при любой технической проблеме у пользователя вроде случайно глюканувшего 3G будет недогруженная страница (без данных) и удалённая запись в БД.
                                                                  0
                                                                  … Лучше и правильней чтобы удаление происходило по AJAX по загрузке страницы.
                                                                    +5
                                                                    Ох уж этот веб 2.0… AJAX может и не отработать, параноик зачастую что-нибудь типа NoScript использует.
                                                                  0
                                                                  FYI: StartSSL.com раздаёт Class 1 сертификаты бесплатно.
                                                                  • UFO just landed and posted this here
                                                                      –3
                                                                      Заметил в статье пассивную рекламу 2 сайтов с пиратским контентом. Вы чего, майл.ру?
                                                                        –3
                                                                        спасибо, не заметили
                                                                        0
                                                                        Шестой подход:
                                                                        Переносим шифрование не клиентскую сторону.
                                                                          0
                                                                          Почему не работает?

                                                                          Only users with full accounts can post comments. Log in, please.