Кибербезопасность для самых маленьких

Marat_1162

Стаж на ФС с 2014 г
ЖУРНАЛИСТ
Private Club
Старожил
Migalki Club
Меценат
Регистрация
25/3/16
Сообщения
4.623
Репутация
8.966
Реакции
22.634
RUB
0
Сделок через гаранта
4
Депозит
3 500 рублей
Добрый день. Данная статья будет очень полезна для тех, кто впервые поднимает свое приложение, которое будет доступно всему этому большому и замечательному Миру и хочет сделать его как можно более безопасным, устойчивым к разным атакам.

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

Как я представляю злоумышленников
Как я представляю злоумышленников

Начало. NGINX​

После покупки и развертывания своего маленького приложения - я не поленился развернуть стэк для мониторинга своей системы и подробно наблюдать за её поведением. Буквально за первые пять минут я заметил нагрузку на приложения, когда клиентские приложения я еще не включал. Мне показалось это странным и я побежал смотреть в логи внимательно и увидел следующую картинку:

Логи сервиса Nginx
Логи сервиса Nginx
О чем говорят логи? Правильно! Они говорят о том, что кто-то пингует и "дергает" наш IP адрес, подыскивает всяческие уязвимые места. Почему уязвимые? Сейчас покажу список URL, которые в основном приходят в запросе:

  • POST /etc/nginx/html/index.html
  • GET //.well-known/security.txt
  • GET /shopdb/index.php
  • GET /login
  • GET /.env
  • GET /var/log/nginx/data-access.log
  • GET /jenkins/login
  • GET /${jndi:ldap://127.0.0.1
  • GET /MySQLAdmin/index.php
  • POST /axis2/axis2-admin/login
  • GET \x00\x0E8\xA6fd\xDA\xA9\x9Az\x8B\x00\x00\x00\x00\x00"
Как можно заметить: это не безобидные запросы для healthchek. В этих запросах кроется злой умысел, ведь есть и , и запросы для login страниц, и попытки пробраться к определенным файлам с чувствительной информацией.
В процессе я заметил, что подобные наплывы запросов одинаковые, равномерные и происходят с определенными периодами, но с разных клиентов. Мы можем сделать здесь смелый вывод, что это чьи-то боты-сканнеры, которые ищут Ваши уязвимые места, а не хаккер-одиночка с браузера тыкает все эти запросы.

P.S. В основном все IP данных клиентов принадлежат хостингу google-cloud. Не знаю, как это к чему-либо привязать, но свои догадки пока оставлю при себе.

Любой параноик бы уже словил панику, а я обрадовался такой удаче, так как появилась возможность в реальных условиях изучить внимательно такой вид атак, отловить на себе основные кейсы и научиться их отражать и спасаться от них.

Защищаем NGINX

Для этого нам необходимо иметь доступ к редактированию файла конфигурации nginx. Обычно он находится по пути /etc/nginx/nginx.conf . Подробнее про конфигурирование этого замечательного приложения можно найти .

1) Защищаемся на уровне валидации доменного имени, если таковое имеется. Это необходимо, чтобы явно сказать серверу, что мы не ждем гостей, которые пришли к нам, обращаясь без уважения по IP-адресу

if ($host !~ ^(example.com|www.example.com|docs.example.com)$ ) {
return 444;
}
2) Если на нашем сервисе не допустимы некоторые HTTP-методы, их лучше исключить. Обычно исключают редкие HEAD, OPTIONS, но для примера укажем любые:

if ($request_method !~ ^(GET|HEAD|POST)$ ) {
return 444;
}
3) Раз боты к нам ходят по тривиальным путям, то и мы изучаем и видим некоторые закономерности в этих ботах. Я заметил заголовок User-Agents, у них есть такие значения, по которым можно понять, что это за потребитель. Большинство ботов научилось себя маскировать под Safari, Mozila, Chrome браузеры, против такой маскировки наш метод не подойдет.

# Некоторые известные боты носят именно такой user-agent в заголовке.
# Не скажу, откуда я это знаю :)
if ($http_user_agent ~* msnbot|scrapbot) {
return 403;
}
# Не позволим тривиальным скриптовым вещам нас трогать
if ($http_user_agent ~* LWP::Simple|BBBike|wget) {
return 403;
}
4) Хотел поговорить про HTTPS и SSL, но лучше расскажут .

5) Если есть смысл определенные Path сервиса запаролить дополнительно - лучше это сделать ( ).

6) Маленький совет: мы заметили, что ходят по тривиальным URL для логина и подкидывают тривиальные basic-auth-credentials к ним. Можно защититься банальным использованием нетипичных username, ставить сложные криптостойкие пароли и попробовать сделать так, чтобы аутентификация могла быть только при ручном переходе на страницу, используя GET параметры state, consumer, api_key, etc или токены при редиректе в headers запроса. Тогда даже при брутфорсе одного верного логина и пароля злоумышленнику будет недостаточно. Нужно будет попотеть.

7) Если у вас есть только определенные URL, которые доступны, например приложение только для API, стоит открыть в Nginx только этот путь. Например, хотим выделить только для /api/v1/my_service/:

location /api/v1/my_service/ {
...
}
Полный конфиг, который у меня получился:

server {
listen 443 ssl; # открыт только 443
listen [::]:443 default_server ssl;

ssl_certificate /ssl/nginx.crt; # Часть с сертификатами
ssl_certificate_key /ssl/nginx.key;
ssl_session_tickets off;
access_log /var/log/nginx/data-access.log combined; # Обязательно логируем процессы
return 444;

location / {
proxy_pass http://example.com;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $remote_addr;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_redirect http:// https://;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection $connection_upgrade;
proxy_read_timeout 20d;
proxy_buffering off;
}
}
Поехали дальше, не один nginx и http порты подвергаются подобным обстрелам зловредными запросами, которые "что-то ищут" с подстановкой типовых паролей и логинов. У нас есть сервисы, которые открыты по другим протоколам, например . На данном порту могут быть любые соединения: socket proxy, database, redis, ssh, etc.
Чем богаты, тем и рады, продолжим и поговорим о БД.

PostgreSQL. За БД нужен глаз да глаз.​

Мой сервис имеет базу данных, до которой тоже дошли злые руки ботов, которые пытаются подобрать пароли. Это видно по следующим логам:

Логи сервиса postgres
Логи сервиса postgres
Изучая логи, можно заметить следующее:

  • IP-адреса злоумышленников
  • Имена баз данных, к которым злоумышленники подключаются.
  • Username базы данных, под которыми злоумышленники хотели проникнуть
    А также, читая логи, вы можете заметить, что у них ничегошеньки не получается. Сейчас расскажу, почему:
Чтобы не было страшно, что наши данные из Базы данных похитят, хочу поделиться советами, как максимально-надежно защитить свою PostgreSQL от таких нападок. В логах видно, что злоумышленники получают отказ на вход в нашу БД. Это достигнуто некоторыми моментами.

Защищаем PostgreSQL

1) Мы не используем стандартные имена для БД. То есть для БД следует исключить очевидные названия: database, postgres, shop, db, sa, psql, mydb, my_database.

2) Мы не используем стандартные имена учетных записей для БД. То есть исключаем очевидные username: root, sa, psql, pgsql, admin, postgres, owner, analyzer, exporter, telegraf, user, some_user, test, etc...

3) Конечно же, защищаем свою БД самым криптостойким паролем на свете. Этого мы добиваемся командой:

# при создании пользователя
CREATE USER my_service_name_user WITH ENCRYPTED PASSWORD 'СамыйСложныйПарольНаЗемле';

# при обновлении пользователя
ALTER USER my_service_name_user WITH ENCRYPTED PASSWORD 'СамыйСложныйПарольНаЗемле';
4) Этим не ограничиваемся. БД - очень тонкая и дорогая вещь, особенно, когда там есть данные и конфиденциальная информация. Поэтому не стоит позволять, чтобы кто-то снаружи вообще смог достучаться до вашей базы данных.

Для этого нам необходимо конфигурить файл доступов к БД pg_hba.conf. .

Покажу на примере, где до сервера с postgres я разрешил доступ только для приложения, которое находится на другом сервере.

host my_service_name_database my_service_name_user my_app_server.com trust
Тут мы явно говорим нашей СУБД, что мы принимаем подключения только от пользователя my_service_name_user в нашу БД my_service_name_database и только тогда, когда получили соединение с хоста приложения my_app_server.com.

ELK. Логи многое скажут о тебе!​

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

Правила защиты на самом деле просты и банальны:

1) Закрываем elastic и logstash на аутентификацию и тогда следом kibana тоже будет на нее и закрыта.

2) Можно постараться сменить дефолтные порты на какие-нибудь другие. Так будет меньше риска, что у ботов будет работать их сценарий по поводу ELK.

# Для примера:
Для elasticsearch по-умолчанию стоит порт 9200 -> меняем на 9222.
Для kibana по-умолчанию стоит порт 5601 -> меняем на 5611.

Приложение. Беречь надо как зеницу ока!​

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

1) Контейнеризация. Docker — отличная программа, позволяющая запускать изолированные контейнеры. Поэтому мы должны использовать их для размещения и запуска наших серверных приложений.

Таким образом, все приложения запускаются в своем собственном контейнере, поэтому они могут работать в своей собственной среде. Это означает, что любые двоичные файлы, которым мы не доверяем, не могут работать вне контейнера Docker. Кроме того, любые изменения, которые мы вносим в контейнер Docker, удаляются, если мы перестраиваем контейнер Docker, поэтому, если мы или какая-то атака испортят контейнер, мы можем просто перестроить его и вернуть в рабочее состояние.

Библиотеки и зависимости разных приложений не будут мешать друг другу.
Кроме того, несколько контейнеров, работающих на одном и том же сервере, будут иметь некоторый уровень доступа к другим контейнерам и самому хосту. Поэтому мы должны защищать все хосты и запускать контейнеры с минимальным набором возможностей. Мы можем делать такие вещи, как отключить сетевой доступ к контейнеру, если нам нужно.

Важно: не используйте пользователя root внутри вашего образа. Создавайте пользователей в Dockerfile:

RUN groupadd --gid 2000 node \
&& useradd --uid 2000 --gid node --shell /bin/bash --create-home node
Важно: не пишите в docker-compose.yml пароли, ключи, токены. Доверьте это файлу с переменными окружения через:

env_file:
- /root/environments.env
Либо через secrets:

secrets:
my_external_secret: # создание секрета через команду docker secret create
external: true
my_file_secret: # секрет лежит в файлике
file: my_file_secret.txt
2) Набор джентельмена, от которого должен себя обезопасить веб-разработчик сразу: , межсайтовый скриптинг ( ), межсайтовая подделка запроса ( ). Также использовать механизм кроссдоменных запросов ( ).

Можно отдельно прорабатывать каждый пункт, но как показывает текущая практика: большинство веб-фраймворков из коробки реализуют данный функционал и не дают разработчику ошибиться и сделать своё детище небезопасным.

3) Всегда помним про шифрование уязвимых данных. Пароли никогда не должны храниться в виде обычного текста. Если нам нужно записывать пароли, то они должны храниться в чем-то, что не показывает пароль. Мы можем использовать асимметричное шифрование для хранения наших паролей, чтобы мы могли хранить их в зашифрованном виде с помощью открытого ключа, а затем расшифровывать их обратно в обычный текст с помощью закрытого ключа. В большинстве случаев пароли следует хранить в виде одностороннего хэша, чтобы их нельзя было расшифровать, даже если злоумышленники войдут в нашу базу данных. Мы шифруем каждый пароль с помощью безопасного hash и salt и просто проверяем хешированный пароль, если нам нужно проверить его для аутентификации.

4) Не храним уязвимые данные в репозитории. Использовать лучше секретные переменные, которые может себе позволить любой CI\CD инструмент, либо просто храним в очень недоступном месте на нашем сервере в файликах.

5) Необходимо вести аудит входов в систему. Это важно, потому что позволит мониторить, кто и когда пытался использовать учетные данные, с какого адреса и зачем. Это сокращает массу работы на выявление уязвимых мест.

Сервер. Он должен быть неприступен.​

Да, дорогие друзья, мы подошли к самому основному. С этого стоило и начинать данную статью, ведь безопасность начинается с безопасного сервера. LINUX - это очень стабильная и безопасная система из коробки, благодаря гибкой работе с сетью и очень надежной работе с правами, группами и пользователями. Но попробуем вернуться к нашим ботам и посмотреть, где самое узкое место.

В каждый сервер можно осуществить удаленный доступ, обычно он осуществляется с помощью . Доступ по ssh напрямую связан с пользователем операционной системы или группой. Для каждого пользователя должны иметься пароли или ключи для доступа.

Я заметил, что боты очень хорошо пытаются попасть по логину-паролю на мой сервер. Как я это заметил? Запоминаем!

Необходимо прочитать те логи системы, которые отвечают за вход по ssh (для сервера это сервер sshd):

tail -n 500 /var/log/auth.log | grep 'sshd'
Вот, как выглядит результат с признаками того, что на наш сервер пытались проникнуть:

Логи /var/log/auth.log
Логи /var/log/auth.log
Мы прекрасно по логам можем заметить, что злоумышленники перебирают с разных IP-адресов ssh подключения, используя логины: root, user, debianuser, kuhniitalia, uggi, ubuntu, etc. Также мы видим, что проникнуть у них не получается, для этого стоит придерживаться следующих практик:

1) Придумать очень крипто-стойкий пароль. Основное!

2) Создать отдельного пользователя с нетривиальным username, чтобы избежать того, чтобы боты угадали с логином пользователя.

sudo useradd -m my_hard_username && sudo passwd мойСамыйСложныйПарольОдин1
3) Дать созданному пользователю необходимый sudo

usermod -aG sudo my_hard_username
4) Запрещаем пользователю root логиниться в систему удаленно через ssh. Правим файл /etc/ssh/sshd_config и ставим следующие параметр:

PermitRootLogin no
5) Гораздо эффективнее, когда к серверу невозможно подобрать строковый пароль, а когда у него аутентификация только по . Это легко реализовать. Сначала пропишем ключи для нашего пользователя my_hard_username:

echo <Мой публичный ключик> >> cd ~/.ssh/authorized_keys
Далее возвращаемся в файл /etc/ssh/sshd_config и запрещаем доступ по паролю и доступ с пустым паролем:

PasswordAuthentication no
PermitEmptyPasswords no
После всех манипуляций с sshd_config перезапускаем сервер удаленного доступа.

sudo systemctl restart ssh.service # Для Ubuntu\Debian
# или
sudo systemctl restart sshd.service # Для RHEL/CentOS
6) Открытые порты. Надо позаботиться о том, чтобы "наружу" из сервера торчало как можно меньше портов, в целом, необходимо сузить порты до самого необходимого, это самое надежное, что можно порекомендовать для счастливой и веселой жизни в безопасности. Для начала посмотрим, какие порты открыты командой:

sudo netstat -tulpn
# или
sudo ss -tulwn | grep LISTEN
Результат будет следующий:

Открытые порты сервера
Открытые порты сервера
Прекрасно видим, какие порты и от какого приложения были открыты на сервере. Можем провести анализ и закрыть ненужные порты, мне в данном примере очень не нужно, чтобы порт 9090 торчал всему Миру на радость наружу, потому что он содержит в себе приложение, которое крутится в docker. А доступ к нему обеспечит мое Nginx приложение и так. Обращаемся к следующим командам для работы с портами:

iptables -A INPUT -p tcp --destination-port 9090 -j DROP
INPUT - входящий порт. tcp - протокол. destination-port - порт, который убираем. DROP - команда о том, что мы закрываем порт. Подробнее о том, как работать с блокировками портов и IP-адресов и прочие сетевые доступы .

Важно: Если у вас сервер на диструбутиве Debian\Ubuntu, а ковыряться в iptables тяжело, то есть прекрасная альтернатива в виде утилиты UFW ( ), который настраивается за пару команд на нужные порты и адреса.

Важно: Для настоящей безопасности обычно закрывают все порты, а потом открывают нужные. Учитывайте это

Вывод​

В данной статье вы увидели способы атак злоумышленников на наше приложение, NGINX, PostgreSQL, ELK, docker и sshd сервера, а я в свое время вам рассказал про эффективную профилактику от НСД, защиту своих драгоценных данных и про инструменты и способы для мониторинга и выявления фактов того, что к нам в гости стучатся злоумышленники. Проблемы и примеры взяты из реальных ситуаций и серверов.

Конечно, защищать сервера и приложения гораздо легче во внутреннем сетьевом контуре, но рано или поздно нам нужен будет выход наружу и мы должны быть к нему готовы. К сожалению, не удалось в этой статье затронуть тему DDoS-атак и других типов уязвимостей и как с ними бороться, но я надеюсь, что это будет материалом для следующих статей.
 
Сверху Снизу