Подделка ssh сервера на Python

от автора

Спойлеры

В этом посте я расскажу, как игрался и создавал на python сокеты, к которым можно подключаться через ssh клиенты.

Для начала (если оно не сломается добрыми людьми) можно попробовать подключиться к примеру:

ssh root@ssh.vsecoder.dev -p 2200
пароль habr

пароль habr

Это стена «Был здесь», поле ввода принимает ник, сохраняет, и потом выводит другим.

Предисловие

Что такое ssh уже хорошо рассказал один автор на хабре: Памятка пользователям ssh.

Я лишь покажу красивую схемку от рег.ру:

принцип работы ssh

принцип работы ssh

И скажу что мы заменим SSH-сервер своим скриптом на Python, а за tcp соединение будет отвечать модуль socket.

Начнём

Socket — встроенный модуль для создания низкоуровневых сетевых интерфейсов. Он включает в себя функции создания объекта сокета Socket, который и обрабатывает канал данных, а также функции, связанных с сетевыми задачами, такими как преобразование имени сервера в IP адрес и форматирование данных для отправки по сети.

Сокеты можно настроить для работы в качестве сервера и прослушивания входящих сообщений или для подключения к другим приложениям в качестве клиента. После подключения обоих концов сокета TCP/IP обмен данными становится двунаправленным.

В пример поднимем простой сервер и клиент:

Подробнее про этот пример

# сервер import socket import sys  # создаем TCP/IP сокет sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)  # Привязываем сокет к порту server_address = ('localhost', 1234) sock.bind(server_address)  # Слушаем входящие подключения sock.listen(1)  while True:     # ждем соединения     connection, client_address = sock.accept()     try:         # Принимаем данные порциями и ретранслируем их         while True:             data = connection.recv(16)             if data:                 data = data.upper()                 print(data)                 connection.sendall(data)             else:                 break      finally:         # Очищаем соединение         connection.close()
# клиент import socket import sys  # Создаем TCP/IP сокет sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)  # Подключаем сокет к порту, через который прослушивается сервер server_address = ('localhost', 10000) print('Подключено к {} порт {}'.format(*server_address)) sock.connect(server_address)  try:     # Отправка данных     mess = 'Hello Wоrld!'     print(f'Отправка: {mess}')     message = mess.encode()     sock.sendall(message)          # Смотрим ответ     amount_received = 0     amount_expected = len(message)     while amount_received < amount_expected:         data = sock.recv(16)         amount_received += len(data)         mess = data.decode()         print(f'Получено: {data.decode()}')  finally:     sock.close()

Но в нашем случае нам придётся писать только серверный сокет, т.к. в роли клиента у нас выступает ssh клиент.

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

данные от клиента ssh

данные от клиента ssh

Клиент хочет с нами пообщаться, и если просто попробовать слать ему не то, что он ожидает, клиент принудительно разорвёт соединение:

Connection reset by 127.0.0.1 port 1234

Конечно можно попробовать со всем этим разобраться ручками, но я нашёл другой модуль, решающий эту проблему — paramiko.

Он позволяет как создавать ssh клиентские подключения, так и сервера. На гитхабе у него уже есть несколько готовых примеров, так и один с созданием сервера, но мне он показался перегруженным, поэтому покажу свой:

import socket import paramiko import threading from paramiko import Transport, RSAKey  # ssh-keygen good_pub_key = RSAKey(filename="test.rsa")   class Server(paramiko.ServerInterface):     def __init__(self):         self.event = threading.Event()         self.user = 'root'         self.password = 'root'      def check_channel_request(self, kind, chanid):         if kind == "session":             return paramiko.OPEN_SUCCEEDED         return paramiko.OPEN_FAILED_ADMINISTRATIVELY_PROHIBITED      def check_auth_password(self, username, password):         if (username == self.user) and (password == self.password):             return paramiko.AUTH_SUCCESSFUL         return paramiko.AUTH_FAILED      def check_auth_publickey(self, username, key):         if (username == self.user) and (key == good_pub_key):             return paramiko.AUTH_SUCCESSFUL         return paramiko.AUTH_FAILED      def enable_auth_gssapi(self):         return False      def get_allowed_auths(self, username):         return "password,publickey"      def check_channel_shell_request(self, channel):         self.event.set()         return True      def check_channel_pty_request(         self, channel, term, width, height, pixelwidth, pixelheight, modes     ):         return True   if __name__ == "__main__":     server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)     server.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)     server.bind(('', 2200))     server.listen(5)      while True:         try:             client, addr = server.accept()              t = Transport(client)             t.load_server_moduli()             t.add_server_key(good_pub_key)             t.start_server(server=Server())              chan = t.accept(20)              if not chan:                 continue              chan.send(                 "Welcome to my test server!\n \r"             )              chan.send("\n \n \r")              chan.close()         except Exception as e:             print(e) 

В примере я создаю серверный интерфейс (класс на строке №10), на основе которого будет создана сессия подключения, аутентификация и тд.

Для запуска примера необходимо создать публичный ключ командой ssh-keygen и указать на 7й строчке путь к нему. Вот что будет, если подключиться к этому серверу:

> ssh root@localhost -p 2200 root@localhost's password: Permission denied, please try again. root@localhost's password:  Welcome to my test server!  Connection to localhost closed.

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

Что дальше?

Дальше можно:

  1. Прикалываться, представьте реакцию, если человек подключится к серверу и получит подобное (но не переборщите, для запуска можно поменять порт ssh, а сокет запустить на 22м порте):

  2. Устраивать опросы или создать свою ASCII галерею:

  3. Да и просто интересная практика для изучения сокетов, tcp подключений и тд.

Полезная литература:

  1. https://docs-python.ru/standart-library/modul-socket-setevoj-interfejs-python/

  2. https://help.reg.ru/support/hosting/dostupy-i-podklyucheniye-panel-upravleniya-ftp-ssh/chto-takoye-ssh

  3. https://www.paramiko.org


ссылка на оригинал статьи https://habr.com/ru/articles/791620/