Building a Software Inventory with Nessus

Casey Reid a.k.a Packet Chaos
7 min readDec 30, 2023

Software inventory is only second in the cyber security importance to Asset inventory according to the CIS critical controls v8; and Nessus can help accomplish both controls.

Tenable has thousands of information plugins that help remediators understand more about the asset while others use them to classify their assets and route vulnerabilities to their asset owners.

I wrote about my favorite information plugin, 19506 some time ago; read about it here. In this article we are going to be talking about and parsing two of my favorite information plugins: 20811 and 22869.

These two plugins are software enumeration plugins and help gather all of the software on the asset; with exceptions to custom applications. Let’s start by seeing an example output of each using my home lab.

Plugin 22869 — Linux Machines

The plugin output below has all of the software managed by the package manager ordered by software package name followed by installation date. Below is the output on a Linux Machine.

Plugin 22869 — Mac OS

The plugin output below is a trimmed version of the plugin 83991. I’ve put the output of 83991 below the 22869 plugin on the same asset for your comparison and knowledge. In my examples that follow, I won’t use 83991 because the data in 22869 is sufficient.

As you compare the two screen shots you will see that 83911 includes the software installation location which is very helpful for removing any software installed. Since my goal is to simply list and dedup software I don’t need this information.

Plugin 83991 — Mac OS

Again, notice below the installation location and the structure difference of the output.

The Challenge

Nessus collects all of this data by default but it isn’t striped from the plugin and made available in the UI. This makes it difficult to get an idea of software installations across a wide swath of assets.

To accomplish this we are going to need to download the plugin data, parse the two plugins and store software installations in a database so it can be queried and reported on later.

Let the Parsing Begin

This is a job for python and navi!

Since navi already has all of the Nessus data from Tenable Vulnerability Management we will rely on the navi.db to extract our plugin output data for parsing.

Parsing 22869

To parse the data we use the built in ‘db_query’ function to query the navi database and pull the results for all assets with 22869 plugin data.

software_data = db_query("select output, asset_uuid from vulns where plugin_id='22869'")

Now we need to loop through the data and split the lines at the “|” since we don’t care about the installation date.

for pkg in str(data[0]).splitlines():
pkg_name = str(pkg.split("|"))

We have all of the software but the line at the top still persists so let’s remove it by skipping it in our loop.

if "packages installed" not in pkg_name:

While working on this I also noticed some outputs contained extra spaces and “ii” preceding the software package name.

if "  ii  " in pkg_name:
new_name = eval(pkg_name)[0][7:]

Finally, we stripped all of the stuff out of the way and have our package name. Now let’s create a dictionary to store the software name and a list of asset uuids that are assoicated with that software version.

if pkg_name not in soft_dict:
soft_dict[new_name] = [asset_uuid]
else:
soft_dict[new_name].append(asset_uuid)

Let’s put it all together, function below is what I use to parse the 22869 plugin in navi.

def parse_22869(soft_dict):
software_data = db_query("select output, asset_uuid from vulns where plugin_id='22869'")
for data in software_data:
asset_uuid = data[1]
for pkg in str(data[0]).splitlines():
pkg_name = str(pkg.split("|"))
if "packages installed" not in pkg_name:
# Some output has "ii" in it after the split
if " ii " in pkg_name:
new_name = eval(pkg_name)[0][7:]
if pkg_name not in soft_dict:
soft_dict[new_name] = [asset_uuid]
else:
soft_dict[new_name].append(asset_uuid)
else:
new_name = eval(pkg_name)[0][2:]
if pkg_name not in soft_dict:
soft_dict[new_name] = [asset_uuid]
else:
soft_dict[new_name].append(asset_uuid)

Parsing 20811

Given I broke out line by line parsing the 22869 plugin I will just provide the function I use in Navi and provide some commentary.

If you follow the code line by line you will see it is very similar to parsing the 22869 plugin. However the line at the top is worded a bit different at the top of the pluing; it uses “software” instead of “packages”. The second major difference is the reference to installation location which we remove.

def parse_20811(soft_dict):
software_data = db_query("select output, asset_uuid from vulns where plugin_id='20811'")
for data in software_data:
asset_uuid = data[1]
for pkg in data:
new_string = str(pkg).splitlines()
list = eval(str(new_string))
for item in list:
if "The following software" not in item:
if "installed" in item:
new_item = item.split(" [installed")
try:
if new_item[0] not in soft_dict:
soft_dict[new_item[0]] = [asset_uuid]
else:
soft_dict[new_item[0]].append(asset_uuid)
except TypeError:
pass

Making use of the Data

The data is parsed and ready to be saved into the Navi database. If you are looking for insight into saving data into a SQLite database check out my article detailing how to save Nessus data into a SQLite database.

For this article I will skip the code covering the creation and inserting data into the database. Let’s now cover the code to cycle through the dictionary we created above and store the data in the database.

These first few lines prepare the database and drop the software table. Since Navi is a CLI tool there is little need to update the database, although this is planned for a future release. The create_software_table function creates the the table.

database = r'navi.db'
new_conn = new_db_connection(database)
drop_tables(new_conn, "software")
create_software_table()

Once the database has been prepped we need to call our to parsers and send the software dictionary to be decorated with the data in the navi.db database.

# Grab 22869 Data
parse_22869(soft_dict)

# Grab 20811 Data
parse_20811(soft_dict)

Now we need to cycle through the software dictionary, “soft_dict” and insert the data into the database using the insert_software function.

with new_conn:
new_list = []
for item in soft_dict.items():
# Save the uuid list as a string
new_list = [item[0], str(item[1])]
insert_software(new_conn, new_list)

Bringing it all together you can see that it is quite simple once we have the data gathered.

def generate():
database = r'navi.db'
new_conn = new_db_connection(database)
drop_tables(new_conn, "software")
create_software_table()
soft_dict = {}

# Grab 22869 Data
parse_22869(soft_dict)

# Grab 20811 Data
parse_20811(soft_dict)

with new_conn:
new_list = []
for item in soft_dict.items():
# Save the uuid list as a string
new_list = [item[0], str(item[1])]
insert_software(new_conn, new_list)

Making it simple

While it is fun to code and work with python I know that a lot of people don’t have the time to build their own solution. You can guess where I’m going with this; it’s built into navi!

The functionality at this point is rather simplistic and I plan on building on top of it as time progresses.

navi software generate

The command above runs the code we just walked through and as a result populates the database with the fully parsed plugin results of any asset with 20811 and 22869. On the screen it will provide some basic statistics:

  • Unique Software total
  • Assets that are in the database
  • Assets with plugins 22869 or 20811
  • Assets without plugins 22869 and 20811

You can use the navi built-in functionality with the software table which automatically extends the value with this new addition.

Export Software Inventory

Once you have run the navi software generate command you can export the data into a csv, and if you have your smtp information entered you can mail the result to yourself.

navi export query "select * from software;"

Identify Assets without a Software inventory

One of the action items besides removing unwanted software is identifying assets that do not have a software inventory and see if you can get credential scans completed on the effected assets.

navi software -missing

The above command identifies any asset that doesn’t have plugin 22869 or 20811 and prints them on to the screen as shown below.

Conclusion

The Tenable information plugins are as good as gold. The information found in Nessus Scans have many more use cases outside of traditional vulnerability management and bleed into more general security like software and asset inventory.

If you have suggestions to improve the functionality of navi please sumit a PR on github or send me an email!

--

--

Casey Reid a.k.a Packet Chaos

I'm a perpetually curious avid learner and athletic hacker/tinker who dabbles in python development, tenable integrations, philosophy, and writing