Is there a way to bulk Exclude multiple vulnerabilities using the API?

Does anyone have a working example of an API script that would allow me to bulk exclude multiple Java vulnerabilities? I’m talking like 800+.

1 Like

@tom_klieber

Hi Tom,

Are you looking for a POST request that puts in exceptions for the API in a bulk manner? Currently working on the same project for my team. I have a general script for it set in python, just waiting to test this and going to use a for loop to do bulk requests. Let me know if you have any questions.

Hi Jacob,
Said slightly differently, I’m looking for a way to exclude 100’s of vulnerabilities in an automated fashion. How that list of vulns gets defined is another dimension to this, whether it’s searching the vuln title for a keyword, or feeding a list of vulns statically to a script, who knows. But once you know what you want to exclude, is there a swift way to execute that? Being new to Rapid7, I only see an exclude button you can click next to a single vuln, but I can’t “select all” and exclude everything on my select.

Hi Tom,

There are a few different ways you can exclude vulnerabilities with different filters in an automated fashion. So you can exclude them at the asset level, site level or even asset group level. You can exclude them on a specific port as well as add a link to why you are excluding that vulnerability. I would suggest looking over the link I have below. That is the only way I found out how to exclude them on a bulk level so far.
https://help.rapid7.com/insightvm/en-us/api/index.html#operation/createVulnerabilityException

I am more than happy to share what I have completed when my automation script is done. Let me know if you have anymore questions.

2 Likes

I don’t have a script on hand for this, but if what you’re looking to do is bulk exclude these Java vulns as findings, then I agree with @jacob_horning that you can use the “create vulnerability exception” endpoint in the API.

You could either read in the vulns you want to exclude from a file or get them via the API, then loop through to post to /api/3/vulnerability_exceptions

1 Like

Thank you both this is great info and I just browsed the link. I have some learning to do both with APIs in general and what Rapid7 offers by way of api v3. This just launched me miles ahead of where I would have been if this discussion site didn’t exist!

2 Likes

Glad to hear! Let us know if you need any help as you’re diving into the API. When I first started learning about APIs I used Postman, which is a tool that makes it a lot easier to test your API calls and see what kind of responses you’re getting. If you scroll through this post there’s a couple Postman examples with the IVM API to give you an idea of what it looks like.

@holly_wilsey

I was wondering if you could clear up some information on the insightvm API documentation. For vulnerability exception POST. There are four links that are associated with the post request. Which ones are optional to use and the ending of the link with the … are those supposed to be nexpose_ids, vulnerability_ids or asset_ids? And for the vulnerability section in scope is it looking of the nexpose_id or vulnerability_id?

Thank you so much!

I answered this over here, let me know if it helps.

Thank you so much holly for your help!

2 Likes

I as well would like to exclude 1000s of vulnerabilities using the API. I have the following post request but receive errors when trying to execute.

curl -k --request POST --header ‘Authorization: Basic username:password’ --header ‘Content-Type: application/json’ --header ‘Host: server’ --data ‘{“expires”: “”,“scope”: {“id”: “0000"links”: [{“id”: “0000”,“href”: “server/api/3/vulnerabilities/nexposeid”,“rel”: “Vulnerability”},{“id”:0000,“href”: “server/api/3/assets/0000”,“rel”: “Asset”}],“type”: “instance”,“vulnerability”: “jre-vuln-cve-2018-2637”},“state”: “approved”,“submit”: {“comment”: “comments go here.”,“links”: [],“name”: “username”,“reason”:“Acceptable Risk”,“user”: 6}}’ https://server/api/3/vulnerability_exceptions/

When I run this Post request, I receive an error stating:
“status” : 400,
“message” : “The JSON input is invalid at line 1, column 39. Details: Unexpected character (‘l’ (code 108)): was expecting comma to separate Object entries.”

Line 1, column 39 is near the authentication portion of this Post request but I don’t think I see any problems with it.

Any help would be much appreciated :slight_smile:

I think there’s potentially a few things going on with your curl command.

  1. The quotes throughout the command might be “smart quotes”, which can cause your request to fail, so try replacing them with normal basic quotes.
  2. The JSON you’re passing in for your data isn’t formatted properly. I see this part: “scope”: {“id”: “0000"links” and it looks like you have some quotes that aren’t opened/closed properly. Try pasting your JSON in a separate file first and formatting it that way.
  3. There’s no need to provide values for the links fields, since they’re read-only.

Let us know if those updates help!

You know, none of these answers actually address the main question. I have an API POST call that works, however I can only make one call at a time. I have a list of vulnerability IDs that I can copy paste each one into the script and then make the call, however, I have hundreds of vulnerabilities to do this for. How can I get them all done in the same call?

I don’t believe there’s a way to bulk add exceptions in a single API call, but you can still bulk create them without having to manually copy-paste a vulnerability ID each time.

One way you can do this is by adding your list of vulnerability ID’s to a file, then having your script read from the file to loop through them and call the API endpoint to create an exception for each one. The loop part means that an exception would be created for all vulnerabilities that are read in from the file, without you having to update the ID.

That was the ticket in the end. Thanks Holly.

1 Like

Of course, glad you got it figured out!

I’m sure there are other ways to accomplish this, but we run the SQL query below to get a list of the vuln IDs. Of course, you can modify the WHERE clause or use an entirely different method to collect the vuln IDs.

SELECT dv.title AS "Vulnerability Title",
       substring(dv.title from '\((.+)\)') AS "CVE",
       dv.nexpose_id as "Vulnerability ID"
     FROM dim_vulnerability dv
     WHERE dv.title ~* '(CVE-2021-30805|CVE-2021-30790|CVE-2021-30781)'
     OR dv.nexpose_id ~* '(CVE-2021-30805|CVE-2021-30790|CVE-2021-30781)'
     ORDER BY dv.nexpose_id

Then, we use that file to pass the list of vuln IDs into a python script that loops through each one and submits the same exception. The scope, state, and comments would just need to be adjusted based on the exception being submitted.

Here is the python script I’m using. The api_endpoint URL would need to be modified to your console URL or IP address. Also, the “Vulnerability IDs for Exception Submission” report is just the SQL query above.

###INSTRUCTIONS###
# 1) Generate "Vulnerability IDs for Exception Submission" report in InsightVM for any CVEs that require an exception to be submitted and name the file vuln_id_list.csv.
# 2) Modify "state" in the def generate_payload line.
# 3) Modify "scope" under the generate_payload function.  Change ID (asset, group, instance, etc), type ("Global", "Site", "Asset", "Asset Group", or "Instance").
# 4) Modify "comment" under main function to describe the reason for the exception.
# 5) Run the script with the command "python vuln_exception.py".
##################

# imports
import base64
import csv
import getpass
import logging
import requests
from requests.packages.urllib3.exceptions import InsecureRequestWarning

# global config
verifySsl = False
inPath = "vuln_id_list.csv" # the input file
vuln_column_name = "Vulnerability ID" # the column header name to identify the vuln_id column
api_endpoint = "https://INSIGHTVM_CONSOLE:3780/api/3/vulnerability_exceptions"

DEBUG = False


def get_creds_encoded(prompt='Password: '):
    """Gets the username and password in user:password format base64 encoded

    Args:
        prompt (str, optional): Prompt to show user when asking for password. Defaults to 'Password: '.

    Returns:
        str: base64 string of the user:password combination
    """
    user = getpass.getuser()
    password = getpass.getpass(prompt, stream=None)
    auth_token = base64.b64encode(f"{user}:{password}".encode("utf-8"))
    return str(auth_token, "utf-8")


def generate_payload(vuln_id, comment="", state="Approved"):
    """Return a object useable as the payload for uploading to insightVM

    Args:
        vuln_id (str): the Nextpose identifier for the vulnerability
        comment (str, optional): Comment to add to the vulnerability exception. Defaults to "".
        state (str, optional): The state of the vulnerability exception. One of: "Deleted", "Expired", "Approved", "Rejected", `"Under Review".. Defaults to "Approved".

    Returns:
        obj: payload object
    """
    return {
            'expires': '',
            'scope': {
                'id': 241,
                'type': 'Asset Group',
                'vulnerability': vuln_id
            },
            'state': state,
            'submit': {
                'comment': comment,
                'reason': 'False Positive'
            }
         }


def main():
    # setup logging and library configs
    requests.packages.urllib3.disable_warnings(InsecureRequestWarning)
    if DEBUG:
        logging.basicConfig(filename='vuln_exception_log.log', level=logging.DEBUG)

    # get username and password from user and save as header
    header = {"Authorization": f"Basic {get_creds_encoded()}"}
    if DEBUG:
        print(f"header: {header}")

    try:
        with open(inPath, "r") as inFile:
            inReader = csv.DictReader(inFile)

            try:
                for row in inReader:
                    vuln_id = row.get(vuln_column_name)
                    if vuln_id:
                        comment = "This vulnerability was remediated in the Catalina Security Update 2021-003.  Additional information can be found in the Apple Security Update URL. https://support.apple.com/HT212326"
                        payload = generate_payload(vuln_id, comment=comment)
                        ret = requests.post(api_endpoint, json=payload, headers=header, verify=verifySsl)
                        if DEBUG:
                            print(f"{vuln_id}\t{ret.status_code}\t{ret.reason}\t{ret.json()}")
                        else:
                            print(f"{vuln_id}\t{ret.status_code}\t{ret.json().get('message', ret.reason)}")

            except csv.Error as e:
                print(f"Error parsing csv file {inPath}, line {inReader.line_num}: {e}")

    except IOError as e:
        print(f"Error reading {inPath}: {e}")


# if this file is being directly run, run the main function
# otherwise, this is being imported into another script and we don't want to immediately run
if __name__ == "__main__":
    main()

Hope this helps!

4 Likes

Thanks for sharing this! It’s a really good example of how to handle bulk exceptions. :+1:

1 Like

Great Example Scott! I leveraged this script to run through a csv for submitting exceptions, but Im running into an issue with the ‘expires’ field. Error message received is the following:

|2777|400|The JSON input is invalid for the ‘expires’ property of the VulnerabilityExceptionResource resource, at line 1, column 13.|
|3102|400|The JSON input is invalid for the ‘expires’ property of the VulnerabilityExceptionResource resource, at line 1, column 13.|

Here’s what my payload part of the script looks like:
def generate_payload(vuln_id, ag_id="", comment="", state=“Approved”):
return {
‘expires’: ‘2023-02-16T00:00:00.000Z’,
‘scope’: {
‘id’: ag_id,
‘type’: ‘Asset Group’,
‘vulnerability’: vuln_id
},
‘state’: ‘Approved’,
‘submit’: {
‘comment’: comment,
‘reason’: ‘Compensating Control’
}
}

Any thoughts on what I might be doing wrong here?