Небольшой пример из практики на тему того, как ломают web сервера на Linux. А то некоторые думают, что под линуксом вирусов нет, или что Linux настолько безопасен, что его даже защищать не надо. В общем, показательная история из жизни. Ничего особенного, просто пример, которых наверняка много у любого админа, который работает с разработчиками.
Пересылает мне утром владелец учетной записи в Hetzner информацию о том, что пришло уведомление, что у нас на одном из серверов подозрительная активность и его вырубят, если мы ничего не сделаем. Это тестовый сервер разработчика. Иду по ssh на сервер и вижу.
Вижу явный вирус. Думаю, что майнер какой-нибудь (на виртуалке за 3 евро, хе-хе). Смотрю дерево процессов, чтобы понять, откуда ноги растут.
Все понятно. На сервере стоит Docker. В нем контейнер Postgres. Вирус сидит в контейнере. Иду смотреть, как запущен контейнер. Ничуть не удивлен тем, что он смотрит напрямую в инет.
Дальше решил проверить, с какими параметрами он запущен. Опять не удивляюсь.
В лучших традициях жанра, все запущено с дефолтной учеткой postgres/postgres. Фраза fixme игнорируется.
Решил на всякий случай проверить прод. Захожу, а там все то же самое, но вируса пока нет. Не стал разбираться, чтобы ненароком не словить бан от Hetzner. Первую виртуалку просто удалили. На второй сразу поменяли пароль.
Вот так легко и просто взламываются веб сервера под Linux. Ожидали чего-то большего? Не нужны никакие хакеры, суперсофт и скилл. Достаточно взять какой-то сканер и найти открытые в инет службы. Затем проверить дефолтные пароли к ним и наверняка что-то подойдет. Разработчиков с каждым годом все больше и больше, докер контейнеров тоже. Так что ломать сервера будет все проще и проще.
Тут, конечно, есть недоработка докера. Он зачем-то по дефолту мапит контейнеры к 0.0.0.0, а не к локалхосту. Понятно, что так проще разработчикам, но результат налицо. Разработчики чаще всего не парятся над этим и даже не обращают внимание. Я постоянно вижу на dev серверах контейнеры с backend, смотрящие напрямую в инет. Так что надо обязательно вручную заблокировать доступ к контейнерам Docker из интернета.
В данном случае, достаточно было заблокировать доступ к postgres с помощью iptables:
/sbin/iptables -I DOCKER-USER -i eth0 -p tcp --dport 5432 -j DROP
Расскажите, встречались с похожими, простыми и очевидными факапами?
Онлайн курс Основы сетевых технологий
Теоретический курс с самыми базовыми знаниями по сетям. Курс подходит и начинающим, и людям с опытом. Практикующим системным администраторам курс поможет упорядочить знания и восполнить пробелы. А те, кто только входит в профессию, получат на курсе базовые знания и навыки, без воды и избыточной теории. После обучения вы сможете ответить на вопросы:- На каком уровне модели OSI могут работать коммутаторы;
- Как лучше организовать работу сети организации с множеством отделов;
- Для чего и как использовать технологию VLAN;
- Для чего сервера стоит выносить в DMZ;
- Как организовать объединение филиалов и удаленный доступ сотрудников по vpn;
- и многое другое.
А полика iptables по-умолчанию DROP для INPUT , FORWARD не спасает? Пусть слушает все интерфейсы, а попадут только если разрешить?
Если не ошибаюсь, docker сам управляет iptables. Я не помню, какие он правила делает по умолчанию, но взаимодействовать с iptables нужно по его правилам, если нужна нормальная работа.
» встречались с похожими, простыми и очевидными факапами?
О. Нынче заходишь иногда на сервер, пишешь ifconfig или iptables-save и глубоко вдыхая констатируешь docker.
Да уж, простой жизни теперь не стало. Я вообще толком так и не понял, как корректно работать с iptables и докером одновременно. Пока спасаюсь тем, что прячу продовые сервера на шлюзы с iptables, но не всегда это возможно. На хостах с докером приходится вручную колхозить скрипты, которые накидывают правила уже после ребута и старта докера.
-A DOCKER-USER -p tcp -m conntrack --ctorigdstport 80 --ctdir ORIGINAL -j RETURN
-A DOCKER-USER -p tcp -m conntrack --ctorigdstport 443 --ctdir ORIGINAL -j RETURN
-A DOCKER-USER -m state --state RELATED,ESTABLISHED -j RETURN
-A DOCKER-USER -m set --match-set whitelist src -j RETURN
-A DOCKER-USER -p tcp -m conntrack --ctdir ORIGINAL -j DROP
Вы можете защищать вашу сеть так как вы привыкли, и при этом запускать контейнеры не в изолированной сети а с использованием хостовой сети ( --net "host" )
Ну хорошо хоть не "мяукнули" прод какой-нибудь.
1. Я всегда думал что 0.0.0.0 в выводе netstat означает - слушать порт на всех интерфейсах сервера . Причем здесь "смотрит напрямую в инет" ?
2. По поводу - " зачем-то по дефолту мапит контейнеры к 0.0.0.0, а не к локалхосту". Ведь в приведенном compose файле явно написано 0.0.0.0:5432
И если "потребитель" Postgres на другой виртуалке или запущен в другом стеке compose, то как иначе ?
ну если дефолт пароль и юзера разрабам поменять религия не позволяет, то прописать правило в айпитейблах с разрешением подключаться только определенному айпи к тому порту что мешает?
Очевидно, всё та же религия. Пофигизм.
Орден святого Лентяиуса.
1. В данном случае виртуальная машина с внешним ip, так что слушая 0.0.0.0 приложение принимает запросы в том числе и из интернета.
2. Потому что в докере так принято по-умолчанию, вот все и оставляют, кто не придет этому значение. Тут все было запущено на одной виртуалке и на одном стеке.
да очень просто:
- network:
external: true
Ещё кстати помогает команда
lsof -p имя процесса
Показывает какие библиотеки, сокеты и логи использует процесс. Можно понять что конкретно делает вирус.
Извиняюсь, не имя а pid процесса
Яч то то не понял, он у вас висел на ipv6 исходя из netstat, вы используете ipv6?
Никто не использовал. Это по дефолту так. Виртуалка вообще не моя, я не следил за ней.
Ну тогда как на нее проникли, если там ipv6, а ее никто не использует
Те сервисы, что висят на ipv6, они же доступны и по ipv4. Такое отображение команды netstat.
Понятно, спасибо
эм, а создать свой network между бд и веб сервером???
Зачем? Есть же дефолтный docker-compose, запустил и поехали кодить :) За отдельный network между бд и веб сервером никто лишних денег разработчику не заплатит. И админа не наймет, чтобы он подсказал.
есть такое. Хотя пару строчек всего и проблема решена.
127.0.0.1 вместо нулей прокатило бы
угу или заменить дефолтные юзернейм и пассворд на свои :-)
вот тут как раз docker не виноват:
```Тут, конечно, есть недоработка докера. Он зачем-то по дефолту мапит контейнеры к 0.0.0.0, а не к локалхосту.```
как указано выше - нужно было писать 127.0.0.1 вместо нулей - как ему сказали мапить, так он и мапит, тут недоработка со стороны мейнтейнера
Тем не менее, если маппинг к адресу вообще не указать, он запустит контейнер на 0.0.0.0, а мне кажется, что по дефолту лучше было бы мапить к 127.0.0.1. Из-за этого и разработчики, видя дефолтное поведение 0.0.0.0, его же и указывают. Просто по факту чаще всего никто ничего не меняет и запускает контейнеры как есть, в дефолте и они слушают 0.0.0.0. Постоянно с этим сталкиваюсь.