Retrieving scan results through Nessus API

In this first article about Nessus API I want to describe process of getting scan results from Nessus.

Of course, it’s also great to create and run scans or even create policies via API. But to be honest, in practice, you may need this functionality rarely. And it’s easier to do it manually in GUI. On the other hand, sometimes it very efficient to create automatically some specific scan task for specific group of hosts using existing (inventory) scan results. But we will talk about this topic next time (Upd. I wrote post about scan creation “Nessus API for hosts scanning“).

Nessus API

Now, imagine that we have configured regular Nessus scans. And we want to get this scan results on a regular basis to make some analysis and maybe create some tickets in Jira.

As usual, I will use curl for all examples, because it is easy to read and easy to test in any Linux terminal.

Starting from Nessus v.6 the API manual is built in GUI: https://<scanner_ip>:8834/api#

In order to communicate with Nessus, we need to get a token. If we have a user “admin” with password “1” (this is wrong, because passwords must be complex, remember the warning during installation). We can token with this command:

curl -s -k -X POST -H 'Content-Type: application/json' -d "{\"username\":\"admin\",\"password\":\"1\"}" https://192.168.56.101:8834/session

{"token":"8b52551086c22dbf8954566099fd469c934f3706aaa6b025"}

Ok, we have a token, that we will use it in X-Cookie for every request. We know, that in GUI our scan task is called “Test Ubuntu Scan”. First of all, we need a scan_id of this scan. Let’s get the list of all scans:

curl -s -k -X GET -H "X-Cookie: token=8b52551086c22dbf8954566099fd469c934f3706aaa6b025" https://192.168.56.101:8834/scans | python -m json.tool

{
    "folders": [
        {
            "custom": 0,
            "default_tag": 1,
            "id": 313,
            "name": "My Scans",
            "type": "main",
            "unread_count": 1
        },
        {
            "custom": 0,
            "default_tag": 0,
            "id": 314,
            "name": "Trash",
            "type": "trash",
            "unread_count": null
        }
    ],
    "scans": [
        {
            "control": true,
            "creation_date": 1464970735,
            "enabled": false,
            "folder_id": 313,
            "id": 318,
            "last_modification_date": 1464970801,
            "name": "Test Ubuntu Scan",
            "owner": "admin",
            "read": false,
            "rrules": null,
            "shared": false,
            "starttime": null,
            "status": "completed",
            "timezone": null,
            "type": "local",
            "user_permissions": 128,
            "uuid": "f18bd8bd-a0c1-410f-79d2-ac427d7798378d1c324929b01998"
        }
    ],
    "timestamp": 1464971208
}

Ok, now we see “Test Ubuntu Scan” has id “318”. Let’s get more information about this scan.

curl -s -k -X GET -H "X-Cookie: token=8b52551086c22dbf8954566099fd469c934f3706aaa6b025" https://192.168.56.101:8834/scans/318 | python -m json.tool

{
    "comphosts": [],
    "compliance": [],
    "filters": [
        {
            "control": {
                "readable_regex": "CVE-YYYY-ID (ie: CVE-2011-0018)",
                "regex": "^(CVE|CAN)-(1999|20[01][0-9])-[0-9]{4,}$",
                "type": "entry"
            },
            "name": "cve",
            "operators": [
                "eq",
                "neq",
                "match",
                "nmatch"
            ],
            "readable_name": "CVE"
        },
[...]
        {
            "control": {
                "type": "datefield"
            },
            "name": "vuln_publication_date",
            "operators": [
                "date-lt",
                "date-gt",
                "date-eq",
                "date-neq",
                "match",
                "nmatch"
            ],
            "readable_name": "Vulnerability Publication Date"
        }
    ],
    "history": [
        {
            "alt_targets_used": false,
            "creation_date": 1444816022,
            "history_id": 319,
            "last_modification_date": 1444816093,
            "owner_id": 4,
            "scheduler": 0,
            "status": "completed",
            "type": "local",
            "uuid": "eec6d22e-27d7-3a56-422d-9941230b0ea88fdc32bd32332d61"
        {
            "alt_targets_used": false,
            "creation_date": 1444816385,
            "history_id": 320,
            "last_modification_date": 1444816451,
            "owner_id": 4,
            "scheduler": 0,
            "status": "completed",
            "type": "local",
            "uuid": "8dcc129f-81dc-780f-cce0-0c3940ef87a234444e6992ea571f"
        {
            "alt_targets_used": false,
            "creation_date": 1444818512,
            "history_id": 321,
            "last_modification_date": 1444818583,
            "owner_id": 4,
            "scheduler": 0,
            "status": "completed",
            "type": "local",
            "uuid": "303ae60a-11b2-6ccf-e3f2-6aadb74b82b92e3f2d40bc1d8233"
        {
            "alt_targets_used": false,
            "creation_date": 1447333579,
            "history_id": 381,
            "last_modification_date": 1447333652,
            "owner_id": 4,
            "scheduler": 0,
            "status": "completed",
            "type": "local",
            "uuid": "cf2ac68c-42c2-3817-f264-de36205bd94385c234ce85392cb2"
        },
        {
            "alt_targets_used": false,
            "creation_date": 1464970735,
            "history_id": 2613,
            "last_modification_date": 1464970801,
            "owner_id": 4,
            "scheduler": 0,
            "status": "completed",
            "type": "local",
            "uuid": "f18bd8bd-a0c1-410f-79d2-ac427d7798378d1cwee111b01998"
        }
    ],
    "hosts": [
        {
            "critical": 0,
            "high": 0,
            "host_id": 2,
            "host_index": 0,
            "hostname": "100.100.14.248",
            "info": 5,
            "low": 0,
            "medium": 0,
            "numchecksconsidered": 465,
            "progress": "465-465/79806-79806",
            "scanprogresscurrent": 465,
            "scanprogresstotal": 465,
            "score": 5,
            "severity": 5,
            "severitycount": {
                "item": [
                    {
                        "count": 5,
                        "severitylevel": 0
                    },
                    {
                        "count": 0,
                        "severitylevel": 1
                    },
                    {
                        "count": 0,
                        "severitylevel": 2
                    },
                    {
                        "count": 0,
                        "severitylevel": 3
                    },
                    {
                        "count": 0,
                        "severitylevel": 4
                    }
                ]
            },
            "totalchecksconsidered": 465
        }
    ],
    "info": {
        "acls": [
            {
                "display_name": null,
                "id": null,
                "name": null,
                "owner": null,
                "permissions": 0,
                "type": "default"
            },
            {
                "display_name": "admin",
                "id": 4,
                "name": "admin",
                "owner": 1,
                "permissions": 128,
                "type": "user"
            }
        ],
        "alt_targets_used": null,
        "control": true,
        "edit_allowed": true,
        "folder_id": 313,
        "hasaudittrail": true,
        "haskb": true,
        "hostcount": 1,
        "name": "Test Ubuntu Scan",
        "no_target": null,
        "object_id": 318,
        "pci-can-upload": false,
        "policy": "Advanced Scan",
        "scan_end": 1464970801,
        "scan_start": 1464970735,
        "scan_type": "local",
        "scanner_end": 1464970800,
        "scanner_name": "Local Scanner",
        "scanner_start": 1464970735,
        "status": "completed",
        "targets": "100.100.14.248",
        "timestamp": 1464970801,
        "user_permissions": 128,
        "uuid": "f18bd8bd-a0c1-410f-79d2-ac427d7798378d1cwee111b01998"
    },
    "notes": null,
    "remediations": {
        "num_cves": 1,
        "num_hosts": 1,
        "num_impacted_hosts": 0,
        "num_remediated_cves": 0,
        "remediations": null
    },
    "vulnerabilities": [
        {
            "count": 1,
            "plugin_family": "General",
            "plugin_id": 10287,
            "plugin_name": "Traceroute Information",
            "severity": 0,
            "severity_index": 0,
            "vuln_index": 9
        },
        {
            "count": 1,
            "plugin_family": "Settings",
            "plugin_id": 19506,
            "plugin_name": "Nessus Scan Information",
            "severity": 0,
            "severity_index": 1,
            "vuln_index": 8
        },
        {
            "count": 1,
            "plugin_family": "Settings",
            "plugin_id": 46215,
            "plugin_name": "Inconsistent Hostname and IP Address",
            "severity": 0,
            "severity_index": 2,
            "vuln_index": 7
        },
        {
            "count": 1,
            "plugin_family": "General",
            "plugin_id": 10114,
            "plugin_name": "ICMP Timestamp Request Remote Date Disclosure",
            "severity": 0,
            "severity_index": 3,
            "vuln_index": 6
        },
        {
            "count": 1,
            "plugin_family": "General",
            "plugin_id": 12053,
            "plugin_name": "Host Fully Qualified Domain Name (FQDN) Resolution",
            "severity": 0,
            "severity_index": 4,
            "vuln_index": 5
        }
    ]
}

The output is quite informative. As you can see, you can get right here statistics by hosts, vulnerabilities, remediations, information about scan policy and methods of results filtering. So, you may want to work with this output (and other API requests) instead of getting results in nessus2 format. As for me, I prefer to analyze nessus2 xml format more because it is the same for Nessus and Security Center, when API for those products is completely different. But it’s a matter of taste.

“History” part contains links (history_id) to the saved scan results. Let’s get scan results by history_id “2613” (with the latest Unix timestamp and “completed” status) in nessus2 xml format:

curl -s -k -X POST -H "X-Cookie: token=8b52551086c22dbf8954566099fd469c934f3706aaa6b025" -H 'Content-Type: application/json' -d '{"format": "nessus"}' https://192.168.56.101:8834/scans/318/export?history_id=2613

{"file":1178397097}

With this id I can check file status:

curl -s -k -X GET -H "X-Cookie: token=8b52551086c22dbf8954566099fd469c934f3706aaa6b025" -H 'Content-Type: application/json' https://192.168.56.101:8834/scans/318/export/1178397097/status

{"status":"loading"}

After some time it status will be changed to

{"status":"ready"}

Now I can download this file:

curl -s -k -X GET -H "X-Cookie: token=8b52551086c22dbf8954566099fd469c934f3706aaa6b025" -H 'Content-Type: application/json' https://192.168.56.101:8834/scans/318/export/1178397097/download

<!--?xml version="1.0" ?-->
<pre>
<?xml version="1.0" ?>
<NessusClientData_v2>
<Policy><policyName>Advanced Scan</policyName>
<Preferences><ServerPreferences><preference><name>service_detection.search_for_ssl</name>
<value>yes</value>
</preference>
<preference><name>plugin_set</name>
<value>36080;32809;84316;61117;35292;87560;75260;83156;[...]
</ReportItem>
</ReportHost>
</Report>
</NessusClientData_v2>

Analysis of scan results in nessus2 file format is very interesting topic, and I hope we will explore it next time.

24 thoughts on “Retrieving scan results through Nessus API

  1. Pingback: Tenable Nessus: registration, installation, scanning and reporting | Alexander V. Leonov

  2. Pingback: Choosing the right time for Nessus update | Alexander V. Leonov

  3. Pingback: Nessus V2 xml report format | Alexander V. Leonov

  4. Pingback: Vulnerability Assessment without Vulnerability Scanner | Alexander V. Leonov

  5. Pingback: Tenable SecurityCenter and its API | Alexander V. Leonov

  6. Pingback: Export anything in Splunk with HTTP Event Collector | Alexander V. Leonov

  7. Pingback: Nessus API for hosts scanning | Alexander V. Leonov

  8. Pingback: Custom Vulnerability Management Reports | Alexander V. Leonov

  9. Pingback: Parsing Nessus v2 XML reports with python | Alexander V. Leonov

  10. Pingback: What’s actually new in Tenable.io VM application | Alexander V. Leonov

  11. Pingback: Tracking software versions using Nessus and Splunk | Alexander V. Leonov

  12. Justin

    Hi Alexander,

    When using this curl command: curl -s -k -X POST -H ‘Content-Type: application/json’ -d “{\”username\”:\”admin\”,\”password\”:\”1\”}” https://192.168.56.101:8834/session

    I am getting the following error: {“error”:”Missing ‘username’ field”}

    I am of course changing your command to include the IP to my Nessus server as well as using the credentials I would log in to the GUI with. Any thoughts as to why this is happening?

    Reply
  13. T. Richardson

    Hi Alexander, I just want to give you credit for your article. I used it to develop an entire program at my company. These APIs are facilitating the management of tens of thousands of hosts with the Nessus Professional product, saving our company hundreds of thousands of dollars.

    While Tenable is preparing to shut down the API for N.P., I am hopeful that I can still use it through Tenable.IO.
    Just a note to say thank-you, it’s really appreciated.

    Reply
  14. Julian N

    Great articles – helped me develop similar in Python. Sadly, all in vain now Tenable have pulled the API in version 7 – we have just had to can the project and cancel our Nessus order.

    Reply
  15. John

    Tenable has pulled the API in version 7. Does this apply to this article?
    I thought the Tenable REST API uses access keys and secret keys. In this article, username and password are used for authentication, which I thought was the basic web functionality.

    Reply
  16. Jim

    Hi Alexander,

    Thanks for puttiung this up. It’s REALLY helpful (especially that the functionality has been readded to 7.0.3).

    Do you knopw if there’s any way to filter the output to severity > 0? (i.e. only low/medium/high/critical?). I’m currently downloading scans of a few thousand hosts and the raw nessus files total around 4Gb. I tarball them before downloading, but then need to process them with perl in their uncopressed state, but the full nessus files are too big.

    Thanks again!

    Jim

    Reply
  17. Jim

    In reply to my previous question… If anyone out there needs an answer:
    xmlstarlet ed -L -d ‘//ReportItem[@severity=”0″]’ filename.nessus
    xmlstarlet ed -L -d //ReportHost[not\(.//ReportItem\)] filename.nessus

    Reply
  18. Aleksey

    Hi Alexander,

    I generated api keys in Nessus scanner and I try use:
    curl -k -H “X-Impersonate: username={test}” -H “X-ApiKeys: accessKey={3ea8251517ff6de23d86f0ee61cc2caa7aee6a052520d715d1692bb9c2575458}; secretKey={bad16019001f7104df864824dc7af091bf867542dd6b8023252ff5738dc6d897}” https://192.168.1.43:8834/scans

    or

    curl -k -H “X-ApiKeys: accessKey={3ea8251517ff6de23d86f0ee61cc2caa7aee6a052520d715d1692bb9c2575458}; secretKey={bad16019001f7104df864824dc7af091bf867542dd6b8023252ff5738dc6d897}” https://192.168.1.43:8834/scans

    I have error “invalid credentials”, I double-checked all from my side, I haven’t error from my side.

    Also if I use session token from your script:
    curl -s -k -X POST -H ‘Content-Type: application/json’ -d “{\”username\”:\”admin\”,\”password\”:\”1\”}” https://192.168.1.43:8834/session

    This script is work, but I can’t use this token because every time if I use this script this token has new value.
    Can you clarify for me this ? How can I use token with one session ?

    Reply
  19. Aleksey

    I found my error in previous message. I used wrong key – “{key}” instead just – “key” . Fields keys needs use without “{}”

    Thanks Alexander !

    Reply
  20. Subhash

    I’m able to get report using python. When I tried to convert it to Ansible module, It is failing at scan.scan_results().
    Any idea

    Reply

Leave a Reply

Your email address will not be published. Required fields are marked *