Создаем простые OSINT и пентест инструменты на Python

от автора

Кобра: создаем OSINT инструмент на Python, часть 1

Итак, каждый программист желает все автоматизировать — и не только программист. В этой статье мы рассмотрим создание OSINT-инструмента на Python.

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

В этой статье мы создадим инструмент для:

  • Получения информации об IP адресе
  • Получения информации об номере телефона
  • Получение информации, есть ли IP в черных листах DNS
  • Парсер всех ссылок с сайта
  • Сканер портов
  • SYN-сканер портов
  • Сканер сессий Meterpreter для Windows 7/10
  • Virus Total API
  • Сканер SQL инъекций
  • Сканер XSS уязвимостей
  • Генератор фейкового User-Agent
  • Скрипт для изменения mac-адреса

Эта статья — первая часть, здесь мы рассмотрим минимум, а в следующей части доделаем код, улучшим его и сделаем полноценный CLI-инструмент, всю информацию по обновлению кода и уведомления о новых частях буду публиковать у себя в канале.

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

Для всего этого нам нужен будет Python >3.9, Linux и пакетный менеджер pip.

Весь исходный код доступен по ссылке. Репозиторий локализирован на английский язык, в директории /docs/ru есть markdown-версия статьи.

Создаем рабочее окружение

Никогда вам не советую устанавливать пакеты в саму систему. Для того чтобы работать над каждым проектом отдельно (дабы не возникало ошибок с пакетами и библиотеками) надо создавать виртуальные окружения.

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

python3 -m venv <название виртуального окружения> # Например, создание виртуального окружения venv python3 -m venv venv

После ввода данной команды у вас в текущем проекте будет создана новая директория — это и будет виртуальное окружение.

Но это еще не все — нам предстоит активировать его командой:

source venv/bin/activate # Или, если у вас fish: source venv/bin/activate.fish

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

Установка зависимостей

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

sudo pacman -S whois            # Arch sudo apt install whois          # Debian/Ubuntu sudo dnf install whois          # Fedora sudo epm install whois          # Alt

После активации нашего окружения, можно установить python-пакеты. Список нужных нам можно получить по ссылке в файле requirements.txt. Просто скачиваете его в проект и выполняете следующию команду:

pip3 install -r requirements.txt

Этим мы установили нужные нам зависимости. Теперь можно начать разработку!

Архитектура проекта

Давайте создадим архитектуру нашего инструмента:

  • Директория core — базовый функционал для самого инструмента
  • Директория modules — главная папка всего проекта, здесь мы будем хранить модули для работы.
    • anonymity — модуль анонимности, безопасности. Небольшой, чаще всего вспомогательный. Есть пока два компонента — генерация фейкового юзер-агента (fakeuseragent.py) и изменение MAC-адреса устройства (machanger.py)
    • network_base — вспомогательно-информационный модуль. Содержит информацию о сети, IP и портах. Один из самых больших модулей. Содержит следующие файлы: адрес из гео-координат, домен из IP, IP из домена, небольшие программы для работы с IPv4, ваш публичный IP, параметры сети, скрипт для пинга адреса, какой сервис на порту и т.д.
    • osint — модуль, как не странно, для OSINT и поиска информации. Здесь уже содержится прокси-файл для взаимодействия с модулем network_base, а также скрипты для получения информации об ip и номере телефона.
    • scanners — модуль для сканеров, как понятно из названия. Здесь есть сканер IP на наличие в черных списках DNS, парсер ссылок с сайта, сканер портов, поиск SQL-Injection и XSS уяизвимостей.
  • Файл main.py — главный файл кода в корневом каталоге проекта.

Итак, в репозитории директория core является хранилищем трех файлов — это

  • highlight_schemes.py — цветовые схемы Pygments.
  • logger.py — файл с классом логгера, дебаггера объектов.
  • style.py — файл с цветами, для того чтобы делать вывод красивее.

К сожалению, эти файлы мы не будем затрагивать — это совершенно другая тема. Если вы хотите, следующая статья будет об создании продвинутых CLI-программ на Python. А пока продолжим.

В репозитории modules есть огромное количество файлов и каталогов, я разберу здесь большую часть.

Создание модулей

Займемся созданием модулей. Начнем с модуля анонимности

Генерация фейкового User-Agent

Для этого мы будем использовать библиотеку fake-useragent.

from fake_useragent import UserAgent  def generate_useragent() -> str:     fua = UserAgent()      return str(fua.random) 

Здесь всего лишь одна функция — генерации случайного юзер-агента.

Изменение MAC-адреса

Для этого нам не нужны будут дополнительные библиотеки. Но вам возможно нужно будет установить набор инструментов net-tools. Этот набор содержит в себе утилиту ifconfig, аналог ipconfig в Windows, который нам нужен будет для управления сетями.

import re import subprocess from random import choice  def ifconfig():     """Возвращаем вывод команды ifconfig"""     output = subprocess.check_output(["sudo", 'ifconfig'])      return str(str(output.decode('utf-8')).replace(r'\n', '\n'))  def change_mac(interface: str, new_mac_address: str) -> None:     """Функция для изменения MAC-адреса при помощи утилиты ifconfig      Аргументы:      + interface: str - название интерфейса (ex. wlan0, wlp1s0, wlp3s0, eth)      + new_mac_address - значение нового mac-адреса"""     print(f'[+] Выключение {interface}')     subprocess.call(["doas", "ifconfig", interface, "down"])     print(f'[+] Замена MAC-адреса {interface} на {new_mac_address}')     subprocess.call(["doas", 'ifconfig', interface, 'hw', 'ether', new_mac_address])     print(f'[+] Включение {interface}')     subprocess.call(["doas", "ifconfig", interface, "up"])  def get_random_mac_address() -> str:     """Функция генерации и получения случайного mac-адреса.     У нас есть словарь с числами от 0 до 9, и латинских букв от a до f, а также начало     нового mac-адреса (00). После мы проходимся в итерационном цикле и добавляем 5 раз еще      по паре символов, а после возвращаем      Возвращает:      + str - новый mac-адрес"""     characters = list("1234567890abcdef")     random_mac_address = "00"      for i in range(5):         random_mac_address += ':' + choice(characters) + choice(characters)      return random_mac_address  def get_current_mac(interface: str):     """Получаем текущий MAC-адрес при помощи утилиты ifconfig"""     output = subprocess.check_output(["doas", "ifconfig", interface])      return re.search(r"\w\w:\w\w:\w\w:\w\w:\w\w:\w\w", str(output)).group(0)  def main(interface: str):     new_mac_address = get_random_mac_address()      try:         current_mac = get_current_mac(interface)     except subprocess.CalledProcessError as ex:         print(f'[!] Произошла ошибка. Скорее всего {interface} не существует')         print(f' !-> {ex}')         return      print(f'[+] Текущий MAC-адрес: {current_mac}')     print(f'[+] Замена MAC-адреса на {new_mac_address}')     change_mac(interface, new_mac_address) 

Модуль network_base

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

Обязательно советую использовать этот код, ибо он требуется для получения информации об IP. Можете просто скопировать код и изучить его, он обильно документирован.

Там вы увидите множество файлов — все они пригодятся.

Модуль OSINT

Теперь займемся самим поиском информации по открытым источникам. Займемся файлом ip.py — он поведует нам некоторую информацию об IP адресе.

import os import requests  def get_info_about_ip(ipaddr: str, fua: str) -> tuple:     """Получаем информацию об IP адресе      Аргументы:      + ipaddr: str - IP адрес      + fua: str - фейковый юзер-агент"""     result = str()      try:         # Отправляем запрос         headers = {             'User-Agent': fua         }         info_data = requests.get(f'https://ipinfo.io/{ipaddr}/json', headers=headers).json()     except Exception as ex:         # При ошибке возвращаем саму ошибку.         return ex      # Получаем информацию об IP     whois_info = os.popen(f'whois {ipaddr}').read().strip()      result += f'IP: {info_data.get("ip")}\n'     result += f'City: {info_data.get("city")}\n'     result += f'Region: {info_data.get("region")}\n'     result += f'Country: {info_data.get("country")}\n'     result += f'Hostname: {info_data.get("hostname")}\n'     result += f'JSON data: {info_data}\n'     result += f'WhoIS: {whois_info}\n'      # возвращаем результат     return result

Следующий шаг — компонент netlib.py. Он как раз и будет неким посредником между модулем OSINT и модулем network_base.

import modules.network_base.ipv4_local_cli as ipv4_cli import modules.network_base.ipv4_local_getmac as ipv4_gm import modules.network_base.ipv4_local_sock as ipv4_sock  import modules.network_base.router_ip as getaway  import modules.network_base.network_params as net_param  import modules.network_base.ip_from_domain as ip_domain import modules.network_base.domain_from_ip as domain_ip import modules.network_base.service_on_port as serv_port  import modules.network_base.my_public_ip as public_ip import modules.network_base.ping_address as ping_addr import modules.network_base.geolocation_ip as geo_ip import modules.network_base.addr_from_geo as addr_geo  def url_info(url):     """Информация об URL.      URL автоматически ретранслируется в IP"""      # Пинг адреса     print(f'Ping domain of address: {ping_addr.ping_addr(ip_domain.ip_from_domain(f"{url}"))}')     # Координаты     print(f"Coords by IP address: {geo_ip.geo_ip(domain_ip.domain_ip(ip_domain.ip_from_domain(f'{url}')))}")     # Физический адрес по координатам     print(f"Physical address by coords: "         f"{addr_geo.get_addr(geo_ip.geo_ip(domain_ip.domain_ip(ip_domain.ip_from_domain(f'{url}'))))}")     # IP из домена     print(f"IP-address of domain {url}: {ip_domain.ip_from_domain(f'{url}')}")     # Домен из IP     print(f"Domain name {url} by IP: {domain_ip.domain_ip(ip_domain.ip_from_domain(f'{url}'))}")  def ip_info(ip: str) -> str:     """Информация об IP"""     print(f'Ping domain or address: {ping_addr.ping_addr(ip)}')     print(f"Coords by IP: {geo_ip.geo_ip(ip)}")     print(f"Geo Addr Coords by IP: {addr_geo.get_addr(geo_ip.geo_ip(domain_ip.domain_ip(ip)))}")     print(f"Domain name: {domain_ip.domain_ip(ip)}")  def check_network():     """Проверяем сетевые параметры"""      # Локальный IP     print(f'Local IP (cli): {ipv4_cli.local_ipv4()}')     print(f'Local IP (gm): {ipv4_gm.local_ipv4()}')     print(f'Local IP (sock): {ipv4_sock.local_ipv4()}')      # IP шлюза     print(f'IP router: {getaway.router_ip()}')      # Параметры сети     print(f'Network interface parameters:\n{net_param.network_param()}')      # Название сервиса работающего на 80 порту     print(f'Name of service work on port 80: {serv_port.type_port(80)}')      # Публичный IP     print(f'Public IP: {public_ip.public_ip()}')

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

import requests  def get_info_phonenumber(phonenumber, fua):     """Получение информации о номере телефона      Аргументы:      + phonenumber - номер телефона      + fua - фейковый юзер-агент"""     result = ''     try:         # Делаем запрос к API         url = f"https://htmlweb.ru/geo/api.php?json&telcod={phonenumber}"         headers = {             'User-Agent': fua         }         info_data = requests.get(url, headers=headers).json()     except Exception as ex:         return ex      result += f'Country: {info_data["country"]["name"]}\n'     result += f'Region: {info_data["region"]["name"]}\n'     result += f'Subregion: {info_data["region"]["okrug"]}\n'     result += f'Operator: {info_data["0"]["oper"]}\n'     result += f'Location: {info_data["country"]["location"]}\n'      return result

После этого можно заняться следующим компонентом — whois_information.py:

#!venv/bin/python3 import socket import time from ipaddress import IPv4Address, AddressValueError from datetime import datetime import ipwhois import whois  def ipwhois_info(ip: str):     """Информация о IP по IPWhois       + ip: str - IP адрес"""     results = ipwhois.IPWhois(ip).lookup_whois()     print(results)     print("\n")  def whois_info(ip):     """Информация о IP по WhoIs       + ip: str - IP адрес"""     results = whois.whois(ip)     print(results)  def ianna(ip):     """Информация о IP через whois.iana.org       + ip: str - IP адрес"""     s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)     s.connect(("whois.iana.org", 43))     s.send((ip + "\r\n").encode())     response = b""      while True:         data = s.recv(4096)         response += data         if not data:             break      s.close()     whois = ''      for resp in response.decode().splitlines():         if resp.startswith('%') or not resp.strip():             continue         elif resp.startswith('whois'):             whois = resp.split(":")[1].strip()             break      return whois if whois else False  def get_whois(ip, whois):     """Получения информации о IP"""     s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)     s.connect((whois, 43))     s.send((ip + "\r\n").encode())     response = b""      while True:         data = s.recv(4096)         response += data         if not data:             break      s.close()     whois_ip = dict()     num = 0      for ln in response.decode().splitlines():         if ln.strip().startswith("%") or not ln.strip():             continue         else:             if ln.strip().split(": ")[0].strip() in ['created', 'last-modified']:                 dt = datetime.fromisoformat(ln.strip().split(": ")[1].strip()).strftime("%Y-%m-%d %H:%M:%S")                 whois_ip.update({f'{ln.strip().split(": ")[0].strip()}_{num}': dt})                 num += 1             else:                 whois_ip.update({ln.strip().split(": ")[0].strip(): ln.strip().split(": ")[1].strip()})      return whois_ip if whois_ip else False  def validate_request(ip):     """Проверка IP       + ip - IP адрес"""     try:         IPv4Address(ip)         if whois := ianna(ip):             time.sleep(1)             if info := get_whois(ip, whois):                 print(info)             else:                 print("Не была получена информация")         else:             if info := get_whois(ip, 'whois.ripe.net'):                 print(info)             else:                 print("Не была получена информация")     except AddressValueError:         print("IP адрес не валидный")     except ConnectionResetError as ex:         print(ex)

После этого давайте займемся предпоследним компонентом модуля OSINT — virus_total.py для анализа бинарников на вирусы, трояны и другие малвари.

Для начала вам потребуется авторизоваться на сайте Virus Total. После активации аккаунта, откройте меню пользователя и перейдите в секцию API Key. Ниже вы увидете сам API ключ:

Не забудьте про ограничения бесплатного API-ключа:

  • Частота запросов — 4 поиска / минута
  • Дневная квота — 500 поисков / день
  • Месячная квота — 15.5 K поисков / месяц

После просто скопируйте и вставьте в код. Есть разные практики хранить важные ключи в коде. Давайте рассмотрим три способа:

  1. Первый и простой способ. Просто вставить ключ прямо в коде

APIKEY='123456abcdef'  # Здесь ваш код # ...

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

  1. Создать файл настроек/конфигов, например settings.py и в нем хранить все важные ключи, а после импортировать в проект. Намного лучше чем 1 способ, но проблемы с безопасностью также остаются.

  2. Переменные окружения. Безопасный способ, мы храним API не в коде, а в переменных окружения. Этот способ мы и будем использовать.

Вам будет надо установить пакет python-dotenv:

pip install python-dotenv

И после создайте в корневом каталоге файл .env, и в него вставьте следующий код:

VIRUS_TOTAL_KEY = "ваш ключ"

После этого давайте немного изменим концепцию, и создадим микро-антивирус. Он будет сперва проверять хеш файла в базе сигнатурах. Да, просто сопоставлять, и это не будет стоять рядом даже с самыми плохими антивирусами — ведь стоит добавить в бинарник малваря хотя-бы один байт, даже если это будет 0, то уже наш антивирус ничего не заподозрит. Но если малварь и пройдет сигнатурный анализ, то дальше его ждет Virus Total!

И еще, мы не будем сами посылать запросы, и парсить результат, мы будем использовать библиотеку vt-py.

А также для локальной проверки файлов нам нужны будут сигнатуры. Собрал я их в каталоге res, доступный по ссылке.

import os import json import requests from dotenv import load_dotenv import os from time import perf_counter from hashlib import sha256, sha1, md5 from colorama import Fore, Style import vt import json  # загружаем переменные окружения load_dotenv()  # получаем API-ключ VIRUS_TOTAL_API_KEY = os.getenv("VIRUS_TOTAL_KEY")  # Сигнатуры signature_resources = ['res/signatures.txt', 'res/signatures2.txt',                         'res/signatures3.txt', 'res/signatures4.txt']  # для своих требований, туда можно поместить хэши зловредов, которых нету в обычных списках сигнатур. А также описание и название. signature_resource_info = 'res/signatures_info.json'  def add_signature(new_signature: dict) -> None:     """Добавление сигнатуры в базу"""     with open(signature_resource_info, 'r') as file:         signatures_info = json.load(file)      for signature in new_signatures:         signatures_info['name'] = new_signatures['name']         signatures_info['desc'] = new_signatures['desc']         signatures_info['date'] = new_signatures['date']      with open(signature_resource_info, 'a') as file:         json.dump(signatures_info, file, indent=4)      return None  def rewrite_signatures(signatures: dict) -> bool:     """Перезапись БД сигнатур"""     try:         with open(signature_resource_info, 'w') as file:             json.dump(signatures, file, indent=4)     except Exception as e:         return False     else:         return True  def get_info_signature(signature: str) -> str:     """Получение информации об сигнатуре из БД"""     with open(signature_resource_info, 'r') as file:         signatures_info = json.load(file)      try:         signature_info = f'''Сигнатура: {signature} Название угрозы: {signatures_info[signature]["name"]} Описание угрозы: {signatures_info[signature]["desc"]} Дата обнаружения угроза: {signatures_info[signature]["date"]}'''     except IndexError:         signature_info = f'По сигнатуре {signature} еще нету информации в наших базах данных'     finally:         return signature_info  def scan_file(filename: str, delete_file: bool, signature_resources: list, VT_API_KEY: str) -> None:     """Сканируем файл       + filename: str - путь до файла      + delete_file: bool - удалять ли файл      + signature_resources: list - список ресурсов сигнатур      + VT_API_KEY: str - API ключ Virus Total"""     result = f'Сканируем {filename} на наличие угроз...\n'      try:         print('Load singatures...')         start = perf_counter()         shahash = sha256()         sha1hash = sha1()         md5hash = md5()          # Получаем хэши         with open(filename, 'rb') as file:             while True:                 data = file.read()                 if not data:                     break                 shahash.update(data) # sha256                 sha1hash.update(data) # sha1                 md5hash.update(data) #md5              result1 = shahash.hexdigest()             result2 = sha1hash.hexdigest()             result3 = md5hash.hexdigest()             result += f'Проверяем наличие хешей "{result1}", "{result2}", "{result3}" (sha256, sha1, md5) в сигнатурах...\n'          # Читаем сигнатуры         with open(signature_resources[0], 'r') as r:             signatures = list(r.read().split('\n'))          with open(signature_resources[1], 'r') as r:             for sign_hash2 in list(r.read().replace(';', '').split('\n')):                 signatures.append(sign_hash2)          with open(signature_resources[2], 'r') as r:             for sign_hash3 in list(r.read().replace(';', '').split('\n')):                 signatures.append(sign_hash3)          with open(signature_resources[3], 'r') as r:             for sign_hash4 in list(r.read().replace(';', '').split('\n')):                 signatures.append(sign_hash4)          result += f'''Сканирование {filename} на Virus Total...\n'''          print('Loading Virus Total...')         client = vt.Client(VT_API_KEY)          # анализ файла         with open(filename, "rb") as f:             analysis = client.scan_file(f, wait_for_completion=True)             file = client.get_object(f"/files/{result1}")             stats = file.last_analysis_stats              result += f'VirusTotal: {filename}\t size: {file.size} bytes\n'             result += f'Безвредный: {stats["harmless"]}\n'             result += f'Неподдерживаемый тип: {stats["type-unsupported"]}\n'             result += f'Подозрительный: {stats["suspicious"]}\n'             result += f'Отказ: {stats["failure"]}\n'             result += f'Злонамеренный: {stats["malicious"]}\n'             result += f'Безопасный: {stats["undetected"]}\n'          print('Get final info...')          if result1 in signatures or result2 in signatures or result3 in signatures:             end = perf_counter()             total = end - start             result += f'Найдена угроза в файле {filename} (поиск по сигнатурам)\n'              # получаем информацию о файле, если он есть в сигнатурах             if result in signatures:                 info = get_info_signature(result)                 result += f'Информация: {info}\n'             elif result2 in signatures:                 info = get_info_signature(result2)                 result += f'Информация: {info}\n'             elif result3 in signatures:                 info = get_info_signature(result3)                 result += f'Информация: {info}\n'              # удаление файла             if delete_file:                 os.remove(filename)                 result += f'[!] Файл {filename} удален!\n'              result += f'Время работы: {(total):.07f}s\n'         else:             end = perf_counter()             total = end - start             result += 'Угроз не найдено\n'             result += f'Время работы: {(total):.07f}s\n'     except FileNotFoundError:         result += f'[!] Файл не найден\n'     except PermissionError:         result += f'[!] Ошибка прав доступа к файлу\n'      print('End')      return result  res = scan_file('<любой файл>', False, signature_resources, VIRUS_TOTAL_API_KEY) print(res)

Вот и все — при запуске этого скрипта мы можем спокойно анализировать файлы на Virus Total.

Модуль scanners

Модуль всеразличных сканеров и парсеров. ПОка есть 6 — поиск IP в черных списках DNS, парсер ссылок, сканер портов, сканер на SQL-Injection и XSS уязвимости. А также есть еще детектор ARP-спуфинга и сканер сессий meterpreter для Windows.

Займемся детектором ARP спуфинга. ARP-spoofing — разновидность сетевой атаки типа MITM, применяемая в сетях с использованием протокола ARP. В основном применяется в сетях Ethernet. Атака основана на недостатках протокола ARP.

Нам нужен будет модуль scapy.

from scapy.all import sniff  class ARPSpoofingDetector:     def __init__(self):         self.IP_MAC_Map = {}      def process_packet(self):         src_IP = packet['ARP'].psrc         src_MAC = packet['Ether'].src          if src_MAC in self.IP_MAC_Map.keys():             if self.IP_MAC_Map[src_MAC] != src_IP :                 try:                     old_IP = self.IP_MAC_Map[src_MAC]                 except:                     old_IP = "Неизвестный"                  message = f'''ARP атака замечена Это возможно со стороны машины с IP адресом {old_IP} для {src_IP}'''                  return message             else:                 self.IP_MAC_Map[src_MAC] = src_IP      def sniffing(self):         # "Нюхаем" пакеты         sniff(count=0, filter="arp", store=0, prn=self.process_packet)  arpspoof_detector = ARPSpoofingDetector() arpspoof_detector.sniffing()

Следующий шаг — сканер meterpreter сессий для Windows 7 и 10.

import re import subprocess from typing import List, Dict  WIN_7_SIGNATURE = ["WINBRAND.dll", "WINHTTP.dll", "webio.dll", "SspiCli.dll", "cscapi.dll"] WIN_10_SIGNATURE = ["rsaenh.dll", "netapi32.dll", "wkscli.dll", "psapi.dll", "cscapi.dll"]  REG_FOR_WINDOWS_VERSION = r'(?:Windows\s+)(\d+|XP|\d+\.\d+)' REG_FOR_EXE_PROCESSES = r'(?<=\\r\\n)[A-Za-z]+\.exe\s+\d+' REG_FOR_LOCAL_SOCKET = r'(?:TCP\s+)(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}:\d{1,5})' REG_FOR_REMOTE_SOCKET = r'(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}:\d{1,5})\s+[A-Z]+\s+(\d+)'  CMD_NETSTAT_COMMAND = 'netstat -aon |find /i "established"' CMD_TASKLIST_COMMAND = "tasklist /M"  class MeterpreterScanner:     def __init__(self):         self._signatures: List[str] = []         self._processes_with_signatures: List[str] = []         self._processes_with_dynamic_port: List[str] = []         self._suspicious_processes: Dict[str:List[str]] = {}      def _check_windows_version(self) -> None:         """Проверка версии windows - 7 или 10"""         info = subprocess.check_output("systeminfo", shell=True)         win = re.findall(REG_FOR_WINDOWS_VERSION, str(info))          if win[0] == '10':             self._signatures = WIN_10_SIGNATURE         elif win[0] == '7':             self._signatures = WIN_7_SIGNATURE         else:             print("[X] Только Windows 7 или Windows 10")      def _search_process_with_dll(self, dll: str) -> None:         """Поиск процессов с DLL библиотеками"""         output_tasklist = subprocess.check_output(f"{CMD_TASKLIST_COMMAND} {dll}", shell=True)         process_list = re.findall(REG_FOR_EXE_PROCESSES, str(output_tasklist))          for process_info in process_list:             process, process_PID = re.split(r'\s+', process_info)             if process in self._suspicious_processes:                 self._suspicious_processes[f'{process}_{process_PID}'].append(dll)             else:                 self._suspicious_processes[f'{process}_{process_PID}'] = [dll]      def _check_suspicious_process(self) -> None:         """Сканируем подозрительные процессы"""         self._check_windows_version()          for dll in self._signatures:             self._search_process_with_dll(dll)         for proc_info in self._suspicious_processes.items():             proc_name, proc_dlls = proc_info             if len(proc_dlls) == 5:                 print(f"[-] Найден подозрительный процесс : {proc_name}")                 self._processes_with_signatures.append(proc_name)         if not self._processes_with_signatures:             print("[+] Meterpreter сигнатура не найдена в памяти")      def _scan_suspicious_ports(self) -> None:         """Сканируем подозрительные порты"""         scan_output = subprocess.check_output(CMD_NETSTAT_COMMAND, shell=True)         local_sockets = re.findall(REG_FOR_LOCAL_SOCKET, str(scan_output))         for l_socket in local_sockets:             l_ip, l_port = l_socket.split(':')             if int(l_port) >= 49152:                 if l_ip != "127.0.0.1":                     victim_socket = f"{l_ip}:{l_port}"                     data_with_suspicious_socket = scan_output.decode().split(victim_socket)                     suspicious_info = re.findall(REG_FOR_REMOTE_SOCKET, data_with_suspicious_socket[1])[0]                     suspicious_socket, suspicious_PID = suspicious_info                     # порт 4444 используется по умолчанию в MSF и meterpreter                     if int(suspicious_socket.split(':')[-1]) == 4444:                         print(f"[!] Найдено MSF подключение: {suspicious_socket}")                     print(f"[-] Подключение {victim_socket} к {suspicious_socket} "                           f"использование динамический PID - {suspicious_PID}")                     self._processes_with_dynamic_port.append(suspicious_PID)      def finding_meterpreter_sessions(self):         """Находим сессии meterpreter"""         found = False         self._check_suspicious_process()         self._scan_suspicious_ports()          # ищем процесс         for proc in self._processes_with_signatures:             proc_name, proc_PID = proc.split('_')             if proc_PID in self._processes_with_dynamic_port:                 print(f'[!] Найдено совпадение в {proc_name} с PID {proc_PID}')                 found = True          if not found:             print(f"[+] Совпадений не найдено")  MeterpreterScanner().finding_meterpreter_sessions()

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

import socket import re from ipaddress import ip_network, ip_address from dns import resolver from requests import get, exceptions  def ip_in_range(ip, addr):     """Сканирование IP"""     if ip_address(ip) in ip_network(addr):         return True     return False  def cloudfare_detect(ip):     """Сканирование в Cloudfare"""     list_addr = ["104.16.0.0/12"]      url = 'https://www.cloudflare.com/ips-v4'     req = get(url=url)      for adr in req.text.split("\n"):         list_addr.append(adr)      for addr in list_addr:         detect = ip_in_range(ip, addr)         if detect:             return True     return False  def public_ip():     """Получить публичный IP"""     try:         return get('https://api.ipify.org/').text     except exceptions.ConnectionError:         return '127.0.0.1'  def dns_bl_check(ip):     bad_dict = dict()     result = str()     req = get('https://raw.githubusercontent.com/evgenycc/DNSBL-list/main/DNSBL')     read = req.text.splitlines()      for serv in read:         print(f'Checking... {serv}')         req = f"{'.'.join(reversed(ip.split('.')))}.{serv.strip()}"         try:             resolv = resolver.Resolver()             resolv.timeout = 5             resolv.lifetime = 5             resp = resolv.resolve(req, 'A')             resp_txt = resolv.resolve(req, 'TXT')             result += f'{serv.strip():30}: [BAD]\n'             pattern = r'(?:https?:\/\/)?(?:[\w\.]+)\.(?:[a-z]{2,6}\.?)(?:\/[\w\.]*)*\/?'             find = re.findall(pattern, str(resp_txt[0]))             if len(find) == 0:                 find = ['No address']             bad_dict.update({serv.strip(): f'{resp[0]} {find[0]}'})         except resolver.NXDOMAIN:             result += f'{serv.strip():30}: [OK]\n'         except (resolver.LifetimeTimeout, resolver.NoAnswer):             continue         except Exception as ex:             raise ex             return ('error', ex)      if len(bad_dict) > 0:         result += f'{ip} is found in black list\n'          for bad in bad_dict:             result += f' - {bad:30} : {bad_dict[bad]}\n'     else:         result += 'IP is not found in black list\n'      return (result, 0)  def check_ip_in_black_list(addr_input):     result = ''     result += f'Your public IP: {public_ip()}\n'      if addr_input.lower() == "x":         exit(0)      ip = ''      try:         ip = socket.gethostbyname(addr_input)     except socket.gaierror as ex:         return ex      if cloudfare_detect(ip):         result += f'Cloudfare detected: {ip}\n'     else:         result += 'Cloudfare not detected\n'      dnsbl_list = dns_bl_check(ip)      if dnsbl_list[0] is None or dnsbl_list[0] == 'error':         return result     else:         result += dnsbl_list[0]      return result  check_ip_in_black_list('<IP адрес>')

Теперь парсер ссылок:

import requests from urllib.parse import urlparse, urljoin from bs4 import BeautifulSoup  internal_urls = set() external_urls = set()  def is_valid(url):     """Данная функция проверяет, валиден ли URL      Аргументы:         url - ссылка на страницу     """     parsed = urlparse(url)      return bool(parsed.netloc) and bool(parsed.scheme)  def get_all_website_links(url):     """Получаем все ссылки с сайта"""     urls = set()     domain_name = urlparse(url).netloc     soup = BeautifulSoup(requests.get(url).content, "html.parser")      for a_tag in soup.findAll("a"):         href = a_tag.attrs.get("href")          if href == "" or href is None:             continue          href = urljoin(url, href)          parsed_href = urlparse(href)         href = parsed_href.scheme + "://" + parsed_href.netloc + parsed_href.path          if not is_valid(href):             continue          if href in internal_urls:             continue          if domain_name not in href:             if href not in external_urls:                 print(f"[!] Внешняя ссылка: {href}")                 external_urls.add(href)             continue          print(f"[*] Внутренняя ссылка: {href}")         urls.add(href)         internal_urls.add(href)      return urls  def crawl(url, max_urls=30):     """Парсинг"""     total_urls_visited = 0      total_urls_visited += 1     print(f"[*] Работа над {url}")     links = get_all_website_links(url)      for link in links:         if total_urls_visited > max_urls:             break         crawl(link, max_urls=max_urls)  def get_links(url, max_urls=30):     """Получаем ссылки"""     crawl(url, max_urls)     print("[+] Всего внутренних ссылок:", len(internal_urls))     print("[+] Всего внешних ссылок:", len(external_urls))     print("[+] Всего ссылок", len(external_urls) + len(internal_urls))     print("[+] Всего проверенных ссылок:", max_urls)

Как видите, ничего сложного.

А мы едем дальше — следующий шаг это сканер портов:

import socket from datetime import datetime  def scan_ports(hostname):     start = datetime.now()      ports = {         20: "FTP-DATA", 21: "FTP", 22: "SSH", 23: "Telnet",         25: "SMTP", 43: "WHOIS", 53: "DNS", 80: "http",         115: "SFTP", 123: "NTP", 143: "IMAP", 161: "SNMP",         179: "BGP", 443: "HTTPS", 445: "MICROSOFT-DS",         514: "SYSLOG", 515: "PRINTER", 993: "IMAPS",         995: "POP3S", 1080: "SOCKS", 1194: "OpenVPN",         1433: "SQL Server", 1723: "PPTP", 3128: "HTTP",         3268: "LDAP", 3306: "MySQL", 3389: "RDP",         5432: "PostgreSQL", 5900: "VNC", 8080: "Tomcat", 10000: "Webmin"     }      open_ports = []     closed_ports = []     [i for i in range(1, 65355)]      ip = socket.gethostbyname(hostname)      for port in range(65535):         cont = socket.socket()         cont.settimeout(1)          # попытаемся подключиться         try:             cont.connect((ip, port))         except socket.error:             print(f'[!] Порт {port} закрыт')             closed_ports.append([port])         else:             # выводим тип порта             try:                 print(f"[{socket.gethostbyname(ip)}:{str(port)}] открыт/{ports[port]}")                 open_ports.append({port, ports[port]})             except KeyError:                 print(f"[{socket.gethostbyname(ip)}:{str(port)}] открыт/неизвестный тип")                 open_ports.append({port, 'неизвестный тип'})             finally:                 cont.close()      ends = datetime.now()     print("<Время работы:{}>".format(ends - start))      print('[+] Закрытые порты: ')     for closed_port in closed_ports:         print(closed_port, end=', ')      print()      print('[+] Открытые порты: ')     for open_port in open_ports:         print(open_port)  scan_ports('<IP адрес>')

Следующий шаг — поиск SQL-Injection уязвимостей. Внедрение SQL-кода — один из распространённых способов взлома сайтов и программ, работающих с базами данных, основанный на внедрении в запрос произвольного SQL-кода.

import requests from bs4 import BeautifulSoup as bs from urllib.parse import urljoin from pprint import pprint  def get_session(user_agent):     session = requests.Session()     session.headers["User-Agent"] = user_agent      return session  def get_all_forms(session, url):     """Дается `url`, и это возвращаем весь html-контент с формами"""     soup = bs(session.get(url).content, "html.parser")      return soup.find_all("form")  def get_form_details(form):     """     Эта функция получает всю доступную информацию о форме     """     details = {}      # получаем действие формы (url цели)     try:         action = form.attrsession.get("action").lower()     except:         action = None      # получаем метод формы (POST, GET, etc.)     method = form.attrsession.get("method", "get").lower()      # получаем все детали ввода     inputs = []      for input_tag in form.find_all("input"):         input_type = input_tag.attrsession.get("type", "text")         input_name = input_tag.attrsession.get("name")         input_value = input_tag.attrsession.get("value", "")         inputsession.append({"type": input_type, "name": input_name, "value": input_value})      # добавляем информацию в словарь деталей     details["action"] = action     details["method"] = method     details["inputs"] = inputs      return details  def is_vulnerable(response):     """Простая логическая функция, определяющая, является ли страница     уязвима ли SQL-инъекция из-за ее «ответа»"""      errors = {         # MySQL         "you have an error in your sql syntax;",         "warning: mysql",         # SQL Server         "unclosed quotation mark after the character string",         # Oracle         "quoted string not properly terminated",     }      for error in errors:         # Если мы находим ошибку, то возвращаем True         if error in response.content.decode().lower():             return True      # Нету ошибок     return False  def scan_sql_injection(session, url):     """Сканирование URL на нахождение уяизвимостей для SQL-инъекции"""      for c in "\"'":         # Добавляем кавычку к URL         new_url = f"{url}{c}"         print("[!] Попытка нахождения уязвимости SQL-инъекции на", new_url)         # Создаем HTTP-запрос         res = session.get(new_url)         if is_vulnerable(res):             # SQL-инъекция обнаружена на самом URL-адресе,              # нет необходимости предварительно извлекать формы и отправлять их             print("[+] SQL инъекция найдена, ссылка:", new_url)             return      # Анализ форм     forms = get_all_forms(session, url)     print(f"[+] Найдено {len(forms)} форм на {url}.")      for form in forms:         form_details = get_form_details(form)         for c in "\"'":             # данные тела для отправки             data = {}             for input_tag in form_details["inputs"]:                 if input_tag["type"] == "hidden" or input_tag["value"]:                     # любая форма ввода, которая скрыта или имеет некоторое значение,                     # просто используем его в теле формы                     try:                         data[input_tag["name"]] = input_tag["value"] + c                     except:                         pass                 elif input_tag["type"] != "submit":                     # все остальные, кроме отправки, используют некоторые ненужные данные со специальным символом                     data[input_tag["name"]] = f"test{c}"              # добавляем к URL ссылку с действием из формы             url = urljoin(url, form_details["action"])              if form_details["method"] == "post":                 res = session.post(url, data=data)             elif form_details["method"] == "get":                 res = session.get(url, params=data)              if is_vulnerable(res):                 print("[+] SQL Injection vulnerability detected, link:", url)                 print("[+] Form:")                 pprint(form_details)                 break  def scanning(fua, url):     """fua - юзер агент, url - ссылка на сайт"""     session = get_session(fua)     scan_sql_injection(session, url)

Займемся поиском XSS-уязвимостей (межсайтовый скриптинг). XSS — тип атаки на веб-системы, заключающийся во внедрении в выдаваемую веб-системой страницу вредоносного кода и взаимодействии этого кода с веб-сервером злоумышленника. Является разновидностью атаки «Внедрение кода». Википедия

import requests from pprint import pprint from bs4 import BeautifulSoup as bs from urllib.parse import urljoin  def get_all_forms(url, fua):     """Вводится `url`, это возвращает все формы из HTML-контента     fua - фейковый юзер агент"""     headers = {         'User-Agent': fua     }      soup = bs(requests.get(url, headers=headers).content, "html.parser")     return soup.find_all("form")  def get_form_details(form):     """Данная функция получает все возможные данные из HTML-формы"""     details = {}     # Получаем действие формы     action = form.attrs.get("action", "").lower()      # Получаем все методы формы (POST, GET, etc.)     method = form.attrs.get("method", "get").lower()      # Получаем все инпуты     inputs = []      for input_tag in form.find_all("input"):         input_type = input_tag.attrs.get("type", "text")         input_name = input_tag.attrs.get("name")         inputs.append({"type": input_type, "name": input_name})      # Добавляем в детали     details["action"] = action     details["method"] = method     details["inputs"] = inputs      return details  def submit_form(form_details, url, value):     """     Отправляем данные формы     Возвращает HTTP ответ     """     target_url = urljoin(url, form_details["action"])     inputs = form_details["inputs"]     data = {}      for input in inputs:         # заменить весь текст и значения поиска на `value`         if input["type"] == "text" or input["type"] == "search":             input["value"] = value          input_name = input.get("name")         input_value = input.get("value")          if input_name and input_value:             data[input_name] = input_value      print(f"[+] Отправка вредоносной полезной нагрузки на {target_url}")     print(f"[+] Данные: {data}")      if form_details["method"] == "post":         return requests.post(target_url, data=data)     else:         # GET request         return requests.get(target_url, params=data)  def scan_xss(url, fua):     """     Получив `url`, он выводит все формы, уязвимые для XSS, и     возвращает True, если кто-то из них уязвим, False в противном случае     """     forms = get_all_forms(url, fua)      print(f"[+] Найдено {len(forms)} форм на {url}.")      js_script = "<Script>alert('Detected')</script>"     is_vulnerable = False      for form in forms:         form_details = get_form_details(form)         content = submit_form(form_details, url, js_script).content.decode()         if js_script in content:             print(f"[+] XSS найдена на {url}")             print("[*] Детали формы:")             pprint(form_details)             is_vulnerable = True      return is_vulnerable  if __name__ == "__main__":     url = "https://xss-game.appspot.com/level1/frame"     print(scan_xss(url, '<юзер агент>'))

Вот и все. Осталось это скомбинировать в одну программу на Python, считайте что это ваше домашнее задание. Попробуйте улучшить код, улучшить вывод сообщений и многое другое. А как насчет асинхронности? Обо всем этом мы будем говорить во второй части статьи — там мы будем красоту наводить, и пытаться оптимизировать.

Заключение

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

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

Больше Python кода, гайдов и полезных инструментов и гайдов вы можете найти в моем канале, заходите в гости.

Спасибо за внимание! Надеюсь вам понравилась статья!

Ссылки


ссылка на оригинал статьи https://habr.com/ru/articles/816889/