Сканировать все возможные порты на хосте с помощью Python

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

def check_open_port(host, port):
    s = socket.socket()
    s.settimeout(0.1)
    # the SO_REUSEADDR flag tells the kernel to reuse a local 
    # socket in TIME_WAIT state, without waiting for its natural
    # timeout to expire.
    s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)

    try:
        code = s.connect_ex((host, port))
        s.close()

        if code == 0:
            return True
        else:
            return False
    except socket.error:
        return False


def get_open_ports(host, max_port=65535):
    open_ports = []

    def worker(port):
        if check_open_port(host, port):
            open_ports.append(port)


    pool = ThreadPoolExecutor(max_workers=10000)
    [pool.submit(worker, port) for port in range(1, max_port + 1)]
    pool.shutdown(wait=True)

    return open_ports

Например, на хосте с открытыми портами 22, 80 и 443 иногда я получаю такой ответ:

[22, 80]

и иногда я получаю:

[22, 80, 443]

или даже:

[22]

Хосты с большим количеством открытых портов производят больше комбинаций.

Я играл со значениями max_workers и settimeout(), но не могу заставить его работать хорошо. Единственный раз, когда это работало, было без использования потоков, но, очевидно, это заняло целую вечность, мне нужно их использовать.

Я что-то упускаю? Есть ли другой способ реализовать это?


person Grender    schedule 06.03.2018    source источник
comment
Вы хотите сделать это, используя этот код, только если вы готовы использовать пакет nmap python? github.com/johanlundberg/python-nmap/blob/master/ nmap/   -  person Tarun Lalwani    schedule 09.03.2018
comment
Я бы предпочел использовать такой код, но я буду использовать все, что работает, если нет другого выбора.   -  person Grender    schedule 09.03.2018
comment
Я полагаю, что у вас есть гоночное состояние при добавлении open_ports. Поскольку к этой переменной обращаются несколько потоков, вы должны использовать threading.Lock, чтобы гарантировать, что только один добавляется за раз... Однако я не могу воспроизвести проблему локально!?   -  person urban    schedule 15.03.2018
comment
Пробовал блокировку, проблема осталась. Кстати, список потокобезопасен.   -  person Grender    schedule 15.03.2018


Ответы (3)


2 вопроса сюда:

  1. Я что-то упускаю
  2. Есть ли другой способ реализовать это?

Я что-то упускаю

Я думаю, что стоит проверить коды ошибок здесь:

if code == 0: 
    return True
else:
    return False

учитывая, что вы пытаетесь запустить пул ! 10 000 потоков, за которыми могут последовать все виды ошибок, т. е. достигнуты некоторые системные/ваши пользовательские ограничения (проверьте ulimit -a), и вы будете рассматривать такие ошибки как закрытый порт без уведомления. Это может объяснить нестабильные результаты, которые вы испытываете.

Кстати, на моем MacBook результаты согласуются (сверка с моим живым сервером на хостинге VPS)

Я бы также выбрал меньшее количество потоков — 10K — это перебор. Например, вот значения по умолчанию, предлагаемые в документах python :

Изменено в версии 3.5: если max_workers имеет значение None или не указано, по умолчанию будет указано количество процессоров на машине, умноженное на 5, при условии, что ThreadPoolExecutor часто используется для перекрытия операций ввода-вывода вместо работы ЦП, а количество рабочих должно быть выше, чем количество воркеров для ProcessPoolExecutor

Есть ли другой способ реализовать это?

Во-первых, нет необходимости использовать threads/processes - неблокирующие сокеты + событийные мультиплексоры например, epoll существуют уже много лет, так что вы сможете чтобы уйти без дополнительных потоков/обработки.

Метод подключения/закрытия также неоптимален, потому что вам просто нужно проверить, открыт порт или нет — здесь вам не нужно полноценное TCP-соединение.

В простейшем случае вам просто нужно отправить сегмент SYN и проверить, что ответит сервер.

Вот хорошая статья с десятком методов с использованием scapy

Scapy — мощная интерактивная программа для управления пакетами. Он способен подделывать или декодировать пакеты большого количества протоколов, отправлять их по сети, перехватывать их, сопоставлять запросы и ответы и многое другое. Он может легко справиться с большинством классических задач, таких как сканирование, трассировка, зондирование, модульные тесты, атаки или обнаружение сети (он может заменить hping, 85% nmap, arpspoof, arp-sk, arping, tcpdump, tethereal, p0f и т. д.).

Вот описание одного из методов («Проверка соединения TCP»):

Клиент отправляет первое рукопожатие, используя флаг SYN и порт для подключения к серверу в TCP-пакете. Если сервер отвечает RST вместо SYN-ACK, то этот конкретный порт закрыт на сервере.

И еще один метод («невидимое сканирование TCP»):

Этот метод аналогичен сканированию соединения TCP. Клиент отправляет TCP-пакет с установленным флагом SYN и номером порта для подключения. Если порт открыт, сервер отвечает флагами SYN и ACK внутри TCP-пакета. Но на этот раз клиент отправляет флаг RST в TCP-пакете, а не RST+ACK, как это было при сканировании TCP-подключения. Этот метод используется, чтобы избежать обнаружения сканирования портов брандмауэрами.

Конечно, если вы просто хотите поиграть с сокетами/потоками, ваш подход также подойдет даже без pcap/scapy.

person ffeast    schedule 11.03.2018

Я попробовал ваш код на ноутбуке jupyter и всегда получаю один и тот же набор портов:

get_open_ports('127.0.0.1')

Выход:

[133, 200, 144...60700]

Возможно ли, что в определенное время для запрашиваемого хоста открыто другое количество портов?

Чтобы проверить небольшой набор портов, я уменьшил max_port до 10000 и все равно каждый раз получаю один и тот же набор портов:

def get_open_ports(host, max_port=10000):
open_ports = []

def worker(port):
    if check_open_port(host, port):
        open_ports.append(port)

with ThreadPoolExecutor(max_workers=10000) as executor:
    [executor.submit(worker, port) for port in range(1, max_port + 1)]
    executor.shutdown(wait=True)
return open_ports

get_open_ports('127.0.0.1')

Выход: [150, 900, 1035, 7789]

Примечание. Я изменил номера портов в целях безопасности.

ИЗМЕНИТЬ:

def get_open_ports(host, max_port=65535):
    open_ports = []

    def worker(port):
        if check_open_port(host, port):
            open_ports.append(port)

# We can use a with statement to ensure threads are cleaned up promptly
    with ThreadPoolExecutor(max_workers=100) as executor:
        print('main:starting')
        wait_for=[executor.submit(worker,port) for port in range(1, max_port + 1)]
        for f in as_completed(wait_for):
            print('main: result: {}'.format(f.result())) #check result on each thread execution

#         executor.shutdown(wait=True)  #not required when using the 'with' statement
    return len(open_ports)

test = get_open_ports('45.60.112.163') #hostname for www.indracompany.com

#max_workers not defined & max_port=10000
# len(test)     #test1: 148
# len(test)     #test 2: 79

#max_workers = 10000 & max_port=65535
# len(test)      #test1: 1
# len(test)      #test2:1
# len(test)      #test3:1

#max_workers = 20000 & max_port=65535

# len(test)  #test1: 14
# len(test)  #test2:1
# len(test)  #test3: 1
# len(test)  #test4:1

#max_workers not defined & max_port=65535 #quite time-consuming
# len(test)   #test1: 63

РЕДАКТИРОВАТЬ 2: более надежное решение

Как предложил @Tarun, библиотека Python python-nmap лучше справляется со сканированием хосты.

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

# The python-nmap library helps to programmatically manipulate scanned results of nmap to automate port scanning tasks. 
# To use this library you must have the Nmap software installed. This can be installed from https://nmap.org/download.html.
# Network Mapper (Nmap) is a free and open-source tool used for network discovery and security auditing. 
# It runs on all major computer operating systems, and official binary packages are available for Linux, Windows, and Mac OS X.
# For Windows 7 and later, you must also upgrade 'NCap' from https://nmap.org/npcap/ 
# For Windows, make sure nmap.exe is added to PATH.
# When you're ready, pip install python-nmap

import time
import nmap
nm = nmap.PortScanner() #initialize PortScanner object
host = '45.60.112.163'  #specify host
nm.scan(host, '1-100') #run the scan, specify host and range of ports to scan

#Optional steps for verification:

#Output: nmap -oX - -p 1-100 -sV 45.60.112.163
print(nm.command_line()) #command_line command to execute on nmap command prompt

#Output: {'tcp': {'method': 'syn', 'services': '1-100'}}
print(nm.scaninfo())   #nmap scan information

#Now we can scan all hosts
#From Official documentation at https://xael.org/pages/python-nmap-en.html
start_time = time.time()   #To get program execution time
for host in nm.all_hosts(): 
    print('----------------------------------------------------')
    print('Host : %s (%s)' % (host, nm[host].hostname()))
    print('State : %s' % nm[host].state())
    for proto in nm[host].all_protocols():
        print('----------')
        print('Protocol : %s' % proto)
        lport = nm[host][proto].keys()
        for key in sorted(lport):
            for port in lport:
                print ('port : %s\tstate : %s' % (port, nm[host][proto][port]['state']))
print('Execution time: %s seconds' % (time.time() - start_time))

    #Output:
    ----------------------------------------------------
    Host : 45.60.112.163 ()
    State : up
    ----------
    Protocol : tcp
    port : 25       state : open
    port : 51       state : open
    port : 53       state : open
    port : 80       state : open
    port : 81       state : open
    port : 85       state : open
    port : 91       state : open
    port : 25       state : open
    port : 51       state : open
    port : 53       state : open
    port : 80       state : open
    port : 81       state : open
    port : 85       state : open
    port : 91       state : open
    port : 25       state : open
    port : 51       state : open
    port : 53       state : open
    port : 80       state : open
    port : 81       state : open
    port : 85       state : open
    port : 91       state : open
    port : 25       state : open
    port : 51       state : open
    port : 53       state : open
    port : 80       state : open
    port : 81       state : open
    port : 85       state : open
    port : 91       state : open
    port : 25       state : open
    port : 51       state : open
    port : 53       state : open
    port : 80       state : open
    port : 81       state : open
    port : 85       state : open
    port : 91       state : open
    port : 25       state : open
    port : 51       state : open
    port : 53       state : open
    port : 80       state : open
    port : 81       state : open
    port : 85       state : open
    port : 91       state : open
    port : 25       state : open
    port : 51       state : open
    port : 53       state : open
    port : 80       state : open
    port : 81       state : open
    port : 85       state : open
    port : 91       state : open
    Execution time: 0.015624761581420898 seconds

Чтобы преобразовать вывод в csv, используйте:

print(nm.csv())

В результате этого расследования на моем компьютере был установлен Nmap. Ради интереса я также запустил сканирование в командной строке, используя приведенную ниже команду. Это сканирование выполнялось для диапазона «1-1000» и заняло более 15 минут (я не просидел весь сеанс!).

введите описание изображения здесь

person amanb    schedule 09.03.2018
comment
Почему ваш ответ сработает? В итоге такой же как у меня. - person Grender; 09.03.2018
comment
На самом деле это не работает: AttributeError: 'set' object has no attribute 'shutdown' - person Grender; 10.03.2018
comment
Извините, я работал с несколькими задачами и не мог редактировать ответ. Я попробовал ваш код, и он отлично работает для определенного мной хоста. - person amanb; 10.03.2018
comment
Попробуйте хост с большим количеством открытых портов, результаты будут отличаться от каждого сканирования. Например, indracompany.com. - person Grender; 10.03.2018
comment
Я проверил indracompany.com, и вы были правы, результаты сильно различаются. Я изменил код с комментариями и заметил, что разница в основном связана с параметром max_workers. Согласно этому ответу link оптимального числа для max_workers не существует. Я добавил в код дополнительную проверку для проверки результата выполнения каждого потока. Например, я получаю длинный список main:result:None, когда max_workers=100. Это означает, что при большом количестве отправленных потоков не возвращается номер порта. - person amanb; 10.03.2018
comment
Итак, что вы предлагаете? Пожертвовать многопоточностью ради сканирования всех портов? - person Grender; 10.03.2018
comment
Я добавил эту информацию, если она может оказаться полезной на данном этапе. Это мои наблюдения в поисках лучшего решения: более низкое значение дает мне хорошее количество портов (хотя и не все из них) и требует времени для выполнения, а более высокое значение дает мне еще меньше нет. портов, но выполняется намного быстрее. В документации Python сказано: If max_workers is None or not given, it will default to the number of processors on the machine, multiplied by 5. Я отредактирую свой ответ, когда у меня будет лучшее решение этой проблемы. - person amanb; 10.03.2018
comment
Большое спасибо за ваши наблюдения и тесты, хотя вопрос остается без ответа. Я очень ценю это. - person Grender; 11.03.2018
comment
Кажется, он хорошо работает с max_workers = None (возможно, из-за небольшого числа). Проверю лучше. - person Grender; 11.03.2018
comment
@Grender, когда max_workers=None, он использует 5 * CPU count - person Tarun Lalwani; 12.03.2018
comment
@TarunLalwani Да, я знаю. :) - person Grender; 12.03.2018

Я столкнулся с этим сканером портов, когда работал с сокетами в python...

import socket
import threading
from queue import *

print_lock = threading.Lock()
target = input("Enter websit or IP Adress to scan: ")
minPort = int(input("Enter minimum Port to scan (1 is the smallest): "))
maxPort = int(input("Enter maximum Port to scan: "))
threadNo = int(input("Enter No. of threads to use(500 is a good all around number): "))

def portscan(port):
    s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    try:
        con = s.connect((target, port))
        with print_lock:
            print("Port", port, "is open!")
        con.close()
    except:
        pass

def threader():
    while True:
        worker = q.get()
        portscan(worker)
        q.task_done()

q = Queue()

for x in range(threadNo):
    t = threading.Thread(target=threader)
    t.daemon = True
    t.start()

for worker in range (minPort, maxPort):
     q.put(worker)

q.join()

Он работает довольно хорошо и может быть легко адаптирован :)

person Jake    schedule 12.03.2018