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


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

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

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

Начало. NGINX

После покупки VPS и развертывания своего маленького приложения - я не поленился развернуть ELK стэк для мониторинга своей системы и подробно наблюдать за её поведением. Буквально за первые пять минут я заметил нагрузку на 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. В этих запросах кроется злой умысел, ведь есть и инъекции log4j, и запросы для 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 порты подвергаются подобным обстрелам зловредными запросами, которые "что-то ищут" с подстановкой типовых паролей и логинов. У нас есть сервисы, которые открыты по другим протоколам, например TCP. На данном порту могут быть любые соединения: 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) Набор джентельмена, от которого должен себя обезопасить веб-разработчик сразу: SQL инъекции, межсайтовый скриптинг (XSS), межсайтовая подделка запроса (CSRF). Также использовать механизм кроссдоменных запросов (CORS).

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

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

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

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

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

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

В каждый сервер можно осуществить удаленный доступ, обычно он осуществляется с помощью ssh. Доступ по 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) Гораздо эффективнее, когда к серверу невозможно подобрать строковый пароль, а когда у него аутентификация только по private-public key. Это легко реализовать. Сначала пропишем ключи для нашего пользователя 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 (Uncomplicated Firewall), который настраивается за пару команд на нужные порты и адреса.

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

Вывод

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

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

До новых встреч, друзья! Спасибо за внимание!

Приглашаю всех на чтение других интересных статей в мой telegram-канал




К сожалению, не доступен сервер mySQL