May 26, 2022

Простой «шпионский» HTTP-сервер на Python

#Обучение

Создать свой собственный HTTP сервер на питоне достаточно просто. Буквально несколько строк кода. И это может быть как файловый сервер, так и сервер, который просто отдает нам странички по запросу. Возьмем для примера сервер, который устанавливается вместе с программой calibre. При должной настройке данный сервер можно использовать как полноценный сайт библиотеки. Но calibre тут только для примера. А суть вот в чем. Когда вы скачиваете себе какой-либо медиасервер и устанавливаете его себе в систему, есть ли у вас уверенность в том, что у данного сервера только лишь тот функционал, который описан в документации? Ведь на самом деле, здесь открывается довольно большое поле для творчества. И давайте посмотрим, какое именно.

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

Что понадобиться?

В данном случае устанавливать сторонние библиотеки не нужно. Все уже есть в стандартной поставке. Нам понадобиться: os, socket и platform. Давайте их импортируем.

Python:

import os
import socket
import platform

Теперь нам нужно создать вспомогательную функцию для определения локального адреса компьютера. Ведь именно по этому адресу будет доступен вызов команд в браузере. Тут нежно оговориться, что данный сервер будет работать в пределах одной локальной сети. Но, если сделать проброс портов, то, думаю можно получить доступ и из интернета. Это при желании. Создадим функцию local_ipv4(). На входе она ничего не принимает, только лишь делает запрос и отдает ip-адрес.

Код функции получения локального ip-адреса:

Python:

def local_ipv4():
    # получаем локальный IP-адрес с помощью широковещательной рассылки
    st = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
    try:
        st.connect(('10.255.255.255', 1))
        ip_l = st.getsockname()[0]
    except Exception:
        ip_l = '127.0.0.1'
    finally:
        st.close()
    return ip_l

Теперь приступим к ваянию самого сервера, непосредственно той его части, что будет прослушивать порт и при поступлении на него запроса вызывать обработчик, который мы напишем чуть позднее. Создадим функцию home_server_start(). В данной функции создадим сервер, в котором необходимо указать два параметра, это ip-адрес и порт, который будет прослушиваться. Здесь адрес мы запрашиваем с помощью вспомогательной функции для его получения. После чего запускаем сервер на прослушивание, с указанием количества запросов в очереди. Пусть будет 4. Сервер у нас не требует большой нагрузки. Он вообще ее по большей части не требует.

Python:

home_server = socket.create_server((local_ipv4(), 2050))
home_server.listen(4)

Но, нам нужно, чтобы сервер не завершал свою работу после того, как выполнит запрос. Поэтому, создадим бесконечный цикл, в котором и будем получать запросы с помощью метода accept(), который в переводе так и означает, принимать. Затем при поступлении запроса принимаем данные из сокета с указанием количества принимаемых байт и декодируем в utf-8. Затем печатаем тот запрос, что получили. На самом деле делать это не обязательно, но в целях отладки, на данном этапе данный принт можно оставить.

Python:

        while True:
            print('Весь в работе...\n')
            client, address = home_server.accept()
            content = client.recv(1024). decode('utf-8')
            print(content)

            send_data = reciever_home_server(content)
            client.send(send_data)
            client.shutdown(socket.SHUT_WR)

В переменную send_data получаем с помощью функции обработчика данные, которые затем отправляем браузеру и закрываем текущее соединение.

Python:

 def home_server_start():
    home_server = socket.create_server((local_ipv4(), 2050))
    home_server.listen(4)
    while True:
        print('Весь в работе...\n')
        client, address = home_server.accept()
        content = client.recv(1024).decode('utf-8')
        print(content)

        send_data = reciever_home_server(content)
        client.send(send_data)
        client.shutdown(socket.SHUT_WR)

Осталось создать функцию, которая будет обрабатывать получаемые запросы. Создадим ее: reciever_home_server(request_data). На входе она будет получать данные запроса, которые впоследствии и обработает. Далее создадим несколько заголовков для браузера, в которых будут отправляться ответы в случае успеха, то есть код 200, в случае отсутствия страницы, ну или команды 404 и в случае, если команда не была введена и просто выполнился запрос непосредственно по ip и порту.

Python:

headers = 'HTTP/1.1 200 OK\r\nContent-Type: text/html; charset=utf-8\r\n\r\n'
headers_404 = 'HTTP/1.1 404 OK\r\nContent-Type: text/html; charset=utf-8\r\n\r\n '

Теперь нужно получить содержимое запроса. То есть то, что идет, к примеру, после: http:// 127.0.0.1:2050/bla-bla-bla. Вот это вот bla-bla-bla нам и нужно получить. Создадим переменную path, в которую и запихаем данное значение.

path = request_data.split(" ")[1]

А дальше начинаются проверки. Так как у нас ведь не совсем HTTP сервер. Вернее, может выполнять и другие функции. Впрочем, как и любой сервер. Это заложено в его основе. Вот только как это использовать, тут уже все ложиться на плечи того, кто этот сервер делает.

Проверяем команду, полученную от браузера. В данном случае команда ls. Но от браузера она приходит даже после обрезки в таком виде. И если команда соответствует, то идем дальше.

if path == '/ls':

На следующем этапе проверяем операционную систему. Если это Linux, то выполняем один код, если Windows – другой. В нашем случае, мы выполняем сканирование содержимого пользовательской папки и отправляем ответ серверу для того, чтобы он отправил его дальше браузеру.

Python:

            if platform.system() == "Linux":
                respons = str(os.listdir(os.environ['HOME'])).encode('utf-8')
                return headers.encode('utf-8') + respons
            elif platform.system() == "Windows":
                respons = str(os.listdir(os.path.join(os.environ['USERPROFILE'], 'Documents'))).encode('utf-8')
                return headers.encode('utf-8') + respons

Ну и добавил еще одну команду, чтобы не было скучно и была возможность посмотреть, как это вообще работает. А вот данная команда работает почти одинаково, уже вне зависимости от того, какая операционная система у вас используется:

Python:

        elif path == "/platform":
            respons = f'ОС: {platform.system()} <p>Версия: {platform.version()}</p>' \
                      f'<p>Архитектура: {platform.machine()}</p>'.encode('utf-8')
            return headers.encode('utf-8') + respons

И пришлось создать заглушку для иконки. Браузер по умолчанию запрашивает ее наличие. А так как у нас ее нет (хотя в нормальном варианте она должна быть), мы указываем вместо этого заглушку:

Python:

elif path == "/favicon.ico":
            respons = 'rel="shortcut icon" href="#"'.encode('utf-8')
            return headers.encode('utf-8') + respons

Еще нужно обработать ошибку. Мало ли, опечатка в команде. И все, сервер вылетел. А так, он просто обработает ошибку, скажет нам что не надо так делать и продолжит работу.

Python:

else:
    return (headers_404 + 'Извиняйте, но этой страницы нет').encode('utf-8')

Python:

def reciever_home_server(request_data):
    headers = 'HTTP/1.1 200 OK\r\nContent-Type: text/html; charset=utf-8\r\n\r\n'
    headers_404 = 'HTTP/1.1 404 OK\r\nContent-Type: text/html; charset=utf-8\r\n\r\n '
    path = request_data.split(" ")[1]
    if path == '/ls':
        if platform.system() == "Linux":
            respons = str(os.listdir(os.environ['HOME'])).encode('utf-8')
            return headers.encode('utf-8') + respons
        elif platform.system() == "Windows":
            respons = str(os.listdir(os.path.join(os.environ['USERPROFILE'], 'Documents'))).encode('utf-8')
            return headers.encode('utf-8') + respons
    elif path == "/platform":
        respons = f'ОС: {platform.system()} <p>Версия: {platform.version()}</p>' \
                  f'<p>Архитектура: {platform.machine()}</p>'.encode('utf-8')
        return headers.encode('utf-8') + respons
    elif path == "/favicon.ico":
        respons = 'rel="shortcut icon" href="#"'.encode('utf-8')
        return headers.encode('utf-8') + respons
    else:
        return (headers_404 + 'Извиняйте, но этой страницы нет').encode('utf-8')

И в функции if __name__ == "__main__": стартуем сервер.

Python:

if __name__ == "__main__":
    home_server_start()

Python:

import os
import socket
import platform


def local_ipv4():
    # получаем локальный IP-адрес с помощью широковещательной рассылки
    st = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
    try:
        st.connect(('10.255.255.255', 1))
        ip_l = st.getsockname()[0]
    except Exception:
        ip_l = '127.0.0.1'
    finally:
        st.close()
    return ip_l


def home_server_start():
    home_server = socket.create_server((local_ipv4(), 2050))
    home_server.listen(4)
    while True:
        print('Весь в работе...\n')
        client, address = home_server.accept()
        content = client.recv(1024).decode('utf-8')
        print(content)

        send_data = reciever_home_server(content)
        client.send(send_data)
        client.shutdown(socket.SHUT_WR)


def reciever_home_server(request_data):
    headers = 'HTTP/1.1 200 OK\r\nContent-Type: text/html; charset=utf-8\r\n\r\n'
    headers_404 = 'HTTP/1.1 404 OK\r\nContent-Type: text/html; charset=utf-8\r\n\r\n '
    path = request_data.split(" ")[1]
    if path == '/ls':
        if platform.system() == "Linux":
            respons = str(os.listdir(os.environ['HOME'])).encode('utf-8')
            return headers.encode('utf-8') + respons
        elif platform.system() == "Windows":
            respons = str(os.listdir(os.path.join(os.environ['USERPROFILE'], 'Documents'))).encode('utf-8')
            return headers.encode('utf-8') + respons
    elif path == "/platform":
        respons = f'ОС: {platform.system()} <p>Версия: {platform.version()}</p>' \
                  f'<p>Архитектура: {platform.machine()}</p>'.encode('utf-8')
        return headers.encode('utf-8') + respons
    elif path == "/favicon.ico":
        respons = 'rel="shortcut icon" href="#"'.encode('utf-8')
        return headers.encode('utf-8') + respons
    else:
        return (headers_404 + 'Извиняйте, но этой страницы нет').encode('utf-8')


if __name__ == "__main__":
    home_server_start()

Как видите, команды успешно отработали и браузер показал результат запросов.

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

Теперь осталось скомпилировать данный код с помощью nuitka и посмотреть, как работает данный код на другой машине с Windows.

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

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

Источник