From 38d2eec065eb47525559c3d3670b53ce68c9a3b1 Mon Sep 17 00:00:00 2001 From: pemontto Date: Wed, 27 Jun 2018 03:03:08 +1000 Subject: [PATCH] Tenable.io support (#70) * Basic tenable.io support * Add tenable config section * Use existing variable * Fix indent * Fix paren * Use ternary syntax * Update Logstash config for tenable.io --- configs/frameworks_example.ini | 11 +++++ logstash/1000_nessus_process_file.conf | 66 +++++++++++++++++++++----- vulnwhisp/frameworks/nessus.py | 9 ++-- vulnwhisp/vulnwhisp.py | 26 +++++++--- 4 files changed, 90 insertions(+), 22 deletions(-) diff --git a/configs/frameworks_example.ini b/configs/frameworks_example.ini index 704ed99..a9a6a89 100755 --- a/configs/frameworks_example.ini +++ b/configs/frameworks_example.ini @@ -9,6 +9,17 @@ db_path=/opt/vulnwhisperer/database trash=false verbose=true +[tenable] +enabled=true +hostname=cloud.tenable.com +port=443 +username=tenable.io_username +password=tenable.io_password +write_path=/opt/vulnwhisperer/tenable/ +db_path=/opt/vulnwhisperer/database +trash=false +verbose=true + [qualys] #Reference https://www.qualys.com/docs/qualys-was-api-user-guide.pdf to find your API enabled = true diff --git a/logstash/1000_nessus_process_file.conf b/logstash/1000_nessus_process_file.conf index 8c903bc..60e1920 100644 --- a/logstash/1000_nessus_process_file.conf +++ b/logstash/1000_nessus_process_file.conf @@ -12,19 +12,26 @@ input { tags => "nessus" type => "nessus" } + file { + path => "/opt/vulnwhisperer/tenable/*.csv" + start_position => "beginning" + tags => "tenable" + type => "tenable" + } } filter { - if "nessus" in [tags]{ + if "nessus" in [tags] or "tenable" in [tags] { # Drop the header column if [message] =~ "^Plugin ID" { drop {} } csv { - columns => ["plugin_id", "cve", "cvss", "risk", "asset", "protocol", "port", "plugin_name", "synopsis", "description", "solution", "see_also", "plugin_output"] + # columns => ["plugin_id", "cve", "cvss", "risk", "asset", "protocol", "port", "plugin_name", "synopsis", "description", "solution", "see_also", "plugin_output"] + columns => ["plugin_id", "cve", "cvss", "risk", "asset", "protocol", "port", "plugin_name", "synopsis", "description", "solution", "see_also", "plugin_output", "asset_uuid", "vulnerability_state", "ip", "fqdn", "netbios", "operating_system", "mac_address", "plugin_family", "cvss_base", "cvss_temporal", "cvss_temporal_vector", "cvss_vector", "cvss3_base", "cvss3_temporal", "cvss3_temporal_vector", "cvss_vector", "system_type", "host_start", "host_end"] separator => "," source => "message" } - + ruby { code => "if event.get('description') event.set('description', event.get('description').gsub(92.chr + 'n', 10.chr).gsub(92.chr + 'r', 13.chr)) @@ -42,7 +49,7 @@ filter { event.set('plugin_output', event.get('plugin_output').gsub(92.chr + 'n', 10.chr).gsub(92.chr + 'r', 13.chr)) end" } - + #If using filebeats as your source, you will need to replace the "path" field to "source" grok { match => { "path" => "(?[a-zA-Z0-9_.\-]+)_%{INT:scan_id}_%{INT:history_id}_%{INT:last_updated}.csv$" } @@ -71,24 +78,57 @@ filter { mutate { add_field => { "risk_number" => 4 }} } - if [cve] == "nan" { + if ![cve] or [cve] == "nan" { mutate { remove_field => [ "cve" ] } } - if [cvss] == "nan" { + if ![cvss] or [cvss] == "nan" { mutate { remove_field => [ "cvss" ] } } - if [see_also] == "nan" { - mutate { remove_field => [ "see_also" ] } + if ![cvss_base] or [cvss_base] == "nan" { + mutate { remove_field => [ "cvss_base" ] } } - if [description] == "nan" { + if ![cvss_temporal] or [cvss_temporal] == "nan" { + mutate { remove_field => [ "cvss_temporal" ] } + } + if ![cvss_temporal_vector] or [cvss_temporal_vector] == "nan" { + mutate { remove_field => [ "cvss_temporal_vector" ] } + } + if ![cvss_vector] or [cvss_vector] == "nan" { + mutate { remove_field => [ "cvss_vector" ] } + } + if ![cvss3_base] or [cvss3_base] == "nan" { + mutate { remove_field => [ "cvss3_base" ] } + } + if ![cvss3_temporal] or [cvss3_temporal] == "nan" { + mutate { remove_field => [ "cvss3_temporal" ] } + } + if ![cvss3_temporal_vector] or [cvss3_temporal_vector] == "nan" { + mutate { remove_field => [ "cvss3_temporal_vector" ] } + } + if ![description] or [description] == "nan" { mutate { remove_field => [ "description" ] } } - if [plugin_output] == "nan" { + if ![mac_address] or [mac_address] == "nan" { + mutate { remove_field => [ "mac_address" ] } + } + if ![netbios] or [netbios] == "nan" { + mutate { remove_field => [ "netbios" ] } + } + if ![operating_system] or [operating_system] == "nan" { + mutate { remove_field => [ "operating_system" ] } + } + if ![plugin_output] or [plugin_output] == "nan" { mutate { remove_field => [ "plugin_output" ] } } - if [synopsis] == "nan" { + if ![see_also] or [see_also] == "nan" { + mutate { remove_field => [ "see_also" ] } + } + if ![synopsis] or [synopsis] == "nan" { mutate { remove_field => [ "synopsis" ] } } + if ![system_type] or [system_type] == "nan" { + mutate { remove_field => [ "system_type" ] } + } mutate { remove_field => [ "message" ] @@ -170,8 +210,8 @@ filter { } output { - if "nessus" in [tags] or [type] == "nessus" { - #stdout { codec => rubydebug } + if "nessus" in [tags] or "tenable" in [tags] or [type] in [ "nessus", "tenable" ] { + # stdout { codec => rubydebug } elasticsearch { hosts => [ "localhost:9200" ] index => "logstash-vulnwhisperer-%{+YYYY.MM}" diff --git a/vulnwhisp/frameworks/nessus.py b/vulnwhisp/frameworks/nessus.py index e5002d3..4db0134 100755 --- a/vulnwhisp/frameworks/nessus.py +++ b/vulnwhisp/frameworks/nessus.py @@ -149,7 +149,7 @@ class NessusAPI(object): req = self.request(query, data=data, method='POST') return req - def download_scan(self, scan_id=None, history=None, export_format="", chapters="", dbpasswd=""): + def download_scan(self, scan_id=None, history=None, export_format="", chapters="", dbpasswd="", profile=""): running = True counter = 0 @@ -162,7 +162,7 @@ class NessusAPI(object): req = self.request(query, data=json.dumps(data), method='POST', json=True) try: file_id = req['file'] - token_id = req['token'] + token_id = req['token'] if 'token' in req else req['temp_token'] except Exception as e: print("[ERROR] %s" % e) print('Download for file id ' + str(file_id) + '.') @@ -178,7 +178,10 @@ class NessusAPI(object): print("") print("") - content = self.request(self.EXPORT_TOKEN_DOWNLOAD.format(token_id=token_id), method='GET', download=True) + if profile=='tenable': + content = self.request(self.EXPORT_FILE_DOWNLOAD.format(scan_id=scan_id, file_id=file_id), method='GET', download=True) + else: + content = self.request(self.EXPORT_TOKEN_DOWNLOAD.format(token_id=token_id), method='GET', download=True) return content @staticmethod diff --git a/vulnwhisp/vulnwhisp.py b/vulnwhisp/vulnwhisp.py index e69794f..94e298b 100755 --- a/vulnwhisp/vulnwhisp.py +++ b/vulnwhisp/vulnwhisp.py @@ -176,7 +176,7 @@ class vulnWhispererBase(object): class vulnWhispererNessus(vulnWhispererBase): - CONFIG_SECTION = 'nessus' + CONFIG_SECTION = None def __init__( self, @@ -187,7 +187,10 @@ class vulnWhispererNessus(vulnWhispererBase): debug=False, username=None, password=None, + profile='nessus' ): + self.CONFIG_SECTION=profile + super(vulnWhispererNessus, self).__init__(config=config) self.port = int(self.config.get(self.CONFIG_SECTION, 'port')) @@ -332,8 +335,10 @@ class vulnWhispererNessus(vulnWhispererBase): folder_id = s['folder_id'] scan_history = self.nessus.get_scan_history(scan_id) - folder_name = next(f['name'] for f in folders if f['id' - ] == folder_id) + if self.CONFIG_SECTION == 'tenable': + folder_name = '' + else: + folder_name = next(f['name'] for f in folders if f['id'] == folder_id) if status == 'completed': file_name = '%s_%s_%s_%s.%s' % (scan_name, scan_id, history_id, norm_time, 'csv') @@ -361,8 +366,8 @@ class vulnWhispererNessus(vulnWhispererBase): filename=relative_path_name)) else: file_req = \ - self.nessus.download_scan(scan_id=scan_id, - history=history_id, export_format='csv') + self.nessus.download_scan(scan_id=scan_id, history=history_id, + export_format='csv', profile=self.CONFIG_SECTION) clean_csv = \ pd.read_csv(io.StringIO(file_req.decode('utf-8' ))) @@ -768,7 +773,8 @@ class vulnWhisperer(object): vw = vulnWhispererNessus(config=self.config, username=self.username, password=self.password, - verbose=self.verbose) + verbose=self.verbose, + profile=self.profile) vw.whisper_nessus() elif self.profile == 'qualys': @@ -778,3 +784,11 @@ class vulnWhisperer(object): elif self.profile == 'openvas': vw_openvas = vulnWhispererOpenVAS(config=self.config) vw_openvas.process_openvas_scans() + + elif self.profile == 'tenable': + vw = vulnWhispererNessus(config=self.config, + username=self.username, + password=self.password, + verbose=self.verbose, + profile=self.profile) + vw.whisper_nessus()