
I just pushed a major version of my open-source FortiGate auditing tool. This one took significant engineering work — the codebase went from 2,555 lines to 5,695 lines and grew from 25 to 35 audit domains with 10 completely new modules.
Here's what actually changed, added, and why.
🔗 github.com/jafartavana01/fortigate-utm-analyzer
━━━━━━━━━━━━━━━━━━━━━━━━
Background
━━━━━━━━━━━━━━━━━━━━━━━━
This tool reads a FortiGate show full-configuration backup and produces severity-tagged security reports — CRITICAL / HIGH / MEDIUM / INFO — across every layer of the device. No dependencies, no cloud, no API keys. Just:
python3 utm.py -c firewall.conf --all
v1.0 covered 5 domains. v2.0 expanded to 13. v3.0 added FortiOS 8.0 syntax support. v4.0 is the biggest jump yet.
━━━━━━━━━━━━━━━━━━━━━━━━
What was ADDED in v4.0
━━━━━━━━━━━━━━━━━━━━━━━━
- Policy Intelligence Module (-intel)
This is the one I've wanted to build since day one. Two checks that matter enormously in real environments:
Shadowed policy detection. If Policy #1 is "all → all ACCEPT" and Policy #2 below it is "10.0.0.0/8 → DMZ ACCEPT", Policy #2 will never be evaluated. The firewall processes rules top-down. The earlier broad rule always wins. In environments where multiple engineers have been adding rules for 5+ years without a full review, 10-20% of rules being effectively dead is not unusual. The tool now flags every one of these as HIGH.
Duplicate policy detection. Same srcintf, dstintf, srcaddr, dstaddr, service, action — two policies doing the exact same thing. Common after copy-paste policy management. HIGH severity, automated detection.
Coverage statistics with visual bars. Every accept policy is counted: how many have IPS? SSL inspection? Antivirus? DLP? The output looks like this:
IPS [████████░░░░░░░░░░░░] 40% (80/200)
SSL Inspect [██████░░░░░░░░░░░░░░] 30% (60/200)
Antivirus [████████████░░░░░░░░] 60% (120/200)
Logging [██████████████░░░░░░] 70% (140/200)
Management loves this slide. Security teams need this number.
- Unused Objects Audit (-unused)
Parses every object type and cross-references against all policy fields:
- Address objects
- Address groups
- Custom services
- Service groups
- VIPs (Medium severity — see below)
- IP pools
- Schedules
The interesting one is VIPs. An "unused" VIP — a port-forward with no matching firewall policy — may still be active at the NAT level. The FortiGate will perform the address translation even if no policy explicitly permits the traffic in some configurations. A forgotten VIP for RDP from 3 years ago is an audit finding regardless. Flagged MEDIUM to force a human decision rather than automatic dismissal.
- VIP / Port-Forward Security Audit (-vip)
37 risky port definitions with per-port severity. The logic is simple: if you have a VIP that maps TCP/3389 from the internet to an internal host, that's an RDP server directly exposed to the internet. BlueKeep, credential brute-force, and ransomware operators have been scanning for this since 2019.
CRITICAL ports: RDP (3389), SMB (445), MySQL (3306), Redis (6379), PostgreSQL (5432), MongoDB (27017), VNC (5900), MS-SQL (1433), Telnet (23), Radmin (4899)
HIGH ports: LDAP (389), RPC (135), NetBIOS (137/138/139), WinRM (5985/5986), FTP (21)
MEDIUM ports: SMTP (25), HTTP (80), DNS (53)
Also checks: source filter missing (any internet IP can hit the mapped host), VIP defined but not referenced in any policy.
- Full Anti-DoS Protection Audit (-antidos)
The existing -dos flag was basic. The new -antidos flag does a proper deep audit.
New anomaly sub-block parser reads every sensor individually:
- Status (enable/disable)
- Action (block vs monitor/pass)
- Threshold value (too high = never fires, 0 = broken)
- Logging enabled
Critical anomalies checked: tcp_syn_flood, tcp_ack_flood, tcp_synack_flood, udp_flood, icmp_flood, ip_src_session, tcp_port_scan, ip_bad_option, ip_land_attack, sctp_flood
Action = monitor in production is HIGH severity. The sensor is technically enabled but never drops a packet. It just logs.
Threshold = 999999 on tcp_syn_flood is also flagged. The sensor will never trigger under any realistic attack. That's as bad as disabled.
NP7/NP6 hardware offload checks: HPE (Host Protection Engine) configuration, fp-anomaly status. On NP-capable hardware, DoS mitigation that doesn't use ASIC offload hits the CPU instead. Under a real SYN flood, that's the difference between the device staying up and going unresponsive.
TCP timer hardening: tcp-halfopen-timer > 10s, anti-replay not set to strict, ip-src-check-on-tx disabled.
Scored 0-10 with ASCII bar and grade: STRONG / ACCEPTABLE / WEAK / POOR / CRITICAL.
- SD-WAN Audit (-sdwan)
New nested parser handles the full SD-WAN config structure: members, health-checks, and service rules as separate sub-blocks.
The most important finding is CRITICAL: if SD-WAN is enabled but has zero health-checks configured, link failures are completely undetected. The device has no way to know a WAN member went down and will never failover. SD-WAN without health monitoring is just load-balancing with a broken failover mechanism.
Also checks: per-member monitoring gaps (each member not covered by any health-check = HIGH), missing latency/jitter/packet-loss thresholds (links with poor quality continue to carry traffic), slow probe intervals (delays failure detection by minutes), missing traffic steering rules.
- Interface Exposure Score (-iface)
Each interface gets a 0-10 exposure score based on weighted protocol analysis:
Telnet weight: 10 (cleartext — worst)
HTTP weight: 9 (cleartext management)
SNMP weight: 8 (community string exposure)
FMG-Access weight: 7 (FortiManager on WAN)
Fabric weight: 6 (Security Fabric facing internet)
SSH weight: 5 (brute-force target)
HTTPS weight: 4 (GUI exposed)
Ping weight: 2 (ICMP recon)
Output per interface:
wan1 [█████████░] 9.2/10 — CRITICAL EXPOSURE
port1 [██░░░░░░░░] 2.1/10 — LOW EXPOSURE
- Certificate Audit (-cert)
Parses config vpn certificate local/ca/remote.
Expiry detection with exact days remaining. CRITICAL if already expired, HIGH if ≤ 30 days, MEDIUM if ≤ 90 days. RSA key size < 2048 bits = CRITICAL (cryptographically broken). Self-signed certificate detection on external services. Auto-renew status.
Note in the report: config backups contain cert metadata but not full DER content. The tool also outputs the CLI commands to run on the device for a live expiry check.
- Fabric Connector / Cloud Integration Audit (-fabric)
Covers AWS, Azure, GCP SDN connectors, external resources, EMS, ZTNA, and FortiSASE.
The AWS finding that appears most often in real configs: static access-key and secret-key stored directly in the FortiGate config. If someone emails that config backup or puts it in the wrong S3 bucket, those AWS credentials are compromised. The fix is IAM role with use-metadata-iam enable. The tool flags this HIGH and tells you exactly why.
Azure client-secret in config gets the same treatment. EMS checks include HTTPS certificate verification and fingerprint pinning — without both, FortiGate will accept any certificate from the EMS server.
ZTNA coverage check: what percentage of your accept policies actually enforce Zero Trust? 0% with ZTNA configured is flagged.
- AI / LLM Application Security Audit (-ai)
This one is new territory for FortiGate auditing tools.
FortiOS 8.0 introduced LLM profiles specifically for AI traffic inspection. The audit checks every quality dimension of those profiles: DLP check (are employees sending source code to ChatGPT?), PII check (are names/SSNs/medical data going to AI APIs?), prompt injection detection, jailbreak detection, logging.
The finding that will matter most for enterprise customers in 2025: Chinese AI data sovereignty. DeepSeek, Ernie (Baidu), Qwen (Alibaba), Kimi (Moonshot) are flagged CRITICAL. Data sent to these services is subject to Chinese PIPL and national security law requirements that are incompatible with GDPR, HIPAA, and most enterprise data governance policies. This is a compliance issue, not just a security preference.
20 AI services in the risk table with per-service severity and risk description. Governance framework section in the report output with phased implementation approach.
- Enhanced Local-in Hardening
Previous versions checked local-in policies in isolation. v4.0 adds a combined check that is significantly more impactful:
WAN interface with management access (HTTPS or SSH in allowaccess) AND no local-in-policy restricting source IPs = CRITICAL.
This is the check that should appear on every FortiGate hardening audit. The interface allowaccess setting says what protocols the device responds to. The local-in-policy says who can reach those protocols. Both must be configured together. Having HTTPS on wan1 with no local-in restriction means any IP on the internet can reach your FortiGate management GUI. That's not a misconfiguration — that's the default.
━━━━━━━━━━━━━━━━━━━━━━━━
What was CHANGED in v4.0
━━━━━━━━━━━━━━━━━━━━━━━━
Parser architecture refactor. main() was moved to the very end of the file. In Python, functions must be defined before they're called. With 58 parsers and 35 weakness functions, the ordering matters. The file now reads: parsers → weakness functions → report generators → main(). Adding a new module no longer risks a NameError.
Object reference engine. The unused objects module required building a full cross-reference map across all policy fields simultaneously — srcaddr, dstaddr, service, schedule, poolname, all UTM profile fields, plus what address groups and service groups reference internally. This is a non-trivial graph traversal built from scratch.
Anomaly sub-block parser. The DoS policy parser was rewritten to capture the nested anomaly config block properly. The old parser only captured top-level policy fields. The new parse_dos_policy_full() reads status/action/threshold/log per individual anomaly sensor.
SD-WAN nested parser. Standard extract_block() couldn't handle SD-WAN's three-level nesting (policy → members block → edit → set, policy → health-check block → edit → set). New dedicated parser handles all three sub-blocks correctly.
Enhanced local-in logic. The old check looked at each local-in-policy in isolation. The new check correlates interface allowaccess against the local-in-policy table to find gaps — the combination that creates real risk, not either field independently.
━━━━━━━━━━━━━━━━━━━━━━━━
What was REMOVED in v4.0
━━━━━━━━━━━━━━━━━━━━━━━━
Nothing removed. All flags from v1.0 through v3.0 still work identically. The tool is fully backward compatible — run it against a FortiOS 6.2 config and it handles the flat profile format, run it against an 8.0 config and it handles per-protocol sub-blocks. Version detection is automatic.
━━━━━━━━━━━━━━━━━━━━━━━━
Numbers
━━━━━━━━━━━━━━━━━━━━━━━━
v1.0 → v4.0 progression:
Lines of code: ~800 → 5,695
Audit domains: 5 → 35
Security checks: 40 → 300+
CLI flags: 5 → 30
Output reports: 5 → 22
New parsers (v4): 0 → 25
New weakness fns: 0 → 10
━━━━━━━━━━━━━━━━━━━━━━━━
What's next
━━━━━━━━━━━━━━━━━━━━━━━━
BGP security audit (route filtering, MD5 authentication, max-prefix, prefix-list coverage) is the biggest remaining gap. VDOM-aware parsing for multi-tenant environments. Multi-device runner that accepts a directory of config files and produces a consolidated JSON — the natural integration point for a Splunk dashboard.
Clone it, run it on a backup, open an issue if something's wrong or missing.
🔗 github.com/jafartavana01/fortigate-utm-analyzer
Top comments (0)