AnsweredAssumed Answered

Qualys VM, Python, and Tagging

Question asked by nwgb on Mar 31, 2015
Latest reply on Mar 31, 2015 by Jeff Leggett

Hi all,

 

This is my first post to the Qualys Community, apologies if I'm not posting it in the right spot, and thanks in advance to anyone who can help.   This question pertains to the Qualys Vulnerability Management (QVM) suite, Python 2.7 & PyCurl & PowerShell 4 on a Windows 7 workstation, and the Qualys API.

 

TL/DR:

 

Is it possible to update QVM Asset Tags via the API and Python (or even PowerShell)?  I can update tags in DOS, and GET tag information in Python, PowerShell and DOS.  If anyone has a working model of this, I'd love to see it. 

 

More info:

 

I've been manually tagging specific groups of assets in the Asset Management module (though using Asset Groups would make more sense, it's not an option in this instance).  The assets in each tag are relatively volatile, and need to be updated on at least a weekly basis.

 

I've just begun trying to automate this by using the API with Python.  I'm new to Python, so despite hours of google-searches, it's possible I'm missing something very simple, or perhaps it's even the API.  I'm at the state where I can't tell. 

 

I've also tried playing with the Python "qualysapi" module, but found it problematic, especially with regard to handling the authenticated proxy that we have to go through. 

 

Again, thanks in advance for any help!

 

-Geoff

 

Some code examples:

 

The XML Document:

<?xml version="1.0" encoding="UTF-8" ?>
<ServiceRequest>
<data>
<Tag>
<name>NEWNAME</name>
</Tag>
</data>
</ServiceRequest>

 

The Python script:

 

# Notes:  This script is a proof of concept intended to read / write Qualys tag information via the cURL API.  
#
# It is run as either:
# "python .\pycurl0003.py"  <- to GET the contents of a tag (for now, the tag # is statically defined)
# -OR-
# "python .\pycurl0003.py .\test.xml"  <- to UPDATE the contents of a tag  (for now, the tag # is statically defined)
#
# The script can take a single argument (".\test.xml"), which contains the payload to update the statically-defined tag.
# For now, that XML data simply updates the name of the XML tag.  Subsequent iterations will do more.


#########
# IMPORTS
#########
import sys, os
import pycurl
import cStringIO
from StringIO import StringIO
from xml.dom import minidom
from urllib import urlencode

################
# AUTHENTICATION
################

proxyUsername = raw_input('Enter Domain Username: ')
proxyPassword = raw_input('Enter Domain Password: ')
qualysUsername = raw_input('Enter Qualys Username: ')
qualysPassword = raw_input('Enter Qualys Password: ')
proxyCredentials = proxyUsername + ":" + proxyPassword
qualysCredentials = qualysUsername + ":" + qualysPassword
nwProxyURL = "http://thisIsMyProxyServer:8080"

###############
# PyCURL SETUP
###############

# INITIALIZE
buffer = cStringIO.StringIO()
c = pycurl.Curl()
c.setopt(c.VERBOSE, True)

# AUTHENTICATION
c.setopt(c.PROXYTYPE, c.PROXYTYPE_HTTP)
c.setopt(c.PROXYAUTH, c.HTTPAUTH_NTLM)
c.setopt(c.PROXYUSERPWD, proxyCredentials) 
c.setopt(c.USERPWD, qualysCredentials)

# FILE HANDLING
if len(sys.argv) > 1:
    xmldoc = unicode(minidom.parse( sys.argv[1] ))
    qualysTagURL = "https://qualysapi.qualys.com/qps/rest/2.0/update/am/tag/thisIsMyTagNumber"
    c.setopt(c.POSTFIELDS, xmldoc)
    # c.setopt(c.POST, 1)  # <-  THIS GENERATES A 404 ERROR
    c.setopt(pycurl.UPLOAD, 1)  # <- THIS DOESN'T GENERATE AN ERROR, BUT HANGS
else:
    qualysTagURL = "https://qualysapi.qualys.com/qps/rest/2.0/get/am/tag/thisIsMyTagNumber"

#############
# PyCURL WORK
#############

c.setopt(c.HTTPHEADER, ["Content-type: text/xml"])
c.setopt(c.URL, qualysTagURL)
c.setopt(c.SSL_VERIFYPEER, 0)
c.setopt(c.WRITEFUNCTION, buffer.write)
c.setopt(c.PROXY, nwProxyURL)

# START TRANSFER
c.perform()
c.close()

print buffer.getvalue()
buffer.close()

print "\n\n##############\n# DEBUG STUFF:\n##############\n"
print "# Argv Length: ", len(sys.argv)
if len(sys.argv) > 1:
    print "# XMLDOC Type: ", type( xmldoc )
else:
    print "# No XMLDOC Specified"    
print "# Qualys Tag URL: " + qualysTagURL
print "# Proxy URL: " + nwProxyURL

# print "\n\n"    
# print "HTTP CODE:\n"
# print c.getinfo(pycurl.HTTP_CODE), c.getinfo(pycurl.EFFECTIVE_URL)

print "\n\nEND OF SCRIPT\n\n"

 

Side-note: PowerShell / DOS:

 

As I mentioned, I CAN successfully read the contents of a tag using Python, PowerShell, and DOS.  I can also WRITE to the contents of the tag using the same XML file via cURL in DOS.  I wound up using DOS instead of PowerShell since PowerShell apparently can't parse the "<" redirector for the XML file, and "Get-Content |" chokes up the Web-Request call entirely.

 

For example, here's the DOS command that DOES work:

 

curl -k -u "thisIsMyQualysUsername:thisIsMyQualysPassword" --proxy-ntlm --proxy-user "thisIsMyProxyUsername:thisIsMyProxyPassword" --proxy thisIsMyProxyServer:8080 -H "Content-type: text/xml" -X "POST" --data-binary @- "https://qualysapi.qualys.com/qps/rest/2.0/update/am/tag/thisIsMyTagNumber" < test.xml

 

Here's the PowerShell equivalent that does NOT work:

 

.\curl -k -u $qualysCredentials --proxy-ntlm --proxy-user $thisIsMyProxyCredential --proxy $thisIsMyProxyServer -H "Content-type: text/xml" -X "POST" --data-binary @- "https://qualysapi.qualys.com/qps/rest/2.0/update/am/tag/thisIsMyTagNumber" < test.xml

 

First, PowerShell yelps about the "@" symbol.  When I escape the "@" with a backtick, it yelps about the redirector symbol.  When I escape that with a backtick, it yelps about the -u argument, which otherwise works if no input file is specified (e.g., during a tag "get").  Again, using "Get-Content |" seems to choke up the Web-Request call entirely.

Outcomes