def _gen_doc_format(self, vuln, fid, fdef): ''' Converts the YAML field definition into the processed result. ''' tconf = self.config.get('truncation', dict()) # if the definition is a list, then we are dealing with a document # structure and should build the appropriate dict structure. if isinstance(fdef, list): content = list() for item in fdef: # The heading is derrived from the name attribute content.append({ 'type': 'heading', 'attrs': { 'level': 1 }, 'content': [{ 'type': 'text', 'text': item['name'] }] }) # The paragraph is derived from the appropriate # field parameter. If the string formatting fails # from a KeyError, then replace the output with # an empty paragraph. try: content.append({ 'type': 'paragraph', 'content': [{ 'type': 'text', 'text': trunc(item[fid].format(vuln=vuln), tconf.get('limit', 10000), tconf.get('suffix', '...')) }] }) except KeyError: content.append({ 'type': 'paragraph', 'content': [{ 'type': 'text', 'text': 'No Output' }] }) return {'version': 1, 'type': 'doc', 'content': content} # if the definition is a dictionary, then this is a simple single-field # response and we should simple return back the processed string. elif isinstance(fdef, dict): return trunc(fdef[fid].format(vuln=vuln), limit=255)
def _process_vuln(self, vuln, fid): ''' Processes a singular vulnerability and adds/updates the appropriate Jira issues. ''' issue = self._gen_issue_skel() subissue = self._gen_subissue_skel() jql = [ 'project = "{}"'.format(self._project['key']), 'issuetype = "{}"'.format(self.task['name']), 'status not in (Closed, Done, Resolved)' ] sjql = [ 'project = "{}"'.format(self._project['key']), 'issuetype = "{}"'.format(self.subtask['name']), 'status not in (Closed, Done, Resolved)' ] sevprio = self.config['tenable'].get('severity_prioritization') for f in self._fields: # determine the JQL operator that we may need to use. if f['type'] == 'labels': oper = '=' else: oper = '~' if f.get('static_value'): value = f.get('static_value') elif f.get('is_platform_id'): _, value = self._get_platform() elif f.get('is_tio_tags') and fid == 'tio_field': value = vuln.get('asset.tags') else: value = vuln.get(f.get(fid)) # Here we will be setting the severity priority for the task and # subtask. if sevprio and f['jira_field'] == 'Finding Severity': subissue['priority'] = { 'id': str(sevprio.get(value.lower(), 4)) } self._log.debug(f'Setting Finding Sev to {value.lower()}') if sevprio and f['jira_field'] == 'Vulnerability Severity': issue['priority'] = {'id': str(sevprio.get(value.lower(), 4))} self._log.debug(f'Setting Vuln Sev to {value.lower()}') processed = None if value: # for text-type fields, only sent the field if there is some # sort of data in it and recast the field as a string. if f['type'] in ['readonlyfield', 'textarea']: processed = str(value) if f['type'] in ['readonlyfield']: processed = trunc(processed, 255) # for labels, just pass on the field as-is elif f['type'] in ['labels']: if isinstance(value, str): if fid == 'tsc_field': processed = value.split(',') else: processed = [ value, ] else: processed = value # For datetime fields, validate that the field actually had # a value and then convert it into the appropriate format. elif f['type'] in ['datetime']: try: processed = arrow.get(value).format( 'YYYY-MM-DDTHH:mm:ss.SSSZ') except arrow.parser.ParserError: processed = arrow.get( int(value)).format('YYYY-MM-DDTHH:mm:ss.SSSZ') # For anything else, just pass through else: processed = value if self.task['name'] in f['issue_type']: issue[f['jira_id']] = processed if self.subtask['name'] in f['issue_type']: subissue[f['jira_id']] = processed # Handle any JQL conversions that need to be done in order to make # the JQL statement valid. if isinstance(processed, list): if len(processed) > 1: oper = "in" svalue = '({})'.format(','.join( ["{}".format(x) for x in processed])) else: svalue = processed[0] elif not processed: oper = "is" svalue = "EMPTY" else: svalue = '"{}"'.format(processed) # construct the JQL statement jql_statement = '"{}" {} {}'.format(f['jira_field'], oper, svalue) # Add the JQL statement as necessary to the appropriate JQL queries. if f['jira_field'] in self.task['search']: jql.append(jql_statement) if f['jira_field'] in self.subtask['search']: sjql.append(jql_statement) # Now to process the default fields. for field in self.config['issue_default_fields']: fdef = self.config['issue_default_fields'][field] if self.task['name'] in fdef: issue[field] = self._gen_doc_format(vuln, fid, fdef[self.task['name']]) if self.subtask['name'] in fdef: subissue[field] = self._gen_doc_format( vuln, fid, fdef[self.subtask['name']]) return issue, subissue, jql, sjql
def _transform_vuln(self, vuln): ''' Transforms a Tenable.io exported vulnerability into the IBM Security Connect format. Args: vuln (dict): The Tenable.io vulnerability format ''' plugin = vuln['plugin'] asset = vuln['asset'] port = vuln['port'] statusmap = { 'OPEN': 'ACTIVE', 'NEW': 'ACTIVE', 'REOPENED': 'ACTIVE', 'FIXED': 'INACTIVE' } sevmap = {0: 0.0, 1: 3.0, 2: 5.0, 3: 7.0, 4: 10.0} self._add_to_cache( 'vulnerability', { 'external_id': str(plugin['id']), 'external_reference': ''.join([ 'https://cloud.tenable.com', '/tio/app.html#/vulnerability-management', '/vulnerabilities/by-plugins/vulnerability-details', '/{}/overview'.format(plugin['id']) ]), 'name': plugin['name'], 'description': plugin['description'], 'disclosed_on': self._ts(plugin.get('vuln_publication_date', '')), 'published_on': self._ts(plugin.get('publication_date', '')), 'updated_at': self._ts(plugin.get('modification_date', '')), 'base_score': plugin.get('cvss_base_score', sevmap[vuln['severity_id']]), 'source': 'tenable.io', ### Extended Fields Specific to Tenable.io 'links': plugin.get('see_also', list()), 'cpes': plugin.get('cpe', list()), 'solution': trunc(plugin.get('solution', ''), 1024), 'synopsis': trunc(plugin.get('synopsis', ''), 1024), }) self._add_to_cache( 'asset_vulnerability', { '_from_external_id': '{}'.format(asset['uuid']), '_to_external_id': '{}'.format(plugin['id']), 'timestamp': self._ts(arrow.utcnow()), 'last_modified': self._ts(vuln['last_found']), 'active': vuln['state'] != 'FIXED', 'report': str(self._report_id), 'source': 'tenable.io', 'port': { 'port_number': int(port['port']), 'protocol': '', 'status': statusmap[vuln['state']], }, ### Extended Fields Specific to Tenable.io 'vuln_output': vuln.get('output', ''), })
def test_trunc(): assert trunc('Hello There!', 128) == 'Hello There!' assert trunc('Too Small', 6) == 'Too...' assert trunc('Too Small', 3, suffix=None) == 'Too'