使用Python打造一个Nmap xml文件解析工具

2022.9.26更新

  • 兼容Python3
  • 修复缺失代码get_xml

工作过程中经常遇到客户让对大量资产开放的端口以及服务进行统计,于是乎就写了个nmap 导出xml批量处理脚本。

脚本基本功能:

  • 提取IP,端口 以及端口对应的服务
  • 将结果导出至Excel( xlsx)
  • 支持批量xml处理

包依赖

  • xlsxwriter

  • 安装


pip install xlsxwriter

Python代码

好久没用过XML解析了,有点生疏

#!/usr/local/bin/python
# -*- coding: utf-8 -*-

"""
@author:随时静听
@file: parserXML.py
@time: 2018/08/23

"""
# http://blog.51cto.com/maoyao/1772102
# https://xlsxwriter.readthedocs.io/format.html
import re
try:
    import xml.etree.cElementTree as ET
except:
    import xml.etree.ElementTree as ET
import glob

import os
import time

# import xlwt
import xlsxwriter

import argparse

from multiprocessing import Process,Pool,Lock

XMLPATH='report'

DEFAULT_STYLE={
        'font_size': 12,  # 字体大小
        'bold': False,  # 是否粗体
        # 'bg_color': '#101010',  # 表格背景颜色
        'font_color': 'black',  # 字体颜色
        'align': 'left',  # 居中对齐
        'valign':'vcenter',
        'font_name':'Courier New',
        'top': 2,  # 上边框
        # 后面参数是线条宽度
        'left': 2,  # 左边框
        'right': 2,  # 右边框
        'bottom': 2  # 底边框
}
TITLE=[
    (u'序号',8),
    ('IP',22),
    (u'端口',10),
    (u'服务',18),
    (u'开放状态',15),
]

def get_xml(filepath=XMLPATH):
    try:
        return map(lambda x:os.path.join(filepath,x),glob.glob1(filepath,'*.xml'))
    except:
        return []
#获取写入格式
def get_style(default=DEFAULT_STYLE,**kw):
    return  default.update(**kw)



def parseNmap(filename):
    try:
        tree=ET.parse(filename)
        root=tree.getroot()
    except Exception as e:

        print (e)
        return {}
    data_lst=[]
    for host in root.iter('host'):
        if host.find('status').get('state') == 'down':
            continue
        address=host.find('address').get('addr',None)
        # print address
        if not address:
            continue
        ports=[]
        for port in host.iter('port'):
            state=port.find('state').get('state','')
            port_num= port.get('portid',None)
            serv=port.find('service')
    
            serv= serv.get('name','') if serv is not None else ""
            # print serv
            ports.append([port_num,serv,state])
        data_lst.append({address:ports})
    # return {address:ports}
    return data_lst



def reportEXCEL(filename,datalst,title=TITLE,style=DEFAULT_STYLE,**kwargs):
    if not datalst:
        return ''
    if  os.path.exists(filename):
        print (u"%s 文件已经存在" % filename)
        path,name=os.path.split(filename)
        filename=os.path.splitext(name)[0]
        filename=filename+str(time.strftime("%Y%m%d%H%M%S",time.localtime()))+'.xlsx'
        filename=os.path.join(path,filename)
        print ('data will save as new file named :%s ' % filename)

    book=xlsxwriter.Workbook(filename)
    title_style= style if not kwargs.get('title',None) else kwargs.get('title')

    row_hight=[20,16] if not kwargs.get('row_set',None) else kwargs.get('row_set')    #标题题和常规的高度
    # col_width=[8,22] if not kwargs.get('col_set',None) else kwargs.get('col_set') #序号,其他宽度
    sheet_name= 'sheet' if not kwargs.get('sheet_name',None) else kwargs.get('sheet_name')
    sheet=book.add_worksheet(sheet_name)

    row_hight=row_hight+(2000-len(row_hight))*[row_hight[-1]]
    for row , h in enumerate(row_hight):
        sheet.set_row(row,h)
    col_width=map(lambda x:x[1],title)
    for col , w in enumerate(col_width):
        sheet.set_column(col,col,w)
    title_style = book.add_format(title_style)
    for index,t in enumerate(title):

        sheet.write(0,index,t[0],title_style)

    row=1
    col=0
    style=book.add_format(style)
    index2=0
    for index,item in enumerate(datalst):
        # print item
        for ip,ports in item.items():
            port_num=len(ports)
            if not ports:
                continue
            index2=index2+1
            for  i,data in enumerate(ports):


                sheet.write(row,2,data[0],style)
                sheet.write(row,3,data[1],style)
                sheet.write(row,4,data[2],style)
                row = row + 1
            if row-port_num+1 != row:
                sheet.merge_range('B'+str(row-port_num+1)+':B'+str(row),ip,style)
                sheet.merge_range('A'+str(row-port_num+1)+':A'+str(row),index2,style)
            else:
                # print index2
                sheet.write(row-1,0,index2,style)
                sheet.write(row-1,1,ip,style)
    print  ('Reprot result of xml parser to file: %s' % filename)
    book.close()




def main(XMLPATH,REPORTFILENAME):

    data_lst=[]
    for xml in get_xml(XMLPATH):

        data=parseNmap(xml)
        if data:
            data_lst.extend(data)
            # print data


    reportEXCEL(REPORTFILENAME,data_lst)


if __name__ == '__main__':
    import sys
    if len(sys.argv)<3:
        print ('[!] Usage: parserXML.py XMLPATH [reportfilename]')
        print ('[!] Demo: parserXML.py  xmldir  result.xlsx')
    else:

        XMLPATH=sys.argv[1]
        REPORTFILENAME = sys.argv[2]
        print ('[-] set parser XML file dir: %s' % XMLPATH)
        print ('[-] set report Excel file name: %s' % REPORTFILENAME)

        if not os.path.exists(XMLPATH):
                print ("[!] '%s' path does not exists!" % XMLPATH)
                exit(1)
        main(XMLPATH,REPORTFILENAME)
    pass


只放代码不教使用就是在耍流氓,接下来,如何在命令行使用

新建getResult.bat

@echo off

python "%~dp0\parser_nmap_xml_2_excel.py" %1 %2 %3 %4 %5 %6 %7 %8 %9

注意:我这里脚本名称是arser_nmap_xml_2_excel.py,你换成你的名称,就是你复制上面python代码放的那个文件的名字

将脚本和getResult.bat放在一个固定额路径下,并将这个路径加入环境变量中:


//在控制台就可以直接使用了
getResult 存放xml的文件夹 导出xlsx文件名

效果图

posted @ 2018-12-14 18:15  随时静听  阅读(2171)  评论(0编辑  收藏  举报