CVE-2025-32433:Erlang/OTP 严重 SSH 漏洞允许未认证的远程代码执行

对最近爆出的CVE-2025-32433进行分析

一、漏洞简介

在 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

1. 安装依赖包

首先,安装构建和运行 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

2. 克隆并构建 Erlang/OTP

接下来,克隆 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

3. 编译 Erlang 代码

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

4. 生成 SSH 密钥

生成一个适合 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

5. 启动 Erlang 服务器

通过 erl 启动 Erlang shell,并运行 SSH 服务器:

erl -noshell -pa /root -s ssh_server start

6. 测试连接

之后可以使用ssh进行登录,使用私钥文件

五、漏洞复现

欧克,知道了具体的流程之后,就很容易,使用了https://github.com/ProDefense/CVE-2025-32433/blob/main/CVE-2025-32433.py,将其中的命令部分改成

command = 'file:write_file("/1.txt", os:cmd("ifconfig")).'

image.png

最后在根目录下发现文件被创建了

image.png

六、修复建议

1.关闭ssh服务器

2.更新到新版本

七、总结

感觉这个漏洞确实过于存在缺陷了,一般都想不到这个问题。最有意思的poc可以说是完全依赖于大模型进行编写的,确实是好用的;但因为官方的补丁里是给出了验证的流程,所以对于单纯依靠漏洞描述进行POC编写,感觉目前的大模型能力存疑,不过确实已经发挥了很大的作用。

参考文献:

  • 发表于 2025-05-08 15:00:00
  • 阅读 ( 709 )
  • 分类:Web应用

0 条评论

请先 登录 后评论
cipher
cipher

1 篇文章

站长统计