company_banner

Простой способ дополнительной защиты: SSH — ALERT

    image

    Администрирование большого количества “чужих” серверов, влечет за собой ответственность за защиту данных наших клиентов. Что бы надежно контролировать список лиц с ssh доступом к серверовам было решено продумать систему авторизации с ограниченого набора хостов.



    Что имеем в условии задачи:
    • Более 400 физических машин;
    • Все клиентские службы хранятся в виртуальных контейнерах openvz ( в основном):
    • root доступ на все сервера закрыт по ssh;
    • доступ через sudo к привилегиям root есть только у админов на физических серверах;
    • несколько серверов, принадлежащих нашей компании, половина из которых территориально распределены. Назовем их: “серверы доступа”.


    Что сделано:
    • Первым шагом мы через систему управления конфигурациями распространили на все физические сервера ssh ключи, для входа на них с серверов доступа без пароля. Для каждого админа свой ключ.
    • Вторым шагом закрываем доступ по ssh на физические сервера, со всех хостов, кроме серверов доступа.

    Конечно, в такой схеме будут исключения, но это все индивидуально.

    Теперь возникает вопрос о безопасности такого решения: любой кто взламывает учетку на сервере доступа получает не ограниченный доступ ко всем серверам, причем по ключу без пароля.
    Не очень хорошо, мягко выражаясь!
    Поступило предложение ограничить ssh доступ на серверы доступа через firewall, разрешив доступ только с ip наших сотрудников. Хорошо. Но тут проблема: админов у нас не мало, у многих динамические ip. Да и что если срочно потребуется “поработать” находясь в поездке и т.д.?
    Решили остановиться на репортах. То есть каждый раз, когда определенный логин, успешно авторизуется с ip, отсутствующего в списке допустимых, нам в redmine падает задача об этом событии с высоким статусом и далее мы выясняем что произошло, применяя допрос с пристрастием.

    Реализация:
    Были просмотрены текущие готовые решения для ssh аудита, но ничего подходящего найдено не было, или очень монструозно, или не о том.
    И как всегда решили написать свой велосипед, то есть скрипт. Который:
    1. анализирует ssh лог на предмет успешной авторизации.
    2. берет данные за последние 10 минут.
    3. сравнивает пару username/ip из соответствующего списка
    4. в случае успешной проверки нич его не делает
    5. в случае не совпадения отправляет алерт, который впоследствии становится задачей в redmine.


    Скрипт написан на python. Писал я на python второй раз в жизни, так что замечания по коду принимаются в объективном виде. К тому же, оно работает!

    Листинг прилагается:
    #!/usr/bin/env python
    
    import  sys, os, time
    from datetime import datetime, timedelta, date, time as dt_time
    
    import socket
    
    hostname = socket.gethostname()
    
    fileList = "./List.ip"
    
    date = datetime.now() - timedelta(minutes=10)
    date = date.strftime('%H:%M:%S')
    from commands import *
    
    import smtplib
    def mail(message):
        smtp_server = "localhost"
        smtp_port = 25
        smtp_user= "root@%s" % hostname
        subject = 'Ahtung!! Security SSH audit alert'
        to = "mail@example.ru"
        mail_lib = smtplib.SMTP(smtp_server, smtp_port)
        msg = 'From: %s\r\nTo: %s\r\nContent-Type: text/html; charset="utf-8"\r\nSubject: %s\r\n Return-Path: <root@%s>\r\n\r\n' % (smtp_user, to, subject, hostname)
        msg += message
        mail_lib.sendmail(smtp_user, to, msg)
    
    
    listLog = getoutput('''cat /var/log/secure |grep "Accepted password for" | awk '{if($3>="'''+date+'''"){print $3 " " $9 " " $11 }}' ''')
    if listLog:
        for line in listLog.split('\n'):
            matchIp = 0
            matchName = 0
            curIp = line.split()[2]
            for lineLs in open(fileList):
                if len(lineLs.strip()) == 0 or lineLs[0] == "#" or lineLs[0] == " ":
                    continue
                if line.split()[1] == lineLs.split()[0]:
                    matchName = 1
                    listIp = lineLs.split()[1]
                    i = 0
                    for c in listIp.strip().split("."):
                        if c.strip() == "0":
                            break
                        i = i + 1
                    if listIp.strip().split(".")[0:i] == curIp.split(".")[0:i]:
                        matchIp = 1
            if not matchIp:
                if not matchName:
                    print("This user %s not found !!!" % line.split()[1])
                    message = "This user %s not found !!!" % line.split()[1]
                    mail(message)
                else:
                    print("Ahtung! User {0} logged from unknown IP {1} in time {2}").format(line.split()[1], line.split()[2], line.split()[0])
                    message = "Ahtung! User "+line.split()[1]+" logged from unknown IP "+line.split()[2]+" in time "+line.split()[0]
                    mail(message)
    


    Файл List.ip имеет вид:

    #Vasya
    vasya 1.2.3.4
    vasya 12.13.14.0
    


    Сопутствующие действия:
    • поместить скрипт в cron на запуск каждые 10 минут;
    • настроить ежедневную ротацию ssh логов на полночь;
    • отключить авторизацию по ключам на серверах доступа;
    • для большей пароноидальности можно хранить список username/ip в гите и раскидывать на сервера доступа через хуки.


    Дополнительный (относительный) плюс этой схемы: если придется увольнять сотрудника ( а такое все же бывает :( ), то нужно будет просто убить сессии на серверах доступа.
    Southbridge
    712,24
    Обеспечиваем стабильную работу серверов
    Поделиться публикацией

    Комментарии 26

      0
      > поместить скрипт в cron на запуск каждые 10 минут;
      Да за 10 минут потенциально можно рута получить и деактивировать вашу систему.
        0
        Да, можно. Но так мы об этом быстро узнаем хотя бы.
          0
          Как вы узнаете, если скрипт не сработает?
            0
            Узнаем. Я не 100% информации выложил.
            К тому же, какие средства Вы можете предложить как альтернативу?
              0
              Мониторинг логов в реальном времени, конечно же. Есть же средства для отслеживания изменений в файлах.
                0
                Они тоже используются. Только не всегда можно позволить постоянно мониторить логи из-за нагрузки.
                  0
                  Из-за нагрузки на сервера доступа? Серьёзно?
                    0
                    Да, на них тоже. Дороговато арендовать несколько серверов только и только для доступа к остальным машинам.
                    0
                    logfmon практически не грузит систему
          • НЛО прилетело и опубликовало эту надпись здесь
            0
            Спасибо за статью.

            Тоже задумывался о таких вещах.
            Предположим взлом случился 31 декабря, все пьяные и отреагировать на письмо оперативно никто не может.
            А почему-бы не лишить злоумышленника информации о том, где этот ключ можно применять?
            Вот что из этого у меня получилось:

            1) Не храним историю

            ln -sf /dev/null ~/.bash_history
            


            2) Не храним known_hosts

            cd ~/.ssh
            ln -sf /dev/null known_hosts
            


            3) Авторизуемся без проверки known_hosts

            ssh -o 'StrictHostKeyChecking no' user@remotehost 
            

            Если дело касается своих собственных виртуалок, наверное, это оправданно.
            И сделать соответствующий alias в .bashrc.

              0
              К счастью(клиентов, конечно:) ) у нас всегда есть дежурный сотрудник, который может оперативно отреагировать на ситуацию.

              Спасибо за комментарий!
              Обдумаем модификацию.
                0
                Вообще-то, known_hosts можно хранить в формате, не позволяющем извлечь явно имена хостов. А с вашей конфигурацией можно MITM провести без проблем.
                  0
                  А поделитесь инфой, плз, как хранить в known_host не в открытом виде.

                  На счет MITM — знаю, поэтому и писал, про свои виртуалки, то есть в рамках своего vlan.
                    +2
                    Опция HashKnownHosts=yes в ssh_config
                      0
                      Вот спасибо вам большое.
                      Добавил в ssh_config, эти строки раздел:

                      Host *
                      HashKnownHosts yes

                      Перезапустил ssh.
                      Теперь при добавлении нового хоста в known_host запись в закрытом виде.
                +1
                Поступило предложение ограничить ssh доступ на серверы доступа через firewall, разрешив доступ только с ip наших сотрудников. Хорошо. Но тут проблема: админов у нас не мало, у многих динамические ip. Да и что если срочно потребуется “поработать” находясь в поездке и т.д.?


                Чем не устроил вариант использования технологии port knocking (другое название «DACL — dynamic access list»)?
                На хабре уже было несколько статей об этом (раз и два)
                Подобный подход использую уже несколько лет на различных Linux-серверах и на маршрутизаторах Mikrotik (у Cisco тоже такой функционал есть, но я не сильно подружился с их железками пока).

                И ещё. В статье не сказано, может у Вас так и реализовано. Хорошей идеей будет перевесить ssh-сервер со стандартного порта. Тогда будет меньше всяких брутов и вообще логи будут меньше

                Итого: «вешаем» ssh-сервер на нестандартный порт (например, 22222). Порт доступен по правилам firewall по спискам. Если IP нет в списке, а доступ очень хочется — используем port knocking, после чего этот IP попадает в список разрешённых. Чтоб списки не загаживались постоянно после использования port knocking с разных адресов, по крону восстанавливаем эталонный список firewall. Например, раз в сутки, в 5 утра — когда меньше вероятность, что кто-то будет работать с сервером
                  0
                  Как показывает практика — взломать можно все что угодно.
                  И не важно на каком порту висит ssh и используется ли при этом port knocking.
                  Если сильно будет нужно, то взломают с помощью той же соц. инженерии.
                  Например, устроятся к ним на работу, подсунут симпотичную девушку одному из админов и т.д.

                  Я не говорю, что не надо использовать port knocking, но в данной статье, человек просто рассказал о своем способе оповещения.
                  А оповещения, тоже штука нужная.
                    0
                    Порты и так нестандартные.
                    Технологию попробуем, спасибо!
                    0
                    1. Вместо крона правильнее использовать специально разработанные для похожих задач средства. Auditd умеет передавать информацию о событиях в сокет или syslog. А ssh и pam могут логировать удачные/неудачные логины в audit.
                    2. Вы пишите, что админы имеют доступ только по ключу, а в скрипте парсите строку «Accepted password for»
                    3. И сама идеология спорна: логиниться с любых ip можно, но если кто-то логинится не с того ip, то мы все начинаем подпрыгивать и что-то выяснять. Пока вы будете устраивать «допрос с пристрастием» злоумышленник может уже сделать все что нужно.
                      0
                      Первым шагом мы через систему управления конфигурациями распространили на все физические сервера ssh ключи, для входа на них с серверов доступа без пароля

                      А чем не устроили парольные ключи и ssh-agent?
                      В идеале, для доступа на клиентские серверы не должен использоваться ключ с центрального узла. Только персонифицированные ключи администраторов, защищенные паролями.

                      Те все должно происходить так:
                      1. Админ экспортирует свой ключ для целевого сервера в ssh-agent и заходит на управляющий сервер («сервер доступа»)
                      2. Используя свой персональный ключ (пробрасываемый агентом) заходит на целевой сервер

                      Это позволяет решить проблему со взломом центарльного сервера: на нем нет никаких ключей, тем более беспарольных, а значит доступ к нему можно по ssh открыть, не боясь что при его взломе получат доступ к внутрененй инфраструктуре. + такой подход позволяет разграничивать доступ для админов: они могут иметь один аккаунт на центральном сервере, но при этом иметь доступ только на те узлы, на которые должны попадать (не говоря уже о том что можно хоть на каждый сервер уникальный ключ иметь и не как не влиять при этом на остальных администраторов)
                        0
                        Как-то не умали об этом варианте.
                        Рассмотрим и потестим.
                        Спасибо за рекомендацию.
                          0
                          Еще в вашем случае уместно такое решение для динамических IP:
                          1. Создаете отдельную цепочку для динамических IP и 22 локального порта на сервере доступа
                          2. По крону, раз в 5 минут, например, цепочку очищаете и добавляете актуальные IP

                          Что бы получать актуальные IP можно воспользоваться сервисами типа DynDNS.
                          Это, конечно, скрывает потенциальную дыру в безопасности (через атаки на DNS), но это все же лучше чем пускать всех подряд.
                        0
                        Стандартная схема — туннелирование ssh через управляющий сервер. Ключ — на машине персонала. Управляющий сервер доступ к агенту не получает. «Сервер-жертва» доступ к агенту не получает.

                        Выглядит это так:
                        Host access
                            Hostname 8.8.8.8
                            Port 22
                            ForwardAgent yes
                            User user
                        
                        Host servers* !access
                            User user
                            Port 22
                            ProxyCommand ssh -W %h:%p -C access
                        
                          0
                          У нас это реализовано приблизительно также (доступ, не алерты).
                          Два сервера доступа, с которых можно попасть на сервера, один открыт со всего мира (авторизация по логину/паролю/токену), второй открыт только для своих сетей (авторизация по логину/паролю).
                          Логины/пароли c радиуса(для первого сервера OTP-железка, для второго AD).
                          Далее, через sudo реализованы права доступа на рабочие сервера с серверов доступа по ключу, данные берутся из AD (пользователи могут попасть только на разрешенные сервера).
                          Шелл на сервера доступа не стандартный ни-разу, в нем ничего не выполнить, кроме разрешенного.

                          Эту систему я реализовывал сам. Сейчас она в продакшене на 2000+ серверов. До того как её внедрить, пробовал кучу возможных готовых решений, все было не то. Главное требование — реализация разграниченного доступа, пользователи в AD и токены.
                          Вот как то так.
                          Я б с удовольствием описал бы все подробности и ньюансы (типа по-шагового руководства к действию), но к сожалению, не могу вот так просто, без одобрения сверху этого сделать.
                            0
                            Можно обойтись и без анализа логов, через использование ~/.ssh/rc или /etc/ssh/sshrc

                            Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

                            Самое читаемое