Posted by & filed under /dev/random, Hacking & Pentesting, Tools & Methodology.

I’ve been using the nmap NSE scripts a lot recently in pentests, and i find the results from many of the scripts invaluable, like smb-enum-users. This script uses some ninja tricks to make the machines spill out all their users using null sessions.

Although the scripts are great, the nmap output format to stdout is not very efficient in terms of building user lists that can be utilized in further attacks, especially if you’re targetting a domain with thousands of users. Nmap of course also supports XML output via the -oX and -oA options, but I still could not find a parser that would extract the users from the output. Weird. So I wrote one myself.

The result is in code (Python 3) below, I’ve called it pwnmaps (Parse/wrestle nmap scripts). It basically parses the NSE script output and outputs a list of users separated by linefeeds. Not a downright breakthrough in advanced coding, but it does the job. Maybe I’ll extend it modular wise to be able to support other output as well in the future.

#!/usr/bin/env python
"""
pwnmaps.py
Parse/Wrestle nmap Scripts

Parses nmap XML NSE script smb-enum-users and prints the users found to stdout.
Should be handy for generating user lists for further (brute force) attacks, to
generate such a list one could for example use sort to make sure we only have
unique user names: $ sort --unique output.txt > unique-users.txt
Created on Oct 9, 2010
@author: Carsten Maartmann-Moe
"""
import xml.dom.minidom
# import locale
import sys
import getopt

# language, output_encoding = locale.getdefaultlocale()
verbose = False

def main(argv):
    doc = ""
    try:
        opts, args = getopt.getopt(argv, "hv", ["help", "verbose"])
    except getopt.GetoptError as err:
        print(err)
        usage()
        sys.exit(2)
    for opt, arg in opts:
        if opt in ("-h", "--help"):
            usage()
            sys.exit()
        elif opt in ("-v", "--verbose"):
            global verbose
            verbose = True

    if len(args) < 1: # Print usage if no arguments are given
        usage()

    input_files = args # The remainder of arguments are treated like input files
    for i in input_files:
        try:
            doc = xml.dom.minidom.parse(i)
        except:
            print("[!] Invalid XML, giving up...")
        parse_nmap(doc)

def parse_nmap(doc):
    usernames = []

    for host in doc.getElementsByTagName("host"):
        ip = name = output = ""

        # Get host IPv4 address (and potentially IPv6 and mac as well)
        addresses = host.getElementsByTagName("address")
        for address in addresses:
            ip = address.getAttribute("addr")

        # Get host name
        hostnames = host.getElementsByTagName("hostname")
        for hostname in hostnames:
            name = hostname.getAttribute("name")

        scripts = host.getElementsByTagName("script")
        for script in scripts:
            output = script.getAttribute("output") #.encode(output_encoding)
            usernames = output.split(',')

        if(verbose):
            print("[*] Users harvested from " + ip + " (" + name + "):")

        for username in usernames:
            print(username.strip())

def usage():
    print("""Usage: ./pwnmaps.py [OPTIONS] results.xml

The input file should be generated using nmap with a similar syntax:
nmap --script=smb-enum-users -oX results.xml 127.0.0.17

    -h/--help:       Displays this message
    -v/--verbose:    Verbose mode""")

if __name__ == "__main__":
    main(sys.argv[1:])

To generate input to pwnmap, scan with nmap, use the NSE script smb-enum-users and output to XML:

$ nmap -oX test.xml --script=smb-enum-users 192.168.124.3

Starting Nmap 5.00 ( http://nmap.org ) at 2010-10-09 16:11 CEST
Interesting ports on 192.168.124.3:
Not shown: 984 closed ports
PORT     STATE SERVICE
135/tcp  open  msrpc
139/tcp  open  netbios-ssn
389/tcp  open  ldap
445/tcp  open  microsoft-ds

Host script results:
|  smb-enum-users:
|_ EXAMPLE\Administrator, EXAMPLE\alice, EXAMPLE\bob, EXAMPLE\Cert Publishers, EXAMPLE\charlie, EXAMPLE\DHCP Administrators, EXAMPLE\DHCP Users, EXAMPLE\DnsAdmins, EXAMPLE\DnsUpdateProxy, EXAMPLE\Domain Admins, EXAMPLE\Domain Computers, EXAMPLE\Domain Controllers, EXAMPLE\Domain Guests, EXAMPLE\Domain Users, EXAMPLE\Enterprise Admins, EXAMPLE\EXAMPLE01$, EXAMPLE\EXAMPLE02$, EXAMPLE\EXAMPLE03$, EXAMPLE\EXAMPLE04$, EXAMPLE\EXAMPLEDC$, EXAMPLE\Group Policy Creator Owners, EXAMPLE\Guest, EXAMPLE\HelpServicesGroup, EXAMPLE\krbtgt, EXAMPLE\Schema Admins, EXAMPLE\SUPPORT_388945a0, EXAMPLE\TelnetClients, EXAMPLE\xena, EXAMPLE\yasir

Nmap done: 1 IP address (1 host up) scanned in 1.51 seconds

To parse the output with pwnmap (verbose switch -v used in the following example):

$ ./pwnmaps.py -v test.xml
[*] Users harvested from 192.168.124.3 ():
EXAMPLE\Administrator
EXAMPLE\alice
EXAMPLE\bob
EXAMPLE\Cert Publishers
EXAMPLE\charlie
EXAMPLE\DHCP Administrators
EXAMPLE\DHCP Users
EXAMPLE\DnsAdmins
EXAMPLE\DnsUpdateProxy
EXAMPLE\Domain Admins
EXAMPLE\Domain Computers
EXAMPLE\Domain Controllers
EXAMPLE\Domain Guests
EXAMPLE\Domain Users
EXAMPLE\Enterprise Admins
EXAMPLE\EXAMPLE01$
EXAMPLE\EXAMPLE02$
EXAMPLE\EXAMPLE03$
EXAMPLE\EXAMPLE04$
EXAMPLE\EXAMPLEDC$
EXAMPLE\Group Policy Creator Owners
EXAMPLE\Guest
EXAMPLE\HelpServicesGroup
EXAMPLE\krbtgt
EXAMPLE\Schema Admins
EXAMPLE\SUPPORT_388945a0
EXAMPLE\TelnetClients
EXAMPLE\xena
EXAMPLE\yasir

One Response to “pwnmaps – A tool to parse nmap NSE output into something useful”

  1. Sam

    Hey,

    Your script breaks for multiple hosts, check logic. You need to move usernames = [] to inside the for host in doc.getElementsByTagname loop. If it stays initialised it will print out the previously found usernames for each host after the first one until it locates a host with new usernames. Also there’s a bit of an encoding fault while you made the blogpost, check: if len(args) < 1: # Print usage if no arguments are given.

    Lastly check the way you iterate through addresses, if nmap was able to get the MAC thats the only address that will be saved, I would suggest somethng along the lines of:
    for address in addresses:
    if address.getAttribute(“addrtype”) == ‘ipv4′ or address.getAttribute(“addrtype”) == ‘ipv6′:
    ip = address.getAttribute(“addr”)

    Reply

Leave a Reply

  • (will not be published)