cancel
Showing results for 
Search instead for 
Did you mean: 
cancel
2701
Views
0
Helpful
0
Comments
AdvocateRick
Cisco Employee
Cisco Employee

I have discussed risk meter creation in a past blog; but what do you do with risk meters once they are created?  In the Kenna Security (now Cisco) GUI, the risk meters have a score and are shown with a nice graphic. This score is created by looking at all the vulnerabilities of the assets associated with the risk meter. You could drill down to find the risk meter vulnerabilities in the GUI, or you could write a program to dump the risk meter vulnerabilities into a CSV file.

Let’s review a couple of definitions:

  • An Asset is a “thing” (a server, a router, a laptop, etc) that vulnerabilities are tied to.
  • A Risk Meter is a group of assets based on search or filter criteria. Each Risk Meter has its own Vulnerability Risk Score, which is a measure of the security risk a group of assets poses to the organization. Also known as Asset Groups.

As a security officer in your organization, you might want to customize a report for your management. This blog will discuss obtaining risk meter vulnerabilities via a program and start you on a path of customizing reports from risk meters.

The program, risk_meter_vulns.py, takes two input parameters:

  1. risk meter name (required).
  2. risk score fence (optional).  If not specified, it is zero (0).

At the beginning

Let’s start with main. After the command line parameters are parsed, and the KENNA_API_KEY is obtained, the risk meter name is verified and a dictionary with the risk meter name as the key and the query string as its value is returned. We will see why the query string is useful later.

185     risk_meters = get_a_risk_meter(base_url, headers, risk_meter_name)
186     if risk_meters == {}:
187         print(f"{risk_meter_name} not found.")
188         sys.exit(1)

Next the CSV file is opened using the writer() function.

190     # Open up the CSV file.
191     vulns_in_risk_meter_fp = open(csv_file_name, 'w', newline='')
192     vulns_writer = csv.writer(vulns_in_risk_meter_fp)

Finally, the code loops through all the risk meters in the risk meter dictionary. In this version of the code, there is only one risk meter in the dictionary; however, with the for loop, a collection of risk meters could be utilized.

Inside the for loop, a title is printed, assets associated with the risk meter are obtained along with the vulnerabilities for each asset in the get_assets_in_risk_meter() function.

194     # Process the risk meter.
195     for risk_meter_name in risk_meters.keys():
196         # Write risk meter title.
197         vulns_writer.writerow([f"Vulnerabilities for Risk Meter {risk_meter_name} score over {risk_meter_score_fence}"])
198         vulns_writer.writerow([])
199 
200         query_string = risk_meters[risk_meter_name]
201         print(f"Processing: {risk_meter_name} with {query_string}: ", end='')
202         (num_assets, num_vulns) = get_assets_in_risk_meter(base_url, headers, query_string,
203                                                            risk_meter_score_fence, vulns_writer)
204         print(f"{num_assets} assets with {num_vulns} vulnerabilities")
205         vulns_writer.writerow([])

Finding the risk meter

Since the Kenna Security (now Cisco) API does not have a “Search Risk Meter” API, the code invokes the “List Risk Meters” API and searches for the risk meter name. This is done to verify that the risk meter exists with the specified risk meter name; and to obtain the query string.

  7 # Obtain risk meter information by searching all the risk meters.
  8 def get_a_risk_meter(base_url, headers, risk_meter_name):
  9     risk_meters = {}
 10     list_risk_meters_url = f"{base_url}asset_groups"
 11 
 12     # Invoke the List Asset Groups (Risk Meters) API.
 13     response = requests.get(list_risk_meters_url, headers=headers)
 14     if response.status_code != 200:
 15         print(f"List Risk Meters Error: {response.status_code} with {list_risk_meters_url}")
 16         sys.exit(1)
 17 
 18     resp_json = response.json()
 19     risk_meters_resp = resp_json['asset_groups']
 20 
 21     # Search for the risk meter by name.
 22     for risk_meter in risk_meters_resp:
 23         if risk_meter['name'] == risk_meter_name:
 24             risk_meters[risk_meter['name']] = risk_meter['querystring']
 25 
 26     return risk_meters

In line 13, “List Risk Meters” API is invoked. In line 19, the asset_groups or risk meters are extracted from the JSON response.  Lines 22 to 24 search for a risk_meter by name.  If found, a dictionary of one is returned.  The key is the name with the value of the query string.

Why return a dictionary instead of a tuple you might ask. The reason is that this function could be replaced with a function that returns all risk meters and the calling code won’t have to change except to call a different function. This was mentioned in the “At the Beginning” section.

Get the assets associated with the risk meter

In the function, get_assets_in_risk_meter(), the assets are obtained in page mode.  See “Acquiring Vulnerabilities per Asset” for more information on page mode.  We can obtain up to 100,000 assets with a 5,000 page size with 20 pages.  Hopefully, you don’t have over 100,000 assets associated with a risk meter.  If you do, this code will not obtain all the vulnerabilities.

If you read over the Kenna Security (now Cisco) API documentation, you’ll notice that when listing an asset group, you don’t get the assets associated with the asset group. Here is where the query string is important. My colleague, Stephan George, gave me this clue. To obtain the assets in an asset group or risk meter, you invoke the “Search Assets” API with the query string provided in the “List Asset Groups” API response.

 78     # Create the search URL with the provided query_string
 79     search_assets_url = f"{base_url}assets/search?{query_string}&per_page=5000"
 80 
 81     # Invoke the Search Assets API.
 82     response = requests.get(search_assets_url, headers=headers)
 83     if response.status_code != 200:
 84         print(f"Search Assets Error: {response.status_code} with {search_assets_url}")
 85         sys.exit(1)

Look at line 79 where the query_string is used along with the 5000 entries per page specification.

Now that we have the assets associated with the risk meter, we obtain the vulnerabilities for each asset.

101     # Get the vulnerabilities for the first page of assets.
102     for asset in assets_resp:
103         # Write the asset locator.
104         vulns_writer.writerow([asset['locator']])
105 
106         vuln_url = asset['urls']['vulnerabilities']
107         vuln_count += get_vuln_info(vuln_url, headers, risk_meter_score_fence, vulns_writer)

Obtaining Vulnerabilities per Asset with FilteringNotice in line 104, the asset locator is written to the file.  In line 107, the function get_vuln_info() is called to obtain the vulnerability information. The URL is provided for in line 106 that is used to invoke the “Show Asset Vulnerabilities” API.

This code is similar to the code in page_asset_vulns.py, except the vulnerabilities are filtered on an open status and if the risk score is equal or higher than the risk score fence.

 49     # Go through the vulnerabilities filtering on open vulnerabilities and risk meter score.
 50     for vuln in vulns_resp:
 51         if not vuln['closed'] and vuln['risk_meter_score'] >= risk_meter_score_fence:
 52             vulns_to_process.append(vuln)

If the vulnerability matches the criteria, it is placed in an array for processing later in line 52.

For each vulnerability that matches the criteria of open status and higher or equal to the fence, the code writes some vulnerability information into the CSV file.

 61     # Write the vulnerability information to the provided CSV file.
 62     for vuln in vulns_to_process:
 63         cve_id = vuln['cve_id']
 64         priority = vuln['priority']
 65         threat = vuln['threat']
 66         severity = vuln['severity']
 67         risk_meter_score = vuln['risk_meter_score']
 68         cve_description = vuln['cve_description']
 69         #print(f"{cve_id} {priority} - {threat} - {severity} : {risk_meter_score} ({risk_meter_score_fence})")
 70         vulns_writer.writerow([cve_id, priority, threat, severity, risk_meter_score, cve_description])

The reason there are temporary variables is because the vulns_writer.writerow() statement in line 70 becomes a very long line. I also left a debug print() statement for your convenience. As you can see, the code writes the CVE ID, along with the priority, threat, severity, risk score, and CVE description.

Conclusion

Here is the output from the command: python risk_meter_vulns.py Windows 70

AdvocateRick_0-1684367574027.jpeg

The filtering can be changed; for example, priority could be used. The information that is written to the CSV file could also be modified for your customized reports. So go have some fun customizing.

The code samples are in the Kenna Security blog_samples repo.

More information

Understanding Vulnerability, Asset and Risk Meter Scoring

Rick Ehrhart

API Evangelist

This blog was originally written for Kenna Security, which has been acquired by Cisco Systems.
Learn more about Cisco Vulnerability Management.

Getting Started

Find answers to your questions by entering keywords or phrases in the Search bar above. New here? Use these resources to familiarize yourself with the community: