python-nmap详解 - r34l!ty - 不负勇往
MENU

python-nmap详解

September 27, 2016 • Security

python-nmap是一个使用nmap进行端口扫描的python库。它可以很轻易的生成nmap扫描报告,并且可以帮助系统管理员进行自动化扫描任务和生成报告。同时,它也支持nmap脚本输出。

下载最新版

python-nmap-0.6.1.tar.gz - 2016-07-30

md5sum : 2795bfcbc05cbbbccfcf4df59facaab1

获取源码

源码存放在 http://bitbucket.org/xael/python-nmap

hg clone http://bitbucket.org/xael/python-nmap

安装

使用PIP安装

使用PIP安装非常简单

pip install python-nmap  

手工安装

首先解压python-nmap-0.4.0.tar.gz文件然后在Shell里执行如下命令:

tar xvzf python-nmap-0.5.0-1.tar.gz  
cd python-nmap-0.5.0-1  
python setup.py install

使用

标准用法:

#!/usr/bin/env python
import nmap # 导入 nmap.py 模块  
nm = nmap.PortScanner() # 实例化nmap.PortScanner对象  
nm.scan('127.0.0.1', '22-443') # 扫描127.0.0.1,端口号从22至443  
nm.command_line() # 获取当前执行扫描的命令行: nmap -oX - -p 22-443 127.0.0.1  
nm.scaninfo() # 获取nmap扫描信息 {'tcp': {'services': '22-443', 'method': 'connect'}}  
nm.all_hosts() # 获取所有已经扫描的主机  
nm['127.0.0.1'].hostname() # 获取一个主机127.0.0.1的主机名,通常为用户记录  
nm['127.0.0.1'].hostnames() # 获取主机127.0.0.1的主机名列表,返回一个字典类型  
# [{'name':'hostname1', 'type':'PTR'}, {'name':'hostname2', 'type':'user'}]
nm['127.0.0.1'].state() # 获取主机127.0.0.1的状态 (up|down|unknown|skipped)  
nm['127.0.0.1'].all_protocols() # 获取执行的协议 ['tcp', 'udp'] 包含 (ip|tcp|udp|sctp)  
nm['127.0.0.1']['tcp'].keys() # 获取tcp协议所有的端口号  
nm['127.0.0.1'].all_tcp() # 获取tcp协议所有的端口号 (按照端口号大小进行排序)  
nm['127.0.0.1'].all_udp() # 获取udp协议所有的端口号 (按照端口号大小进行排序)  
nm['127.0.0.1'].all_sctp() # 获取sctp协议所有的端口号 (按照端口号大小进行排序)  
nm['127.0.0.1'].has_tcp(22) # 主机127.0.0.1是否有关于22端口的任何信息  
nm['127.0.0.1']['tcp'][22] # 获取主机127.0.0.1关于22端口的信息  
nm['127.0.0.1'].tcp(22) # 获取主机127.0.0.1关于22端口的信息  
nm['127.0.0.1']['tcp'][22]['state'] # 获取主机22端口的状态 (open)  

一些示例:

>>> for host in nm.all_hosts():
>>>     print('----------------------------------------------------')
>>>     print('Host : %s (%s)' % (host, nm[host].hostname()))
>>>     print('State : %s' % nm[host].state())
>>>     for proto in nm[host].all_protocols():
>>>         print('----------')
>>>         print('Protocol : %s' % proto)
>>> 
>>>         lport = nm[host][proto].keys()
>>>         lport.sort()
>>>         for port in lport:
>>>             print ('port : %s\tstate : %s' % (port, nm[host][proto][port]['state']))
----------------------------------------------------
Host : 127.0.0.1 (localhost)  
State : up  
----------
Protocol : tcp  
port : 22   state : open  
port : 25   state : open  
port : 80   state : open  
port : 111  state : open  
port : 443  state : open


>>> print(nm.csv())
host;protocol;port;name;state;product;extrainfo;reason;version;conf  
127.0.0.1;tcp;22;ssh;open;OpenSSH;protocol 2.0;syn-ack;5.9p1 Debian 5ubuntu1;10  
127.0.0.1;tcp;25;smtp;open;Exim smtpd;;syn-ack;4.76;10  
127.0.0.1;tcp;53;domain;open;dnsmasq;;syn-ack;2.59;10  
127.0.0.1;tcp;80;http;open;Apache httpd;(Ubuntu);syn-ack;2.2.22;10  
127.0.0.1;tcp;111;rpcbind;open;;;syn-ack;;10  
127.0.0.1;tcp;139;netbios-ssn;open;Samba smbd;workgroup: WORKGROUP;syn-ack;3.X;10  
127.0.0.1;tcp;443;;open;;;syn-ack;;


>>> nm.scan(hosts='192.168.1.0/24', arguments='-n -sP -PE -PA21,23,80,3389')
>>> hosts_list = [(x, nm[x]['status']['state']) for x in nm.all_hosts()]
>>> for host, status in hosts_list:
>>>     print('{0}:{1}'.host)
192.168.1.0:down  
192.168.1.1:up  
192.168.1.10:down  
192.168.1.100:down  
192.168.1.101:down  
192.168.1.102:down  
192.168.1.103:down  
192.168.1.104:down  
192.168.1.105:down  
[...]



>>> nma = nmap.PortScannerAsync()
>>> def callback_result(host, scan_result):
>>>     print '------------------'
>>>     print host, scan_result
>>> 
>>> nma.scan(hosts='192.168.1.0/30', arguments='-sP', callback=callback_result)
>>> while nma.still_scanning():
>>>     print("Waiting >>>")
>>>     nma.wait(2)   # you can do whatever you want but I choose to wait after the end of the scan
>>> 
192.168.1.1 {'nmap': {'scanstats': {'uphosts': '1', 'timestr': 'Mon Jun  7 11:31:11 2010', 'downhosts': '0', 'totalhosts': '1', 'elapsed': '0.43'}, 'scaninfo': {}, 'command_line': 'nmap -oX - -sP 192.168.1.1'}, 'scan': {'192.168.1.1': {'status': {'state': 'up', 'reason': 'arp-response'}, 'hostname': 'neufbox'}}}  
------------------
192.168.1.2 {'nmap': {'scanstats': {'uphosts': '0', 'timestr': 'Mon Jun  7 11:31:11 2010', 'downhosts': '1', 'totalhosts': '1', 'elapsed': '0.29'}, 'scaninfo': {}, 'command_line': 'nmap -oX - -sP 192.168.1.2'}, 'scan': {'192.168.1.2': {'status': {'state': 'down', 'reason': 'no-response'}, 'hostname': ''}}}  
------------------
192.168.1.3 {'nmap': {'scanstats': {'uphosts': '0', 'timestr': 'Mon Jun  7 11:31:11 2010', 'downhosts': '1', 'totalhosts': '1', 'elapsed': '0.29'}, 'scaninfo': {}, 'command_line': 'nmap -oX - -sP 192.168.1.3'}, 'scan': {'192.168.1.3': {'status': {'state': 'down', 'reason': 'no-response'}, 'hostname': ''}}}

>>> nm = nmap.PortScannerYield()
>>> for progressive_result in nm.scan('127.0.0.1/24', '22-25'):
>>>     print(progressive\_result)

简单实例

>>> import nmap  
>>> nm = nmap.PortScanner()  
>>> nm.scan('115.239.210.26', '20-443')  
{'nmap': {'scanstats': {'uphosts': u'1', 'timestr': u'Mon Jul 29 18:52:34 2013', 'downhosts': u'0', 'totalhosts': u'1', 'elapsed': u'14.88'}, 'scaninfo': {u'tcp': {'services': u'20-443', 'method': u'syn'}}, 'command_line': u'nmap -oX - -p 20-443 -sV 115.239.210.26'}, 'scan': {u'115.239.210.26': {'status': {'state': u'up', 'reason': u'echo-reply'}, 'hostname': '', u'tcp': {80: {'state': u'open', 'reason': u'syn-ack', 'name': u'http'}}}}}  

创建PortScanner实例,然后扫描159.239.210.26这个IP的20-443端口。

>>> nm.scaninfo()  
{u'tcp': {'services': u'20-443', 'method': u'syn'}}  
>>> nm.command_line()  
u'nmap -oX - -p 20-443 -sV 115.239.210.26'  

打印简单信息

>>> nm.all_hosts()  
[u'115.239.210.26']  

查看有多少个host

>>> nm['115.239.210.26']  
{'status': {'state': u'up', 'reason': u'echo-reply'}, 'hostname': '', u'tcp': {80: {'state': u'open', 'reason': u'syn-ack', 'name': u'http'}}} 

查看该host的详细信息

>>> nm['115.239.210.26'].all_protocols()  
[u'tcp']  

查看该host包含的所有协议

>>> nm['115.239.210.26']['tcp']           
{80: {'state': u'open', 'reason': u'syn-ack', 'name': u'http'}}  
>>> nm['115.239.210.26']['tcp'].keys()  
[80] 

查看该host的哪些端口提供了tcp协议

>>> nm['115.239.210.26']['tcp'][80]     
{'state': u'open', 'reason': u'syn-ack', 'name': u'http'}  
>>> nm['115.239.210.26']['tcp'][80]['state']  
u'open'  

查看80端口的详细信息

>>> nm['115.239.210.26'].has_tcp(21)          
False  
>>> nm['115.239.210.26'].has_tcp(80)  
True 

查看该端口是否提供了tcp协议

>>> nm.scan(hosts='192.168.1.0/24', arguments='-n -sP -PE -PA21,23,80,3389')  

还可以像这样设置nmap执行的参数

以上只是一些常用的比较简单的功能,如果想了解更复杂的功能,请参考python-nmap官网

使用python-nmap不出http踩到的坑

from:http://0cx.cc/solve_bug_withe-python-nmap.jspx

今天在使用python-nmap来进行扫描入库的时候发现http端口硬生生的给判断成了http

1.png

2.png

仔细测试了好几次都是这样子。后来果断的不服气,仔细看了下扫描的参数。发现python-nmap调用的时候会强制加上-ox -这个参数

正常扫描是

nmap 45.33.49.119 -p T:443 -Pn -sV --script=banner

3.png

然而经过python-nmap以后就是

nmap -oX - 45.33.49.119 -p T:443 -Pn -sV --script=banner

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE nmaprun>
<?xml-stylesheet href="file:///usr/local/bin/../share/nmap/nmap.xsl" type="text/xsl"?>
<!-- Nmap 7.12 scan initiated Thu Sep  1 00:02:07 2016 as: nmap -oX - -p T:443 -Pn -sV -&#45;script=banner 45.33.49.119 -->
<nmaprun scanner="nmap" args="nmap -oX - -p T:443 -Pn -sV -&#45;script=banner 45.33.49.119" start="1472659327" startstr="Thu Sep  1 00:02:07 2016" version="7.12" xmloutputversion="1.04">
<scaninfo type="connect" protocol="tcp" numservices="1" services="443"/>
<verbose level="0"/>
<debugging level="0"/>
<host starttime="1472659328" endtime="1472659364"><status state="up" reason="user-set" reason_ttl="0"/>
<address addr="45.33.49.119" addrtype="ipv4"/>
<hostnames>
<hostname name="ack.nmap.org" type="PTR"/>
</hostnames>
<ports><port protocol="tcp" portid="443"><state state="open" reason="syn-ack" reason_ttl="0"/><service name="http" product="Apache httpd" version="2.4.6" extrainfo="(CentOS)" tunnel="ssl" method="probed" conf="10"><cpe>cpe:/a:apache:http_server:2.4.6</cpe></service><script id="http-server-header" output="Apache/2.4.6 (CentOS)"><elem>Apache/2.4.6 (CentOS)</elem>
</script></port>
</ports>
<times srtt="191238" rttvar="191238" to="956190"/>
</host>
<runstats><finished time="1472659364" timestr="Thu Sep  1 00:02:44 2016" elapsed="36.65" summary="Nmap done at Thu Sep  1 00:02:44 2016; 1 IP address (1 host up) scanned in 36.65 seconds" exit="success"/><hosts up="1" down="0" total="1"/>
</runstats>
</nmaprun>

经过格式化以后看到的内容是

4.png

其中的一个参数tunnel.但是看了下http://bitbucket.org/xael/python-nmap/raw/8ed37a2ac20d6ef26ead60d36f739f4679fcdc3e/nmap/nmap.py这里的内容。发现没有与之关联的。

for dport in dhost.findall('ports/port'):
                # protocol
                proto = dport.get('protocol')
                # port number converted as integer
                port =  int(dport.get('portid'))
                # state of the port
                state = dport.find('state').get('state')
                # reason
                reason = dport.find('state').get('reason')
                # name, product, version, extra info and conf if any
                name = product = version = extrainfo = conf = cpe = ''
                for dname in dport.findall('service'):
                    name = dname.get('name')
                    if dname.get('product'):
                        product = dname.get('product')
                    if dname.get('version'):
                        version = dname.get('version')
                    if dname.get('extrainfo'):
                        extrainfo = dname.get('extrainfo')
                    if dname.get('conf'):
                        conf = dname.get('conf')
 
                    for dcpe in dname.findall('cpe'):
                        cpe = dcpe.text
                # store everything
                if not proto in list(scan_result['scan'][host].keys()):
                    scan_result['scan'][host][proto] = {}
 
                scan_result['scan'][host][proto][port] = {'state': state,
                                                          'reason': reason,
                                                          'name': name,
                                                          'product': product,
                                                          'version': version,
                                                          'extrainfo': extrainfo,
                                                          'conf': conf,
                                                          'cpe': cpe}

试想下如果把name以及tunnel取出来同时匹配不就好了。于是对此进行修改。410-440行

name = product = version = extrainfo = conf = cpe = tunnel =''
for dname in dport.findall('service'):
    name = dname.get('name')
    if dname.get('product'):
        product = dname.get('product')
    if dname.get('version'):
        version = dname.get('version')
    if dname.get('extrainfo'):
        extrainfo = dname.get('extrainfo')
    if dname.get('conf'):
        conf = dname.get('conf')
    if dname.get('tunnel'):
        tunnel = dname.get('tunnel')
 
    for dcpe in dname.findall('cpe'):
        cpe = dcpe.text
# store everything
if not proto in list(scan_result['scan'][host].keys()):
    scan_result['scan'][host][proto] = {}
 
scan_result['scan'][host][proto][port] = {'state': state,
                                          'reason': reason,
                                          'name': name,
                                          'product': product,
                                          'version': version,
                                          'extrainfo': extrainfo,
                                          'conf': conf,
                                          'tunnel':tunnel,
                                          'cpe': cpe}

还有在654-670行里面增加我们添加的tunnel

csv_ouput = csv.writer(fd, delimiter=';')
csv_header = [
    'host',
    'hostname',
    'hostname_type',
    'protocol',
    'port',
    'name',
    'state',
    'product',
    'extrainfo',
    'reason',
    'version',
    'conf',
    'tunnel',
    'cpe'
    ]

然后我们import这个文件。在获取的内容里面进行判断.如果name为http的同时tunnel为ssl,则判断为http

for targetHost in scanner.all_hosts():
    if scanner[targetHost].state() == 'up' and scanner[targetHost]['tcp']:
        for targetport in scanner[targetHost]['tcp']:
            #print(scanner[targetHost]['tcp'][int(targetport)])
            if scanner[targetHost]['tcp'][int(targetport)]['state'] == 'open' and scanner[targetHost]['tcp'][int(targetport)]['product']!='tcpwrapped':
                if scanner[targetHost]['tcp'][int(targetport)]['name']=='http' and scanner[targetHost]['tcp'][int(targetport)]['tunnel'] == 'ssl':
                    scanner[targetHost]['tcp'][int(targetport)]['name'] = 'http'
                else:
                    scanner[targetHost]['tcp'][int(targetport)]['name'] = scanner[targetHost]['tcp'][int(targetport)]['name']
                print(domain+'\t'+targetHosts+'\t'+str(targetport) + '\t' + scanner[targetHost]['tcp'][int(targetport)]['name'] + '\t' + scanner[targetHost]['tcp'][int(targetport)]['product']+scanner[targetHost]['tcp'][int(targetport)]['version'])
                #if scanner[targetHost]['tcp'][int(targetport)]['name'] in ["http","http"]:

改造后的文件扫描结果

5.png

Archives QR Code
QR Code for this page
Tipping QR Code