@@ -120,7 +120,12 @@ def do_scan(host, nmap_path, ports=None, arguments=None):
120120 perform the nmap scan
121121 """
122122 if arguments is None :
123- arguments = "-sV"
123+ lib .output .misc_info ("using default scan arguments" )
124+ arguments = [
125+ "-sF" , "-Pn" , "-sV" ,
126+ "-O" , "-F" , "--reason" ,
127+ "-vvv"
128+ ]
124129 launch_arguments = [
125130 nmap_path , '-oX' , '-' , host ,
126131 '-p ' + ports if ports is not None else "" ,
@@ -150,106 +155,235 @@ def do_scan(host, nmap_path, ports=None, arguments=None):
150155 return output_data , "" .join (nmap_warn_tracestack ), "" .join (nmap_error_tracestack )
151156
152157
158+ # copy pasta :DD
159+ # https://github.com/komand/python-nmap/blob/master/nmap/nmap.py#L273
153160def parse_xml_output (output , warnings , error ):
154161 """
155- parse the XML data out of the file into a dict
162+ Analyses NMAP xml scan ouput
163+ May raise PortScannerError exception if nmap output was not xml
164+ Test existance of the following key to know if something went wrong : ['nmap']['scaninfo']['error']
165+ If not present, everything was ok.
166+ :param nmap_xml_output: xml string to analyse
167+ :returns: scan_result as dictionnary
156168 """
157- results = {}
169+ # nmap xml output looks like :
170+ # <host starttime="1267974521" endtime="1267974522">
171+ # <status state="up" reason="user-set"/>
172+ # <address addr="192.168.1.1" addrtype="ipv4" />
173+ # <hostnames><hostname name="neufbox" type="PTR" /></hostnames>
174+ # <ports>
175+ # <port protocol="tcp" portid="22">
176+ # <state state="filtered" reason="no-response" reason_ttl="0"/>
177+ # <service name="ssh" method="table" conf="3" />
178+ # </port>
179+ # <port protocol="tcp" portid="25">
180+ # <state state="filtered" reason="no-response" reason_ttl="0"/>
181+ # <service name="smtp" method="table" conf="3" />
182+ # </port>
183+ # </ports>
184+ # <hostscript>
185+ # <script id="nbstat" output="NetBIOS name: GROSTRUC, NetBIOS user: <unknown>, NetBIOS MAC: <unknown>
" />
186+ # <script id="smb-os-discovery" output=" 
 OS: Unix (Samba 3.6.3)
 Name: WORKGROUP\Unknown
 System time: 2013-06-23 15:37:40 UTC+2
" />
187+ # <script id="smbv2-enabled" output="Server doesn't support SMBv2 protocol" />
188+ # </hostscript>
189+ # <times srtt="-1" rttvar="-1" to="1000000" />
190+ # </host>
191+ # <port protocol="tcp" portid="25">
192+ # <state state="open" reason="syn-ack" reason_ttl="0"/>
193+ # <service name="smtp" product="Exim smtpd" version="4.76" hostname="grostruc" method="probed" conf="10">
194+ # <cpe>cpe:/a:exim:exim:4.76</cpe>
195+ # </service>
196+ # <script id="smtp-commands" output="grostruc Hello localhost [127.0.0.1], SIZE 52428800, PIPELINING, HELP, 
 Commands supported: AUTH HELO EHLO MAIL RCPT DATA NOOP QUIT RSET HELP "/>
197+ # </port>
198+ scan_result = {}
158199 try :
159- root = ElementTree .fromstring (output )
200+ dom = ElementTree .fromstring (output )
160201 except Exception :
161- if len (error ) != 0 :
202+ if len (error ) > 0 :
162203 raise lib .errors .NmapScannerError (error )
163204 else :
164205 raise lib .errors .NmapScannerError (output )
165- results ['nmap_scan' ] = {
166- 'full_command_line' : root .get ('args' ),
167- 'scan_information' : {},
168- 'scan_stats' : {
169- 'time_string' : root .find ('runstats/finished' ).get ('timestr' ),
170- 'elapsed' : root .find ('runstats/finished' ).get ('elapsed' ),
171- 'hosts_up' : root .find ('runstats/hosts' ).get ('up' ),
172- 'down_hosts' : root .find ('runstats/hosts' ).get ('down' ),
173- 'total_hosts_scanned' : root .find ('runstats/hosts' ).get ('total' )
206+ # nmap command line
207+ scan_result ['nmap' ] = {
208+ 'command_line' : dom .get ('args' ),
209+ 'scaninfo' : {},
210+ 'scanstats' : {
211+ 'timestr' : dom .find ("runstats/finished" ).get ('timestr' ),
212+ 'elapsed' : dom .find ("runstats/finished" ).get ('elapsed' ),
213+ 'uphosts' : dom .find ("runstats/hosts" ).get ('up' ),
214+ 'downhosts' : dom .find ("runstats/hosts" ).get ('down' ),
215+ 'totalhosts' : dom .find ("runstats/hosts" ).get ('total' )}
174216 }
175- }
176- if len (error ) != 0 :
177- results ['nmap_scan' ]['scan_information' ]['errors' ] = error
178- if len (warnings ) != 0 :
179- results ['nmap_scan' ]['scan_information' ]['warnings' ] = warnings
180- for info in root .findall ('scaninfo' ):
181- results ['nmap_scan' ]['scan_information' ][info .get ('protocol' )] = {
182- 'method' : info .get ('type' ),
183- 'services' : info .get ('services' )
184- }
185- for attempted_host in root .findall ('host' ):
217+ # if there was an error
218+ if len (error ) > 0 :
219+ scan_result ['nmap' ]['scaninfo' ]['error' ] = error
220+ # if there was a warning
221+ if len (warnings ) > 0 :
222+ scan_result ['nmap' ]['scaninfo' ]['warning' ] = warnings
223+ # info about scan
224+ for dsci in dom .findall ('scaninfo' ):
225+ scan_result ['nmap' ]['scaninfo' ][dsci .get ('protocol' )] = {
226+ 'method' : dsci .get ('type' ),
227+ 'services' : dsci .get ('services' )
228+ }
229+ scan_result ['scan' ] = {}
230+ for dhost in dom .findall ('host' ):
231+ # host ip, mac and other addresses
186232 host = None
187- addresses = {}
188- vendors = {}
189- for address in attempted_host .findall (" address" ):
190- address_type = address .get ('addrtype' )
191- addresses [ address_type ] = address .get ('addr' )
192- if address_type == " ipv4" :
193- host = addresses [ address_type ]
194- elif address_type == " mac" and address .get ('vendor' ) is not None :
195- vendors [ addresses [ address_type ]] = address .get ('vendor' )
233+ address_block = {}
234+ vendor_block = {}
235+ for address in dhost .findall (' address' ):
236+ addtype = address .get ('addrtype' )
237+ address_block [ addtype ] = address .get ('addr' )
238+ if addtype == ' ipv4' :
239+ host = address_block [ addtype ]
240+ elif addtype == ' mac' and address .get ('vendor' ) is not None :
241+ vendor_block [ address_block [ addtype ]] = address .get ('vendor' )
196242 if host is None :
197- host = attempted_host .find ('address' ).get ('addr' )
243+ host = dhost .find ('address' ).get ('addr' )
198244 hostnames = []
199- if len (attempted_host .findall ('hostnames/hostname' )) != 0 :
200- for current_hostnames in attempted_host .findall ('hostnames/hostname' ):
245+ if len (dhost .findall ('hostnames/hostname' )) > 0 :
246+ for dhostname in dhost .findall ('hostnames/hostname' ):
201247 hostnames .append ({
202- 'hostname ' : current_hostnames .get ('name' ),
203- 'host_type ' : current_hostnames .get ('type' )
248+ 'name ' : dhostname .get ('name' ),
249+ 'type ' : dhostname .get ('type' ),
204250 })
205251 else :
206252 hostnames .append ({
207- 'hostname ' : None ,
208- 'host_type ' : None
253+ 'name ' : '' ,
254+ 'type ' : '' ,
209255 })
210-
211- results ['nmap_scan' ][host ] = {}
212- results ['nmap_scan' ][host ]['hostnames' ] = hostnames
213- results ['nmap_scan' ][host ]['addresses' ] = addresses
214- results ['nmap_scan' ][host ]['vendors' ] = vendors
215-
216- for status in attempted_host .findall ('status' ):
217- results ['nmap_scan' ][host ]['status' ] = {
218- 'state' : status .get ('state' ),
219- 'reason' : status .get ('reason' )
220- }
221- for uptime in attempted_host .findall ('uptime' ):
222- results ['nmap_scan' ][host ]['uptime' ] = {
223- 'seconds' : uptime .get ('seconds' ),
224- 'lastboot' : uptime .get ('lastboot' )
225- }
226- for discovered_port in attempted_host .findall ('ports/port' ):
227- protocol = discovered_port .get ('protocol' )
228- port_number = discovered_port .get ('portid' )
229- port_state = discovered_port .find ('state' ).get ('state' )
230- port_reason = discovered_port .find ('state' ).get ('reason' )
231-
232- # this is actually a thing!!
233- name = discovered_config = discovered_version = extra_information = discovered_product = stuff = ""
234- for discovered_name in discovered_port .findall ('service' ):
235- name = discovered_name .get ('name' )
236- if discovered_name .get ('product' ):
237- discovered_product = discovered_name .get ('product' )
238- if discovered_name .get ('version' ):
239- discovered_version = discovered_name .get ('version' )
240- if discovered_name .get ('extrainfo' ):
241- extra_information = discovered_name .get ('extrainfo' )
242- if discovered_name .get ('conf' ):
243- discovered_config = discovered_name .get ('conf' )
244-
245- for other_stuff in discovered_name .findall ('cpe' ):
246- stuff = other_stuff .text
247- if protocol not in results ['nmap_scan' ][host ].keys ():
248- results ['nmap_scan' ][host ][protocol ] = list ()
249- results ['nmap_scan' ][host ][protocol ].append ({
250- 'port' : port_number , 'state' : port_state , 'reason' : port_reason ,
251- 'name' : name , 'product' : discovered_product , 'version' : discovered_version ,
252- 'extrainfo' : extra_information , 'conf' : discovered_config , 'cpe' : stuff
256+ scan_result ['scan' ][host ] = {'hostnames' : hostnames }
257+ scan_result ['scan' ][host ]['addresses' ] = address_block
258+ scan_result ['scan' ][host ]['vendor' ] = vendor_block
259+ for dstatus in dhost .findall ('status' ):
260+ # status : up...
261+ scan_result ['scan' ][host ]['status' ] = {'state' : dstatus .get ('state' ),
262+ 'reason' : dstatus .get ('reason' )}
263+ for dstatus in dhost .findall ('uptime' ):
264+ # uptime : seconds, lastboot
265+ scan_result ['scan' ][host ]['uptime' ] = {'seconds' : dstatus .get ('seconds' ),
266+ 'lastboot' : dstatus .get ('lastboot' )}
267+ for dport in dhost .findall ('ports/port' ):
268+ # protocol
269+ proto = dport .get ('protocol' )
270+ # port number converted as integer
271+ port = int (dport .get ('portid' ))
272+ # state of the port
273+ state = dport .find ('state' ).get ('state' )
274+ # reason
275+ reason = dport .find ('state' ).get ('reason' )
276+ # name, product, version, extra info and conf if any
277+ name = product = version = extrainfo = conf = cpe = ''
278+ for dname in dport .findall ('service' ):
279+ name = dname .get ('name' )
280+ if dname .get ('product' ):
281+ product = dname .get ('product' )
282+ if dname .get ('version' ):
283+ version = dname .get ('version' )
284+ if dname .get ('extrainfo' ):
285+ extrainfo = dname .get ('extrainfo' )
286+ if dname .get ('conf' ):
287+ conf = dname .get ('conf' )
288+ for dcpe in dname .findall ('cpe' ):
289+ cpe = dcpe .text
290+ # store everything
291+ if proto not in list (scan_result ['scan' ][host ].keys ()):
292+ scan_result ['scan' ][host ][proto ] = list ()
293+ # Komand - change proto from dict to list to ease output spec
294+ scan_result ['scan' ][host ][proto ].append ({
295+ 'port' : port ,
296+ 'state' : state ,
297+ 'reason' : reason ,
298+ 'name' : name ,
299+ 'product' : product ,
300+ 'version' : version ,
301+ 'extrainfo' : extrainfo ,
302+ 'conf' : conf ,
303+ 'cpe' : cpe
304+ })
305+ script_id = ''
306+ script_out = ''
307+ # get script output if any
308+ for dscript in dport .findall ('script' ):
309+ script_id = dscript .get ('id' )
310+ script_out = dscript .get ('output' )
311+ if 'script' not in list (scan_result ['scan' ][host ][proto ][port ].keys ()):
312+ scan_result ['scan' ][host ][proto ][port ]['script' ] = {}
313+ scan_result ['scan' ][host ][proto ][port ]['script' ][script_id ] = script_out
314+ # <hostscript>
315+ # <script id="nbstat" output="NetBIOS name: GROSTRUC, NetBIOS user: <unknown>, NetBIOS MAC: <unknown>
" />
316+ # <script id="smb-os-discovery" output=" 
 OS: Unix (Samba 3.6.3)
 Name: WORKGROUP\Unknown
 System time: 2013-06-23 15:37:40 UTC+2
" />
317+ # <script id="smbv2-enabled" output="Server doesn't support SMBv2 protocol" />
318+ # </hostscript>
319+ for dhostscript in dhost .findall ('hostscript' ):
320+ for dname in dhostscript .findall ('script' ):
321+ hsid = dname .get ('id' )
322+ hsoutput = dname .get ('output' )
323+ if 'hostscript' not in list (scan_result ['scan' ][host ].keys ()):
324+ scan_result ['scan' ][host ]['hostscript' ] = []
325+ scan_result ['scan' ][host ]['hostscript' ].append (
326+ {
327+ 'id' : hsid ,
328+ 'output' : hsoutput
329+ }
330+ )
331+ # <osmatch name="Juniper SA4000 SSL VPN gateway (IVE OS 7.0)" accuracy="98" line="36241">
332+ # <osclass type="firewall" vendor="Juniper" osfamily="IVE OS" osgen="7.X"
333+ # accuracy="98"><cpe>cpe:/h:juniper:sa4000</cpe><cpe>cpe:/o:juniper:ive_os:7</cpe></osclass>
334+ # </osmatch>
335+ # <osmatch name="Cymphonix EX550 firewall" accuracy="98" line="17929">
336+ # <osclass type="firewall" vendor="Cymphonix" osfamily="embedded"
337+ # accuracy="98"><cpe>cpe:/h:cymphonix:ex550</cpe></osclass>
338+ # </osmatch>
339+ for dos in dhost .findall ('os' ):
340+ osmatch = []
341+ portused = []
342+ for dportused in dos .findall ('portused' ):
343+ # <portused state="open" proto="tcp" portid="443"/>
344+ state = dportused .get ('state' )
345+ proto = dportused .get ('proto' )
346+ portid = dportused .get ('portid' )
347+ portused .append ({
348+ 'state' : state ,
349+ 'proto' : proto ,
350+ 'portid' : portid ,
253351 })
254-
255- return results
352+ scan_result ['scan' ][host ]['portused' ] = portused
353+ for dosmatch in dos .findall ('osmatch' ):
354+ # <osmatch name="Linux 3.7 - 3.15" accuracy="100" line="52790">
355+ name = dosmatch .get ('name' )
356+ accuracy = dosmatch .get ('accuracy' )
357+ line = dosmatch .get ('line' )
358+ osclass = []
359+ for dosclass in dosmatch .findall ('osclass' ):
360+ # <osclass type="general purpose" vendor="Linux" osfamily="Linux" osgen="2.6.X" accuracy="98"/>
361+ ostype = dosclass .get ('type' )
362+ vendor = dosclass .get ('vendor' )
363+ osfamily = dosclass .get ('osfamily' )
364+ osgen = dosclass .get ('osgen' )
365+ accuracy = dosclass .get ('accuracy' )
366+ cpe = []
367+ for dcpe in dosclass .findall ('cpe' ):
368+ cpe .append (dcpe .text )
369+ osclass .append ({
370+ 'type' : ostype ,
371+ 'vendor' : vendor ,
372+ 'osfamily' : osfamily ,
373+ 'osgen' : osgen ,
374+ 'accuracy' : accuracy ,
375+ 'cpe' : cpe ,
376+ })
377+ osmatch .append ({
378+ 'name' : name ,
379+ 'accuracy' : accuracy ,
380+ 'line' : line ,
381+ 'osclass' : osclass
382+ })
383+ else :
384+ scan_result ['scan' ][host ]['osmatch' ] = osmatch
385+ for dport in dhost .findall ('osfingerprint' ):
386+ # <osfingerprint fingerprint="OS:SCAN(V=5.50%D=11/[...]S)
"/>
387+ fingerprint = dport .get ('fingerprint' )
388+ scan_result ['scan' ][host ]['fingerprint' ] = fingerprint
389+ return scan_result
0 commit comments