tenable / pyTenable

Python Library for interfacing into Tenable's platform APIs
https://pytenable.readthedocs.io
MIT License
345 stars 172 forks source link

Vulnerability Analysis filters do not work correctly in some circumstances. #515

Closed BrianSidebotham closed 1 year ago

BrianSidebotham commented 2 years ago

Describe the bug

A number of vulnerabilities cannot be correctly filtered through pyTenable and the Tenable.SC API. We have a number of vulnerabilites that have the following plugin family set:

    "family": {
        "id": "0",
        "name": "N/A",
        "type": "compliance"
    }

Using the filters ( "family", "=", [0] ) or ( "familyID", "=", 0 ) results in no vulnerabilities returned using the default vulnDetails tool with the analysis endpoint.

For any other family value (i.e. not 0 ) everything works as expected.

Similarly, using the filter ("pluginType", "=", "compliance") also returns zero vulnerabilities.

When these filters are not used, vulnerabilities with these settings are returned and are the vulnerabilities we're interested in filtering out. The problem is that with a large (~500k) set of vulnerabilities post-filtering in software is very slow due to the amount of API calls required to retrieve the entire dataset.

Perhaps there's another filter I've missed where we can filter these particular vulnerabilities.

To Reproduce

Use the analysis.vulns() method with any of the filters above with appropriate data in Tenable.SC

Expected behaviour

Vulnerabilities matching the filter(s) to be returned.

Additional context

Tracing this through, it looks like pyTenable is doing the right thing and the Tenable.SC API is not producing the correct results. But wanted to note an issue in case there's an answer that involves a different filtering method within pyTenable that will work.

SteveMcGrath commented 2 years ago
>>> from tenable.sc import TenableSC
>>> tsc = TenableSC()
>>> tsc.version
'5.20.0'

>>> vulns = tsc.analysis.vulns(('pluginType', '=', 'compliance'))
>>> vulns.next()
{'pluginID': '1000558', 'severity': {'id': '2', 'name': 'Medium', 'description': 'Medium Severity'}, 'hasBeenMitigated': '0', 'acceptRisk': '0', 'recastRisk': '0', 'ip': '10.238.64.9', 'uuid': '', 'port': '0', 'protocol': 'TCP', 'pluginName': 'CIS_Red_Hat_EL6_Server_L1_v2.1.0.audit Level 1', 'firstSeen': '1551394516', 'lastSeen': '1641662242', 'exploitAvailable': 'No', 'exploitEase': '', 'exploitFrameworks': '', 'synopsis': '', 'description': '', 'solution': '', 'seeAlso': '', 'riskFactor': '', 'stigSeverity': '', 'vprScore': '', 'vprContext': '', 'baseScore': '', 'temporalScore': '', 'cvssVector': '', 'cvssV3BaseScore': '', 'cvssV3TemporalScore': '', 'cvssV3Vector': '', 'cpe': '', 'vulnPubDate': '', 'patchPubDate': '', 'pluginPubDate': '', 'pluginModDate': '', 'checkType': '', 'version': '', 'cve': '', 'bid': '', 'xref': 'auditFile #unix', 'pluginText': '<cm:compliance-benchmark-version>2.1.0</cm:compliance-benchmark-version>\n<cm:compliance-check-name>CIS_Red_Hat_EL6_Server_L1_v2.1.0.audit Level 1</cm:compliance-check-name>\n<cm:compliance-check-id>75377b14cb8a87a7cb0e72852cb1e4a7167bb6133858165643c9f54a4c1069b7</cm:compliance-check-id>\n<cm:compliance-source>custom</cm:compliance-source>\n<cm:compliance-audit-file>0a4e3981-f50b-52ea-8eb0-1e674e3d9161-4347106-scfile_urvzlI</cm:compliance-audit-file>\n<cm:compliance-policy-value>WARNING</cm:compliance-policy-value>\n<cm:compliance-functional-id>3f365ea2f1</cm:compliance-functional-id>\n<cm:compliance-uname>Linux target-cent7 5.8.0-1035-aws #37~20.04.1-Ubuntu SMP Tue Jun 1 09:54:15 UTC 2021 x86_64 x86_64 x86_64 GNU/Linux</cm:compliance-uname>\n<cm:compliance-info>NOTE: Nessus has not identified that the chosen audit applies to the target device.</cm:compliance-info>\n<cm:compliance-result>WARNING</cm:compliance-result>\n<cm:compliance-informational-id>6edf8bc5944b55f0ab04a90baec2add786cfc1cde0f6cccea718235240aded91</cm:compliance-informational-id>\n<cm:compliance-benchmark-name>CIS Red Hat 6 Server L1</cm:compliance-benchmark-name>\n<cm:compliance-control-id>e4c4de2af5309adbc5a30635d811f2e6bad5d73d5b8de4f7bb47f6820358496c</cm:compliance-control-id>\n<cm:compliance-see-also>https://workbench.cisecurity.org/files/1859</cm:compliance-see-also>\n<cm:compliance-full-id>75377b14cb8a87a7cb0e72852cb1e4a7167bb6133858165643c9f54a4c1069b7</cm:compliance-full-id>', 'dnsName': 'target-cent7.lxd', 'macAddress': '00:16:3e:5d:7a:71', 'netbiosName': '', 'operatingSystem': 'Linux Kernel 5.8.0-1035-aws on CentOS Linux release 7.6.1810 (Core)', 'ips': '10.238.64.9', 'hostUniqueness': 'repositoryID,ip,dnsName', 'uniqueness': 'repositoryID,ip,dnsName', 'family': {'id': '0', 'name': 'N/A', 'type': 'compliance'}, 'repository': {'id': '1', 'name': 'Live', 'description': '', 'sciID': '1', 'dataFormat': 'IPv4'}, 'pluginInfo': '1000558 (0/6) CIS_Red_Hat_EL6_Server_L1_v2.1.0.audit Level 1'}

>>> vulns.total
2180
BrianSidebotham commented 2 years ago

@SteveMcGrath Thanks for the response. On it's own, things appear to work, however given the examples below operating on the same session when other filters are applied at the same time pluginType and family appear to not work:

Other filters are necessary in order to limit the time frame for example.

>>> tsc.version
'5.19.0'

>>> vulns = tsc.analysis.vulns(('lastSeen', '=', '1641727524-1641900324'), ('severity', '=', '1,2,3,4'))
>>> for v in vulns:
...     if v["family"]["type"] == "compliance" and v["family"]["id"] == "0":
...         compliance_count += 1
... 
>>> compliance_count
50
>>> vulns = tsc.analysis.vulns(('lastSeen', '=', '1641727524-1641900324'),
                               ('severity', '=', '1,2,3,4'),
                               ('family', '=', [0]))
>>> vulns.next()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File ".../python3.9/site-packages/tenable/base/v1.py", line 113, in next
    raise StopIteration()
StopIteration
>>> vulns.total
0
>>> vulns = tsc.analysis.vulns(('lastSeen', '=', '1641727524-1641900324'),
                               ('severity', '=', '1,2,3,4'),
                               ('pluginType', '=', 'compliance'))
>>> vulns.next()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File ".../python3.9/site-packages/tenable/base/v1.py", line 113, in next
    raise StopIteration()
StopIteration
>>> vulns.total
0
SteveMcGrath commented 2 years ago

can you run the same queries in the UI and see what it looks like? The library itself isn't doing anything that could interfere, its blindly constructing the filter objects based on what you're telling it to do.

Lets try this approach:

#!/usr/bin/env python
from tenable.sc import TenableSC
import arrow
tsc = TenableSC()

def print_sevsum(i: dict) -> None:
    print(f'{i["severity"]["id"]}:{i["severity"]["name"]} = {i["count"]}')

def run():
    print('\nPerforming Query with Compliance pluginType && severity')
    for sevitem in tsc.analysis.vulns(('pluginType', '=', 'compliance'), 
                                      ('severity', '=', '0,2,3'), 
                                      tool='sumseverity'
                                      ):
        print_sevsum(sevitem)

    print('\nPerforming Query with compliance filters && relative datetime')
    for sevitem in tsc.analysis.vulns(('pluginType', '=', 'compliance'),
                                      ('severity', '=', '0,2,3'),
                                      ('lastSeen', '=', '0:7'),
                                      tool='sumseverity'
                                      ):
        print_sevsum(sevitem)

    print('\nPerforming Query with compliance filters && static datetime')
    now = arrow.now()
    last_seen_filter = (f'{now.shift(days=-7).timestamp():0.0f}-'
                        f'{now.timestamp():0.0f}'
                        )
    print(f'using {last_seen_filter}')
    for sevitem in tsc.analysis.vulns(('pluginType', '=', 'compliance'),
                                      ('severity', '=', '0,2,3'),
                                      ('lastSeen', '=', last_seen_filter),
                                      tool='sumseverity'
                                      ):
        print_sevsum(sevitem)

if __name__ == '__main__':
    run()

This yielded the following output:

Performing Query with Compliance pluginType && severity
4:Critical = 0
3:High = 871
2:Medium = 401
1:Low = 0
0:Info = 908

Performing Query with compliance filters && relative datetime
4:Critical = 0
3:High = 616
2:Medium = 124
1:Low = 0
0:Info = 697

Performing Query with compliance filters && static datetime
using 1641310329-1641915129
4:Critical = 0
3:High = 616
2:Medium = 124
1:Low = 0
0:Info = 697
aseemsavio commented 1 year ago

Closing due to inactivity.