| name | performing-ot-vulnerability-scanning-safely |
| description | Perform vulnerability scanning in OT/ICS environments safely using passive monitoring, native protocol queries, and carefully controlled active scanning with Tenable OT Security to identify vulnerabilities without disrupting industrial processes or crashing legacy controllers.
|
| domain | cybersecurity |
| subdomain | ot-ics-security |
| tags | ["ot-security","ics","vulnerability-scanning","tenable","nessus","passive-scanning","risk-management","nist"] |
| version | 1.0 |
| author | mahipal |
| license | Apache-2.0 |
| nist_csf | ["PR.IR-01","DE.CM-01","ID.AM-05","GV.OC-02"] |
| mitre_attack | ["T1078","T1190","T1059","T1046","T0816"] |
Performing OT Vulnerability Scanning Safely
When to Use
- When conducting vulnerability assessments in OT environments with legacy controllers
- When implementing continuous vulnerability monitoring without impacting process availability
- When preparing for IEC 62443 or NERC CIP compliance audits requiring vulnerability data
- When evaluating risk-based patching priorities for OT assets
- When validating that compensating controls protect unpatched ICS devices
Do not use for aggressive active scanning of production PLCs (can crash legacy controllers), for IT vulnerability scanning using standard Nessus profiles on OT networks, or for penetration testing of live OT systems (see performing-ics-penetration-testing).
Prerequisites
- Tenable OT Security (formerly Tenable.ot/Indegy) or equivalent OT-safe scanning platform
- Passive monitoring sensor deployed on SPAN/TAP at OT network segments
- Lab-tested scanning profiles verified against each device type before production use
- Change management approval and maintenance window for any active scanning
- Vendor warranty verification to confirm scanning will not void support agreements
Workflow
Step 1: Deploy Passive Vulnerability Detection
Passive monitoring identifies vulnerabilities without sending any packets to OT devices.
"""OT Safe Vulnerability Scanner Orchestrator.
Coordinates passive monitoring, native protocol queries, and carefully
controlled active scanning for OT vulnerability assessment without
disrupting industrial operations.
"""
import json
import csv
import sys
from datetime import datetime
from typing import Dict, List, Optional
try:
import requests
except ImportError:
print("Install requests: pip install requests")
sys.exit(1)
class OTVulnerabilityScanner:
SCAN_SAFETY_LEVELS = {
: {
: ,
: ,
: [, , ],
: ,
},
: {
: ,
: ,
: [, , , ],
: ,
},
: {
: ,
: ,
: [, , ],
: ,
},
}
():
.tenable_url = tenable_url.rstrip()
.session = requests.Session()
.session.headers.update({
: ,
: ,
})
.session.verify = verify_ssl
.findings = []
() -> :
checks = {
: scan_level,
: target_subnet,
: .SCAN_SAFETY_LEVELS[scan_level],
: [],
: [],
: ,
}
prerequisites = [
{
: ,
: ,
: [, ],
},
{
: ,
: ,
: [, ],
},
{
: ,
: ,
: [, ],
},
{
: ,
: ,
: [],
},
{
: ,
: ,
: [],
},
{
: ,
: ,
: [, , ],
},
]
prereq prerequisites:
scan_level prereq[]:
checks[].append(prereq[])
checks
():
()
()
:
resp = .session.get(
,
params={: site_id}
)
resp.raise_for_status()
assets = resp.json().get(, [])
asset assets:
asset_id = asset.get()
vuln_resp = .session.get(
)
vuln_resp.status_code == :
vulns = vuln_resp.json().get(, [])
vuln vulns:
.findings.append({
: asset.get(, ),
: asset.get(, ),
: asset.get(, ),
: asset.get(, ),
: vuln.get(, ),
: vuln.get(, ),
: vuln.get(, ),
: vuln.get(, ),
: ,
: vuln.get(, ),
})
()
requests.RequestException e:
()
():
.findings.sort(key= x: x.get(, ), reverse=)
()
()
()
()
()
severity_counts = {}
f .findings:
sev = f.get(, )
severity_counts[sev] = severity_counts.get(sev, ) +
()
sev [, , , ]:
()
()
()
i, finding (.findings[:], ):
()
()
()
()
()
()
finding.get():
()
output_file:
(output_file, , newline=) f:
writer = csv.DictWriter(f, fieldnames=.findings[].keys())
writer.writeheader()
writer.writerows(.findings)
()
__name__ == :
scanner = OTVulnerabilityScanner(
tenable_url=,
api_key=,
verify_ssl=,
)
safety_check = scanner.check_safety_prerequisites(, )
()
scanner.run_passive_assessment(site_id=)
scanner.generate_prioritized_report()