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
This commit is contained in:
@ -9,6 +9,17 @@ db_path=/opt/vulnwhisperer/database
|
|||||||
trash=false
|
trash=false
|
||||||
verbose=true
|
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]
|
[qualys]
|
||||||
#Reference https://www.qualys.com/docs/qualys-was-api-user-guide.pdf to find your API
|
#Reference https://www.qualys.com/docs/qualys-was-api-user-guide.pdf to find your API
|
||||||
enabled = true
|
enabled = true
|
||||||
|
@ -12,19 +12,26 @@ input {
|
|||||||
tags => "nessus"
|
tags => "nessus"
|
||||||
type => "nessus"
|
type => "nessus"
|
||||||
}
|
}
|
||||||
|
file {
|
||||||
|
path => "/opt/vulnwhisperer/tenable/*.csv"
|
||||||
|
start_position => "beginning"
|
||||||
|
tags => "tenable"
|
||||||
|
type => "tenable"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
filter {
|
filter {
|
||||||
if "nessus" in [tags]{
|
if "nessus" in [tags] or "tenable" in [tags] {
|
||||||
# Drop the header column
|
# Drop the header column
|
||||||
if [message] =~ "^Plugin ID" { drop {} }
|
if [message] =~ "^Plugin ID" { drop {} }
|
||||||
|
|
||||||
csv {
|
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 => ","
|
separator => ","
|
||||||
source => "message"
|
source => "message"
|
||||||
}
|
}
|
||||||
|
|
||||||
ruby {
|
ruby {
|
||||||
code => "if event.get('description')
|
code => "if event.get('description')
|
||||||
event.set('description', event.get('description').gsub(92.chr + 'n', 10.chr).gsub(92.chr + 'r', 13.chr))
|
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))
|
event.set('plugin_output', event.get('plugin_output').gsub(92.chr + 'n', 10.chr).gsub(92.chr + 'r', 13.chr))
|
||||||
end"
|
end"
|
||||||
}
|
}
|
||||||
|
|
||||||
#If using filebeats as your source, you will need to replace the "path" field to "source"
|
#If using filebeats as your source, you will need to replace the "path" field to "source"
|
||||||
grok {
|
grok {
|
||||||
match => { "path" => "(?<scan_name>[a-zA-Z0-9_.\-]+)_%{INT:scan_id}_%{INT:history_id}_%{INT:last_updated}.csv$" }
|
match => { "path" => "(?<scan_name>[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 }}
|
mutate { add_field => { "risk_number" => 4 }}
|
||||||
}
|
}
|
||||||
|
|
||||||
if [cve] == "nan" {
|
if ![cve] or [cve] == "nan" {
|
||||||
mutate { remove_field => [ "cve" ] }
|
mutate { remove_field => [ "cve" ] }
|
||||||
}
|
}
|
||||||
if [cvss] == "nan" {
|
if ![cvss] or [cvss] == "nan" {
|
||||||
mutate { remove_field => [ "cvss" ] }
|
mutate { remove_field => [ "cvss" ] }
|
||||||
}
|
}
|
||||||
if [see_also] == "nan" {
|
if ![cvss_base] or [cvss_base] == "nan" {
|
||||||
mutate { remove_field => [ "see_also" ] }
|
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" ] }
|
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" ] }
|
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" ] }
|
mutate { remove_field => [ "synopsis" ] }
|
||||||
}
|
}
|
||||||
|
if ![system_type] or [system_type] == "nan" {
|
||||||
|
mutate { remove_field => [ "system_type" ] }
|
||||||
|
}
|
||||||
|
|
||||||
mutate {
|
mutate {
|
||||||
remove_field => [ "message" ]
|
remove_field => [ "message" ]
|
||||||
@ -170,8 +210,8 @@ filter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
output {
|
output {
|
||||||
if "nessus" in [tags] or [type] == "nessus" {
|
if "nessus" in [tags] or "tenable" in [tags] or [type] in [ "nessus", "tenable" ] {
|
||||||
#stdout { codec => rubydebug }
|
# stdout { codec => rubydebug }
|
||||||
elasticsearch {
|
elasticsearch {
|
||||||
hosts => [ "localhost:9200" ]
|
hosts => [ "localhost:9200" ]
|
||||||
index => "logstash-vulnwhisperer-%{+YYYY.MM}"
|
index => "logstash-vulnwhisperer-%{+YYYY.MM}"
|
||||||
|
@ -149,7 +149,7 @@ class NessusAPI(object):
|
|||||||
req = self.request(query, data=data, method='POST')
|
req = self.request(query, data=data, method='POST')
|
||||||
return req
|
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
|
running = True
|
||||||
counter = 0
|
counter = 0
|
||||||
|
|
||||||
@ -162,7 +162,7 @@ class NessusAPI(object):
|
|||||||
req = self.request(query, data=json.dumps(data), method='POST', json=True)
|
req = self.request(query, data=json.dumps(data), method='POST', json=True)
|
||||||
try:
|
try:
|
||||||
file_id = req['file']
|
file_id = req['file']
|
||||||
token_id = req['token']
|
token_id = req['token'] if 'token' in req else req['temp_token']
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print("[ERROR] %s" % e)
|
print("[ERROR] %s" % e)
|
||||||
print('Download for file id ' + str(file_id) + '.')
|
print('Download for file id ' + str(file_id) + '.')
|
||||||
@ -178,7 +178,10 @@ class NessusAPI(object):
|
|||||||
print("")
|
print("")
|
||||||
|
|
||||||
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
|
return content
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
|
@ -176,7 +176,7 @@ class vulnWhispererBase(object):
|
|||||||
|
|
||||||
class vulnWhispererNessus(vulnWhispererBase):
|
class vulnWhispererNessus(vulnWhispererBase):
|
||||||
|
|
||||||
CONFIG_SECTION = 'nessus'
|
CONFIG_SECTION = None
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
@ -187,7 +187,10 @@ class vulnWhispererNessus(vulnWhispererBase):
|
|||||||
debug=False,
|
debug=False,
|
||||||
username=None,
|
username=None,
|
||||||
password=None,
|
password=None,
|
||||||
|
profile='nessus'
|
||||||
):
|
):
|
||||||
|
self.CONFIG_SECTION=profile
|
||||||
|
|
||||||
super(vulnWhispererNessus, self).__init__(config=config)
|
super(vulnWhispererNessus, self).__init__(config=config)
|
||||||
|
|
||||||
self.port = int(self.config.get(self.CONFIG_SECTION, 'port'))
|
self.port = int(self.config.get(self.CONFIG_SECTION, 'port'))
|
||||||
@ -332,8 +335,10 @@ class vulnWhispererNessus(vulnWhispererBase):
|
|||||||
|
|
||||||
folder_id = s['folder_id']
|
folder_id = s['folder_id']
|
||||||
scan_history = self.nessus.get_scan_history(scan_id)
|
scan_history = self.nessus.get_scan_history(scan_id)
|
||||||
folder_name = next(f['name'] for f in folders if f['id'
|
if self.CONFIG_SECTION == 'tenable':
|
||||||
] == folder_id)
|
folder_name = ''
|
||||||
|
else:
|
||||||
|
folder_name = next(f['name'] for f in folders if f['id'] == folder_id)
|
||||||
if status == 'completed':
|
if status == 'completed':
|
||||||
file_name = '%s_%s_%s_%s.%s' % (scan_name, scan_id,
|
file_name = '%s_%s_%s_%s.%s' % (scan_name, scan_id,
|
||||||
history_id, norm_time, 'csv')
|
history_id, norm_time, 'csv')
|
||||||
@ -361,8 +366,8 @@ class vulnWhispererNessus(vulnWhispererBase):
|
|||||||
filename=relative_path_name))
|
filename=relative_path_name))
|
||||||
else:
|
else:
|
||||||
file_req = \
|
file_req = \
|
||||||
self.nessus.download_scan(scan_id=scan_id,
|
self.nessus.download_scan(scan_id=scan_id, history=history_id,
|
||||||
history=history_id, export_format='csv')
|
export_format='csv', profile=self.CONFIG_SECTION)
|
||||||
clean_csv = \
|
clean_csv = \
|
||||||
pd.read_csv(io.StringIO(file_req.decode('utf-8'
|
pd.read_csv(io.StringIO(file_req.decode('utf-8'
|
||||||
)))
|
)))
|
||||||
@ -768,7 +773,8 @@ class vulnWhisperer(object):
|
|||||||
vw = vulnWhispererNessus(config=self.config,
|
vw = vulnWhispererNessus(config=self.config,
|
||||||
username=self.username,
|
username=self.username,
|
||||||
password=self.password,
|
password=self.password,
|
||||||
verbose=self.verbose)
|
verbose=self.verbose,
|
||||||
|
profile=self.profile)
|
||||||
vw.whisper_nessus()
|
vw.whisper_nessus()
|
||||||
|
|
||||||
elif self.profile == 'qualys':
|
elif self.profile == 'qualys':
|
||||||
@ -778,3 +784,11 @@ class vulnWhisperer(object):
|
|||||||
elif self.profile == 'openvas':
|
elif self.profile == 'openvas':
|
||||||
vw_openvas = vulnWhispererOpenVAS(config=self.config)
|
vw_openvas = vulnWhispererOpenVAS(config=self.config)
|
||||||
vw_openvas.process_openvas_scans()
|
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()
|
||||||
|
Reference in New Issue
Block a user