Python в кибербезопасности: от скриптов до анализа уязвимостей

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

Введение в Python для безопасности: работа с сокетами и сетевыми протоколами

Введение в Python для безопасности: работа с сокетами и сетевыми протоколами

Добро пожаловать на курс «Python в кибербезопасности». Это наша первая статья, и мы начнем с фундамента, на котором строится всё сетевое взаимодействие — с сокетов.

Почему именно Python стал стандартом де-факто в индустрии информационной безопасности? Ответ прост: это идеальный баланс между простотой написания кода и мощностью доступных инструментов. Python позволяет специалисту по безопасности создать сканер портов, брутфорс-утилиту или анализатор трафика буквально за несколько минут, в то время как на C++ или Java это заняло бы часы.

В этой статье мы разберем, как компьютеры общаются друг с другом, напишем свои первые клиент-серверные приложения и создадим простейший инструмент для разведки — «Banner Grabber».

Что такое сокеты?

Прежде чем писать код, нужно понять теорию. Представьте, что вы хотите позвонить другу. Чтобы разговор состоялся, вам нужно знать две вещи: номер телефона (адрес) и конкретного человека, которого нужно позвать к трубке (если это офисная АТС с добавочным номером).

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

Адресация сокета описывается простой математической моделью:

S={IP,Port}S = \{IP, Port\}

где SS — это идентификатор сокета, IPIP — это IP-адрес узла в сети (например, 192.168.1.1), а PortPort — это числовой идентификатор процесса (порта), который ожидает данные (например, 80 для веб-сервера).

Типы сокетов

В Python мы чаще всего будем работать с двумя типами семейств адресов и протоколов:

  1. AF_INET — семейство адресов для IPv4 (самый распространенный формат IP-адресов).
  2. SOCK_STREAM — протокол TCP (Transmission Control Protocol). Гарантирует доставку данных в правильном порядке. Используется для HTTP, SSH, FTP.
  3. SOCK_DGRAM — протокол UDP (User Datagram Protocol). Работает быстрее, но не гарантирует доставку. Используется для DNS, видеостриминга.

Библиотека socket в Python

В Python работа с сетью встроена в стандартную библиотеку. Вам не нужно ничего устанавливать дополнительно, достаточно импортировать модуль socket.

Давайте разберем базовый синтаксис создания сокета:

import socket

# Создаем объект сокета
# socket.AF_INET указывает на использование IPv4
# socket.SOCK_STREAM указывает на использование TCP
client_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

Этот объект client_socket теперь имеет методы для подключения, отправки и получения данных.

Практика: Создание TCP-сервера

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

import socket

def start_server():
    # 1. Создаем сокет
    server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

    # 2. Привязываем сокет к адресу и порту (bind)
    # '0.0.0.0' означает, что мы слушаем все сетевые интерфейсы
    server.bind(('0.0.0.0', 9999))

    # 3. Начинаем прослушивание (listen)
    # Параметр 5 — это размер очереди подключений
    server.listen(5)
    print("[+] Сервер запущен и слушает порт 9999")

    while True:
        # 4. Принимаем входящее соединение (accept)
        # Метод возвращает кортеж: новый объект сокета и адрес клиента
        client_sock, address = server.accept()
        print(f"[+] Принято соединение от {address[0]}:{address[1]}")

        # 5. Получаем данные (recv)
        # 1024 — размер буфера в байтах
        data = client_sock.recv(1024)
        print(f"[+] Получено: {data.decode('utf-8')}")

        # 6. Отправляем ответ
        client_sock.send("Сообщение принято!".encode('utf-8'))

        # 7. Закрываем соединение с клиентом
        client_sock.close()

if __name__ == '__main__':
    start_server()

Разбор ключевых методов сервера:

  • bind((host, port)): Резервирует порт за нашей программой. Если порт занят другим приложением, возникнет ошибка.
  • listen(backlog): Переводит сокет в режим ожидания. backlog определяет, сколько соединений может висеть в очереди, пока сервер занят.
  • accept(): Блокирует выполнение программы, пока кто-то не подключится. Как только клиент стучится, метод возвращает пару (socket, address).

Практика: Создание TCP-клиента

Теперь нам нужен клиент, который подключится к нашему серверу. Это основа для написания сканеров или эксплойтов.

import socket

def start_client():
    # 1. Создаем сокет
    client = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

    try:
        # 2. Подключаемся к серверу (connect)
        # Используем localhost (127.0.0.1), если сервер на той же машине
        client.connect(('127.0.0.1', 9999))

        # 3. Отправляем данные
        # Важно: данные нужно кодировать в байты (.encode)
        message = "Привет, сервер! Это Python."
        client.send(message.encode('utf-8'))

        # 4. Получаем ответ
        response = client.recv(4096)
        print(f"[+] Ответ от сервера: {response.decode('utf-8')}")

    except ConnectionRefusedError:
        print("[-] Не удалось подключиться. Сервер запущен?")
    finally:
        # 5. Всегда закрываем сокет
        client.close()

if __name__ == '__main__':
    start_client()

Важно помнить: сетевые данные передаются в виде байтов (bytes), а не строк (str). Поэтому мы всегда используем методы .encode() перед отправкой и .decode() после получения.

Применение в безопасности: Banner Grabbing

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

Многие сетевые службы (FTP, SSH, SMTP) при подключении отправляют приветственное сообщение — баннер. Мы можем написать скрипт, который подключается к порту, читает баннер и отключается. Эта техника называется Banner Grabbing.

Рассмотрим пример простого скрипта для получения баннера:

import socket

def grab_banner(ip, port):
    try:
        s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        # Устанавливаем таймаут, чтобы скрипт не завис, если порт открыт, но молчит
        s.settimeout(2)
        s.connect((ip, port))

        # Пытаемся прочитать данные
        banner = s.recv(1024)
        return banner.decode().strip()
    except Exception as e:
        return f"Ошибка: {e}"
    finally:
        s.close()

# Пример использования (сканируем, например, FTP порт)
target_ip = "192.168.1.50" # Замените на реальный IP в вашей лаборатории
target_port = 21

print(f"Сканируем {target_ip}:{target_port}...")
banner = grab_banner(target_ip, target_port)
print(f"Баннер: {banner}")

Зачем нужен таймаут?

При сканировании сети время — критический ресурс. Если мы сканируем тысячи портов, ожидание ответа от каждого может занять вечность. Время сканирования можно оценить по формуле:

Ttotal=N×toutT_{total} = N \times t_{out}

где TtotalT_{total} — общее максимальное время сканирования, NN — количество проверяемых портов, а toutt_{out} — установленный таймаут соединения в секундах. Без таймаута скрипт может зависнуть на фильтруемом порту (когда пакеты отбрасываются фаерволом) до системного таймаута, который часто составляет 60 секунд и более.

Обработка ошибок

В скриптах для безопасности обработка исключений (try...except) обязательна. Сеть нестабильна: хосты могут быть выключены, порты закрыты, фаерволы могут блокировать пакеты. Если ваш скрипт упадет с ошибкой на 5-м хосте из 1000, вы потеряете время и данные.

Основные исключения модуля socket:

  • socket.error: общее исключение для ошибок сокетов.
  • socket.timeout: истекло время ожидания.
  • ConnectionRefusedError: соединение отвергнуто (порт закрыт).

Заключение

Мы разобрали основы работы с модулем socket в Python. Вы узнали, как создавать клиентские и серверные приложения, поняли разницу между TCP и UDP на уровне кода, и даже написали свой первый инструмент для разведки — сборщик баннеров.

В следующих статьях мы углубимся в работу с сырыми сокетами (Raw Sockets) для создания снифферов и разберем библиотеку Scapy, которая позволяет манипулировать сетевыми пакетами на еще более глубоком уровне.

Полезные ссылки

Сетевая разведка и анализ трафика с использованием библиотеки Scapy

Сетевая разведка и анализ трафика с использованием библиотеки Scapy

В предыдущей статье мы научились работать с сетью на низком уровне, используя стандартную библиотеку socket. Мы вручную создавали соединения, отправляли байты и декодировали ответы. Это отличный способ понять, как работают протоколы «под капотом». Однако в реальных задачах кибербезопасности — будь то тестирование на проникновение или анализ вредоносного трафика — нам часто требуется инструмент более высокого уровня, способный гибко манипулировать пакетами без написания сотен строк кода.

Сегодня мы познакомимся со Scapy — «швейцарским армейским ножом» для работы с сетью на Python.

Что такое Scapy?

Scapy — это мощная интерактивная программа и библиотека для манипулирования пакетами. В отличие от socket, где вы оперируете потоками данных, Scapy позволяет вам оперировать пакетами и полями протоколов напрямую.

Она умеет:

  • Сканировать сети (замена Nmap).
  • Перехватывать трафик (замена Wireshark/Tcpdump).
  • Генерировать и подделывать пакеты (спуфинг).
  • Атаковать сетевые клиенты и серверы.

Главная философия Scapy заключается в том, что она не пытается интерпретировать данные за вас, если вы этого не попросите. Она дает вам «сырой» доступ к полям заголовков IP, TCP, UDP и многих других протоколов.

Установка и основы архитектуры

Scapy не входит в стандартную библиотеку Python, поэтому её нужно установить:

pip install scapy

Принцип матрешки: инкапсуляция

В сетевых технологиях данные передаются слоями. Ethernet-кадр содержит IP-пакет, который содержит TCP-сегмент, который содержит полезную нагрузку (например, HTTP-запрос). Scapy реализует этот принцип с помощью перегрузки оператора деления /.

Математически структуру пакета в Scapy можно выразить следующим образом:

Ptotal=L2/L3/L4/PayloadP_{total} = L_{2} / L_{3} / L_{4} / Payload

Где:

  • PtotalP_{total} — итоговый сформированный пакет.
  • L2L_{2} — протокол канального уровня (например, Ethernet).
  • L3L_{3} — протокол сетевого уровня (например, IP).
  • L4L_{4} — протокол транспортного уровня (например, TCP или UDP).
  • PayloadPayload — полезная нагрузка (данные).
  • Оператор // — обозначает инкапсуляцию (вложение) одного слоя в другой.

Давайте создадим наш первый пакет. Допустим, мы хотим отправить TCP-пакет на 80-й порт сервера Google (8.8.8.8).

from scapy.all import *

# Создаем IP-слой
# dst - destination (адрес назначения)
ip_layer = IP(dst="8.8.8.8")

# Создаем TCP-слой
# dport - destination port (порт назначения)
# flags="S" означает установку флага SYN (начало соединения)
tcp_layer = TCP(dport=80, flags="S")

# Собираем пакет вместе
packet = ip_layer / tcp_layer

# Можно вывести детали пакета
packet.show()

Метод .show() выведет подробную структуру всех полей заголовков, которые Scapy заполнил автоматически (например, src IP-адрес, контрольные суммы, размер окна), и тех, что мы задали вручную.

Отправка и получение пакетов

В Scapy есть семейство функций для отправки данных, и выбор правильной функции зависит от того, на каком уровне модели OSI вы работаете и ждете ли вы ответа.

  1. send() — отправляет пакеты на 3-м уровне (IP) и не ждет ответа. («Выстрелил и забыл»).
  2. sendp() — то же самое, но для 2-го уровня (Ethernet).
  3. sr() — (Send and Receive) отправляет пакеты на 3-м уровне и возвращает все ответы.
  4. sr1() — (Send and Receive 1) отправляет пакет и возвращает только первый ответ. Это самая часто используемая функция для простых сканеров.
  5. srp() — отправка и получение на 2-м уровне (используется для ARP-запросов).

Пример: Реализация Ping (ICMP)

Давайте напишем аналог утилиты ping, используя Scapy. Для этого нам нужно отправить ICMP-запрос (Echo Request) внутри IP-пакета.

from scapy.all import *

target = "8.8.8.8"

# Формируем пакет: IP -> ICMP
# type=8 - это код Echo Request
ping_pkt = IP(dst=target)/ICMP(type=8)

# Отправляем и ждем 1 ответ
# timeout=2 означает, что мы ждем не более 2 секунд
response = sr1(ping_pkt, timeout=2, verbose=0)

if response:
    print(f"[+] Узел {target} доступен!")
    # response[IP].src вернет IP-адрес отправителя ответа
    print(f"Ответ от: {response[IP].src}")
else:
    print(f"[-] Узел {target} не отвечает.")

Сетевая разведка: TCP SYN Scan

В предыдущей статье мы делали сканер портов через socket.connect(). Это называется «полное соединение» (Full Connect Scan). Оно надежно, но очень заметно для систем защиты, так как устанавливает полноценную TCP-сессию.

С помощью Scapy мы можем реализовать SYN-сканирование (Stealth Scan). Мы отправляем пакет с флагом SYN (запрос на соединение), и если порт открыт, сервер ответит SYN-ACK. Вместо того чтобы завершить соединение (отправить ACK), мы отправим RST (сброс), чтобы не создавать лог соединения на сервере.

Логику определения статуса порта можно описать следующими условиями:

  1. Если получен SYN-ACK →\rightarrow Порт открыт.
  2. Если получен RST-ACK →\rightarrow Порт закрыт.
  3. Если ответа нет (таймаут) или получен ICMP Unreachable →\rightarrow Порт фильтруется (фаервол).

Реализация на Python:

from scapy.all import *

def syn_scan(target_ip, target_port):
    # Формируем пакет с флагом SYN
    # sport - source port (выбираем случайный высокий порт)
    pkt = IP(dst=target_ip)/TCP(dport=target_port, sport=RandShort(), flags="S")

    # Отправляем
    resp = sr1(pkt, timeout=1, verbose=0)

    if resp:
        # Проверяем наличие слоя TCP в ответе
        if resp.haslayer(TCP):
            # Флаги в Scapy представлены битовой маской
            # 0x12 - это SYN (0x02) + ACK (0x10)
            if resp[TCP].flags == 0x12:
                # Отправляем RST, чтобы сбросить соединение (вежливость хакера)
                send(IP(dst=target_ip)/TCP(dport=target_port, flags="R"), verbose=0)
                return "OPEN"
            elif resp[TCP].flags == 0x14: # RST + ACK
                return "CLOSED"
    return "FILTERED"

print(f"Порт 80: {syn_scan('scanme.nmap.org', 80)}")

Анализ трафика (Сниффинг)

Scapy умеет не только отправлять, но и перехватывать пакеты, работая как сниффер. Для этого используется функция sniff().

Основные аргументы sniff():

  • iface: интерфейс для прослушивания (например, "eth0" или "Wi-Fi").
  • count: количество пакетов для захвата (0 — бесконечно).
  • filter: BPF-фильтр (Berkeley Packet Filter) — язык фильтрации, используемый в Wireshark/Tcpdump.
  • prn: функция обратного вызова (callback), которая будет применяться к каждому перехваченному пакету.

Пример: Перехват HTTP-запросов

Напишем скрипт, который выводит IP-адреса, к которым обращается наш компьютер по протоколу HTTP.

from scapy.all import *

def process_packet(packet):
    # Проверяем, есть ли в пакете слой IP
    if packet.haslayer(IP):
        src = packet[IP].src
        dst = packet[IP].dst
        print(f"[Traffic] {src} -> {dst} | Protocol: {packet[IP].proto}")

        # Если есть Raw данные (полезная нагрузка), попробуем вывести начало
        if packet.haslayer(Raw):
            try:
                payload = packet[Raw].load.decode('utf-8', errors='ignore')
                if "GET" in payload or "POST" in payload:
                    print(f"   >>> HTTP Data: {payload[:50]}...")
            except:
                pass

print("Запуск сниффера... (Нажмите Ctrl+C для остановки)")
# Фильтр "tcp port 80" отсеивает все, кроме веб-трафика
sniff(filter="tcp port 80", prn=process_packet, store=0)

Примечание: Аргумент store=0 важен при длительном сниффинге. Он указывает Scapy не сохранять пакеты в оперативной памяти, иначе скрипт быстро съест все ресурсы компьютера.

Практическая задача: ARP-сканер локальной сети

В завершение объединим полученные знания и напишем инструмент для обнаружения всех устройств в вашей локальной сети. Для этого используется протокол ARP (Address Resolution Protocol), который сопоставляет IP-адреса с MAC-адресами.

Мы отправим широковещательный (Broadcast) запрос «Кто имеет IP X?», и активное устройство ответит «Я имею IP X, мой MAC — Y».

Так как ARP работает на канальном уровне (Layer 2), мы будем использовать Ether() и srp().

from scapy.all import *

def arp_scan(network):
    # 1. Создаем Ethernet-кадр для широковещательной рассылки
    # ff:ff:ff:ff:ff:ff - это broadcast MAC-адрес (всем устройствам)
    ether = Ether(dst="ff:ff:ff:ff:ff:ff")

    # 2. Создаем ARP-запрос
    # pdst - целевая подсеть (например, "192.168.1.1/24")
    arp = ARP(pdst=network)

    # 3. Объединяем слои
    packet = ether / arp

    print(f"Сканируем сеть: {network}...")

    # 4. Отправляем и получаем ответы (srp возвращает две группы: ответившие и неответившие)
    # result содержит пары (отправленный_пакет, полученный_ответ)
    result, _ = srp(packet, timeout=3, verbose=0)

    devices = []
    for sent, received in result:
        # Извлекаем IP и MAC из ответа
        devices.append({'ip': received.psrc, 'mac': received.hwsrc})

    return devices

# Пример использования
# Внимание: укажите вашу реальную подсеть!
found_devices = arp_scan("192.168.1.0/24")

print("\nНайденные устройства:")
print("IP Address\t\tMAC Address")
print("-" * 40)
for device in found_devices:
    print(f"{device['ip']}\t\t{device['mac']}")

Заключение

Библиотека Scapy предоставляет невероятную гибкость. Мы прошли путь от создания простых пакетов до написания собственного сетевого сканера и сниффера. В отличие от socket, где мы вынуждены заботиться о деталях реализации протоколов, Scapy позволяет нам мыслить на уровне сетевой архитектуры.

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

Полезные ссылки

Веб-безопасность: автоматизация поиска уязвимостей и парсинг данных

Веб-безопасность: автоматизация поиска уязвимостей и парсинг данных

В предыдущих модулях мы работали на низком уровне: создавали сокеты, анализировали TCP-пакеты и манипулировали заголовками IP с помощью Scapy. Теперь мы поднимаемся выше по модели OSI — на прикладной уровень (Application Layer). Именно здесь обитает веб: сайты, API, административные панели и, конечно же, львиная доля современных уязвимостей.

Веб — это основной вектор атак сегодня. Для специалиста по кибербезопасности умение взаимодействовать с веб-приложениями программно (а не только через браузер) является критическим навыком. В этой статье мы научимся использовать Python для автоматизации взаимодействия с сайтами, парсинга HTML-кода и написания простых сканеров уязвимостей.

Инструментарий: requests и BeautifulSoup

Хотя модуль socket позволяет отправить HTTP-запрос вручную, это неудобно и трудоемко. В мире Python стандартом де-факто для работы с HTTP является библиотека requests, а для разбора (парсинга) HTML-кода — BeautifulSoup (из пакета bs4).

Установим необходимые библиотеки:

pip install requests beautifulsoup4

Анатомия HTTP-запроса в Python

Когда вы вводите адрес в браузере, происходит отправка GET-запроса. Сервер возвращает HTML-код, который браузер отрисовывает. С точки зрения Python, нас интересует не красота отрисовки, а сырой код ответа и заголовки.

Рассмотрим базовый пример получения страницы:

import requests

url = 'http://example.com'
response = requests.get(url)

print(f"Статус код: {response.status_code}")
print(f"Заголовки: {response.headers}")
# Вывод первых 100 символов тела ответа
print(f"Тело ответа: {response.text[:100]}...")

Ключевой элемент здесь — код состояния (status code). В скриптах безопасности мы опираемся на него для принятия решений:

  • 200 OK: Ресурс существует и доступен.
  • 301/302: Перенаправление.
  • 403 Forbidden: Доступ запрещен (интересно для пентестера, значит там что-то есть).
  • 404 Not Found: Ресурс не существует.

Маскировка под браузер

Многие сайты блокируют запросы, если видят, что они идут от скрипта (по умолчанию requests честно пишет в заголовке User-Agent, что он — python-requests). Чтобы обойти простейшие защиты, мы должны притвориться обычным браузером.

headers = {
    'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36'
}
response = requests.get(url, headers=headers)

Парсинг данных: поиск точек входа

Чтобы найти уязвимость, нужно знать, куда «бить». Точками входа в веб-приложениях обычно являются:

  1. Ссылки (параметры в URL).
  2. Формы (поля ввода логина, поиска, комментариев).

Для извлечения этих элементов из HTML-каши используется BeautifulSoup.

from bs4 import BeautifulSoup
import requests

url = "http://testphp.vulnweb.com" # Тестовый сайт для тренировки
response = requests.get(url)

# Создаем объект супа
soup = BeautifulSoup(response.text, 'html.parser')

# 1. Поиск всех ссылок
print("[+] Найденные ссылки:")
for link in soup.find_all('a'):
    href = link.get('href')
    if href:
        print(href)

# 2. Поиск форм и полей ввода
print("\n[+] Найденные формы:")
for form in soup.find_all('form'):
    action = form.get('action')
    method = form.get('method')
    print(f"Форма отправляет данные на {action} методом {method}")

    # Ищем инпуты внутри формы
    inputs = form.find_all('input')
    for i in inputs:
        print(f" - Поле: {i.get('name')} (тип: {i.get('type')})")

Этот скрипт — основа для создания Web Crawler (паука), который составляет карту сайта перед атакой.

Автоматизация поиска скрытых директорий (Directory Busting)

Одна из частых проблем безопасности — оставленные разработчиками служебные файлы: /admin, /backup, /config.php, .git. Они не видны на главной странице, но доступны по прямому перебору.

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

Оценка времени сканирования

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

Формула расчета времени сканирования:

T=WRT = \frac{W}{R}

Где TT — время в секундах, WW — количество слов в словаре (wordlist), а RR — скорость запросов в секунду (requests per second). Если у вас словарь на 10 000 путей, а скрипт делает 5 запросов в секунду, сканирование займет 10000/5=200010000 / 5 = 2000 секунд (около 33 минут). Это объясняет, почему в реальных сканерах используется многопоточность.

Реализация сканера директорий

import requests

def dir_buster(target_url, wordlist):
    print(f"Запуск сканирования {target_url}...")

    for path in wordlist:
        # Формируем полный URL
        url = f"{target_url}/{path}"

        try:
            response = requests.get(url, timeout=2)

            # Если код 200 - страница существует
            if response.status_code == 200:
                print(f"[+] НАЙДЕНО: {url} (Код: 200)")
            # Если код 403 - доступ запрещен, но папка есть
            elif response.status_code == 403:
                print(f"[*] ЗАПРЕЩЕНО: {url} (Код: 403)")

        except requests.ConnectionError:
            pass

# Пример списка (в реальности загружается из файла)
common_paths = ['admin', 'login', 'uploads', 'images', 'backup.zip', 'config.php']
target = "http://testphp.vulnweb.com"

dir_buster(target, common_paths)

Автоматизация поиска SQL-инъекций

SQL Injection (SQLi) возникает, когда данные пользователя попадают в запрос к базе данных без фильтрации. Самый простой способ обнаружить потенциальную уязвимость — добавить в параметр запроса спецсимвол (например, кавычку ') и посмотреть, не сломается ли сайт.

Если сайт вернет ошибку базы данных (например, "You have an error in your SQL syntax"), значит, мы нащупали уязвимость.

Напишем сканер, который проверяет URL на наличие таких ошибок.

import requests

def check_sqli(url):
    # Символ, который часто ломает SQL-запросы
    payload = "'"
    target = f"{url}{payload}"

    try:
        response = requests.get(target)

        # Список типичных ошибок БД в тексте ответа
        errors = [
            "You have an error in your SQL syntax",
            "Warning: mysql_fetch_array()",
            "Unclosed quotation mark after the character string",
            "ORA-01756: quoted string not properly terminated"
        ]

        for error in errors:
            if error in response.text:
                return True
        return False

    except Exception as e:
        print(f"Ошибка соединения: {e}")
        return False

# Тестируем на заведомо уязвимом параметре
# Обычно параметры выглядят как ?id=1, ?cat=2 и т.д.
vulnerable_url = "http://testphp.vulnweb.com/artists.php?artist=1"

if check_sqli(vulnerable_url):
    print(f"[!!!] Возможна SQL-инъекция: {vulnerable_url}")
else:
    print("[-] Уязвимость не обнаружена.")

Этот метод называется Error-Based Detection. Мы не эксплуатируем уязвимость (не крадем данные), а лишь констатируем факт её наличия по реакции сервера.

Работа с сессиями и аутентификацией

Часто уязвимости скрыты за формой входа. Чтобы сканировать такие зоны, скрипт должен уметь «логиниться» и сохранять сессию (cookies). В requests для этого есть объект Session.

import requests

# Создаем сессию
s = requests.Session()

login_url = "http://example.com/login.php"
secure_url = "http://example.com/admin_dashboard.php"

# Данные для входа (имена полей нужно подсмотреть в HTML-коде формы)
credentials = {
    'username': 'admin',
    'password': 'password123',
    'submit': 'Login'
}

# 1. Выполняем вход (POST-запрос)
# Сессия автоматически сохранит полученные cookies
s.post(login_url, data=credentials)

# 2. Переходим на защищенную страницу
# Cookies отправятся автоматически
response = s.get(secure_url)

if "Welcome, admin" in response.text:
    print("Успешный вход!")
else:
    print("Не удалось войти.")

Использование requests.Session() позволяет писать сложные сценарии: зайти на сайт, положить товар в корзину, перейти к оплате и проверить уязвимости на каждом шаге.

Этические и правовые аспекты

Автоматизированные сканеры создают большую нагрузку на сервер. Запуск такого скрипта против чужого ресурса без письменного разрешения может быть расценен как DDoS-атака или попытка взлома.

"С большой силой приходит большая ответственность."

Всегда используйте для тренировки специально созданные уязвимые площадки, такие как DVWA (Damn Vulnerable Web App) или OWASP Juice Shop.

Заключение

Мы рассмотрели, как с помощью Python и пары библиотек можно превратить рутинный процесс поиска уязвимостей в автоматизированный конвейер. Мы научились:

  1. Отправлять HTTP-запросы и обходить простые проверки User-Agent.
  2. Парсить HTML и извлекать ссылки и формы.
  3. Искать скрытые файлы методом перебора.
  4. Детектировать SQL-инъекции по ошибкам сервера.

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

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

Цифровая криминалистика: анализ метаданных файлов и системных логов

Цифровая криминалистика: анализ метаданных файлов и системных логов

Добро пожаловать на четвертый модуль курса «Python в кибербезопасности». В предыдущих статьях мы учились действовать как атакующие: сканировали сети, перехватывали пакеты и искали уязвимости на веб-сайтах. Теперь пришло время сменить шляпу и встать на сторону защиты.

Цифровая криминалистика (Digital Forensics) — это наука о восстановлении и расследовании материалов, обнаруженных на цифровых устройствах. Когда происходит инцидент безопасности (взлом, утечка данных, удаление файлов), именно криминалист должен ответить на вопросы: «Кто это сделал?», «Когда?» и «Как?».

Python является одним из главных инструментов в арсенале форензик-эксперта (от англ. forensics). Он позволяет автоматизировать рутинный анализ гигабайтов логов и извлекать скрытую информацию из тысяч файлов за считанные секунды.

В этой статье мы разберем два ключевых аспекта расследования:

  1. Анализ метаданных: что файлы «помнят» о своих создателях.
  2. Анализ логов: как найти следы хакера в системных журналах.

Метаданные: невидимый паспорт файла

Каждый файл в вашей системе — это не просто набор байтов, содержащий картинку или текст. Это контейнер, к которому прикреплена служебная информация — метаданные.

Метаданные — это «данные о данных». Они могут рассказать, кто создал файл, когда он был изменен в последний раз, какая программа использовалась и даже где географически находился автор в момент создания (если речь о фото).

Работа с временными метками (Timestamps)

Самый базовый уровень метаданных — это временные метки файловой системы. В Python мы можем получить к ним доступ через стандартный модуль os.

import os
import datetime

file_path = 'secret_document.docx'

# Получаем статистику файла
file_stat = os.stat(file_path)

# Извлекаем время (в формате Unix Timestamp)
created = file_stat.st_ctime
modified = file_stat.st_mtime
accessed = file_stat.st_atime

# Конвертируем в читаемый вид
def convert_time(timestamp):
    return datetime.datetime.fromtimestamp(timestamp)

print(f"Создан: {convert_time(created)}")
print(f"Изменен: {convert_time(modified)}")
print(f"Открыт: {convert_time(accessed)}")

Однако, анализируя время, важно понимать, как компьютер его хранит. В большинстве систем используется Unix Timestamp — количество секунд, прошедших с полуночи 1 января 1970 года (UTC).

Математически текущее время TT можно выразить как:

T=Tepoch+ΔtT = T_{epoch} + \Delta t

где TT — текущее время системы, TepochT_{epoch} — начало эпохи Unix (1970-01-01 00:00:00 UTC), а Δt\Delta t — количество секунд, прошедших с этого момента. Понимание этого важно, так как злоумышленники могут подделывать временные метки (техника Timestomping), но несоответствия в Δt\Delta t между разными атрибутами файла могут выдать подделку.

Извлечение EXIF-данных из изображений

Особый интерес для криминалистов представляют изображения. Формат JPEG поддерживает стандарт EXIF (Exchangeable Image File Format), который записывает внутрь картинки настройки камеры, модель телефона и, что самое важное, GPS-координаты.

Для работы с изображениями нам понадобится библиотека Pillow (современный форк PIL):

pip install Pillow

Напишем скрипт, который извлекает метаданные из фото:

from PIL import Image
from PIL.ExifTags import TAGS

def get_exif_data(image_path):
    try:
        image = Image.open(image_path)
        # Получаем сырые EXIF данные
        exif_data = image._getexif()

        if not exif_data:
            return "EXIF данные не найдены"

        result = {}
        for tag_id, value in exif_data.items():
            # Переводим числовые ID тегов в понятные названия
            tag_name = TAGS.get(tag_id, tag_id)
            result[tag_name] = value

        return result
    except IOError:
        return "Ошибка открытия файла"

metadata = get_exif_data("photo.jpg")

# Выводим интересные поля
if isinstance(metadata, dict):
    print(f"Камера: {metadata.get('Make')} {metadata.get('Model')}")
    print(f"Дата съемки: {metadata.get('DateTimeOriginal')}")
    # GPSInfo хранится в сложном формате, его нужно парсить отдельно

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

Анализ системных логов: чтение следов

Если метаданные — это паспорт, то логи (журналы событий) — это дневник, в который система записывает всё, что с ней происходит. Веб-серверы (Apache, Nginx), базы данных, операционные системы — все они генерируют текстовые логи.

Типичная задача: у нас есть лог веб-сервера access.log размером 500 МБ. Нам нужно найти IP-адреса, которые пытались подобрать пароль к админке.

Формат лога Apache

Типичная строка лога выглядит так:

192.168.1.5 - - [10/Oct/2023:13:55:36 +0000] "GET /admin.php HTTP/1.1" 401 2326

Здесь мы видим:

  1. IP-адрес (192.168.1.5)
  2. Дата и время
  3. HTTP-запрос (GET /admin.php)
  4. Код ответа (401 — Unauthorized, ошибка авторизации)

Регулярные выражения (Regex) — лучший друг аналитика

Для разбора таких строк в Python используется модуль re. Регулярные выражения позволяют создать шаблон поиска.

Напишем парсер, который ищет попытки брутфорса (подбора паролей). Признак атаки: много ответов с кодом 401 (ошибка входа) с одного IP.

import re
from collections import Counter

def parse_log(log_file):
    # Шаблон для извлечения IP и кода ответа
    # (\d+\.\d+\.\d+\.\d+) - ищет IP адрес (группа 1)
    # .*? - пропускаем всё до кода ответа
    # "\s(\d{3})\s - ищет код ответа из 3 цифр после кавычки и пробела (группа 2)
    log_pattern = re.compile(r'(\d+\.\d+\.\d+\.\d+).*?"\s(\d{3})\s')

    ip_counter = Counter()

    with open(log_file, 'r') as f:
        for line in f:
            match = log_pattern.search(line)
            if match:
                ip_address = match.group(1)
                status_code = match.group(2)

                # Нас интересуют только ошибки авторизации
                if status_code == '401':
                    ip_counter[ip_address] += 1

    return ip_counter

# Анализируем
suspicious_ips = parse_log('access.log')

print("Подозрительная активность (IP : кол-во неудачных входов):")
for ip, count in suspicious_ips.most_common(5):
    print(f"{ip}: {count}")

Математика аномалий

Как понять, что количество запросов является аномальным? Для этого мы можем рассчитать частоту запросов.

Формула частоты событий:

R=NΔtR = \frac{N}{\Delta t}

где RR — частота (Rate) запросов в секунду, NN — общее количество запросов от одного IP, а Δt\Delta t — временной интервал, за который эти запросы пришли. Если RR превышает определенный порог (например, 5 запросов в секунду к странице логина), скрипт должен пометить этот IP как вредоносный.

PDF-криминалистика

Документы PDF часто используются для фишинга или передачи скрытых данных. Библиотека PyPDF2 позволяет заглянуть внутрь.

import PyPDF2

def get_pdf_metadata(file_name):
    with open(file_name, 'rb') as f:
        pdf = PyPDF2.PdfReader(f)
        info = pdf.metadata

        print(f"[*] Метаданные для: {file_name}")
        if info:
            for key, value in info.items():
                print(f"    {key}: {value}")
        else:
            print("    Метаданные не найдены.")

# Часто в поле '/Producer' или '/Creator' можно найти версию ПО,
# которая может указать на конкретную уязвимость или привычки автора.

Объединение данных (Correlation)

Сила Python в том, что вы можете объединить эти методы. Представьте сценарий расследования:

  1. Вы находите подозрительный файл на сервере.
  2. Скрипт извлекает дату его создания через os.stat.
  3. Скрипт автоматически сканирует логи веб-сервера (access.log) за этот временной промежуток.
  4. Вы находите IP-адрес, который загрузил этот файл, используя POST-запрос.
  5. Вы проверяете этот IP по базам Threat Intelligence.

Такая автоматизация превращает часы ручного поиска в секунды машинной обработки.

Заключение

Мы рассмотрели основы цифровой криминалистики с использованием Python. Вы научились извлекать скрытую информацию из файлов и анализировать поведение пользователей через логи. Эти навыки критически важны для Incident Response (реагирования на инциденты).

Помните, что метаданные можно подделать, а логи — очистить. Профессиональный криминалист никогда не полагается на один источник данных, а ищет перекрестные подтверждения.

В следующем модуле мы погрузимся в мир тайн и шифров. Мы разберем тему «Криптография в Python», научимся шифровать данные и даже попробуем взломать простые шифры методом частотного анализа.

Полезные ссылки

Разработка собственных инструментов для автоматизации тестирования на проникновение

Разработка собственных инструментов для автоматизации тестирования на проникновение

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

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

Почему это важно? Существующие инструменты (Nmap, Metasploit, Burp Suite) великолепны, но они универсальны. В реальной работе пентестера часто возникают уникальные задачи: обойти специфичный фаервол, распарсить нестандартный протокол или автоматизировать цепочку действий, характерную только для целевой инфраструктуры. Именно здесь навык разработки собственных инструментов становится решающим.

Архитектура инструмента безопасности

Хороший инструмент отличается от «скрипта на коленке» тремя вещами:

  1. Удобный интерфейс командной строки (CLI).
  2. Модульность и расширяемость.
  3. Обработка ошибок и логирование.

Работа с аргументами командной строки

В Python стандартом для создания CLI является библиотека argparse. Она позволяет задавать параметры запуска, генерировать справку (--help) и валидировать ввод.

Рассмотрим шаблон профессионального инструмента:

import argparse
import sys

def parse_arguments():
    parser = argparse.ArgumentParser(description="Python Security Toolkit v1.0")

    # Обязательный аргумент: цель
    parser.add_argument("target", help="IP-адрес или домен цели")

    # Опциональные аргументы
    parser.add_argument("-p", "--port", type=int, default=80, help="Целевой порт (по умолчанию: 80)")
    parser.add_argument("-t", "--threads", type=int, default=5, help="Количество потоков")
    parser.add_argument("-o", "--output", help="Файл для сохранения отчета")

    return parser.parse_args()

if __name__ == "__main__":
    args = parse_arguments()
    print(f"[*] Запуск атаки на {args.target} с {args.threads} потоками...")

Теперь ваш скрипт можно запускать как настоящую утилиту: python tool.py 192.168.1.1 -p 443 -t 10.

Параллелизм и многопоточность

В статье про сокеты мы писали сканер портов, который проверял их по одному. Это называется синхронным выполнением. Если проверка одного порта занимает 1 секунду, то проверка 1000 портов займет почти 17 минут. Это недопустимо долго.

Для ускорения мы используем многопоточность (multithreading). В задачах, связанных с сетью (I/O bound), процессор большую часть времени ждет ответа от удаленного сервера. В это время ожидания мы можем запустить другие потоки.

Математика производительности

Оценим теоретический выигрыш во времени при использовании многопоточности. Время выполнения задачи можно описать формулой:

Ttotal≈N×treqK+CT_{total} \approx \frac{N \times t_{req}}{K} + C

Где:

  • TtotalT_{total} — общее время выполнения сканирования.
  • NN — количество задач (например, портов для сканирования).
  • treqt_{req} — среднее время выполнения одного запроса (включая ожидание ответа).
  • KK — количество параллельных потоков.
  • CC — накладные расходы на создание потоков и переключение контекста (обычно пренебрежимо малы при N≫KN \gg K).

Если N=1000N = 1000, treq=1t_{req} = 1 сек, а K=100K = 100, то время сократится с 1000 секунд до примерно 10 секунд. Разница колоссальная.

Реализация с concurrent.futures

Современный Python предлагает высокоуровневый интерфейс concurrent.futures, который удобнее старого модуля threading.

import socket
import concurrent.futures

def scan_port(ip, port):
    try:
        with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
            s.settimeout(1)
            result = s.connect_ex((ip, port))
            if result == 0:
                return port
    except:
        pass
    return None

def run_scanner(target_ip, ports, thread_count):
    open_ports = []
    # Создаем пул потоков
    with concurrent.futures.ThreadPoolExecutor(max_workers=thread_count) as executor:
        # Формируем словарь {future: port}
        future_to_port = {executor.submit(scan_port, target_ip, port): port for port in ports}

        for future in concurrent.futures.as_completed(future_to_port):
            port = future_to_port[future]
            try:
                if future.result():
                    print(f"[+] Порт {port} открыт")
                    open_ports.append(port)
            except Exception as exc:
                print(f"[-] Ошибка при сканировании порта {port}: {exc}")
    return open_ports

# Пример вызова
# run_scanner('192.168.1.1', range(1, 1025), 50)

Фаззинг: автоматизация поиска ошибок

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

Если веб-сервер падает или выдает 500 Internal Server Error при получении строки длиной 10 000 символов, это потенциальная уязвимость переполнения буфера (Buffer Overflow) или отказа в обслуживании (DoS).

Напишем простой фаззер для HTTP-параметров:

import requests
import random
import string

def generate_payload(length):
    # Генерируем случайную строку из букв и цифр
    letters = string.ascii_letters + string.digits
    return ''.join(random.choice(letters) for i in range(length))

def fuzz_url(url):
    # Список длин нагрузок: от 10 байт до 10 КБ
    lengths = [10, 100, 1000, 5000, 10000]

    for length in lengths:
        payload = generate_payload(length)
        try:
            # Отправляем данные в параметре 'data'
            response = requests.get(url, params={'data': payload}, timeout=3)

            print(f"Длина: {length} | Код ответа: {response.status_code}")

            if response.status_code >= 500:
                print(f"[!!!] Возможная уязвимость! Сервер вернул ошибку 500 при длине {length}")

        except requests.exceptions.RequestException as e:
            print(f"[!!!] Сервер перестал отвечать при длине {length}. Возможен DoS.")
            break

# fuzz_url('http://testphp.vulnweb.com/search.php')

Генерация отчетов

Результат работы инструмента безопасности — это не текст в консоли, а отчет. Отчеты нужны для передачи информации клиенту или для интеграции с другими системами. Стандартом обмена данными является JSON.

Добавим функцию сохранения результатов в нашем сканере:

import json
import datetime

def save_report(data, filename):
    report = {
        "timestamp": datetime.datetime.now().isoformat(),
        "tool": "Python Security Scanner",
        "results": data
    }

    with open(filename, 'w', encoding='utf-8') as f:
        json.dump(report, f, indent=4, ensure_ascii=False)

    print(f"[+] Отчет сохранен в {filename}")

Теперь, объединив все части (argparse, threading, scanning, json), мы получаем полноценный инструмент, который можно встроить в CI/CD пайплайн или использовать при регулярных аудитах.

Этика и ответственность

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

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

White Hat (Белые шляпы) используют эти знания для защиты, поиска уязвимостей и их исправления. Black Hat (Черные шляпы) используют их для нанесения вреда. Разница не в коде, а в намерении и наличии разрешения.

Заключение курса

Мы прошли большой путь:

  1. Разобрали работу сокетов и поняли, как компьютеры «разговаривают» друг с другом.
  2. Использовали Scapy для хирургической работы с пакетами и сетевой разведки.
  3. Автоматизировали веб-атаки с Requests и BeautifulSoup.
  4. Научились расследовать инциденты, анализируя метаданные и логи.
  5. И сегодня мы объединили эти знания, создав архитектуру для собственных инструментов автоматизации.

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

Развивайтесь, изучайте новые библиотеки (например, asyncio для асинхронного программирования или selenium для эмуляции браузера) и всегда оставайтесь на светлой стороне силы.

Удачи в ваших исследованиях!