PyQual: Simple Python code for the Qualys API

Document created by Brian Fackler on Oct 14, 2013Last modified by Brian Fackler on Oct 14, 2013
Version 2Show Document
  • View in full screen mode

# Caveats:

#

# This requires the "reqeusts" python module: http://docs.python-requests.org/en/latest/

#

# This is simple code for people who just want to run a friggin scan on a few IPs and want the result

# from those scans.  You won't be running any enterprise systems with this, no asset management,

# no tags and stuff.  This is not a substitute for Parag Baxi's code.  This is a simple primer.

#

# Also: There is NO error checking in this code, there is NO try catch, there is only do or do not.

#

# On the other hand, you can use this code to help understand in a simple way what all the buttons do.

 

 

import requests

import xml.etree.ElementTree as ET

import time

 

 

def login(s):

    #

    # ---Session Login---

    #

    # Logs into a Qualys session given a requests.Session object 's'. A Session.post()

    # always returns a response, here assigned 'r'.

    #

    payload = {

               'action':'login',

               'username':'your_username',

               'password':'your_password'

               }

    r = s.post('https://qualysapi.qualys.com/api/2.0/fo/session/', data=payload)

 

    # Now that all the hard work was done, lets parse the response.

    xmlreturn = ET.fromstring(r.text)

    for elem in xmlreturn.findall('.//TEXT'):

        print elem.text #Prints the "Logged in" message. Not really needed, but reassuring.

 

 

 

def logout(s):

    #

    # ---Logout---

    #

    # Really, you need a description for what this does?

    #

    payload = {

               'action':'logout'

               }

    r = s.post('https://qualysapi.qualys.com/api/2.0/fo/session/', data=payload)

 

    # Now that all the hard work was done, lets parse the response.

    xmlreturn = ET.fromstring(r.text)

    for elem in xmlreturn.findall('.//TEXT'):

        print elem.text   #Prints the "Logged out" message. Not really needed, but reassuring.

 

 

 

def launchScan(s, targetIP):

    #

    #---Launch Scan---

    #

    # Launches a scan given a target IP and returns the reference string to be

    # used when checking the scan status and starting a report.

    # Note: The field 'iscanner_name' is the same as the the dropdown list of

    # scanner appliances when launching a scan from the web interface.

    # I just have a thing for using the last appliance in the list.

    # The 'option_id' is found on the info page of the scanner options profile

    # you want to use.

    #

    payload = {

               'action':'launch',

               'ip':targetIP,

               'iscanner_name':'your_scanner_here',

               'option_id':'324575',

               'scan_title':targetIP,

               }

    r = s.post('https://qualysapi.qualys.com/api/2.0/fo/scan/', data=payload)

 

    # Now that all the hard work was done, lets parse the response.

    #print r.text   #prints the full xml response from Qualys just for fun.

    xmlreturn = ET.fromstring(r.text)

    for elem in xmlreturn.findall('.//ITEM'):

        if (elem[0].text == 'REFERENCE'): scanRef = elem[1].text

    return scanRef

 

 

 

 

def checkScan(s, scanRef):

    #

    #---Check Scans---

    #

    # Checks the status of our scan.

    #

    payload = {

               'action':'list',

               'scan_ref':scanRef,

               }

    r = s.post('https://qualysapi.qualys.com/api/2.0/fo/scan/', data=payload)

 

    # Now that all the hard work was done, lets parse the response.

    #print r.text   #prints the full xml response from Qualys just for fun.

    xmlreturn = ET.fromstring(r.text)

    for elem in xmlreturn.findall('.//STATUS'):

        status = elem[0].text

        print elem[0].text   #Prints the status message. Not really needed, but reassuring.

    return status

 

 

 

def launchReport(s, scanRef, reportType, targetIP):

    #

    #---Launch Report---

    #

    # Launches a Report given a scanRef and type of report and returns the reference string

    # to be used when checking the status of the report and downloading it.

    # Note: I got template_id from the web interface, reports > templates and then select 'info'

    # from the drop-down on the particular template you want to use.

    # Make sure the report template you use is of the "Manual" type

    #

    payload = {

               'action':'launch',

               'report_type':'Scan',

               'template_id':'736275',

               'output_format':reportType,

               'report_refs':scanRef,

               'report_title':targetIP,

               }

    r = s.post('https://qualysapi.qualys.com/api/2.0/fo/report/', data=payload)

 

    # Now that all the hard work was done, lets parse the response.

    #print r.text   #prints the full xml response from Qualys just for fun.

    xmlreturn = ET.fromstring(r.text)

    for elem in xmlreturn.findall('.//ITEM'):

        if (elem[0].text == 'ID'): reportID = elem[1].text

    return reportID

 

 

 

def checkReport(s, reportID):

    #

    #---Check Reports---

    #

    # Checks the status of our Report.

    #

    payload = {

               'action':'list',

               'id':reportID,

               }

    r = s.post('https://qualysapi.qualys.com/api/2.0/fo/report/', data=payload)

 

    # Now that all the hard work was done, lets parse the response.

    #print r.text   #prints the full xml response from Qualys just for fun.

    xmlreturn = ET.fromstring(r.text)

    for elem in xmlreturn.findall('.//STATUS'):

        status = elem[0].text

        print elem[0].text  #Prints the status message. Not really needed, but reassuring.

    return status

 

 

 

def downloadReport(s, reportID, targetIP):

    #

    #---Download Report---

    #

    # Lets get the report.

    #

    payload = {

               'action':'fetch',

               'id':reportID,

               }

    r = s.post('https://qualysapi.qualys.com/api/2.0/fo/report/', data=payload)

 

    # Now that all the hard work was done, lets get that report.

    # No chunking needed due to small size of a single IP scan report.

    with open("Z:/Shared/RelayScans/Scan_Report_"+targetIP+".pdf", "wb") as report:

        report.write(r.content)

 

 

def main():

    s = requests.Session()

    s.headers.update({'X-Requested-With':'Facklers PyQual python primer'})

    login(s)

 

    ipList = ['192.168.1.15','10.0.1.28']

 

    for targetIP in ipList:

        scanRef = launchScan(s, targetIP)

 

        # These API calls are valuable commodities, lets wait at least 10 minutes before we check.

        time.sleep(600)

 

        # Ok, now lets check on the scan.

        while checkScan(s, scanRef) != "Finished":

            time.sleep(600)

 

        reportType = 'pdf'

        reportID = launchReport(s, scanRef, reportType, targetIP)

 

        # Apparently asking about a report too soon after initiating it causes bad

        # responses which breaks checkReport()

        time.sleep(120)

 

        # OK, now lets check on the report, reports go faster so we can shorten the interval.

        while checkReport(s, reportID) != "Finished":

            time.sleep(120)

 

        downloadReport(s, reportID, targetIP)

 

    logout(s)

    s.close()

if __name__ == "__main__": main()

1 person found this helpful

Attachments

    Outcomes