Статья Получение информации о домене с помощью Python #02

Продолжим описание модулей скрипта для получения информации о домене. Первая часть данной статьи расположена здесь.

000.jpg


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


Получение данных из SSL-сертификата домена

Создадим модуль ssl_check.py. С его помощью мы сможем получить данные из SSL-сертификата. Для начала импортируем в модуль нужные библиотеки:

Python:
import socket
from datetime import datetime

import OpenSSL
from OpenSSL.SSL import Connection, Context, SSLv3_METHOD, TLSv1_2_METHOD

Теперь создадим функцию sert_domain_info(domain). На входе она получает имя домена. Установим метод, с помощью которого будем подключаться к базе данных. И обработаем ошибку получения значений.

Python:
        try:
            ssl_connection_setting = Context(SSLv3_METHOD)
        except ValueError:
            ssl_connection_setting = Context(TLSv1_2_METHOD)

Теперь установим таймаут для соединения.

ssl_connection_setting.set_timeout(5)

С помощью сокетов подключимся к домену на порт 443. Теперь создадим соединение, в котором передаем установленные ранее параметры и указываем подключение к домену, с которым будем работать. Затем произведем попытку аутентификации на домене с помощью SSL. Установим соединение для работы в клиентском режиме и выполним рукопожатие SSL.

Python:
        with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
            s.connect((domain, 443))
            c = Connection(ssl_connection_setting, s)
            c.set_tlsext_host_name(str.encode(domain))
            c.set_connect_state()
            c.do_handshake()

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

Python:
            cert = c.get_peer_certificate()
            cert_info.update({'Алгоритм шифрования': cert.get_signature_algorithm().decode('utf-8')})
            cert_info.update({'Серийный номер': cert.get_serial_number()})
            cert_info.update({'Хэш имени субъекта': cert.subject_name_hash()})

Получаем словарь объектов сертификата, а также его эмитента.

Python:
            sub_list = cert.get_subject().get_components()
            issuer = cert.get_issuer().get_components()

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

Python:
            for item in sub_list:
                if item[0].decode('utf-8') == 'CN':
                    cert_info.update({'Кому выдан': item[1].decode('utf-8')})

            for item in issuer:
                if item[0].decode('utf-8') == 'CN':
                    cert_info.update({'Кем выдан': item[1].decode('utf-8')})
                if item[0].decode('utf-8') == 'C':
                    cert_info.update({'Страна': item[1].decode('utf-8')})
                if item[0].decode('utf-8') == 'ST':
                    cert_info.update({'Штат/Область': item[1].decode('utf-8')})
                if item[0].decode('utf-8') == 'L':
                    cert_info.update({'Город': item[1].decode('utf-8')})
                if item[0].decode('utf-8') == 'O':
                    cert_info.update({'Организация': item[1].decode('utf-8')})

Проверяем дату истечения сертификата, и если она еще не закончилась, добавляем ее в словарь с ключом «Действует до». Если же сертификат истек, добавляем «Истек срок действия» и дату, когда это случилось. После чего закрываем соединение и возвращаем из функции словарь со значениями.

Python:
            if not cert.has_expired():
                cert_info.update({'Действует до': str(datetime.strptime(str(cert.get_notAfter().decode('utf-8')),
                                                                          "%Y%m%d%H%M%SZ"))})
            else:
                cert_info.update({'Истек срок действия': str(datetime.strptime(str(cert.get_notAfter().decode('utf-8')),
                                                                 "%Y%m%d%H%M%SZ"))})
            c.shutdown()
            s.close()
            return cert_info

Обрабатываем различные ошибки соединения и получения сертификатов. Здесь мы будем печатать, что соединение не удалось и возвращать пустой словарь.

Python:
# pip install pyOpenSSL

import socket
from datetime import datetime

import OpenSSL
from OpenSSL.SSL import Connection, Context, SSLv3_METHOD, TLSv1_2_METHOD


def sert_domain_info(domain):
    cert_info = {}

    try:
        try:
            ssl_connection_setting = Context(SSLv3_METHOD)
        except ValueError:
            ssl_connection_setting = Context(TLSv1_2_METHOD)
        ssl_connection_setting.set_timeout(5)
        with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
            s.connect((domain, 443))
            c = Connection(ssl_connection_setting, s)
            c.set_tlsext_host_name(str.encode(domain))
            c.set_connect_state()
            c.do_handshake()

            cert = c.get_peer_certificate()
            cert_info.update({'Алгоритм шифрования': cert.get_signature_algorithm().decode('utf-8')})
            cert_info.update({'Серийный номер': cert.get_serial_number()})
            cert_info.update({'Хэш имени субъекта': cert.subject_name_hash()})

            sub_list = cert.get_subject().get_components()
            issuer = cert.get_issuer().get_components()

            for item in sub_list:
                if item[0].decode('utf-8') == 'CN':
                    cert_info.update({'Кому выдан': item[1].decode('utf-8')})

            for item in issuer:
                if item[0].decode('utf-8') == 'CN':
                    cert_info.update({'Кем выдан': item[1].decode('utf-8')})
                if item[0].decode('utf-8') == 'C':
                    cert_info.update({'Страна': item[1].decode('utf-8')})
                if item[0].decode('utf-8') == 'ST':
                    cert_info.update({'Штат/Область': item[1].decode('utf-8')})
                if item[0].decode('utf-8') == 'L':
                    cert_info.update({'Город': item[1].decode('utf-8')})
                if item[0].decode('utf-8') == 'O':
                    cert_info.update({'Организация': item[1].decode('utf-8')})

            if not cert.has_expired():
                cert_info.update({'Действует до': str(datetime.strptime(str(cert.get_notAfter().decode('utf-8')),
                                                                          "%Y%m%d%H%M%SZ"))})
            else:
                cert_info.update({'Истек срок действия': str(datetime.strptime(str(cert.get_notAfter().decode('utf-8')),
                                                                 "%Y%m%d%H%M%SZ"))})
            c.shutdown()
            s.close()
            return cert_info
    except (TypeError, ConnectionRefusedError, socket.gaierror, OSError, OpenSSL.SSL.Error):
        print(f"Соединение с {domain} не удалось")
        return cert_info


Получение истории IP-адресов домена

Есть такой замечательный сервис viewdns.info, который помимо всего прочего предоставляет историю ip-адресов домена, если они есть в базе. Но для его использования нужно получить доступ к бесплатному API. Он ограничен 250-ю запросами, но с его помощью можно получить информацию для обработки в удобном виде – json. Вот только, почему-то не захотелось мне этого делать по той простой причине, что я не хотел привязки скрипта к какому-либо ключу. Потому я хмм… перехватил запрос который отправляется серверу для поиска истории с помощью Burp Suite и, уже после этого просто стал парсить возвращаемую страницу. Тут тоже есть свои нюансы использования. Не следует нагружать сервис бесконечными запросами, так как, помимо того, что он сам находится за Cloudflare, в случае подозрительной активности он просто блокирует вас по ip. А так, если выполнять запросы не слишком часто и делать небольшие паузы между ними, можно пользоваться долгое время.

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

Python:
import requests
from bs4 import BeautifulSoup
from colorama import Fore
from colorama import init

requests.packages.urllib3.disable_warnings()

init()

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

Python:
headers = {
    'Host': 'viewdns.info',
    'Sec-Ch-Ua': '"(Not(A:Brand";v="8", "Chromium";v="98"',
    'Sec-Ch-Ua-Mobile': '?0',
    'Sec-Ch-Ua-Platform': '"Windows"',
    'Upgrade-Insecure-Requests': '1',
    'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) '
                  'Chrome/98.0.4758.82 Safari/537.36',
    'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,'
              'application/signed-exchange;v=b3;q=0.9',
    'Sec-Fetch-Site': 'none',
    'Sec-Fetch-Mode': 'navigate',
    'Sec-Fetch-User': '?1',
    'Sec-Fetch-Dest': 'document',
    'Accept-Language': 'ru-RU,ru;q=0.9,en-US;q=0.8,en;q=0.7',
    'Connection': 'close',
}

Создадим функцию hystory_search(domain), которая на входе принимает имя домена. Добавим множество и очистим его значения. Очистка значений не обязательно, но если вы будете использовать данный модуль без завершения работы, то в нем будут храниться полученные ранее значения. И для того, чтобы они не смешивались с новыми, множество надо очищать. Определим параметры, которые будем передавать в запросе, а именно имя домена.

Python:
    history_set = set()
    history_set.clear()

    params = {'domain': domain}

Теперь выполним запрос, передадим в него параметры, заголовки и установим верификацию в значение False. Передадим ответ в библиотеку BeautifulSoup, после чего выполним поиск всех тегов содержащих нужные нам данные. Соответственно, обработаем исключение, если нужных атрибутов не окажется, то есть, запрос выполниться с ошибкой или просто нет домена в базе. Тогда и нужных атрибутов не будет. Поэтому возвратим пустое множество.

Python:
    resp = requests.get('https://viewdns.info/iphistory/', params=params, headers=headers, verify=False)
    soup = BeautifulSoup(resp.text, 'lxml')
    try:
        table_find = soup.find('table', id='null').find_all('td')[1].find('table').find_all('tr')
    except AttributeError:
        return history_set

Запустим цикл, в котором для начала пропускаем первую строку, в которой находятся заголовки таблицы. Затем ищем все теги «td». Проверяем, есть ли в них значение домена Cloudflare, если есть, пропускаем, если нет, добавляем ip-адрес в множество. И возвращаем множество с полученными адресами.

Python:
    for num, tr in enumerate(table_find):
        if num != 0:
            td_in_line = tr.find_all('td')
            if 'Cloudflare' in td_in_line[2].text or 'CloudFlare' in td_in_line[2].text:
                continue

            print(Fore.RESET + f'\r- Поиск:{td_in_line[0].text}', end='')
            history_set.add(td_in_line[0].text)

Python:
# pip install requests bs4 lxml

import requests
from bs4 import BeautifulSoup
from colorama import Fore
from colorama import init

requests.packages.urllib3.disable_warnings()

init()

headers = {
    'Host': 'viewdns.info',
    'Sec-Ch-Ua': '"(Not(A:Brand";v="8", "Chromium";v="98"',
    'Sec-Ch-Ua-Mobile': '?0',
    'Sec-Ch-Ua-Platform': '"Windows"',
    'Upgrade-Insecure-Requests': '1',
    'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) '
                  'Chrome/98.0.4758.82 Safari/537.36',
    'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,'
              'application/signed-exchange;v=b3;q=0.9',
    'Sec-Fetch-Site': 'none',
    'Sec-Fetch-Mode': 'navigate',
    'Sec-Fetch-User': '?1',
    'Sec-Fetch-Dest': 'document',
    'Accept-Language': 'ru-RU,ru;q=0.9,en-US;q=0.8,en;q=0.7',
    'Connection': 'close',
}


def hystory_search(domain):
    history_set = set()
    history_set.clear()

    params = {'domain': domain}

    resp = requests.get('https://viewdns.info/iphistory/', params=params, headers=headers, verify=False)
    soup = BeautifulSoup(resp.text, 'lxml')
    try:
        table_find = soup.find('table', id='null').find_all('td')[1].find('table').find_all('tr')
    except AttributeError:
        return history_set

    for num, tr in enumerate(table_find):
        if num != 0:
            td_in_line = tr.find_all('td')
            if 'Cloudflare' in td_in_line[2].text or 'CloudFlare' in td_in_line[2].text:
                continue

            print(Fore.RESET + f'\r- Поиск:{td_in_line[0].text}', end='')
            history_set.add(td_in_line[0].text)

    return history_set


Поиск адресов админ-панелей домена

Вопрос поиска админ-панелей достаточно спорный. И тут есть несколько вариантов решения проблемы. Первый – получение значений из robots.txt, если таковой имеется и поиск в нем по списку значений, которые бы указывали на наличие админ-панели, так как их обычно исключают из поиска в Disallow. Но это далеко не всегда. Поэтому, нужно поискать что-то дополнительно. Это простой перебор тех же значений из списка, но уже с помощью модуля requests. Тут сложность заключается в том, что некоторые сайты просто не возвращают код 404 или 403.
Они вместо этого подсовывают страницу 404, которая будет иметь статус-код 200. В этом случае ошибки не будет и все ваши значения из списка попадут в результаты поиска. Чтобы частично это отфильтровать можно проверять текст на каждой странице с помощью регулярок. Что тоже иногда проблематично, так как сервер не возвращает 404 страницу. Вместо этого он возвращает 403 Forbidden. Так что, от поиска простым перебором, пока я не пойму, как фильтровать такие страницы я отказался. Оставил только те значения, которые есть для поиска в robots.txt.

Создадим модуль admin_panel.py. Импортируем в него библиотеки, которые нужны для его работы, а также, сразу подавим предупреждения requests:

Python:
import threading
import time

import requests

requests.packages.urllib3.disable_warnings()

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

Python:
headers = {
    'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/103.0.0.0 '
                  'Safari/537.36',
    'accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,'
              'application/signed-exchange;v=b3;q=0.9 '
}

suffics = ['wp-admin', 'wp-', 'wp-login', 'admin', '?q=admin', 'bitrix', 'auth', 'phpMyAdmin', 'bitrix/admin',
           'administrator', 'panel', 'ckeditor', 'adm', 'manager', 'user/login', 'netcat', 'typo3', 'admin.php',
           'admin.js', 'user/register', 'register', '?do=register', 'do=', 'netcat/admin', 'koobooCMS/admin',
           'apanel', '_ аdmin/indеx.php', 'login', 'admincp', 'db', 'dbadmin', 'myadmin', 'mysql', 'mysqladmin',
           'mysql-admin', 'phpmyadmin', 'acart', 'access', 'citrix', 'cgi-bin', 'administrator.php', 'cpanel',
           'klarnetCMS', 'cabinet', 'plesk-stat', 'geomiXCMS', 'portal', 'loginpanel', 'memberlogin', 'wpadmin.html']

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

adm_list = set()

Создадим функцию req_robots(domain). С ее помощью мы будем получать данные из файла robots.txt. На входе она принимает имя домена. Затем выполним запрос с созданными ранее заголовками, отключенной верификацией и ограничим с помощью timeout время ожидания ответа от сервера. Так же обработаем ошибку соединения.

Python:
    print('- Получение robots.txt')
    try:
        resp = requests.get(f'https://{domain}/robots.txt', headers=headers, verify=False, timeout=10)
    except requests.exceptions.ConnectionError:
        return

Разобьем полученный текст по строкам, после чего в цикле пробежимся по каждой из них. Здесь мы будем искать ключевые слова, которые определили ранее. И если ключевое слово присутствует в строке, разбиваем ее по «:», забираем значение с ключевым словом, добавляем к нему протокол и все это отправляем в множетсво. Еще здесь делаем проверку, нет ли в строке адреса base64. Если есть, пропускаем значение, так как там, обычно, содержится закодированная картинка.

Python:
    robot_line = resp.text.splitlines()
    for line in robot_line:
        for suf in suffics:
            if suf in line:
                if 'base64' in line:
                    return
                try:
                    adm_list.add(f'https://{domain}{line.split(":")[1].strip()}')
                except IndexError:
                    adm_list.add(f'https://{domain}{line.strip()}')

Теперь создадим еще одну функцию, которая будет искать те же ключевые слова, но уже не в robots.txt, потому как его может и не оказаться, а просто подставляя к адресу домена и проверяя полученный статус-код. Назову ее req_search(domain, suf). На входе она принимает имя домена, и ключ для поиска. Сначала я искал все без использования потоков. Но, когда значение ключей увеличилось до 50-ти, понял, что все же использовать их придется.

Выполняем запрос к предполагаемой странице. Проверяем статус-код. Если он 200, добавляем значение в множество. Делаем небольшую паузу. Если же истечет время ожидания ответа или будет ошибка соединения, просто выходим из функции.

Python:
def req_search(domain, suf):
    try:
        resp = requests.head(f'https://{domain}/{suf}', headers=headers, verify=False, timeout=5)
    except (requests.exceptions.ConnectionError, requests.exceptions.ReadTimeout):
        return
    if resp.status_code == 200:
        adm_list.add(resp.url)
    time.sleep(0.4)

Создадим функцию, которая будет запускать потоки с запросами. Назову ее thread_func(domain), на входе она принимает имя домена. Создаем список для подсчета потоков. Запускаем цикл по значениям ключей для поиска, создаем объект потока с параметрами, где указываем название целевой функции и параметры, которые в нее будут передаваться. Определяем поток как демона, после чего запускаем его на выполнение и добавляем в список. Далее, создаем цикл, который будет перебирать список потоков и сообщать пользователю, сколько из потоков уже выполнено.

Python:
def thread_func(domain):
    threads = []

    for suf in suffics:
        t = threading.Thread(target=req_search, kwargs={'domain': domain, 'suf': suf})
        t.daemon = True
        t.start()
        threads.append(t)

    for num, thread in enumerate(threads):
        print(f'\r   - Обработано: {num + 1}', end='')
        thread.join()

И еще одну функцию нужно создать для запуска поиска robots.txt и потоков. Назову ее start_adm_search(domain), куда будет передаваться имя домена. Здесь мы очищаем множество от значений, запускаем функцию поиска robots.txt, после чего стартуем потоки и возвращаем множество с или без значений из функции.

Python:
# pip install requests

import threading
import time

import requests

requests.packages.urllib3.disable_warnings()

headers = {
    'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/103.0.0.0 '
                  'Safari/537.36',
    'accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,'
              'application/signed-exchange;v=b3;q=0.9 '
}

suffics = ['wp-admin', 'wp-', 'wp-login', 'admin', '?q=admin', 'bitrix', 'auth', 'phpMyAdmin', 'bitrix/admin',
           'administrator', 'panel', 'ckeditor', 'adm', 'manager', 'user/login', 'netcat', 'typo3', 'admin.php',
           'admin.js', 'user/register', 'register', '?do=register', 'do=', 'netcat/admin', 'koobooCMS/admin',
           'apanel', '_ аdmin/indеx.php', 'login', 'admincp', 'db', 'dbadmin', 'myadmin', 'mysql', 'mysqladmin',
           'mysql-admin', 'phpmyadmin', 'acart', 'access', 'citrix', 'cgi-bin', 'administrator.php', 'cpanel',
           'klarnetCMS', 'cabinet', 'plesk-stat', 'geomiXCMS', 'portal', 'loginpanel', 'memberlogin', 'wpadmin.html']


adm_list = set()


def req_robots(domain):
    print('- Получение robots.txt')
    try:
        resp = requests.get(f'https://{domain}/robots.txt', headers=headers, verify=False, timeout=10)
    except requests.exceptions.ConnectionError:
        return
    robot_line = resp.text.splitlines()
    for line in robot_line:
        for suf in suffics:
            if suf in line:
                if 'base64' in line:
                    return
                try:
                    adm_list.add(f'https://{domain}{line.split(":")[1].strip()}')
                except IndexError:
                    adm_list.add(f'https://{domain}{line.strip()}')


def thread_func(domain):
    threads = []

    for suf in suffics:
        t = threading.Thread(target=req_search, kwargs={'domain': domain, 'suf': suf})
        t.daemon = True
        t.start()
        threads.append(t)

    for num, thread in enumerate(threads):
        print(f'\r   - Обработано: {num + 1}', end='')
        thread.join()


def req_search(domain, suf):
    try:
        resp = requests.head(f'https://{domain}/{suf}', headers=headers, verify=False, timeout=5)
    except (requests.exceptions.ConnectionError, requests.exceptions.ReadTimeout):
        return
    if resp.status_code == 200:
        adm_list.add(resp.url)
    time.sleep(0.4)


def start_adm_search(domain):
    adm_list.clear()
    req_robots(domain)
    thread_func(domain)

    return adm_list


Поиск открытых портов домена

Создадим модуль scan_port.py. Импортируем библиотеки в модуль и инициализируем colorama.

Python:
import socket
import threading

from colorama import Fore
from colorama import init

init()

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

Python:
frequently_ports = [1, 3, 4, 6, 7, 9, 13, 17, 19, 20, 21, 22, 23, 24, 25, 26, 30, 32, 33, 37, 42, 43, 49, 53, 70, 79,
                    80, 81, 82, 83, 84, 85, 88, 89, 90, 99, 100, 106, 109, 110, 111, 113, 119, 125, 135, 139, 143, 144,
                    146, 161, 163, 179, 199, 211, 212, 222, 254, 255, 256, 259, 264, 280, 301, 306, 311, 340, 366, 389,
                    406, 407, 416, 417, 425, 427, 443, 444, 445, 458, 464, 465, 481, 497, 500, 512, 513, 514, 515, 524,
                    541, 543, 544, 545, 548, 554, 555, 563, 587, 593, 616, 617, 625, 631, 636, 646, 648, 666, 667, 668,
                    683, 687, 691, 700, 705, 711, 714, 720, 722, 726, 749, 765, 777, 783, 787, 800, 801, 808, 843, 873,
                    880, 888, 898, 900, 901, 902, 903, 911, 912, 981, 987, 990, 992, 993, 995, 999, 1000, 1001, 1002,
                    1007, 1009, 1010, 1011, 1021, 1022, 1023, 1024, 1025, 1026, 1027, 1028, 1029, 1030, 1031, 1032,
                    1033, 1034, 1035, 1036, 1037, 1038, 1039, 1040, 1041, 1042, 1043, 1044, 1045, 1046, 1047, 1048,
                    1049, 1050, 1051, 1052, 1053, 1054, 1055, 1056, 1057, 1058, 1059, 1060, 1061, 1062, 1063, 1064,
                    1065, 1066, 1067, 1068, 1069, 1070, 1071, 1072, 1073, 1074, 1075, 1076, 1077, 1078, 1079, 1080,
                    1081, 1082, 1083, 1084, 1085, 1086, 1087, 1088, 1089, 1090, 1091, 1092, 1093, 1094, 1095, 1096,
                    1097, 1098, 1099, 1100, 1102, 1104, 1105, 1106, 1107, 1108, 1110, 1111, 1112, 1113, 1114, 1117,
                    1119, 1121, 1122, 1123, 1124, 1126, 1130, 1131, 1132, 1137, 1138, 1141, 1145, 1147, 1148, 1149,
                    1151, 1152, 1154, 1163, 1164, 1165, 1166, 1169, 1174, 1175, 1183, 1185, 1186, 1187, 1192, 1198,
                    1199, 1201, 1213, 1216, 1217, 1218, 1233, 1234, 1236, 1244, 1247, 1248, 1259, 1271, 1272, 1277,
                    1287, 1296, 1300, 1301, 1309, 1310, 1311, 1322, 1328, 1334, 1352, 1417, 1433, 1434, 1443, 1455,
                    1461, 1494, 1500, 1501, 1503, 1521, 1524, 1533, 1556, 1580, 1583, 1594, 1600, 1641, 1658, 1666,
                    1687, 1688, 1700, 1717, 1718, 1719, 1720, 1721, 1723, 1755, 1761, 1782, 1783, 1801, 1805, 1812,
                    1839, 1840, 1862, 1863, 1864, 1875, 1900, 1914, 1935, 1947, 1971, 1972, 1974, 1984, 1998, 1999,
                    2000, 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010, 2013, 2020, 2021, 2022, 2030,
                    2033, 2034, 2035, 2038, 2040, 2041, 2042, 2043, 2045, 2046, 2047, 2048, 2049, 2065, 2068, 2099,
                    2100, 2103, 2105, 2106, 2107, 2111, 2119, 2121, 2126, 2135, 2144, 2160, 2161, 2170, 2179, 2190,
                    2191, 2196, 2200, 2222, 2251, 2260, 2288, 2301, 2323, 2366, 2381, 2382, 2383, 2393, 2394, 2399,
                    2401, 2492, 2500, 2522, 2525, 2557, 2601, 2602, 2604, 2605, 2607, 2608, 2638, 2701, 2702, 2710,
                    2717, 2718, 2725, 2800, 2809, 2811, 2869, 2875, 2909, 2910, 2920, 2967, 2968, 2998, 3000, 3001,
                    3003, 3005, 3006, 3007, 3011, 3013, 3017, 3030, 3031, 3052, 3071, 3077, 3128, 3168, 3211, 3221,
                    3260, 3261, 3268, 3269, 3283, 3300, 3301, 3306, 3322, 3323, 3324, 3325, 3333, 3351, 3367, 3369,
                    3370, 3371, 3372, 3389, 3390, 3404, 3476, 3493, 3517, 3527, 3546, 3551, 3580, 3659, 3689, 3690,
                    3703, 3737, 3766, 3784, 3800, 3801, 3809, 3814, 3826, 3827, 3828, 3851, 3869, 3871, 3878, 3880,
                    3889, 3905, 3914, 3918, 3920, 3945, 3971, 3986, 3995, 3998, 4000, 4001, 4002, 4003, 4004, 4005,
                    4006, 4045, 4111, 4125, 4126, 4129, 4224, 4242, 4279, 4321, 4343, 4443, 4444, 4445, 4446, 4449,
                    4550, 4567, 4662, 4848, 4899, 4900, 4998, 5000, 5001, 5002, 5003, 5004, 5009, 5030, 5033, 5050,
                    5051, 5054, 5060, 5061, 5080, 5087, 5100, 5101, 5102, 5120, 5190, 5200, 5214, 5221, 5222, 5225,
                    5226, 5269, 5280, 5298, 5357, 5405, 5414, 5431, 5432, 5440, 5500, 5510, 5544, 5550, 5555, 5560,
                    5566, 5631, 5633, 5666, 5678, 5679, 5718, 5730, 5800, 5801, 5802, 5810, 5811, 5815, 5822, 5825,
                    5850, 5859, 5862, 5877, 5900, 5901, 5902, 5903, 5904, 5906, 5907, 5910, 5911, 5915, 5922, 5925,
                    5950, 5952, 5959, 5960, 5961, 5962, 5963, 5987, 5988, 5989, 5998, 5999, 6000, 6001, 6002, 6003,
                    6004, 6005, 6006, 6007, 6009, 6025, 6059, 6100, 6101, 6106, 6112, 6123, 6129, 6156, 6346, 6389,
                    6502, 6510, 6543, 6547, 6565, 6566, 6567, 6580, 6646, 6666, 6667, 6668, 6669, 6689, 6692, 6699,
                    6779, 6788, 6789, 6792, 6839, 6881, 6901, 6969, 7000, 7001, 7002, 7004, 7007, 7019, 7025, 7070,
                    7100, 7103, 7106, 7200, 7201, 7402, 7435, 7443, 7496, 7512, 7625, 7627, 7676, 7741, 7777, 7778,
                    7800, 7911, 7920, 7921, 7937, 7938, 7999, 8000, 8001, 8002, 8007, 8008, 8009, 8010, 8011, 8021,
                    8022, 8031, 8042, 8045, 8080, 8081, 8082, 8083, 8084, 8085, 8086, 8087, 8088, 8089, 8090, 8093,
                    8099, 8100, 8180, 8181, 8192, 8193, 8194, 8200, 8222, 8254, 8290, 8291, 8292, 8300, 8333, 8383,
                    8400, 8402, 8443, 8500, 8600, 8649, 8651, 8652, 8654, 8701, 8800, 8873, 8888, 8899, 8994, 9000,
                    9001, 9002, 9003, 9009, 9010, 9011, 9040, 9050, 9071, 9080, 9081, 9090, 9091, 9099, 9100, 9101,
                    9102, 9103, 9110, 9111, 9200, 9207, 9220, 9290, 9415, 9418, 9485, 9500, 9502, 9503, 9535, 9575,
                    9593, 9594, 9595, 9618, 9666, 9876, 9877, 9878, 9898, 9900, 9917, 9929, 9943, 9944, 9968, 9998,
                    9999, 10000, 10001, 10002, 10003, 10004, 10009, 10010, 10012, 10024, 10025, 10082, 10180, 10215,
                    10243, 10566, 10616, 10617, 10621, 10626, 10628, 10629, 10778, 11110, 11111, 11967, 12000, 12174,
                    12265, 12345, 13456, 13722, 13782, 13783, 14000, 14238, 14441, 14442, 15000, 15002, 15003, 15004,
                    15660, 15742, 16000, 16001, 16012, 16016, 16018, 16080, 16113, 16992, 16993, 17877, 17988, 18040,
                    18101, 18988, 19101, 19283, 19315, 19350, 19780, 19801, 19842, 20000, 20005, 20031, 20221, 20222,
                    20828, 21571, 22939, 23502, 24444, 24800, 25734, 25735, 26214, 27000, 27352, 27353, 27355, 27356,
                    27715, 28201, 30000, 30718, 30951, 31038, 31337, 32768, 32769, 32770, 32771, 32772, 32773, 32774,
                    32775, 32776, 32777, 32778, 32779, 32780, 32781, 32782, 32783, 32784, 32785, 33354, 33899, 34571,
                    34572, 34573, 35500, 38292, 40193, 40911, 41511, 42510, 44176, 44442, 44443, 44501, 45100, 48080,
                    49152, 49153, 49154, 49155, 49156, 49157, 49158, 49159, 49160, 49161, 49163, 49165, 49167, 49175,
                    49176, 49400, 49999, 50000, 50001, 50002, 50003, 50006, 50300, 50389, 50500, 50636, 50800, 51103,
                    51493, 52673, 52822, 52848, 52869, 54045, 54328, 55055, 55056, 55555, 55600, 56737, 56738, 57294,
                    57797, 58080, 60020, 60443, 61532, 61900, 62078, 63331, 64623, 64680, 65000, 65129, 65389]

open_ports = dict()

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

Создадим функцию portscanner(port, target). На вход она получает порт для сканирования и имя домена. Затем создадим объект сокета TCP, установим timeout, выполним соединение с доменом по указанному порту. После чего, если соединение успешное, закрываем порт. С помощью функции socket.getservbyport определяем сервис, который работает на данном порту, если он доступен. И добавляем полученные значения в словарь. Если срабатывает исключение по истечении времени соединения, закрываем сокет. Если же ошибка при определении сервиса, то добавляем имя сервиса как unassigned.

Python:
def portscanner(port, target):
    try:
        s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        s.settimeout(0.5)
        s.connect((target, port))
        s.close()

        serv = socket.getservbyport(port, 'tcp')

        open_ports.update({port: serv})
    except socket.timeout:
        s.close()
    except OSError:
        serv = 'unassigned'
        open_ports.update({port: serv})

Теперь нам нужна функция, которая будет запускать потоки. Подробно описывать ее не имеет смысла, так как она была уже описана ранее. Назову ее thread_func(target). На вход она получает имя домена. Далее в цикле перебирает порты и запускает потоки, в которых выполняется соединение с хостом по указанному порту.

Python:
def thread_func(target):
    threads = []

    for item in frequently_ports:
        t = threading.Thread(target=portscanner, kwargs={'port': item, 'target': target})
        t.daemon = True
        t.start()
        threads.append(t)

    for num, thread in enumerate(threads):
        print(Fore.GREEN + f'\r   - Обработано: {num + 1}', end='')
        thread.join()

И еще одна функция, в которой будет запускаться сканирование. Назову ее start_func(domain). На входе она принимает имя домена, после чего очищает словарь с портами и запускает функцию сканирования портов. Когда все завершится словарь возвращается в функцию, из которой вызывалось сканирование портов.

Python:
# pip install colorama

import socket
import threading

from colorama import Fore
from colorama import init

init()

frequently_ports = [1, 3, 4, 6, 7, 9, 13, 17, 19, 20, 21, 22, 23, 24, 25, 26, 30, 32, 33, 37, 42, 43, 49, 53, 70, 79,
                    80, 81, 82, 83, 84, 85, 88, 89, 90, 99, 100, 106, 109, 110, 111, 113, 119, 125, 135, 139, 143, 144,
                    146, 161, 163, 179, 199, 211, 212, 222, 254, 255, 256, 259, 264, 280, 301, 306, 311, 340, 366, 389,
                    406, 407, 416, 417, 425, 427, 443, 444, 445, 458, 464, 465, 481, 497, 500, 512, 513, 514, 515, 524,
                    541, 543, 544, 545, 548, 554, 555, 563, 587, 593, 616, 617, 625, 631, 636, 646, 648, 666, 667, 668,
                    683, 687, 691, 700, 705, 711, 714, 720, 722, 726, 749, 765, 777, 783, 787, 800, 801, 808, 843, 873,
                    880, 888, 898, 900, 901, 902, 903, 911, 912, 981, 987, 990, 992, 993, 995, 999, 1000, 1001, 1002,
                    1007, 1009, 1010, 1011, 1021, 1022, 1023, 1024, 1025, 1026, 1027, 1028, 1029, 1030, 1031, 1032,
                    1033, 1034, 1035, 1036, 1037, 1038, 1039, 1040, 1041, 1042, 1043, 1044, 1045, 1046, 1047, 1048,
                    1049, 1050, 1051, 1052, 1053, 1054, 1055, 1056, 1057, 1058, 1059, 1060, 1061, 1062, 1063, 1064,
                    1065, 1066, 1067, 1068, 1069, 1070, 1071, 1072, 1073, 1074, 1075, 1076, 1077, 1078, 1079, 1080,
                    1081, 1082, 1083, 1084, 1085, 1086, 1087, 1088, 1089, 1090, 1091, 1092, 1093, 1094, 1095, 1096,
                    1097, 1098, 1099, 1100, 1102, 1104, 1105, 1106, 1107, 1108, 1110, 1111, 1112, 1113, 1114, 1117,
                    1119, 1121, 1122, 1123, 1124, 1126, 1130, 1131, 1132, 1137, 1138, 1141, 1145, 1147, 1148, 1149,
                    1151, 1152, 1154, 1163, 1164, 1165, 1166, 1169, 1174, 1175, 1183, 1185, 1186, 1187, 1192, 1198,
                    1199, 1201, 1213, 1216, 1217, 1218, 1233, 1234, 1236, 1244, 1247, 1248, 1259, 1271, 1272, 1277,
                    1287, 1296, 1300, 1301, 1309, 1310, 1311, 1322, 1328, 1334, 1352, 1417, 1433, 1434, 1443, 1455,
                    1461, 1494, 1500, 1501, 1503, 1521, 1524, 1533, 1556, 1580, 1583, 1594, 1600, 1641, 1658, 1666,
                    1687, 1688, 1700, 1717, 1718, 1719, 1720, 1721, 1723, 1755, 1761, 1782, 1783, 1801, 1805, 1812,
                    1839, 1840, 1862, 1863, 1864, 1875, 1900, 1914, 1935, 1947, 1971, 1972, 1974, 1984, 1998, 1999,
                    2000, 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010, 2013, 2020, 2021, 2022, 2030,
                    2033, 2034, 2035, 2038, 2040, 2041, 2042, 2043, 2045, 2046, 2047, 2048, 2049, 2065, 2068, 2099,
                    2100, 2103, 2105, 2106, 2107, 2111, 2119, 2121, 2126, 2135, 2144, 2160, 2161, 2170, 2179, 2190,
                    2191, 2196, 2200, 2222, 2251, 2260, 2288, 2301, 2323, 2366, 2381, 2382, 2383, 2393, 2394, 2399,
                    2401, 2492, 2500, 2522, 2525, 2557, 2601, 2602, 2604, 2605, 2607, 2608, 2638, 2701, 2702, 2710,
                    2717, 2718, 2725, 2800, 2809, 2811, 2869, 2875, 2909, 2910, 2920, 2967, 2968, 2998, 3000, 3001,
                    3003, 3005, 3006, 3007, 3011, 3013, 3017, 3030, 3031, 3052, 3071, 3077, 3128, 3168, 3211, 3221,
                    3260, 3261, 3268, 3269, 3283, 3300, 3301, 3306, 3322, 3323, 3324, 3325, 3333, 3351, 3367, 3369,
                    3370, 3371, 3372, 3389, 3390, 3404, 3476, 3493, 3517, 3527, 3546, 3551, 3580, 3659, 3689, 3690,
                    3703, 3737, 3766, 3784, 3800, 3801, 3809, 3814, 3826, 3827, 3828, 3851, 3869, 3871, 3878, 3880,
                    3889, 3905, 3914, 3918, 3920, 3945, 3971, 3986, 3995, 3998, 4000, 4001, 4002, 4003, 4004, 4005,
                    4006, 4045, 4111, 4125, 4126, 4129, 4224, 4242, 4279, 4321, 4343, 4443, 4444, 4445, 4446, 4449,
                    4550, 4567, 4662, 4848, 4899, 4900, 4998, 5000, 5001, 5002, 5003, 5004, 5009, 5030, 5033, 5050,
                    5051, 5054, 5060, 5061, 5080, 5087, 5100, 5101, 5102, 5120, 5190, 5200, 5214, 5221, 5222, 5225,
                    5226, 5269, 5280, 5298, 5357, 5405, 5414, 5431, 5432, 5440, 5500, 5510, 5544, 5550, 5555, 5560,
                    5566, 5631, 5633, 5666, 5678, 5679, 5718, 5730, 5800, 5801, 5802, 5810, 5811, 5815, 5822, 5825,
                    5850, 5859, 5862, 5877, 5900, 5901, 5902, 5903, 5904, 5906, 5907, 5910, 5911, 5915, 5922, 5925,
                    5950, 5952, 5959, 5960, 5961, 5962, 5963, 5987, 5988, 5989, 5998, 5999, 6000, 6001, 6002, 6003,
                    6004, 6005, 6006, 6007, 6009, 6025, 6059, 6100, 6101, 6106, 6112, 6123, 6129, 6156, 6346, 6389,
                    6502, 6510, 6543, 6547, 6565, 6566, 6567, 6580, 6646, 6666, 6667, 6668, 6669, 6689, 6692, 6699,
                    6779, 6788, 6789, 6792, 6839, 6881, 6901, 6969, 7000, 7001, 7002, 7004, 7007, 7019, 7025, 7070,
                    7100, 7103, 7106, 7200, 7201, 7402, 7435, 7443, 7496, 7512, 7625, 7627, 7676, 7741, 7777, 7778,
                    7800, 7911, 7920, 7921, 7937, 7938, 7999, 8000, 8001, 8002, 8007, 8008, 8009, 8010, 8011, 8021,
                    8022, 8031, 8042, 8045, 8080, 8081, 8082, 8083, 8084, 8085, 8086, 8087, 8088, 8089, 8090, 8093,
                    8099, 8100, 8180, 8181, 8192, 8193, 8194, 8200, 8222, 8254, 8290, 8291, 8292, 8300, 8333, 8383,
                    8400, 8402, 8443, 8500, 8600, 8649, 8651, 8652, 8654, 8701, 8800, 8873, 8888, 8899, 8994, 9000,
                    9001, 9002, 9003, 9009, 9010, 9011, 9040, 9050, 9071, 9080, 9081, 9090, 9091, 9099, 9100, 9101,
                    9102, 9103, 9110, 9111, 9200, 9207, 9220, 9290, 9415, 9418, 9485, 9500, 9502, 9503, 9535, 9575,
                    9593, 9594, 9595, 9618, 9666, 9876, 9877, 9878, 9898, 9900, 9917, 9929, 9943, 9944, 9968, 9998,
                    9999, 10000, 10001, 10002, 10003, 10004, 10009, 10010, 10012, 10024, 10025, 10082, 10180, 10215,
                    10243, 10566, 10616, 10617, 10621, 10626, 10628, 10629, 10778, 11110, 11111, 11967, 12000, 12174,
                    12265, 12345, 13456, 13722, 13782, 13783, 14000, 14238, 14441, 14442, 15000, 15002, 15003, 15004,
                    15660, 15742, 16000, 16001, 16012, 16016, 16018, 16080, 16113, 16992, 16993, 17877, 17988, 18040,
                    18101, 18988, 19101, 19283, 19315, 19350, 19780, 19801, 19842, 20000, 20005, 20031, 20221, 20222,
                    20828, 21571, 22939, 23502, 24444, 24800, 25734, 25735, 26214, 27000, 27352, 27353, 27355, 27356,
                    27715, 28201, 30000, 30718, 30951, 31038, 31337, 32768, 32769, 32770, 32771, 32772, 32773, 32774,
                    32775, 32776, 32777, 32778, 32779, 32780, 32781, 32782, 32783, 32784, 32785, 33354, 33899, 34571,
                    34572, 34573, 35500, 38292, 40193, 40911, 41511, 42510, 44176, 44442, 44443, 44501, 45100, 48080,
                    49152, 49153, 49154, 49155, 49156, 49157, 49158, 49159, 49160, 49161, 49163, 49165, 49167, 49175,
                    49176, 49400, 49999, 50000, 50001, 50002, 50003, 50006, 50300, 50389, 50500, 50636, 50800, 51103,
                    51493, 52673, 52822, 52848, 52869, 54045, 54328, 55055, 55056, 55555, 55600, 56737, 56738, 57294,
                    57797, 58080, 60020, 60443, 61532, 61900, 62078, 63331, 64623, 64680, 65000, 65129, 65389]

open_ports = dict()


def portscanner(port, target):
    try:
        s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        s.settimeout(0.5)
        s.connect((target, port))
        s.close()

        serv = socket.getservbyport(port, 'tcp')

        open_ports.update({port: serv})
    except socket.timeout:
        s.close()
    except OSError:
        serv = 'unassigned'
        open_ports.update({port: serv})


def thread_func(target):
    threads = []

    for item in frequently_ports:
        t = threading.Thread(target=portscanner, kwargs={'port': item, 'target': target})
        t.daemon = True
        t.start()
        threads.append(t)

    for num, thread in enumerate(threads):
        print(Fore.GREEN + f'\r   - Обработано: {num + 1}', end='')
        thread.join()


def start_func(domain):
    open_ports.clear()
    thread_func(domain)
    return open_ports


Модуль запуска и обработки полученных значений. Основной модуль

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

Python:
import socket
from pathlib import Path

from colorama import Fore
from colorama import init

from admin_panel import start_adm_search
from cloudflare_detect import cloudf_detect
from crimeflare_search import crimf_search
from hystory_domain import hystory_search
from report_template import *
from scan_port import start_func
from ssl_check import sert_domain_info
from subdomain_cert import subdomain_in_sert
from whois_info import whois_domain

init()

Создадим функцию search_cloudflare(domain). В ней мы будем запускать функцию проверки адреса из модуля. А также обрабатывать полученные значения. Запускаем функцию проверки адреса. Проверяем, не является ли полученный ответ False. Если обнаружен адрес в диапазоне Cloudflare выводим об этом информацию на экран, запускаем функцию получения данных из базы CrimeFlare. После чего возвращаем полученные значения.

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

Создадим функцию save_data(clo_dat, wh_dat, cer_ssl, sub_cert, his_dat, adm_dat, ports_dict, domain). Как видите, она получает значения из всех модулей, а также имя проверяемого домена. Создаем путь к папке, в которой будем сохранять все отчеты. Затем создаем саму папку, если она еще не существует. Также создаем папку с именем домена, в которую будем сохранять отчет. Получаем ip-адрес домена.

Python:
    path_dom = Path(Path.cwd() / 'domains')
    if not path_dom.exists():
        path_dom.mkdir()
    dir_path = Path(path_dom / f'{domain}')
    if not dir_path.exists():
        dir_path.mkdir()
    try:
        ip = socket.gethostbyname(domain)
    except socket.gaierror:
        return

Тут нужно добавить, что заранее я создал модуль, в котором храниться html-шаблон. Не весь, а только его части, которые не требуют изменений. Сам шаблон я создал в бесплатном редакторе шаблонов для писем . Он позволяет даже без регистрации скопировать получившийся код. Модуль, в котором я сохранил части шаблона, я назвал report_template.py. Его содержание я здесь приводить не буду, а прикреплю к статье.

Теперь двигаемся дальше. Создаем html-файл, в который сохраняем заголовки и полученные параметры имени домена и ip-адреса.

Python:
    with open(dir_path / f'{domain}_info.html', 'w', encoding='utf-8') as file:
        file.write(f'{header1}<title>Отчет поиска данных по домену: {domain}</title>{header2}'
                   f'<strong>Результат поиска данных по домену: {domain}&nbsp;</strong>{header3}<br>'
                   f'<br>IP-адрес домена: {ip}<br><br>')

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

Python:
    if clo_dat[0]:
        with open(dir_path / f'{domain}_info.html', 'a', encoding='utf-8') as clo:
            clo.write(f'<strong>Информация об адресе за Cloudflare</strong></span><br><br>IP-адрес: {ip}&nbsp;')
            if clo_dat[1] != '':
                clo.write(f'<br>Возможный адрес по CrimeFlare: {clo_dat[1]}&nbsp;<br><br><span style="color:#006699;">')
    else:
        with open(dir_path / f'{domain}_info.html', 'a', encoding='utf-8') as clo:
            clo.write(f'<strong>Адресов за Cloudflare не обнаружено</strong></span><br><br>IP-адрес: {ip}&nbsp;'
                      f'<br><br><span style="color:#006699;">')

    if len(wh_dat) > 0:
        with open(dir_path / f'{domain}_info.html', 'a', encoding='utf-8') as wh:
            wh.write(header4)
            for key_wh in wh_dat:
                wh.write(f'  {key_wh}: {wh_dat[key_wh]}<br>')

Python:
def save_data(clo_dat, wh_dat, cer_ssl, sub_cert, his_dat, adm_dat, ports_dict, domain):
    path_dom = Path(Path.cwd() / 'domains')
    if not path_dom.exists():
        path_dom.mkdir()
    dir_path = Path(path_dom / f'{domain}')
    if not dir_path.exists():
        dir_path.mkdir()
    try:
        ip = socket.gethostbyname(domain)
    except socket.gaierror:
        return
    with open(dir_path / f'{domain}_info.html', 'w', encoding='utf-8') as file:
        file.write(f'{header1}<title>Отчет поиска данных по домену: {domain}</title>{header2}'
                   f'<strong>Результат поиска данных по домену: {domain}&nbsp;</strong>{header3}<br>'
                   f'<br>IP-адрес домена: {ip}<br><br>')
    if clo_dat[0]:
        with open(dir_path / f'{domain}_info.html', 'a', encoding='utf-8') as clo:
            clo.write(f'<strong>Информация об адресе за Cloudflare</strong></span><br><br>IP-адрес: {ip}&nbsp;')
            if clo_dat[1] != '':
                clo.write(f'<br>Возможный адрес по CrimeFlare: {clo_dat[1]}&nbsp;<br><br><span style="color:#006699;">')
    else:
        with open(dir_path / f'{domain}_info.html', 'a', encoding='utf-8') as clo:
            clo.write(f'<strong>Адресов за Cloudflare не обнаружено</strong></span><br><br>IP-адрес: {ip}&nbsp;'
                      f'<br><br><span style="color:#006699;">')

    if len(wh_dat) > 0:
        with open(dir_path / f'{domain}_info.html', 'a', encoding='utf-8') as wh:
            wh.write(header4)
            for key_wh in wh_dat:
                wh.write(f'  {key_wh}: {wh_dat[key_wh]}<br>')

    if len(cer_ssl) > 0:
        with open(dir_path / f'{domain}_info.html', 'a', encoding='utf-8') as c_ssl:
            c_ssl.write(header5)
            for cert_d in cer_ssl:
                c_ssl.write(f'   {cert_d}: {cer_ssl[cert_d]}<br>')

    if len(sub_cert[0]) > 0:
        with open(dir_path / f'{domain}_info.html', 'a', encoding='utf-8') as s_cert:
            s_cert.write(header6)
            for sub_d in sub_cert[0]:
                s_cert.write(f'  Субдомен: {sub_d}                      IP-адрес: {socket.gethostbyname(sub_d)}<br>')

    if len(sub_cert[1]) > 0:
        with open(dir_path / f'{domain}_info.html', 'a', encoding='utf-8') as cc_cert:
            cc_cert.write(header7)
            for sub_d in sub_cert[0]:
                cc_cert.write(f'  Субдомен: {sub_d}                      IP-адрес: {socket.gethostbyname(sub_d)}<br>')

    if len(his_dat) > 0:
        with open(dir_path / f'{domain}_info.html', 'a', encoding='utf-8') as h_dat:
            h_dat.write(header8)
            for adr in his_dat:
                h_dat.write(f'  Адрес: {adr}<br>')

    if len(adm_dat) > 0:
        with open(dir_path / f'{domain}_info.html', 'a', encoding='utf-8') as a_dat:
            a_dat.write(header9)
            for adm in adm_dat:
                a_dat.write(f'  Ссылка: <a href="{adm}" target="_blank">{adm}</a><br>')

    if len(ports_dict) > 0:
        with open(dir_path / f'{domain}_info.html', 'a', encoding='utf-8') as p_dat:
            p_dat.write(f'{header9_1}')
            for pdm in ports_dict:
                p_dat.write(f'  Протокол: {pdm}  Порт: {ports_dict[pdm]}<br>')

    with open(dir_path / f'{domain}_info.html', 'a', encoding='utf-8') as footer:
        footer.write(header10)

И здесь же создаем функцию main(). Именно из нее мы будем производить все манипуляции по запуску функций. Для начала запросим у пользователя имя домена. Обрежем его до нужных нам значений, если он ввел что-то вроде: . Затем предоставим пользователю на выбор, что он желает сделать. Произвести полное сканирование или только по отдельным параметрам.

Python:
    in_input = input('\nВведите домен для поиска данных: ')
    domain = f'{in_input.split("/")[-1].split(".")[-2]}.{in_input.split("/")[-1].split(".")[-1]}'

    user_change = input('\n[~] Для поиска полной информации введите: all\n'
                        '[~] CrimeFlare: поиск по базе адресов за Cloudflare: clo\n'
                        '[~] Получение информации Whois о домене: who\n'
                        '[~] Получение информации из SSL-сертификата домена: ssl\n'
                        '[~] Получение информации о субдоменах из сертификата: sub\n'
                        '[~] Получение истории IP-адресов домена: his\n'
                        '[~] Поиск административных панелей домена: adm\n'
                        '[~] Сканирование портов домена: port\n'
                        '    >>> ')

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

Python:
ln_string = len(f'ПОЛУЧЕНИЕ ДАННЫХ О ДОМЕНЕ: {domain.upper()}') + 2
    header_mess = Fore.BLUE + f'\n{"*" * ln_string}\nПОЛУЧЕНИЕ ДАННЫХ О ДОМЕНЕ: {domain.upper()} *\n{"*" * ln_string}'

Затем проверяем ввод пользователя и запускаем нужные функции.

Python:
def main():
    in_input = input('\nВведите домен для поиска данных: ')
    domain = f'{in_input.split("/")[-1].split(".")[-2]}.{in_input.split("/")[-1].split(".")[-1]}'

    user_change = input('\n[~] Для поиска полной информации введите: all\n'
                        '[~] CrimeFlare: поиск по базе адресов за Cloudflare: clo\n'
                        '[~] Получение информации Whois о домене: who\n'
                        '[~] Получение информации из SSL-сертификата домена: ssl\n'
                        '[~] Получение информации о субдоменах из сертификата: sub\n'
                        '[~] Получение истории IP-адресов домена: his\n'
                        '[~] Поиск административных панелей домена: adm\n'
                        '[~] Сканирование портов домена: port\n'
                        '    >>> ')

    ln_string = len(f'ПОЛУЧЕНИЕ ДАННЫХ О ДОМЕНЕ: {domain.upper()}') + 2
    header_mess = Fore.BLUE + f'\n{"*" * ln_string}\nПОЛУЧЕНИЕ ДАННЫХ О ДОМЕНЕ: {domain.upper()} *\n{"*" * ln_string}'
    if user_change == 'all':
        print(header_mess)
        clo_dat = search_cloudflare(domain)
        wh_dat = whois_info_search(domain)
        cer_ssl = ssl_cert_info(domain)
        sub_cert = cert_subdomain_search(domain)
        his_dat = history_domain_search(domain)
        adm_dat = admin_panel_search(domain)
        ports_dict = port_scan_domain(domain)
        save_data(clo_dat, wh_dat, cer_ssl, sub_cert, his_dat, adm_dat, ports_dict, domain)
    elif user_change == "clo":
        print(header_mess)
        search_cloudflare(domain)
    elif user_change == "who":
        print(header_mess)
        whois_info_search(domain)
    elif user_change == "ssl":
        print(header_mess)
        ssl_cert_info(domain)
    elif user_change == "sub":
        print(header_mess)
        cert_subdomain_search(domain)
    elif user_change == "his":
        print(header_mess)
        history_domain_search(domain)
    elif user_change == "adm":
        print(header_mess)
        admin_panel_search(domain)
    elif user_change == "port":
        print(header_mess)
        port_scan_domain(domain)

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

Python:
# pip install colorama

import socket
from pathlib import Path

from colorama import Fore
from colorama import init

from admin_panel import start_adm_search
from cloudflare_detect import cloudf_detect
from crimeflare_search import crimf_search
from hystory_domain import hystory_search
from report_template import *
from scan_port import start_func
from ssl_check import sert_domain_info
from subdomain_cert import subdomain_in_sert
from whois_info import whois_domain

init()


def search_cloudflare(domain):
    clo_detect = ''
    info_crime = ''

    try:
        print(Fore.YELLOW + f'\n- IP-адрес домена {domain}: {socket.gethostbyname(domain)}')
        clo_detect = cloudf_detect(domain)

        if clo_detect:
            print(Fore.RED + '[!] ОБНАРУЖЕН АДРЕС CLOUDFLARE')
            print(Fore.YELLOW + '\n- Поиск информации в базе CrimeFlare')
            info_crime = crimf_search(domain)
            if info_crime != False:
                print(Fore.GREEN + f'НАЙДЕН IP-АДРЕС: {info_crime}')
            else:
                print(Fore.RED + '[-] Информации в базе CrimeFlare не найдено')
        else:
            print(Fore.GREEN + '[+] Адресов Cloudflare не обнаружено')
        return clo_detect, info_crime
    except socket.gaierror:
        return clo_detect, info_crime


def whois_info_search(domain):
    info_whois = whois_domain(domain)
    if len(info_whois) > 0:
        print(Fore.GREEN + f'\nИНФОРМАЦИЯ WHOIS\n{"*" * 16}')
        for key_wh in info_whois:
            print(Fore.RESET + f'{key_wh}: {info_whois[key_wh]}')
    else:
        print(Fore.RED + '[-] Информация WHOIS не получена')
    return info_whois


def ssl_cert_info(domain):
    print(Fore.YELLOW + '\n- Попытка получения данных SSL-сертификата')
    cert_data = sert_domain_info(domain)
    if len(cert_data) > 0:
        print(Fore.GREEN + f'\nПОЛУЧЕНЫ ДАННЫЕ SSL-СЕРТИФИКАТА\n{"*" * 31}')
        for cert_d in cert_data:
            print(Fore.RESET + f' - {cert_d}: {cert_data[cert_d]}')
    else:
        print(Fore.RED + '[-] Данные SSL-сертификата не получены\n    - Возможно работает на HTTP')
    return cert_data


def cert_subdomain_search(domain):
    clo_det = cloudf_detect(domain)
    print(Fore.YELLOW + '\n- Попытка получения сертификата для поиска субдоменов')
    cert_sub = subdomain_in_sert(domain)
    if len(cert_sub) > 0:
        print(f'\r ', end='')
        print(Fore.GREEN + f'\nНАЙДЕНЫ СУБДОМЕНЫ В СЕРТИФИКАТЕ\n{"*" * 31}')
        for sub_d in cert_sub:
            print(Fore.RESET + f' - Субдомен: {sub_d}  IP-адрес: {socket.gethostbyname(sub_d)}')
    else:
        print(Fore.RED + 'Сертификат не найден')

    clo_dict = dict()
    if clo_det:
        for addr in cert_sub:
            ip = socket.gethostbyname(addr)
            detect = cloudf_detect(ip)
            if detect == False:
                clo_dict.update({addr: socket.gethostbyname(addr)})
        if len(clo_dict) > 0:
            print(Fore.GREEN + f'\nСУБДОМЕНЫ ВНЕ CLOUDFLARE\n{"*" * 24}')
            for cd in clo_dict:
                print(Fore.RESET + f' - Субдомен: {cd}  IP-адрес: {clo_dict[cd]}')
    return cert_sub, clo_dict


def history_domain_search(domain):
    print(Fore.YELLOW + '\n- Попытка получения IP из истории домена')
    history = hystory_search(domain)
    print('\r ', end='')
    if len(history) > 0:
        print(Fore.GREEN + f'\nIP-АДРЕСА ИСТОРИИ ДОМЕНА\n{"*" * 24}')
        for adr in history:
            print(Fore.RESET + f' - Адрес: {adr}')
    return history


def admin_panel_search(domain):
    print(Fore.YELLOW + '\n- Попытка получения ссылок на админ-панели')
    adm_panel = start_adm_search(domain)
    if len(adm_panel) > 0:
        print(Fore.GREEN + f'\n\nНАЙДЕНЫ ВОЗМОЖНЫЕ ССЫЛКИ НА АДМИН-ПАНЕЛИ\n{"*"*40}')
        for adm in adm_panel:
            print(Fore.RESET + f' - Ссылка: {adm}')
    else:
        print(Fore.RED + '\n\n[-] Ссылок на админ-панели не найдено')
    return adm_panel


def port_scan_domain(domain):
    print(Fore.YELLOW + '\n- Попытка сканирования портов на домене')
    ports = start_func(domain)
    if len(ports) > 0:
        print(Fore.GREEN + f'\n\nНАЙДЕНЫ ОТКРЫТЫЕ ПОРТЫ\n{"*" * 21}')
        for port in ports:
            print(Fore.RESET + f' - Порт: {port}  Протокол: {ports[port]}')
    else:
        print(Fore.RED + '\n\n[-] Открытых портов не найдено')
    return ports


def save_data(clo_dat, wh_dat, cer_ssl, sub_cert, his_dat, adm_dat, ports_dict, domain):
    path_dom = Path(Path.cwd() / 'domains')
    if not path_dom.exists():
        path_dom.mkdir()
    dir_path = Path(path_dom / f'{domain}')
    if not dir_path.exists():
        dir_path.mkdir()
    try:
        ip = socket.gethostbyname(domain)
    except socket.gaierror:
        return
    with open(dir_path / f'{domain}_info.html', 'w', encoding='utf-8') as file:
        file.write(f'{header1}<title>Отчет поиска данных по домену: {domain}</title>{header2}'
                   f'<strong>Результат поиска данных по домену: {domain}&nbsp;</strong>{header3}<br>'
                   f'<br>IP-адрес домена: {ip}<br><br>')
    if clo_dat[0]:
        with open(dir_path / f'{domain}_info.html', 'a', encoding='utf-8') as clo:
            clo.write(f'<strong>Информация об адресе за Cloudflare</strong></span><br><br>IP-адрес: {ip}&nbsp;')
            if clo_dat[1] != '':
                clo.write(f'<br>Возможный адрес по CrimeFlare: {clo_dat[1]}&nbsp;<br><br><span style="color:#006699;">')
    else:
        with open(dir_path / f'{domain}_info.html', 'a', encoding='utf-8') as clo:
            clo.write(f'<strong>Адресов за Cloudflare не обнаружено</strong></span><br><br>IP-адрес: {ip}&nbsp;'
                      f'<br><br><span style="color:#006699;">')

    if len(wh_dat) > 0:
        with open(dir_path / f'{domain}_info.html', 'a', encoding='utf-8') as wh:
            wh.write(header4)
            for key_wh in wh_dat:
                wh.write(f'  {key_wh}: {wh_dat[key_wh]}<br>')

    if len(cer_ssl) > 0:
        with open(dir_path / f'{domain}_info.html', 'a', encoding='utf-8') as c_ssl:
            c_ssl.write(header5)
            for cert_d in cer_ssl:
                c_ssl.write(f'   {cert_d}: {cer_ssl[cert_d]}<br>')

    if len(sub_cert[0]) > 0:
        with open(dir_path / f'{domain}_info.html', 'a', encoding='utf-8') as s_cert:
            s_cert.write(header6)
            for sub_d in sub_cert[0]:
                s_cert.write(f'  Субдомен: {sub_d}                      IP-адрес: {socket.gethostbyname(sub_d)}<br>')

    if len(sub_cert[1]) > 0:
        with open(dir_path / f'{domain}_info.html', 'a', encoding='utf-8') as cc_cert:
            cc_cert.write(header7)
            for sub_d in sub_cert[0]:
                cc_cert.write(f'  Субдомен: {sub_d}                      IP-адрес: {socket.gethostbyname(sub_d)}<br>')

    if len(his_dat) > 0:
        with open(dir_path / f'{domain}_info.html', 'a', encoding='utf-8') as h_dat:
            h_dat.write(header8)
            for adr in his_dat:
                h_dat.write(f'  Адрес: {adr}<br>')

    if len(adm_dat) > 0:
        with open(dir_path / f'{domain}_info.html', 'a', encoding='utf-8') as a_dat:
            a_dat.write(header9)
            for adm in adm_dat:
                a_dat.write(f'  Ссылка: <a href="{adm}" target="_blank">{adm}</a><br>')

    if len(ports_dict) > 0:
        with open(dir_path / f'{domain}_info.html', 'a', encoding='utf-8') as p_dat:
            p_dat.write(f'{header9_1}')
            for pdm in ports_dict:
                p_dat.write(f'  Протокол: {pdm}  Порт: {ports_dict[pdm]}<br>')

    with open(dir_path / f'{domain}_info.html', 'a', encoding='utf-8') as footer:
        footer.write(header10)


def main():
    in_input = input('\nВведите домен для поиска данных: ')
    domain = f'{in_input.split("/")[-1].split(".")[-2]}.{in_input.split("/")[-1].split(".")[-1]}'

    user_change = input('\n[~] Для поиска полной информации введите: all\n'
                        '[~] CrimeFlare: поиск по базе адресов за Cloudflare: clo\n'
                        '[~] Получение информации Whois о домене: who\n'
                        '[~] Получение информации из SSL-сертификата домена: ssl\n'
                        '[~] Получение информации о субдоменах из сертификата: sub\n'
                        '[~] Получение истории IP-адресов домена: his\n'
                        '[~] Поиск административных панелей домена: adm\n'
                        '[~] Сканирование портов домена: port\n'
                        '    >>> ')

    ln_string = len(f'ПОЛУЧЕНИЕ ДАННЫХ О ДОМЕНЕ: {domain.upper()}') + 2
    header_mess = Fore.BLUE + f'\n{"*" * ln_string}\nПОЛУЧЕНИЕ ДАННЫХ О ДОМЕНЕ: {domain.upper()} *\n{"*" * ln_string}'
    if user_change == 'all':
        print(header_mess)
        clo_dat = search_cloudflare(domain)
        wh_dat = whois_info_search(domain)
        cer_ssl = ssl_cert_info(domain)
        sub_cert = cert_subdomain_search(domain)
        his_dat = history_domain_search(domain)
        adm_dat = admin_panel_search(domain)
        ports_dict = port_scan_domain(domain)
        save_data(clo_dat, wh_dat, cer_ssl, sub_cert, his_dat, adm_dat, ports_dict, domain)
    elif user_change == "clo":
        print(header_mess)
        search_cloudflare(domain)
    elif user_change == "who":
        print(header_mess)
        whois_info_search(domain)
    elif user_change == "ssl":
        print(header_mess)
        ssl_cert_info(domain)
    elif user_change == "sub":
        print(header_mess)
        cert_subdomain_search(domain)
    elif user_change == "his":
        print(header_mess)
        history_domain_search(domain)
    elif user_change == "adm":
        print(header_mess)
        admin_panel_search(domain)
    elif user_change == "port":
        print(header_mess)
        port_scan_domain(domain)

Что ж. Вот в принципе и все. Конечно же, данный сканер ищет и получает далеко не все параметры и не со всех сайтов. Но это только попытка понять, как работает данная технология. И иногда попадаются довольно интересные результаты. Более того, пока я делал данный модуль я узнал, что сертификат у сайта stackoverflow.com выдан вообще на странный *.stackexchange.com, у whatsapp.com, на whatsapp.net. А сертификат github.io все равно выдан на github.com.

screenshot1.png

screenshot1.png

Все модули, которые описаны в статье, прикреплены во вложениях, вместе с необходимыми шаблонами.

Спасибо за внимание. Надеюсь, что данная информация будет вам полезна
 

Вложения

  • search.zip
    13,7 КБ · Просмотры: 156
Последнее редактирование:
12.12.2021
22
4
BIT
47
Очень интересно, стараюсь воспроизводить код перепечатывая, изучая как работают различные библиотеки, методы и функции. Спасибо
 
Последнее редактирование:
  • Нравится
Реакции: Johan Van

admirus

New member
04.12.2022
2
0
BIT
0
Ребята подскажите а как этот скрипт запустить в проекте.
Вроде ошибок нет, но и ничего не запускается. Через PyCharm
 

Johan Van

Green Team
13.06.2020
363
691
BIT
390
Ребята подскажите а как этот скрипт запустить в проекте.
Вроде ошибок нет, но и ничего не запускается. Через PyCharm

Модуль для запуска: start_search.py
Могу сразу сказать, что некоторые модули не будут работать. Вернее информация получена не будет. Для примера, история домена. Я проверял, код страницы изменился. А значит нужно менять логику парсинга страницы.
 

admirus

New member
04.12.2022
2
0
BIT
0
Я запускаю у меня вылезает такая ошибка.
C:\Users\a\PycharmProjects\pythonProject1\venv\Scripts\python.exe C:\Users\a\PycharmProjects\pythonProject1\start_search.py
Traceback (most recent call last):
File "C:\Users\alexm\PycharmProjects\pythonProject1\start_search.py", line 15, in <module>
from ssl_check import sert_domain_info
File "C:\Users\a\PycharmProjects\pythonProject1\ssl_check.py", line 7, in <module>
from OpenSSL.SSL import Connection, Context, SSLv3_METHOD, TLSv1_2_METHOD
ImportError: cannot import name 'SSLv3_METHOD' from 'OpenSSL.SSL' (C:\Users\a\PycharmProjects\pythonProject1\venv\lib\site-packages\OpenSSL\SSL.py)
 

Johan Van

Green Team
13.06.2020
363
691
BIT
390
Я запускаю у меня вылезает такая ошибка.
C:\Users\a\PycharmProjects\pythonProject1\venv\Scripts\python.exe C:\Users\a\PycharmProjects\pythonProject1\start_search.py
Traceback (most recent call last):
File "C:\Users\alexm\PycharmProjects\pythonProject1\start_search.py", line 15, in <module>
from ssl_check import sert_domain_info
File "C:\Users\a\PycharmProjects\pythonProject1\ssl_check.py", line 7, in <module>
from OpenSSL.SSL import Connection, Context, SSLv3_METHOD, TLSv1_2_METHOD
ImportError: cannot import name 'SSLv3_METHOD' from 'OpenSSL.SSL' (C:\Users\a\PycharmProjects\pythonProject1\venv\lib\site-packages\OpenSSL\SSL.py)

С момента написания статьи библиотека обновилась и теперь нужно импортировать чуть по другому:

from OpenSSL.SSL import Connection, Context, SSLv23_METHOD, TLSv1_2_METHOD

Соответственно внести изменения вот в эту часть кода:

Python:
def check_ssl(domain):
    try:
        try:
            ssl_connection_setting = Context(SSLv23_METHOD)
        except ValueError:
            ssl_connection_setting = Context(TLSv1_2_METHOD)
 
Мы в соцсетях:

Обучение наступательной кибербезопасности в игровой форме. Начать игру!