Статья для участия в конкурсе Тестирование Веб-Приложений на проникновение
Приветствую тебя читатель!Сегодня мы окунёмся в настоящую эволюцию, и напишем целых три программы, от простого к более сложному.
В арсенале любого хакера одним из важнейших инструментов является сканер открытых портов IP-адресов. Сканер портов – это программа, написанная для поиска хостов в сети, у которых есть открытые, интересующие хакера порты. Применяется для предварительной разведки, с последующим проникновением через уязвимости или админпанели.
У разных портов бывает разное назначение. Приведу буквально несколько примеров:
21 — FTP (File Transfer Protocol) Протокол передачи файлов. FTP позволяет подключаться к серверам, просматривать содержимое каталогов и загружать файлы с сервера или на сервер.
23 — TELNET (TELecommunication NETwork) Позволяет управлять Операционной Системой удаленно.
53 – DNS (Domain Name System) Показывает установлен ли DNS. Может использоваться для так называемого DNS Spoofing, то есть подменой объекта DNS.
Первая ступень – черепаха
Приступим:
Первым делом подключаем модуль socket для работы с сокетами (интерфейс для обеспечения обмена данными между процессами). И модуль netaddr для работы с IP-адресами.
Python:
import socket
from netaddr import *
Далее создаём функцию сканирования портов, в которой создаётся сокет и выставляется таймаут. Мы будем пытаться приконнектиться к порту, и в случае соединения, пишем что порт открыт. Открытое соединение обязательно закрываем. Если соединение не удалось, то ничего не пишем, оператор-заглушка pass пропустит все ошибки.
Python:
def port_scan(port, host):
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.settimeout(0.5)
try:
connection = s.connect((host, port))
print(host, 'Port :', port, "is open.")
connection.close()
except:
pass
Потом делаем ввод диапазонов IP-адресов, с помощью split определяем разделитель, в данном случае дефис. То есть ввод осуществляется так 87.248.98.0-87.248.98.255 Также назначаем начало и конец диапазона. Делаем ввод нужного порта.
Python:
ipStart, ipEnd = input("Enter IP-IP: ").split("-")
iprange = IPRange(ipStart, ipEnd)
port = int(input('Enter port: '))
Ну и напоследок накидаем цикл перебора IP-адресов.
Python:
for ip in iprange:
host = str(ip)
port_scan(port, host)
input()
Скрипт готов. Давайте его протестируем. Вводим диапазон 87.248.98.0-87.248.98.255 и порт 443.
Сканер работает, но посмотрите на скорость – 127 секунд понадобилось, чтобы просканировать 256 позиций. Это ужасно медленно – настоящая черепаха! И немудрено, ведь он однопоточный.
Python:
import socket
from netaddr import *
def port_scan(port, host):
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.settimeout(0.5)
try:
connection = s.connect((host, port))
print(host, 'Port :', port, "is open.")
connection.close()
except:
pass
ipStart, ipEnd = input("Enter IP-IP: ").split("-")
iprange = IPRange(ipStart, ipEnd)
port = int(input('Enter port: '))
for ip in iprange:
host = str(ip)
port_scan(port, host)
input()
Вторая ступень – арабский скакун
Если предыдущую программу сравнивать с лошадью, то у нас получилась жалкая кляча с заплетающимися копытами. Так давайте её пришпорим, и придадим прыти арабского скакуна!
Поскакали!
Для этого мы дополнительно импортируем модуль threading, необходимый для создания многопоточности.
Python:
import threading
import socket
from netaddr import *
Теперь изменим цикл, и добавим туда создание и запуск потока.
Python:
for ip in iprange:
host = str(ip)
t = threading.Thread(target=port_scan, args=(port, host))
t.start()
Ну что же, недолго думая, начнём тестирование. Возьмём тот же диапазон и попробуем разные порты, которые точно открыты в данном диапазоне.
Й-я-х-у-у!!! Фантастика!!! Всего три строчки добавлены в код, и программу просто не узнать. Скорость отличная, 8-9 сотых секунды на 256 хостов. Настоящий жеребец!
Ну а теперь ложка дёгтя. Выставим-ка диапазон побольше 87.248.95.0-87.248.98.255 Здесь уже 1024 адреса. Запускаем программу, и … получаем ошибку – не удаётся запустить новый поток. Похоже наш арабский скакун подвернул себе ногу
А так всё хорошо начиналось… Программа получилась очень шустрая, но больше двух диапазонов(512 адресов), у неё не получается осилить. Произошёл конфликт потоков. Почему так произошло? Программа запускает потоки быстрее, чем они успевают завершаться.
На самом деле этот вопрос решается использованием замков или семафоров для блокировки потоков. Но мы на этот раз воспользуемся совершенно другой возможностью, которая есть в python – это мультипроцессинг.
Python:
import threading
import socket
from netaddr import *
def port_scan(port, host):
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.settimeout(0.5)
try:
connection = s.connect((host, port))
print(host, 'Port :', port, "is open.")
connection.close()
except:
pass
print('-' * 36)
port = int(input('Enter port: '))
ipStart, ipEnd = input("Enter IP-IP: ").split("-")
iprange = IPRange(ipStart, ipEnd)
print('-' * 36)
for ip in iprange:
host = str(ip)
t = threading.Thread(target=port_scan, args=(port, host))
t.start()
input()
Третья ступень – зверь
Ну что же друзья, вот мы и добрались до самого интересного. Начнём с того, чем же отличается мультипроцессинг от потоков. Потоки создаются в одном главном потоке GIL (Global Interpreter Lock). То есть все потоки работают внутри одного главного потока.
А модуль multiprocessing позволяет нам запускать множество процессов – сколько процессов, столько GIL'ов. Круто, да? Кроме того, имеется замечательный класс Pool умеющий выполнять функции параллельно. То есть исключается ситуация, когда несколько потоков пытаются обратиться одновременно к одному объекту. Для модуля multiprocessing также можно применять semaphore и lock, как и в threading. Но у нас будет чистый эксперимент без использования замков.
Возьмёмся за дело – вместо threading импортируем Pool. И немного изменим функцию port_scan.
Python:
import socket
from multiprocessing import Pool
from netaddr import *
def port_scan(arg):
host, port = arg
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.settimeout(0.5)
try:
s.connect((host, port))
print(host, 'Port :', port, "is open.")
s.close()
return port, True
except (socket.timeout, socket.error):
return port, False
Добавим вместо ввода порта количество процессов. А портов нарисуем целый список, чего уж тут вошкаться с одним портом
Python:
if __name__ == '__main__':
print('-' * 38)
ipStart, ipEnd = input("Enter IP-IP: ").split("-")
iprange = IPRange(ipStart, ipEnd)
number = int(input('Number of processes: '))
print('-' * 38)
ports = [43, 80, 109, 110, 115, 118, 119, 143,
194, 220, 443, 540, 585, 591, 1112, 1433, 1443, 3128, 3197,
3306, 4000, 4333, 5100, 5432, 6669, 8000, 8080, 9014, 9200]
Ну и в завершение нахимичим с циклами for, вложив их в друг друга. Первый цикл останется без изменений, в нём мы прокрутим ip. Второй цикл создаёт пул процессов с аргументами порт и хост.
Python:
pool = Pool(processes=number)
for ip in iprange:
host = str(ip)
pool.imap_unordered(port_scan, [(host, port) for port in ports])
Так, уже чешутся руки потестить Не будем мелочиться, и забубеним вот такенный диапазон 87.248.85.0-87.248.98.255, но сколько же потоков сделать? Пусть будет 400! Ну вот, выставил от балды, а сколько же там операций понадобится провернуть программе? Немного математики не помешает.
14*256*34 = 121 856
Расшифрую – 14 диапазонов по 256 хостов и 34 порта.
Ну ничего себе!!! А не подавится?! Нечего гадать, старт! Первые секунды ничего не происходило, оно и понятно, в это время программа запускала 400 процессов. А потом понеслось…
Ё-оу!!! Программа даже не поморщилась, проглотила и пережевала в своей пасти 121 856 операций. Настоящий зверь!!! Ушло времени 30.5 минут (1834.28 секунд).
Давайте сделаем синтетический подсчёт - производительность операций в секунду:
1] Черепаха
256/127.28 = 2.01
2] Арабский скакун
256/0.09 = 2844.44
3] Зверь
121 856/1834.28 = 66.43
Что мы видим – черепаха самая медленная, но она стабильная, и её скорость всегда будет примерно одинаковой.
Арабский скакун – просто огонь, лучшее решение для проверки 256 хостов.
Зверь – всеядный и бесстрашный, работает с огромными диапазонами ip, и большим количеством процессов, одновременно тестируя множество портов.
Ну а теперь ещё о звере. Мои наблюдения следующие:
- Если выставить 1 порт и маленький диапазон на 256 хостов, то скорость идентична черепахе. Меньше задач – меньше производительность.
- Пропуски открытых портов замечены не были.
- Разумеется при разном количестве процессов, результаты могут отличаться в разы.
- Количество процессов ограничено исключительно производительностью машины. Даже на моём древнем компе, 1000 запущенных процессов прекрасно переваривалось.
Однако не стоит думать, что чем больше процессов, тем лучше. После любого пика идёт падение, так что нужно выбирать золотую середину. Часто 90-100 процессов давали лучшие результаты. В общем нужно подбирать оптимальный параметр под своё железо.
Условия тестирования вкратце:
Windows 7, оперативной памяти 4 Гб, процессор Dual-Core E5400, видеокарта GeForce GT 630, скорость интернета очень низкая.
Послесловие:
В исходном коде зверя, я оставил таймер для измерения выполнения скорости программы. Так будет проще найти оптимальное количество процессов. Тестируйте, подбирайте лучшие значения для своей машины.
Нет предела совершенству, и есть много сторонних моделей для реализации многопоточности. С удовольствием посмотрю на другие варианты. Кто программирует на Python, можете использовать эти программы как базу, делайте лучше и быстрее. Ведь эволюция продолжается!
P.P.S Сначала было интересно наблюдать как появляются всё новые списки закрытых портов. Но нет смысла выводить ненужную информацию в консоль, поэтому выводятся только гуды.
После запуска зверя тестирование портов идёт до тех пор, пока не появится надпись Enter to exit
Python:
import socket
from multiprocessing import Pool
from netaddr import *
import time
def port_scan(arg):
host, port = arg
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.settimeout(0.5)
try:
s.connect((host, port))
print(host, 'Port :', port, "is open.")
s.close()
return port, True
except (socket.timeout, socket.error):
return port, False
if __name__ == '__main__':
print('-' * 38)
ipStart, ipEnd = input("Enter IP-IP: ").split("-")
iprange = IPRange(ipStart, ipEnd)
number = int(input('Number of processes: '))
print('-' * 38)
ports = [43, 80, 109, 110, 115, 118, 119, 143,
194, 220, 443, 540, 585, 591, 1112, 1433, 1443, 3128, 3197,
3306, 4000, 4333, 5100, 5432, 6669, 8000, 8080, 9014, 9200]
pool = Pool(processes=number)
start_time = time.clock()
for ip in iprange:
host = str(ip)
pool.imap_unordered(port_scan, [(host, port) for port in ports])
print('\nScanning Completed in:', "%.3f" % (time.clock() - start_time), "seconds")
input('Enter to exit')