CVE-2025-10966:wolfSSH后端缺失SFTP主机密钥验证
报告摘要
当curl使用wolfSSH后端构建时,lib/vssh/wolfssh.c中的SSH/SFTP实现未执行服务器主机密钥验证,且在curl工具中未提供主机身份验证选项。通过本地构建带wolfSSH的curl(二进制显示wolfssh/1.4.20),确认工具中缺少SSH主机验证选项,并检查了wolfSSH代码路径,发现其连接时未进行任何主机密钥检查或known_hosts处理。
AI使用声明:本报告文本在协助下准备,但以下发现通过本地构建、运行时行为和代码检查手动验证。
受影响版本
本地构建于macOS(Apple Silicon):
$ ./src/curl --version
curl 8.17.0-DEV (aarch64-apple-darwin23.3.0) libcurl/8.17.0-DEV wolfSSL/5.8.2 zlib/1.2.12 brotli/1.1.0 zstd/1.5.7 libidn2/2.3.8 libpsl/0.21.5 wolfssh/1.4.20 nghttp2/1.66.0 librtmp/2.3
Protocols: dict file ftp ftps gopher gophers http https imap imaps ipfs ipns ldap ldaps mqtt pop3 pop3s rtmp rtsp sftp smb smbs smtp smtps telnet tftp ws wss
复现步骤
构建wolfSSH(带SFTP)并本地安装(以macOS/Homebrew为例):
# 前置条件(Homebrew)
brew install wolfssl libpsl libidn2 libnghttp2
# 构建并安装启用SFTP的wolfSSH
git clone --depth=1 https://github.com/wolfSSL/wolfssh.git
cd wolfssh
./autogen.sh
./configure \
--prefix=$HOME/.local/wolfssh \
--with-wolfssl=/opt/homebrew \
--enable-sftp \
--enable-sshclient \
--enable-examples \
CPPFLAGS='-I/opt/homebrew/include' \
LDFLAGS='-L/opt/homebrew/lib'
make -j$(sysctl -n hw.ncpu)
make install
配置并使用wolfSSH构建curl:
cd /Users/$USER/scanner-repos/curl
autoreconf -fi
PKG_CONFIG_PATH="$HOME/.local/wolfssh/lib/pkgconfig:/opt/homebrew/lib/pkgconfig" \
CPPFLAGS="-I$HOME/.local/wolfssh/include -I/opt/homebrew/include" \
LDFLAGS="-L$HOME/.local/wolfssh/lib -L/opt/homebrew/lib" \
./configure \
--without-libssh2 \
--without-libssh \
--with-wolfssh=$HOME/.local/wolfssh \
--with-wolfssl=/opt/homebrew \
--disable-shared
make -j$(sysctl -n hw.ncpu)
验证构建显示wolfSSH:
./src/curl --version
# 输出包含:wolfssh/1.4.20
观察此构建中SSH主机身份选项不可用:
./src/curl --help ssh
# 未列出--ssh-knownhosts或--hostpubsha256
./src/curl --ssh-knownhosts /tmp/kh -vvv sftp://localhost:22/
# 观察到错误:
# curl: option --ssh-knownhosts: is unknown
# 观察到错误:
# curl: option --hostpubsha256: the installed libcurl version does not support this
这些运行时观察结果与wolfSSH后端代码一致,该代码未实现主机密钥验证或known_hosts集成。
lib/vssh/wolfssh.c和lib/vssh/ssh.h中的代码引用(具体行数)
连接设置:无主机密钥验证回调或known_hosts加载,然后立即进入状态机
static CURLcode wssh_connect(struct Curl_easy *data, bool *done)
{
struct connectdata *conn = data->conn;
struct ssh_conn *sshc = Curl_conn_meta_get(conn, CURL_META_SSH_CONN);
struct SSHPROTO *sshp = Curl_meta_get(data, CURL_META_SSH_EASY);
curl_socket_t sock = conn->sock[FIRSTSOCKET];
int rc;
if(!sshc || !sshp)
return CURLE_FAILED_INIT;
/* We default to persistent connections. We set this already in this connect
function to make the reuse checks properly be able to check this bit. */
connkeep(conn, "SSH default");
if(conn->handler->protocol & CURLPROTO_SCP) {
conn->recv[FIRSTSOCKET] = wscp_recv;
conn->send[FIRSTSOCKET] = wscp_send;
}
else {
conn->recv[FIRSTSOCKET] = wsftp_recv;
conn->send[FIRSTSOCKET] = wsftp_send;
}
sshc->ctx = wolfSSH_CTX_new(WOLFSSH_ENDPOINT_CLIENT, NULL);
if(!sshc->ctx) {
failf(data, "No wolfSSH context");
goto error;
}
sshc->ssh_session = wolfSSH_new(sshc->ctx);
if(!sshc->ssh_session) {
failf(data, "No wolfSSH session");
goto error;
}
rc = wolfSSH_SetUsername(sshc->ssh_session, conn->user);
if(rc != WS_SUCCESS) {
failf(data, "wolfSSH failed to set username");
goto error;
}
/* set callback for authentication */
wolfSSH_SetUserAuth(sshc->ctx, userauth);
wolfSSH_SetUserAuthCtx(sshc->ssh_session, data);
rc = wolfSSH_set_fd(sshc->ssh_session, (int)sock);
if(rc) {
failf(data, "wolfSSH failed to set socket");
goto error;
}
#if 0
wolfSSH_Debugging_ON();
#endif
*done = TRUE;
if(conn->handler->protocol & CURLPROTO_SCP)
wssh_state(data, sshc, SSH_INIT);
else
wssh_state(data, sshc, SSH_SFTP_INIT);
return wssh_multi_statemach(data, done);
error:
wssh_sshc_cleanup(sshc);
return CURLE_FAILED_INIT;
}
状态机:wolfSSH_connect()成功后,直接转换到SSH_STOP,无任何SSH_HOSTKEY验证步骤
case SSH_S_STARTUP:
rc = wolfSSH_connect(sshc->ssh_session);
if(rc != WS_SUCCESS)
rc = wolfSSH_get_error(sshc->ssh_session);
if(rc == WS_WANT_READ) {
*block = TRUE;
conn->waitfor = KEEP_RECV;
return CURLE_OK;
}
else if(rc == WS_WANT_WRITE) {
*block = TRUE;
conn->waitfor = KEEP_SEND;
return CURLE_OK;
}
else if(rc != WS_SUCCESS) {
wssh_state(data, sshc, SSH_STOP);
return CURLE_SSH;
}
infof(data, "wolfssh connected");
wssh_state(data, sshc, SSH_STOP);
break;
SSH_HOSTKEY状态存在于通用SSH状态枚举中,但未被上述wolfSSH状态机使用
typedef enum {
SSH_NO_STATE = -1, /* Used for "nextState" so say there is none */
SSH_STOP = 0, /* do nothing state, stops the state machine */
SSH_INIT, /* First state in SSH-CONNECT */
SSH_S_STARTUP, /* Session startup */
SSH_HOSTKEY, /* verify hostkey */
SSH_AUTHLIST,
SSH_AUTH_PKEY_INIT,
SSH_AUTH_PKEY,
SSH_AUTH_PASS_INIT,
SSH_AUTH_PASS,
SSH_AUTH_AGENT_INIT, /* initialize then wait for connection to agent */
SSH_AUTH_AGENT_LIST, /* ask for list then wait for entire list to come */
SSH_AUTH_AGENT, /* attempt one key at a time */
SSH_AUTH_HOST_INIT,
SSH_AUTH_HOST,
SSH_AUTH_KEY_INIT,
SSH_AUTH_KEY,
SSH_AUTH_GSSAPI,
SSH_AUTH_DONE,
SSH_SFTP_INIT,
SSH_SFTP_REALPATH, /* Last state in SSH-CONNECT */
支持材料/参考
本地wolfSSH构建的工具帮助和错误输出:
$ ./src/curl --help ssh
ssh: SSH protocol
--compressed-ssh Enable SSH compression
--hostpubmd5 <md5> Acceptable MD5 hash of host public key
--hostpubsha256 <sha256> Acceptable SHA256 hash of host public key
-k, --insecure Allow insecure server connections
--key <key> Private key filename
--pass <phrase> Passphrase for the private key
--pubkey <key> SSH Public key filename
$ ./src/curl --ssh-knownhosts /tmp/kh -vvv sftp://localhost:22/
curl: option --ssh-knownhosts: is unknown
curl: option --hostpubsha256: the installed libcurl version does not support this
这些观察结果,结合上述wolfSSH连接和状态机代码,表明此后端未实现主机身份验证。
影响
摘要
如果curl/libcurl使用wolfSSH后端构建并用于SFTP,则不会验证服务器主机身份。在使用wolfSSH后端的环境中,这允许对SSH/SFTP连接进行潜在的中间人攻击,因为客户端实际上接受任何呈现的服务器主机密钥(无known_hosts或指纹强制执行,且状态机中无主机密钥验证步骤)。
时间线记录
- 2025年9月23日,下午3:14 UTC:giant_anteater向curl提交报告
- 2025年9月23日,下午4:35 UTC:bagder(curl工作人员)回复确认收到报告
- 2025年9月24日,上午4:35 UTC:bagder确认问题正确,建议移除wolfSSH支持
- 2025年9月24日,上午6:35 UTC:giant_anteater强调这是一个具体的安全缺陷
- 2025年9月24日,下午8:57 UTC:bagder确认问题存在但认为严重性较低
- 2025年9月24日,下午9:07 UTC:bagder移除wolfSSH支持(PR #18700)
- 2025年9月25日,上午4:06 UTC:giant_anteater请求将此作为安全问题处理并分配CVE
- 2025年9月25日,上午6:48 UTC:bagder将严重性从高调整为低,状态改为"已分类"
- 2025年9月25日,上午8:10 UTC:bagder发布安全公告草案
- 2025年9月25日,上午11:12 UTC:更新CVE引用为CVE-2025-10966
- 2025年10月29日,上午8:55 UTC:通知distros@openwall邮件列表
- 8天前:报告关闭,状态改为"已解决",并公开披露
最终状态
- 报告ID:#3355218
- 状态:已解决
- 严重性:低(0.1 ~ 3.9)
- 披露时间:2025年11月5日,下午9:57 UTC
- 弱点:不当证书验证
- CVE ID:CVE-2025-10966
- 赏金:无
curl项目决定移除wolfSSH支持作为解决方案,认为该后端实际用户极少,且KNOWN_BUGS文档中已记录其功能缺陷。