CVE-2025-26465: MitM attack against OpenSSH’s VerifyHostKeyDNS-enabledclientCVE-2025-26466: DoS attack against OpenSSH’s client and server原文:https://seclists.org/oss-sec/2025/q1/144
Background
OpenSSH的代码库中大量使用了如下范式:
1387 int1388 sshkey_to_base64(const struct sshkey *key, char **b64p)1389 {1390 int r = SSH_ERR_INTERNAL_ERROR;....1398 if ((r = sshkey_putb(key, b)) != 0)1399 goto out;1400 if ((uu = sshbuf_dtob64_string(b, 0)) == NULL) {1401 r = SSH_ERR_ALLOC_FAIL;1402 goto out;1403 }....1409 r = 0;1410 out:....1413 return r;1414 }
即用goto处理异常情况,根据返回值来报告出错的情况(看来祖师爷说过不要用goto是有道理的)。在上述代码中,如果没有1401行,在sshbuf_dtob64_string
出错后,就没有对返回值r
进行赋值,sshkey_to_base64
则会返回0
在[CVE-2023-2283][https://securitylab.github.com/advisories/GHSL-2023-085_libssh/]里中的pki_verify_data_signature
函数中:
int rc = SSH_ERROR;rc = pki_key_check_hash_compatible(pubkey, signature->hash_type);if (rc != SSH_OK) { return SSH_ERROR;}ctx = EVP_MD_CTX_new();if (ctx == NULL) { SSH_LOG(SSH_LOG_TRACE, "Failed to create EVP_MD_CTX: %s", ERR_error_string(ERR_get_error(), NULL)); goto out;}/* Verify the signature */evp_rc = EVP_DigestVerifyInit(ctx, NULL, md, NULL, pkey);if (evp_rc != 1){ SSH_LOG(SSH_LOG_TRACE, "EVP_DigestVerifyInit() failed: %s", ERR_error_string(ERR_get_error(), NULL)); goto out;}/...../out: if (ctx != NULL) { EVP_MD_CTX_free(ctx); } EVP_PKEY_free(pkey); return rc;
第二行首先将rc
返回值赋值为SSH_OK
可以发现如果EVP_DigestVerifyInit
报错,返回值不为1,就会直接跳转到out返回,并没有对rc进行重新赋值,函数的返回值仍然为SSH_OK
Experiments
研究人员认为在OpenSSH的代码库中,可能仍然存在其他遗漏返回值赋值语句的情况。于是他们进行了两个实验:首先利用CodeQL来审计所有使用goto out
但忘记重新赋值返回值的函数。CodeQL发现了50个结果,其中有37个是误报。
接着他们进一步人工审计了所有使用goto
的函数,发现之前CodeQL的结果里并没有漏报(Ctrl-F大法)。在剩下的13个函数中,虽然实现逻辑上有错误,并不会对安全性有影响。
尽管如此,在CtrlF审计中,他们发现了本次漏洞的主角verify_host_key_callback
。
------------------------------------------------------------------------ 93 static int 94 verify_host_key_callback(struct sshkey *hostkey, struct ssh *ssh) 95 { ... 101 if (verify_host_key(xxx_host, xxx_hostaddr, hostkey, 102 xxx_conn_info) == -1) 103 fatal("Host key verification failed."); 104 return 0; 105 }------------------------------------------------------------------------1470 int1471 verify_host_key(char *host, struct sockaddr *hostaddr, struct sshkey *host_key,1472 const struct ssh_conn_info *cinfo)1473 {....1538 if (options.verify_host_key_dns) {....1543 if ((r = sshkey_from_private(host_key, &plain)) != 0)1544 goto out;....1571 out:....1580 return r;1581 }------------------------------------------------------------------------
在verify_host_key_dns
被启用的情况下,sshkey_from_private
的非0返回值会被直接返回,交给verify_host_key_callback
处理;然而verify_host_key_callback
只考虑了返回值为-1
的情况。实际上,sshkey_from_private
的返回值还有很多,比如-10 (SSH_ERR_INVALID_ARGUMENT), -14 (SSH_ERR_KEY_TYPE_UNKNOWN)还有-2 (SSH_ERR_ALLOC_FAIL)
。
所以只要sshkey_from_private
返回一个非0且非-1的值,verify_host_key_callback
就会返回0,从而绕过了对主机HostKey的检测。
但是就目前为止,研究人员发现唯一的可能是让它返回SSH_ERR_ALLOC_FAIL
,即OOM错误。因此,还需要寻找一个漏洞来耗尽客户端的内存。
耗尽内存:DoS attack against OpenSSH’s client and server
经过人工审计,研究人员成功找到了一个无限制的内存泄露漏洞
简单来说,在整个认证过程中,如果客户端收到了SSH2_MSG_PING
类型的数据,它就会去返回一个PONG
数据。在密钥协商过程之外,这个实现是直接把PONG
包添加到output
缓冲区末尾,而这个缓冲区有一个上限SSHBUF_SIZE_MAX
。但是在密钥协商过程中,PONG
的实现变成了调用sshbuf_new()
并添加到一个list
中。OpenSSH并没有对PONG
包的数量进行限制,因此可以发送大量PING
包来让客户端分配内存给PONG
,且分配的内存到密钥协商完全结束后才会被释放,完美符合利用的要求。
除此之外,在密钥协商结束后,客户端会将所有列表中的PONG
包重新添加到output
缓冲区。但是这里的算法复杂度到达了O(n^2):每有一个PONG
包,客户端会malloc()
一个新的缓冲区,然后将旧缓冲区的数据复制到新的缓冲区。
作者进行了一个计算:如果服务器发送128MB的PING
,客户端就需要不断复制总共32TB的数据,进而耗尽CPU资源。
小结
将这两个漏洞结合起来,服务器只要在协商中发生大量PING
包耗尽客户端资源,sshkey_from_private
就会返回SSH_ERR_ALLOC_FAIL
,在客户端启用verify_host_key_dns
的情况下就能绕过对服务器HostKey的校验。
原文始发于微信公众号(安全研究GoSSIP):G.O.S.S.I.P 安全漏洞分析 2025-0220 OpenSSH CVE-2025-26465/26466
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论