Parse weak ciphers out of an nmap.xml report I wrote the following code to parse some weak ciphers out of an nmap.xml report. I am wondering if there is a more elegant way to write this code in order to avoid the nested for loops with the double if loops. Also,  I had to create the two queues. There has to be a better way using list comprehension or a generator or something.

from xml.etree import ElementTree

md5Stack = []
shaStack = []
ip = []

with open('nmap.xml', 'rt') as f:
    tree = ElementTree.parse(f)
    for node in tree.getiterator('address'):
        ip = node.attrib.get('addr')
        for node in tree.getiterator('elem'):
            key = node.attrib.get('key')
            if key == "sha1" and ip not in shaStack:
                print ' %s :: %s' % (ip, key)
                shaStack += ip.split(",")
            if key == "md5" and ip not in md5Stack:
                print ' %s :: %s' % (ip, key)
                md5Stack += ip.split(",") 


Example XML

&lt;host starttime="1" endtime="1"&gt;&lt;status state="up" reason="user-set" reason_ttl="0"/&gt;
&lt;address addr="1.1.1.1" addrtype="ipv4"/&gt;
&lt;table key="pubkey"&gt;
&lt;elem key="type"&gt;md5&lt;/elem&gt;
&lt;elem key="bits"&gt;56&lt;/elem&gt;
&lt;/table&gt;
&lt;table key="validity"&gt;
&lt;elem key="notBefore"&gt;00:00+00:00&lt;/elem&gt;
&lt;elem key="notAfter"&gt;:00:00+00:00&lt;/elem&gt;
&lt;/table&gt;
&lt;elem key="md5"&gt;34lk5jl4k5jlk34j5lk34j5lk&lt;/elem&gt;
&lt;elem key="sha1"&gt;234lk6j23lk6j2l3kj32lk4j3l4&lt;/elem&gt;
&lt;elem key="pem"&gt;BEGIN CERTIFICATE;END CERTIFICATE;&lt;/elem&gt;
&lt;/script&gt;&lt;/port&gt;
&lt;/ports&gt;
&lt;times srtt="98" rttvar="4" to="2"/&gt;
&lt;/host&gt;
I don't believe that this does what you want, for a few reasons.

First, I'm not 100% on interleaving ElementTree iterators, but this may pick up more elements than you want; for example, what happens when the document is something like

&lt;host&gt;
&lt;address addr="1.1.1.1"/&gt;
&lt;/host&gt;
&lt;host&gt;
&lt;address addr="2.2.2.2"/&gt;
&lt;elem key="md5"/&gt;
&lt;/host&gt;


It's at least plausible to me that both IP addresses would show up in the md5 list. Even if it doesn't, you should strive to use code that has clear semantics.

Second, your filters check that ip not in md5Stack but what you add to md5Stack is ip.split(',').



Let's try this:

from xml.etree import ElementTree

def extract_weak_ips(nmap_root):
    md5_ips = set()
    sha1_ips = set()
    hosts = list(nmap_root.iter('host'))
    for host in hosts:
        addresses = [address.get('addr').split(',') 
                     for address in host.findall(".//address[@addr]")]
        if host.find(".//elem[@key='md5']") is not None:
            md5_ips.update(addresses)
        if host.find(".//elem[@key='sha1']") is not None:
            sha1_ips.update(addresses)

def print_ip_set(s, key):
    for ip in s:
        print ' %s :: %s' % (ip, key)

if __name__ == '__main__':
    parser = ElementTree.XMLParser()
    parser.parser.UseForeignDTD(True)
    md5_ips, sha1_ips = extract_weak_ips(ElementTree.parse('nmap.xml', parser).getroot())
    print_ip_set(md5_ips, 'md5')
    print_ip_set(sha1_ips, 'sha1')


I've extracted code into clear functions, and I'm using XPath queries so that the XML engine is doing the work of querying XML, not your application-specific code.



I think that in this case the list-comprehension approach is less readable than the code with for-loops, but if you really want list comprehensions, try this:

def extract_ips_with_key_type(nmap_root, key_type):
    query = ".//elem[@key='%s']" % key_type
    return set(apply(
        itertools.chain,
        (address.get('addr').split(',')
         for host in nmap_root.iter('host')
         if host.find(query) is not None
         for address in host.findall('.//address[@addr]')))