Демон для удаленного управления компьтером через e-mail

Здравствуй, Хабр!

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

Введение


В своей работе я использую множество удаленных машин. Зачастую доступ к ним ограничен фильтром по IP, поэтому приходится использовать длинные цепочки из хостов для входа на машину.
После прохождения в очередной раз этого квеста, ради выполнения пары команд, я понял, что нужно что-то менять. Конечно, самым простым решением было бы создать прямой SSH-тоннель и забыть про все трудности, но, во-первых, этому мешает строгая политика безопасности, а, во-вторых, хотелось бы иметь гибкую и независимую систему.

Со временем выработался ряд требований:
  • безопасность системы;
  • простой доступ к системе без лишних телодвижений (с телефона, чужого компьютера и т. д.);
  • история выполненных команд и результатов выполнения.

После детального анализа требований я пришел к выводу, что наиболее подходящим решением является бот, работающий через электронную почту. Ведь это очень удобно. В наше время отправить E-mail можно с любого устройства, у которого есть экран и клавиатура (компьютер, телефон, Kindle, даже телевизоры уже умеют делать это). Список писем всегда доступен на сервере и можно удобно анализировать изменение состояния системы.
Среди готовых решений ничего подходящего найдено не было, поэтому решил делать сам.

Реализация


В качестве языка программирования был выбран Python, критерием выбора послужила не только гибкость самого языка, но и давнее желание использовать его на практике.
Алгоритм программы довольно простой:
  1. Получение команд по E-Mail
  2. Выполнение команд
  3. Отправка результатов обратно пользователю

1. Получение команд по E-Mail

Для начала устанавливаем соединение с сервером, существует два варианта: POP3 или IMAP4. Выбор зависит как от поддерживаемых протоколов на почтовом сервере, так и от открытости портов на целевой машине.
Соединение с сервером для POP3 протокола
if is_enabled(self.get_param_str("Mail", "USE_SSL")):
    session = poplib.POP3_SSL(self.get_param_str("Mail", "POP_SERVER"),
                              self.get_param_int("Mail", "POP_SSL_PORT"))
else:
    session = poplib.POP3(self.get_param_str("Mail", "POP_SERVER"),
                          self.get_param_int("Mail", "POP_PORT"))
#if
if is_enabled(self.get_param_str("Debug", "NETWORK_COMM_LOGGING")):
    session.set_debuglevel(10)
#if
try:
    session.user(self.get_param_str("Mail", "EMAIL_USER"))
    session.pass_(self.get_param_str("Mail", "EMAIL_PASS"))
except poplib.error_proto as e:
    sys.stderr.write("Got an error while connecting to POP server: '%s'\n"  % (e))
    return False
#try
    

Соединение с сервером для IMAP4 протокола
if is_enabled(self.get_param_str("Mail", "USE_SSL")):
    session = imaplib.IMAP4_SSL(self.get_param_str("Mail", "IMAP_SERVER"),
                                self.get_param_int("Mail", "IMAP_SSL_PORT"))
else:
    session = imaplib.IMAP4(self.get_param_str("Mail", "IMAP_SERVER"),
                            self.get_param_int("Mail", "IMAP_PORT"))
#if
if is_enabled(self.get_param_str("Debug", "NETWORK_COMM_LOGGING")):
    session.debug = 10
#if
try:
    session.login(self.get_param_str("Mail", "EMAIL_USER"),
                  self.get_param_str("Mail", "EMAIL_PASS"))
except imaplib.IMAP4.error as e:
    sys.stderr.write("Got an error while connecting to IMAP server: '%s'\n" % (e))
    return False
#try
   


После того как соединение установлено, нужно отфильтровать из всех сообщений команды для нашего бота. Я решил использовать трехуровневую фильтрацию:
  • фильтрация по теме сообщения;
  • фильтрация отправителей по белым и черным спискам;
  • авторизация по login+password.

Алгоритм для фильтрации по теме в случае POP3 следующий: получить только заголовки сообщения, проверить поле «Subject:», если тема правильная — получаем сообщение полностью и передаем на дальнейшую обработку.
numMessages = len(session.list()[1])
for i in range(numMessages):
    m_parsed = Parser().parsestr("\n".join(session.top(i+1, 0)[1]))
    if self.get_param_str('Main', 'SUBJECT_CODE_PHRASE') == m_parsed['subject']:
        #Looks like valid cmd for bot, continue
        if self._process_msg("\n".join(session.retr(i+1)[1])):
            session.dele(i+1)
        #if
    #if
#for
    

В случае с IMAP все немного проще, протокол позволяет выполнять выборки на стороне сервера, т. е. нам достаточно указать тему, и сервер сам выдаст нам все подходящие письма.
session.select(self.get_param_str('Mail', 'IMAP_MAILBOX_NAME'))
typ, data = session.search(None,
                           'SUBJECT', self.get_param_str("Main", "SUBJECT_CODE_PHRASE"))
    


Следующий шаг — фильтрация отправителя по белым и черным спискам (можно использовать регулярные выражения)

И последний бастион — авторизация по паре login:password, которые должны идти в первой строке письма с командой.
На клиенте вместо паролей хранятся только md5 хеши.

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

2. Выполнение команд

Так как потенциально выполнение некоторых команд может занять значительное время, то было решено каждую команду выполнять в отдельном процессе. Так же было введено ограничение сверху на количество активных процессов.
Минусом выполнения произвольных команд является возможность подвесить систему запуском интерактивной программы (mc, htop, etc). Пока что не придумал как с этим бороться.

3. Отправка результатов обратно пользователю

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

self.__send_lock.acquire()
if not msg is None:
    print "[%s] Sending response to '%s'" % (datetime.today().strftime('%d/%m/%y %H:%M'), email_from)
    recipients = [email_from, self.get_param_str('Mail', 'SEND_COPY_TO')]
    message = "%s%s%s\n%s" % ('From: %s \n' % (self.get_param_str('Main', 'BOT_NAME')),
                              'To: %s \n' % (email_from),
                              'Subject: Report %s \n' % (datetime.today().strftime('%d/%m/%y %H:%M')),
                               msg)
    # Currently in python SMTP_SSL is broken, so always using usual version
    session = smtplib.SMTP(self.get_param_str("Mail", "SMTP_SERVER"),
                           self.get_param_int("Mail", "SMTP_PORT"))
    if is_enabled(self.get_param_str("Debug", "NETWORK_COMM_LOGGING")):
        session.set_debuglevel(10)
    #if
    session.login(self.get_param_str("Mail", "EMAIL_USER"),
                  self.get_param_str("Mail", "EMAIL_PASS"))
    session.sendmail(self.get_param_str("Mail", "EMAIL_USER"),
                     recipients,
                     message)
    session.quit()
#if
self.__send_lock.release()
    


Для создания демона был использован вот этот класс.

Заключение


В качестве примера посылаем команду боту:


Спустя некоторое время видим ответ:


Код проекта доступен на github

Надеюсь что кому-нибудь данная информация будет полезна.

Спасибо за внимание, жду ваших комментариев.

UPD: исправлен баг связанный с некорректной обработкой multi-part сообщений, спасибо github юзеру megres.

UPD2: добавлена возможность выставления произвольного таймаута для комманды. Для использования необходимо добавить префикс ":time=x" перед коммандой, т.е. ":time=10 make", даст 10 секунда на сборку, а потом отстрелит.
Спасибо tanenn за идею.
Метки:
Поделиться публикацией
Похожие публикации
Реклама помогает поддерживать и развивать наши сервисы

Подробнее
Реклама
Комментарии 47
  • +1
    Хорошая статья, спасибо. Но мне моя параноя не позволяет довериться почте. Подобный бот был для жаббера, вот это мне кажеться более безопасным решением.
    Что-бы не запускались интерактивные приложения, можно указать TERM="".
    • +1
      Да про бот для жаббера слышал, но народ отзывался что его нужно допиливать.
      Да и руки давно чесались опробовать питон в действии.

      Спасибо за подсказку с TERM, попробую.
      • +1
        А может ограничить время работы треда с выполнением процесса? А то «tail -f» и всё…
        • +1
          Думал об этом, но теоретически команда может быть ресурсоёмкой и требовать значительное время для выполнения.
          • +1
            Тогда с другой стороны подойти: команда которая убьет предыдущую команду :)
            • +3
              Ага, наверное стоит завести пару служебных команд для бота, одну для отображения списка выполняемых заданий с pid-ами или неким внутренним id, а другую для принудительного завершения по id, чтобы ненароком не пристрелить соседа по пиду :)
        • +1
          code.google.com/p/emailshell/source/browse/trunk/emailshell.py

          я где то год назад новоял что то подобное за одним исключением.

          gpg использовал… нужен один и тот же ключ на хозяине и на управляемом компе.

          Посылаешь сообщение зашифрованное…
          На компе управляемом расшифровывается, дальше вывод комманд зашифровывается и отправляется обратно.
      • 0
        получается, что если получат доступ к вашей отправленной почте, то получат и доступ к серврам :(
        • +1
          Ну, безопасность у этого «решения», боюсь, на уровне telnet в современном интернете. Хоть бы подписывали сообщения…
          • +23
            самым простым решением было бы создать прямой SSH-тоннель и забыть про все трудности, но, во-первых, этому мешает строгая политика безопасности

            А дальше идет речь о боте, который отправляет письма открытым текстом по email… да, это конечно намного безопаснее, чем использовать SSH-туннель.
          • 0
            SSL/TLS для почты достаточно, чтобы не светить почту в открытую. Но это уже надо почтовый сервис настраивать.
            • +1
              Многие e-mail хостинги имеют поддержку SSL. В моём боте реализована поддержка использования как SSL соединения так и оычного
              • 0
                опечаточка вышла, имел в виду «обычного»
                • +1
                  Это абсолютно ничего не дает: между SMTP-серверами почта будет почти наверняка ходить открыто.
                  • 0
                    Да ладно, в чем же тогда преимущество использования SSL? Как раз весь трафик будет передан внутри шифрованного соединения, полная аналогия с SSH-туннелированием.
                    • +2
                      в том что между клиентом и сервером будет шифрованное соединение. между сервера обычно нешифрованное и ходит.
                      • +2
                        <img src="facepalm.jpg"/>…

                        SSH-туннель — такая замечательная штука, где влияние внешних угроз очень здорово ограничено. В частности, вы абсолютно не зависите от той среды, через которую передается SSH-трафик. Вы не обязаны доверять серверам провайдера клиента, серверам провайдера хостинга сервера, любым промежуточным узлам. Есть понятие сессионного ключа, есть понятие ключей на endpoint'ах — а за счет интерактивности установки соединения можно делать многие штуковины, которые в однопроходном обмене в принципе невозможны.

                        При использовании SSL для соединения с SMTP-сервером у вас образуются следующие дырки, как минимум:

                        • вам приходится доверять SMTP-серверу провайдера (что он не подменит команду или просто не стырит драгоценные логин-пароль), т.к. он имеет на своей стороне конец SSL-сессии и весь трафик в нешифрованном виде
                        • вам приходится доверять всем SMTP-серверам по ходу движения почты — т.е. неширофованному, как правило, соединению, между SMTP-серверами в цепочке между отправителем и получателем.
                        • т.к. трафик скорее всего нешифрованный — вам приходится доверять среде, т.е. всем администраторам всех провайдеров и бэкбонов, через которые проходит сообщение
                        • вам приходится доверять SMTP-серверу на стороне провайдера у сервера в ДЦ

                        Любой в этой цепочке может либо стащить пароль, либо подменить трафик и отправить любую команду от вашего имени. При использовании SSH ничего подобного нет.
                        • 0
                          Можно посылать сообщения зашифрованные PGP и тогда вопрос подмены частично решается… Только ключи должны быть приватные 100%, публичную часть (ключа хозяина не должен никто знать). Иначе можно будет подменять вывод сервера…
                  • 0
                    Если вы про подписывание через S/MIME — то, субъективно, это более сложный путь, чем подпись через GPG.

                    Если вы про SSL/TLS соединение при передаче письма от отправителя к SMTP-серверу и/или при забирании письма с IMAP/POP3-сервера — то, боюсь, это абсолютно ничем не поможет: между SMTP-серверами в подавляющем большинстве случаев почта ходит в открытую и вы влиять на это толком никак не можете.
                • 0
                  ну можно архивировать командный файл с помощью 7-zip с заранее определенным паролем.
                  этим можно будет избежать столь откровенной демонстрации данных которые должны быть скрыты.
                  да и результат можно отдавать в зашифрованном архиве.
                  • 0
                    в случае шифрования, придется использовать сторонний клиент для дешифровки. С мобильника уже не проверишь ничего.
                    • –1
                      с мобильника всегда можно постучаться на ближайший доступный девайс.
                      хотя это тоже извращение.
                      но все-равно отсылать в явном виде логин/пароль мне кажется неправильным.

                      кстати на тему отправки команд: у меня когда-то возникала подобная мысль.
                      но в качестве инструмента думалось использовать dropbox.
                      мониторить определенную папку на предмет появления новых файлов и отдавать результаты в другую — выглядит столь-же просто как и работа с почтой и даже в чем-то проще. ну и субъективно безопаснее :)
                    • 0
                      Можно использовать симметричное шифрование. В голове шифровать команду, а потом расшифровывать результат :)
                      • 0
                        Достаточно просто подписать сообщение, чтобы сервер мог проверить подлинность подписи и удостовериться, что автор команды — действительно его владелец. Совершенно классическая задача цифровой подписи. Я бы использовал GnuPG-подпись, как наиболее распространенную и доступную.
                    • 0
                      а можно делать первый запрос запрос на получение некоего секретного кода
                      этот код генерируется, отправляется СМСкой на мобильный и записуется в файл паролей этого бота
                      следующим этапом уже шлете команды с полученым кодом
                      завершаете свою сессию отправкой письма с просьбой удалить код с сервера
                      и\или задать время хранения этого ключа
                      как-то так
                      • 0
                        Классическая уязвимость man-in-the-middle. Злоумышленник ловит сообщение с кодом на лету посередине и вместо ваших легитимных команд вставляет свои. Код остается валидным, сервер их охотно выполняет.
                        • 0
                          можно это комбинировать с постоянным кодом, который никуда не передается, но есть только у вас в голове и на сервере
                          • 0
                            Пардон, не понял. «Сервер», где есть «постоянный код», как-то телепатически должен влезть в «голову» и произвести проверку?
                            • 0
                              как сейчас сделал автор: на сервере есть пароль github.com/chuda/msh/blob/master/users.csv
                              вы его прописываете в теле письма. на этом всё

                              я же преложил к этому коду добавлять ещё один, который генерируется на сервере отправкой на него какого-то запроса и отправляет его на телефон, записавшись в файл.
                              после этого вы указываете в теле письма этот первый статический и второй из СМС
                              после завершения работы с сервером вы отправляете запрос на удаление файла с «динамическим» кодом
                              • 0
                                Еще раз: это классический man-in-the-middle; ваше «тело письма» с логином и двумя паролями (статическим и из СМС) ловят по дороге, убирают оттуда невинные команды типа "/sbin/ip a" и вставляют другие команды, затем отправляют дальше. Сервер получает корректные логин и два пароля и выполняет.
                                • 0
                                  ну так шифрование же
                                  • 0
                                    тогда зачем вообще нужны пароли и СМСки?
                                    • 0
                                      если кто-то получает доступ к исходящим письмам
                      • НЛО прилетело и опубликовало эту надпись здесь
                        • НЛО прилетело и опубликовало эту надпись здесь
                          • 0
                            Это все хорошо, но мы уже обсудили что команды могут требовать время для выполнения (например я захочу найти на терабайтном хранилище файлик с определенным текстом).
                            • НЛО прилетело и опубликовало эту надпись здесь
                              • 0
                                Прошу прощения, сразу не понял вашу идею. Звучит не плохо, обязательно реализую.
                            • 0
                              Так можно же посылать Control коды в терминал специально обрабатываемой командой — вроде #>cc [ASCII code number]
                              Дальше уже человек пусть сам думает — зависла программа или ждет ввода или еще чего. Чего не так — Ctrl-C.

                              Так можно даже в nano редактировать:
                              nano somefile
                              #>cc ^A
                              #>cc ^BS ^BS ^BS
                              мир
                              #>cc ^X ^C
                          • 0
                            А DNS tunneling не рассматривали? По-моему, он идеально решил бы вашу задачу.
                            • 0
                              Вариант конечно интересный, но UDP не самый лучший протокол. К томуже прийдется использовать нестандартный клиент для работы поверх DNS.
                              • 0
                                Спокойно делается IP поверх DNS. А по этому IP запускайте обычный ssh.
                                • 0
                                  был проект заворачивать PPPD через jabber траффик.
                            • –1
                              а скриншот долговыполняемого приложения можно послать?
                              • 0
                                ещё можно через астериск:
                                habrahabr.ru/blogs/sysadm/127997/
                                • +1
                                  Так, к слову: некогда в debian был такой пакет как grunt — «Secure remote execution via UUCP or e-mail using GPG». Документации, правда, не было, а на за запрос автор мне ответил просто — «читай исходники», что и пришлось делать :)
                                  • +1
                                    Безопасность управления можно было бы усилить одноразовыми паролями.
                                    Например, вычисляется hash=HASH(salt, command, password) и на сервер передается salt:command:hash. Демон запоминает salt и не дает его повторно использовать. Для генерации строки с мобильного можно использовать свой доверенный https сервер.
                                    Это не так удобно, но позволяет, во-первых, не дать полное управление системой при перехвате единственного письма, во-вторых, защищает от атак replay и man-in-the-middle.

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