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

Моя цель - предложение широкого ассортимента товаров и услуг на постоянно высоком качестве обслуживания по самым выгодным ценам.

Спойлеры

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

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

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

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

Предисловие

Что такое ssh уже хорошо рассказал один автор на хабре: https://habr.com/ru/articles/122445/

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

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

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

Начнём

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

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

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

Подробнее про этот пример: https://docs-python.ru/standart-library/modul-socket-setevoj-interfejs-python/

# сервер
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/


Интересные статьи

Интересные статьи

В наше время автоматизация стала ключевым фактором в повышении эффективности бизнес-процессов. В этой статье мы рассмотрим детальный план внедрения автоматизации в новый проект, используя язык програм...
В этой статье хочу рассказать о том, как написать полезный сервис, для получения ИНН по персональным данным (паспортные данные). ИНН физического лица получаем с использование сайта https://service.nal...
Всем привет! Меня зовут Евгений Степанов, и я Python Full Stack-Developer в компании PVS-Studio. В этой статье вы узнаете, что нужно сделать, чтобы за полгода стать Python backend-разработчиком. После...
Рассмотрим ситуацию, когда вы пишете свой класс, наследуюетесь от класса библиотеки и вам потребовалось значение локальной переменной функции родительского класса.Хороший пример: класс SSHOperator, ме...
Мой опыт разработки игры «Змейка» на Brython «Погоди, что?» – думаю, большинство читателей отреагирует на заголовок именно так. В смысле «просто использовать Python в браузе...