前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >防火墙自动化(一) 防火墙的配置解析

防火墙自动化(一) 防火墙的配置解析

原创
作者头像
晴日飞鸢
发布2022-03-05 17:41:00
2.6K0
发布2022-03-05 17:41:00
举报
文章被收录于专栏:防火墙自动化防火墙自动化

网络运维工程师日常打交道的设备,无非是路由器交换机还有防火墙。防火墙在其中,属于有一定技术含量,但又比较繁琐的部分。为了更细粒度的企业安全,企业环境中的防火墙一般都是先deny all,再逐个开启需要访问的网络关系,而网络关系的载体就是一条条的防火墙策略,这样就带来了大量的防火墙策略配置工作。在过往的防火墙管理中,经常需要付出一定的人力物力去执行防火墙的策略生成和下发工作,如何对防火墙进行自动化,削减这方面的工作。本系列会分专题进行阐述并针对实际产生的问题提出优化建议。

首先是防火墙的配置解析。本文主要针对Juniper防火墙,H3C防火墙,思科ASA防火墙,给出配置解析的一般思路,并提供参考代码。分为以下三点进行阐述。

  • 需要明确的前置问题
  • 各种解析相关的知识
  • 防火墙配置解析代码

1. 需要明确的问题

在进入正式的防火墙配置解析之前,先对以下这些问题做一些说明。

1.1 如何获取防火墙现有的信息?有哪些途径?

[HTTP/HTTPS]

部分厂商部分型号的防火墙是支持web页面管理的,也就是说支持通过80/8080/443端口进行信息的查询和修改。这类防火墙的官网一般都会有对应的 API文档,如果所在企业已经购买了产品和服务,可以向厂商或代理商索要对应的API文档。但也存在部分厂商或产品有web界面但给不出对应的API文档的情况,此时处理过程可能要麻烦一些,需要通过分析web页面的方式获取到背后的数据API。利用API文档中的API或分析出来的API,我们可能可以获取到的信息包括防火墙策略、nat策略、运行状态等。

web界面示例
web界面示例

[SSH]

一般的防火墙都带有远程ssh管理功能(如无,请尽早退货),如果可以通过secureCRT、putty、Teminal这些工具访问到防火墙的管理页面,则可以通过SSH协议获取到我们想要的策略数据。通用的做法是用代码模拟SSH登录,在输入流里输入对应的命令,从输出流里获取到我们需要的数据。

SSH界面示例
SSH界面示例

[SNMP]

作为专门设计用于在 IP 网络管理网络节点(服务器、工作站、路由器、交换机及HUBS等)的一种标准协议,基本上防火墙设备都支持这种协议。如果防火墙开通了SNMP功能,可以视作一个key-value形式的数据库,我们只要拿着OID这个key,通过SNMP方法就能取出对应的value.这种方法适合用于监控,实时状态信息一般都能从这个数据库取到。

SNMP抓取数据示例
SNMP抓取数据示例

[NETCONF协议]

这类协议也是可以获取到防火墙的网络设备的信息,但是依赖于专门的厂商,并且使用到xml格式进行传输。具体能获取到的信息和HTTP/HTTPS类似,也可以对网络设备进行管理。

NETCONF使用的XML示例
NETCONF使用的XML示例

1.2 上述获取信息的途径有哪些区别?

       首先理解一点,现在商用的防火墙或其他网络设备,大多都是转发和控制层面使用的CPU会进行分离(ps:转发和控制分离是SDN)。所以即使是控制层面的CPU性能消耗殆尽,也会停留在控制层面瘫痪,下面**转发还是会按原有的规则转发**,但这样就已经**无法对防火墙做任何修改**,已经属于比较严重的事故。上面四种途径,都需要在控制层面(可以看作一个linux系统)启动后台进程,提供出来的服务,都需要消耗控制层面的CPU和内存,所以必须要尽量高效地使用。

[HTTP/HTTPS]

如果拿到了API文档,开发过程会十分简单,API是规律的,返回数据也一般是json格式,在python中可以直接使用。但开启HTTP/HTTPS服务消耗防火墙CPU和内存会较多。

[SSH]

开发起来也比较简单,数据以文本流输入输出,输出时可能会有一定的数据粘连问题,以及获取到文本后,还需要二次解析才能建立对应的映射。SSH消耗防火墙CPU和内存会较少。

[SNMP]

开发起来比较复杂,因为有相当多的OID,需要根据情况挑选OID,再去取数据。key-value类型的取数据方式导致的是取单个数据最快,取连续大块数据最慢。比如同样的一个CPU使用率值,如果使用SNMP去取,时间开销肯定是最少的,但如果我想去取一整个路由表下来,则需要花费最长的时间。所以SNMP适用于监控场景,较少用于大块数据的获取。

[NETCONF协议]

开发过程和消耗都和HTTP/HTTPS方式差不多,可能略低于HTTP/HTTPS,但开发中使用XML格式进行传输导致有额外的学习和使用成本。

1.3上述途径获取到的防火墙信息是什么形式?需不需要解析?

[HTTP/HTTPS]

获取到的信息一般都为JSON格式,一般可以从HTTPresponse中取出来直接使用,有可能需要编写程序解析。

[SSH]

获取到的信息一般都为文本格式,返回值一般要经过编写程序解析,才能投入后续自动化开发的使用。

[SNMP]

获取到的信息一般都为文本格式的单个值,不需要太多额外的解析。

[NETCONF协议]

获取到信息一般都为XML格式,需要解析,可以使用特定的包进行解析。

 1.4 获取防火墙策略的最佳方式是什么?为什么?

       这个问题实际上要结合实际情况来回答。我只给出我倾向于使用什么,以及使用的理由。个人倾向是HTTP/HTTPS>SSH>NETCONF>SNMP。

       SNMP排末尾是因为不知道能不能取出来,到底有没有这个OID,因为通用OID范围内是不包含这个信息的。

       NETCONF排倒二是因为xml的解析比较麻烦,而且是专有协议,开发出来只能CISCO设备使用。

       SSH作为次选,是可以一次性取出所有配置的,相当于一次性取出所有策略。但是通过配置解析出防火墙策略需要对防火墙策略很熟悉。

       HTTP/HTTPS体验最佳,一次或多次请求,就能获取到json格式的策略数据。但是防火墙不一定有HTTP/HTTPS功能,有这个功能不一定会允许开(别问我为什么,一般是领导觉得有风险不给开),所以能不能用上还得看情况。

       所以下面的内容都基于SSH取到完整的配置,然后对之进行解析。以下为SSH取数据的简单示例,关于SSH如何取数据,已经有大量可搜索到文章讲解,此处不在赘述。

代码语言:python
复制
#encoding=utf-8
import paramiko
import socket
import time
import re

class SSHConnection(object):
    
    def __init__(self, host, port, username, password):
        self._host = host
        self._port = port
        self._username = username
        self._password = password

    def get_connect(self):
        ssh = paramiko.SSHClient()
        ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
        ssh.connect(self._host, 22, self._username, self._password, timeout=300, allow_agent=False,
                    look_for_keys=False)
        return ssh

    def close(cls, ssh):
        if ssh:
            ssh.close()

class SSHInfo(object):

    def __init__(self,ip,username, password,port=22,delay=0.5,buffsize =99999999):
        self.ip = ip
        self.usename = username
        self.password = password
        self.port = port
        self.delay = delay
        self.buffsize = buffsize
        self.conn = SSHConnection(ip, 22, username, password)
        self.ssh = self.conn.get_connect()
        self.ch = self.ssh.invoke_shell()

    def read_buff(self, nbyte, delay=0.5, greedy_mode=True):
        buff = ""
        try:
            self.ch.settimeout(3)
            time.sleep(delay)
            if greedy_mode:
                while True:
                    _buff = self.ch.recv(nbyte)
                    try:
                        buff += str(_buff, encoding = "utf-8")
                    except:
                        buff += str(_buff, encoding="gbk")
            else:
                buff = self.ch.recv(nbyte)
        except socket.timeout as ex:
            pass
            print("{}:{} read buff to end: {}(socket timeout)".format(self.ip,self.port,ex))
        except Exception as ex:
            more = traceback.format_exc()
            print(more)
            print("{}:{} {}".format(self.ip,self.port,ex))
        return buff

    def runCmd(self, cmd):
        _cmd = cmd + "\n"
        print("{}:{} {}".format(self.ip,self.port,cmd))
        time.sleep(self.delay)
        self.ch.send(_cmd)
    
    def getInfo(self, cmd):
        self.runCmd(cmd)
        time.sleep(self.delay)
        config_content = self.read_buff(self.buffsize, 5)
        idx = config_content.rfind('\r\n')
        self.runCmd("quit")
        return config_content[:idx]
    
    def getInfoNoQuit(self, cmd):
        self.runCmd(cmd)
        time.sleep(self.delay)
        config_content = self.read_buff(self.buffsize, 5)
        idx = config_content.rfind('\r\n')
        return config_content[:idx]

cn = SSHInfo(ip, username, password, port, delay, buffsize) #请自定对括号中的变量进行定义
cn.runCmd("show config")
content = cn.read_buff(100000)
print(content)

2. 各种解析相关的知识

2.1 点分十进制如何转化成int数

每个独立的IP地址都能用一个单独的int数来存储,点分十进制字符串形式和int形式的IP地址在一定意义上是等价的。

下图从完整的二进制数据包开始,到三层IP头部的构成,再到IP头部中源地址的构成,最终描述了点分十进制字符串形式和int形式的IP的关系。

点分十进制字符串和int的转化
点分十进制字符串和int的转化

由上图可知,不管是使用int形式,还是点分十进制字符串形式,它们都可以对一个二进制的IP地址进行准确描述,并且两者之间存在一定的转化关系。高效的转化关系用python函数可以描述为:

代码语言:python
复制
#encoding=utf-8
import struct
import socket

def ip2num(ip):
    """将点分十进制ip转化成int数"""
    return struct.unpack("!L", socket.inet_aton(ip))[0]

def num2ip(num):
    """将int数转化成对应的点分十进制ip"""
    a, c, b, d = (num & 0xff000000) >> 24, (num & 0x0000ff00) >> 8, (num & 0x00ff0000) >> 16, (num & 0x000000ff) >> 0
    return "{}.{}.{}.{}".format(a, b, c, d)

2.2 何时用int数存储IP

点分十进制字符串形式和int形式,在表示IP地址时各有优缺。点分十进制字符串易读,日常使用的多半是是直接以这种格式存储。而什么时候使用int数存储IP?这里列出了常用的三种情况:

2.2.1 节省存储空间

如果使用字符串来存储IP地址,使用ASCII编码,每个IP地址需要7到15个字节;使用utf-8或者gbk编码,则会更多。存储的IP地址很多时,这将是一笔庞大的存储开销。而如果将IP地址转化成INT(uint32)数存储,每个IP地址固定消耗一个字节的的空间,缩减存储空间可达50%以上。

代码语言:javascript
复制
ip1 = "1.1.1.1"           # 7个字符,如使用ASCII编码,占用7个字节
ip2 = "111.111.111.111"   # 15个字符,如使用ASCII编码,占用15个字节
ip1 = 16843009            # "1.1.1.1"的INT形式,32位INT数,占用4个字节
ip2 = 1869573999          # "111.111.111.111"的INT形式,32位INT数,占用4个字节

2.2.2 方便顺序查询

如果有查询整个网段IP地址的需求,比如说要查询1.1.4.0/24这个网段。IP字符串存储,返回结果的顺序会错位,如"1.1.4.1"后面的IP是"1.1.4.10",而非"1.1.4.2",需要重新排序。但如果是使用INT数的形式,范围查询和顺序查询都会变得比较简单,直接使用">" 、"<"这样的操作即可,匹配表项的速度会很快;同时,因为IP已经转化成数字了,分库分表也会很简单,直接用数字映射到对应的表即可,比如0-100000使用iptable表1,100000-200000使用iptable表2,可以有效将数据分开存储,支持更多的并发查询。

代码语言:javascript
复制
//ip使用字符串存储,查询1.1.4.0/24网段
mysql>SELECT ip,state FROM ipble where ip like "1.1.4.%";
//返回结果为字符串,按字符串排序方式进行排序
| 1.1.4.1   | 0 |
| 1.1.4.10  | 0 |
| 1.1.4.100 | 1 |
| 1.1.4.101 | 1 |
| 1.1.4.102 | 1 |
| 1.1.4.103 | 1 |


//ip使用INT数存储,查询1.1.4.0/24网段
mysql>SELECT ip,state FROM ipble where ip >=16843776 and ip <=16843776+256;
//返回结果为int数,不易人工读,按数字排序方式进行排序
| 16843777 | 0 |
| 16843778 | 0 |
| 16843779 | 1 |
| 16843780 | 1 |
| 16843781 | 1 |
| 16843782 | 1 |

2.2.3 IP网段快速匹配

IP网段可以理解为IP的复数形式,多个IP地址可以组成一个IP网段。为了存储IP网段,最通用的做法是,在IP的基础上引入了一个新的量也就是MASK(掩码),用SUBNET表示IP网段,SUBNET = (IP address, MASK)。MASK和IP地址一样,也可以进行 **1.1** 中的点分十进制形式和int形式互转。如果需要判断一个IP地址是否在一个网段中,可以直接通过如下的位运算进行判断,效率会非常高(更高效率可以用C代码)。但因必须是int形式才可以进行下面位运算,所以这种情况下也必须用int形式的IP地址。

代码语言:python
复制
#encoding=utf-8
ipaddress = 3232235777                #"192.168.1.1"的int形式
subnet = (3232235776,4294967040)      #("192.168.1.0","255.255.255.0")的int形式

def isIpInSubnet(ipaddress,subnet):
    """快速判断一个int形式的ip是否在subnet中"""
    base, mask = subnet
    if ip&mask==base&mask:
        return True
    return False
    
print(isIpInSubnet(ipaddress,subnet))

2.3 IP网段的三种常用表现形式

在生产环境中,我们一般会有三种IP网段的表现形式,这些形式被广泛用于防火墙规则编写中。

  • 初始IP地址+子网掩码 (base,subnet mask)
  • 初始IP地址+通配符掩码 (base,wildcard mask)
  • 初始IP地址+末尾IP地址 (base,end)

这三种IP网段的表现形式在生产环境均有应用,但根据公司或组织的不同,约定的内部规范不同,采用的表现形式也不同。下文会对这三种表现形式逐一讲解。

在了解这三种表现形式之前,先了解一下子网掩码和通配符掩码的区别。

代码语言:javascript
复制
"""
子网掩码:
    点分十进制形式:255.255.255.0
    二进制形式:1111111111111111111111100000000
    规律:二进制形式前面全部为1,后面全部为0
    
通配符掩码:
    点分十进制形式:127.255.31.0
    二进制形式:1111111111111110001111100000000
    规律:1和0可以交替使用,不需要前面全部为1
tips:两种掩码的IP网段,都可以用1.2.3中的isIpInSubnet判断某个IP地址在不在这个网段中。
"""

接下来是这三种形式的说明,假设下面的base,subnet mask,wildcard mask,end都已经通过1.1中ip2num函数转化成了int数。

2.3.1 初始IP地址+子网掩码 (base,subnet mask)

在思科ASA防火墙配置中,会有这样的定义语句

代码语言:javascript
复制
object network ALL_net
 subnet 70.0.0.0 255.0.0.0
object network TB-SW
 subnet 70.2.12.0 255.255.255.0
object network TS-SW
 subnet 70.2.24.0 255.255.255.0

使用的便是初始IP地址+子网掩码的方式定义IP网段,这些IP网段存在以下转化:

代码语言:python
复制
#encoding=utf-8

#base_str为IP网段的开始,一般为形如"192.168.1.0"的字符串
base_str = "192.168.1.0"
#submask_str为IP网段的子网掩码,一般为形如"255.255.255.0"的字符串
submask_str = "255.255.255.0"

#base_str,submask_str的int形式,用到了1.1的ip2num
base = ip2num(base_str)       #3232235776
submask = ip2num(submask_str) #4294967040

#IP网段包含的IP地址个数,以及反掩码的int形式
ipnum = (1<<32)-1-submask    #256

#子网掩码的反掩码的字符串形式
revmask = num2ip(ipnum)      #"0.0.0.255"

#判断某个ip是否在该IP网段中,可以使用1.2.3中的isIpInSubnet方法
isIpInSubnet("192.168.1.1",(base,widcmask)) #True

2.3.2 初始IP地址+通配符掩码 (base,wildcard mask)

在H3C的防火墙配置中,会有这样的定义语句

代码语言:javascript
复制
object-group ip address dbserve
 0 network subnet 40.10.21.0 255.255.255.0
 10 network subnet 45.20.21.0 255.255.255.0
 20 network subnet 45.0.16.0 wildcard 0.255.0.255

其中的“20 network subnet 45.0.16.0 wildcard 0.255.0.255”使用的便是通配符掩码定义IP网段,这些IP网段存在以下转化:

代码语言:python
复制
#encoding=utf-8

#base_str为IP网段的开始,一般为形如"192.168.1.0"的字符串
base_str = "192.168.1.0"
#submask_str为IP网段的通配符掩码,一般为形如"127.255.31.0"的字符串
widcmask_str = "127.255.31.0"

#base_str,submask_str的int形式,用到了1.1的ip2num
base = ip2num(base_str)        #3232235776
widcmask = ip2num(widcmask_str) #2147426048

#IP网段包含的IP地址个数,以及反掩码的int形式
ipnum = (1<<32)-1-submask    #2147541247

#子网掩码的反掩码的字符串形式
revmask = num2ip(ipnum)      #"128.0.224.255"

#判断某个ip是否在该IP网段中,可以使用1.2.3中的isIpInSubnet方法
isIpInSubnet(ip2num("192.168.1.0"),(base,widcmask)) #True

2.3.3 初始IP地址+末尾IP地址 (base,end)

在思科ASA防火墙中,存在这样的定义语句

代码语言:javascript
复制
object network IP_20.2.132.137_154
 range 20.2.132.137 20.2.132.154
object network host_20.2.190.2-9
 range 20.2.190.2 20.2.190.9

使用的初始IP地址+末尾IP地址的方式定义IP网段,这些IP网段存在以下转化:

代码语言:python
复制
#encoding=utf-8

#base_str为IP网段的开始,一般为形如"192.168.1.1"的字符串
base_str = "192.168.1.1"
#end_str为IP网段的结尾,一般为形如"192.168.1.136"的字符串
end_str = "192.168.1.101"

#base_str,submask_str的int形式,用到了1.1的ip2num
base = ip2num(base_str)        #3232235777
end  = ip2num(widcmask_str)    #3232235877

#IP网段包含的IP地址个数,以及反掩码的int形式
ipnum = end-base+1    #101

#判断某个ip是否在该IP网段中,需要另外写方法
def isIpInSubnetRange(ip,ranges)
    base,end = ranges
    if ip>=base and ip <=end:
        return True
    return False
isIpInSubnetRange(ip2num("192.168.1.100"),(base,end)) #True

2.4 如何对IP网段做合并

在1.3介绍的三种IP网段表现形式中,“初始IP地址+末尾IP地址”(后称“区间”)这种表现方式是最通用的。其他两种表现形式都能等价地转化成一个或多个区间的形式(转化成多个区间一般为使用通配符掩码),但任取一个区间,不一定能很好地转化成其它两种表现形式(可以实现但产生结果比较复杂)。

下面分别对“初始IP地址+子网掩码”、“初始IP地址+通配符掩码”转成“初始IP地址,末尾IP地址”做了代码示例,并提供了区间的合并函数。

代码语言:python
复制
def standard2Range(ipobject):
    """
    (初始IP地址,子网掩码)->(初始IP地址,末尾IP地址)
    输入输出全部为int数
    """
    ip,submask = ipobject
    ipnum = (1<<32)-1-submask
    return ip,ip+ipnum

def wildcard2Ranges(ipobject):
    """
    (初始IP地址,通配符掩码)->[(初始IP地址,末尾IP地址)...]
    输入输出全部为int数
    仅提供转化方法,实际转换出来的区间数根据通配符掩码会非常非常多,建议使用wildcard mask的地址不参与整体的合并。
    """
    ip, wildcardmask = ipobject
    wildcardmasksav,icount,nums = wildcardmask, 0,[]
    while wildcardmask!=0:
        if wildcardmask&1==1:
            if icount<0:
                nums.append(icount)
                icount = 1
            else:
                icount+=1
        else:
            if icount>0:
                nums.append(icount)
                icount = -1
            else:
                icount -= 1
        wildcardmask = wildcardmask >> 1
    if icount!=0:
        nums.append(icount)
    baseRange = (0,(1<<nums[0])-1)
    cnt = nums[0]
    multiplyRanges = []
    for it in nums[1:]:
        if it>0:
            rs = (cnt,(1<<it)-1)
            multiplyRanges.append(rs)
        cnt += abs(it)
    lastresult = []
    for rs in multiplyRanges:
        cnt,base =rs
        newresult = []
        if not lastresult:
            for i in range(base + 1):
                lastresult.append([i << cnt])
        else:
            for i in range(base+1):
                for item in lastresult:
                    nt = item+[i<<cnt]
                    newresult.append(nt)
            lastresult = newresult
    base = ip& ((1<<32)-1-wildcardmask)
    res = []
    for it in lastresult:
        start = base + sum(it)
        end = start +  baseRange[1]
        res.append((start,end))
    return res

def sectionInsert(discreteOrNot,subs,*addsubs):
    """
    用来合并subs和addsubs,subs是区间的集合,addsubs也是区间的集合
    discreteOrNot用于区分是否是离散数据,比如 subs=[(1,3)] addsubs=[(4,5)],
    在离散的做法中,3和4这两个数字是连续的,subs和addsubs可以合并成[(1,5)]
    但在连续的做法中,3和4之间还有大量空白,比如3.1、3.2、3.3..,是不连续的,最终结果为[(1,3),(4,5)]
    """
    for addsub in addsubs:
        subs.append(addsub)
    subs=list(set(subs)) #去重
    subs = sorted(subs,key=lambda k:(k[0],k[1]))
    subsextend = []
    for i in range(len(subs)):
        if discreteOrNot:
            subsextend.append([subs[i][0]-0.6, 2 * i])
            subsextend.append([subs[i][1]+0.6, 2 * i + 1])
        else:
            subsextend.append([subs[i][0],2*i])
            subsextend.append([subs[i][1],2*i+1])
    subsextend.sort(key=lambda k:k[0])
    start,end= 0,0
    final = []
    for i in range(len(subsextend)):
        target = subsextend[i]
        if i%2==0 and target[1]==i:
            start = target[0]
        elif i%2==1 and target[1]==i:
            end = target[0]
            if discreteOrNot:
                final.append((int(start+0.6),int(end-0.6)))
            else:
                final.append((start,end))
    return final
    
sectionInsert(True,[(1,3)],(4,5))

2.5 常用协议以及对应的协议号

2.5.1 运行在三层的协议名以及协议号(tcp/ip协议族)

以下为三层采用IP封装的,协议号不同的协议(靠IP头部中的协议字段区分),来自CISCO官网文档。

Literal

Value

Description

ah

51

Authentication Header for IPv6, RFC 1826.

eigrp

88

Enhanced Interior Gateway Routing Protocol.

esp

50

Encapsulated Security Payload for IPv6, RFC 1827.

gre

47

Generic Routing Encapsulation.

icmp

1

Internet Control Message Protocol, RFC 792.

icmp6

58

Internet Control Message Protocol for IPv6, RFC 2463.

igmp

2

Internet Group Management Protocol, RFC 1112.

igrp

9

Interior Gateway Routing Protocol.

ip

0

Internet Protocol.

ipinip

4

IP-in-IP encapsulation.

ipsec

50

IP Security. Entering the ipsec protocol literal is equivalent to entering the esp protocol literal.

nos

94

Network Operating System (Novell’s NetWare).

ospf

89

Open Shortest Path First routing protocol, RFC 1247.

pcp

108

Payload Compression Protocol.

pim

103

Protocol Independent Multicast.

pptp

47

Point-to-Point Tunneling Protocol. Entering the pptp protocol literal is equivalent to entering the gre protocol literal.

snp

109

Sitara Networks Protocol.

tcp

6

Transmission Control Protocol, RFC 793.

udp

17

User Datagram Protocol, RFC 768.

2.5.2 运行在四层上的协议名以及对应的协议号

以下为运行在四层TCP/UDP的协议

Literal

TCP or UDP?

Value

Description

aol

TCP

5190

America Online

bgp

TCP

179

Border Gateway Protocol, RFC 1163

biff

UDP

512

Used by mail system to notify users that new mail is received

bootpc

UDP

68

Bootstrap Protocol Client

bootps

UDP

67

Bootstrap Protocol Server

chargen

TCP

19

Character Generator

cifs

TCP, UDP

3020

Common Internet File System

citrix-ica

TCP

1494

Citrix Independent Computing Architecture (ICA) protocol

cmd

TCP

514

Similar to exec except that cmd has automatic authentication

ctiqbe

TCP

2748

Computer Telephony Interface Quick Buffer Encoding

daytime

TCP

13

Day time, RFC 867

discard

TCP, UDP

9

Discard

dnsix

UDP

195

DNSIX Session Management Module Audit Redirector

domain

TCP, UDP

53

DNS

echo

TCP, UDP

7

Echo

exec

TCP

512

Remote process execution

finger

TCP

79

Finger

ftp

TCP

21

File Transfer Protocol (control port)

ftp-data

TCP

20

File Transfer Protocol (data port)

gopher

TCP

70

Gopher

h323

TCP

1720

H.323 call signaling

hostname

TCP

101

NIC Host Name Server

http

TCP, UDP

80

World Wide Web HTTP

https

TCP

443

HTTP over SSL

ident

TCP

113

Ident authentication service

imap4

TCP

143

Internet Message Access Protocol, version 4

irc

TCP

194

Internet Relay Chat protocol

isakmp

UDP

500

Internet Security Association and Key Management Protocol

kerberos

TCP, UDP

750

Kerberos

klogin

TCP

543

KLOGIN

kshell

TCP

544

Korn Shell

ldap

TCP

389

Lightweight Directory Access Protocol

ldaps

TCP

636

Lightweight Directory Access Protocol (SSL)

login

TCP

513

Remote login

lotusnotes

TCP

1352

IBM Lotus Notes

lpd

TCP

515

Line Printer Daemon - printer spooler

mobile-ip

UDP

434

Mobile IP-Agent

nameserver

UDP

42

Host Name Server

netbios-dgm

UDP

138

NetBIOS Datagram Service

netbios-ns

UDP

137

NetBIOS Name Service

netbios-ssn

TCP

139

NetBIOS Session Service

nfs

TCP, UDP

2049

Network File System - Sun Microsystems

nntp

TCP

119

Network News Transfer Protocol

ntp

UDP

123

Network Time Protocol

pcanywhere-data

TCP

5631

pcAnywhere data

pcanywhere-status

UDP

5632

pcAnywhere status

pim-auto-rp

TCP, UDP

496

Protocol Independent Multicast, reverse path flooding, dense mode

pop2

TCP

109

Post Office Protocol - Version 2

pop3

TCP

110

Post Office Protocol - Version 3

pptp

TCP

1723

Point-to-Point Tunneling Protocol

radius

UDP

1645

Remote Authentication Dial-In User Service

radius-acct

UDP

1646

Remote Authentication Dial-In User Service (accounting)

rip

UDP

520

Routing Information Protocol

rsh

TCP

514

Remote Shell

rtsp

TCP

554

Real Time Streaming Protocol

secureid-udp

UDP

5510

SecureID over UDP

sip

TCP, UDP

5060

Session Initiation Protocol

smtp

TCP

25

Simple Mail Transport Protocol

snmp

UDP

161

Simple Network Management Protocol

snmptrap

UDP

162

Simple Network Management Protocol - Trap

sqlnet

TCP

1521

Structured Query Language Network

ssh

TCP

22

Secure Shell

sunrpc

TCP, UDP

111

Sun Remote Procedure Call

syslog

UDP

514

System Log

tacacs

TCP, UDP

49

Terminal Access Controller Access Control System Plus

talk

TCP, UDP

517

Talk

telnet

TCP

23

RFC 854 Telnet

tftp

UDP

69

Trivial File Transfer Protocol

time

UDP

37

Time

uucp

TCP

540

UNIX-to-UNIX Copy Program

vxlan

UDP

4789

Virtual eXtensible Local Area Network (VXLAN)

who

UDP

513

Who

whois

TCP

43

Who Is

www

TCP, UDP

80

World Wide Web

xdmcp

UDP

177

X Display Manager Control Protocol

2.5.3 常用协议的树状层级

以下为采取树状层级对协议进行归类的一个python代码示例。

代码语言:python
复制
protoSet = {
    "ah":{
        "v":51,
        "children":{}
    },
    "eigrp":{
        "v":88,
        "children":{}
    },
    "esp":{
        "v":50,
        "children":{}
    },
    "gre":{
        "v":47,
        "children":{}
    },
    "icmp":{
        "v":1,
        "children":{
            "echo-reply":  { "v":0},
            "unreachable":  { "v":3},
            "source-quench":  { "v":4},
            "redirect":  { "v":5},
            "alternate-address":  { "v":6},
            "echo":  { "v":8},
            "router-advertisement":  { "v":9},
            "router-solicitation":  { "v":10},
            "time-exceeded":  { "v":11},
            "parameter-problem":  { "v":12},
            "timestamp-request":  { "v":13},
            "timestamp-reply":  { "v":14},
            "information-request":  { "v":15},
            "information-reply":  { "v":16},
            "mask-request":  { "v":17},
            "mask-reply":  { "v":18},
            "traceroute":  { "v":30},
            "conversion-error":  { "v":31},
            "mobile-redirect":  { "v":32},
        }
    },
    "icmp6":{
        "v":58,
        "children":{}
    },
    "igmp":{
        "v":2,
        "children":{}
    },
    "igrp":{
        "v":9,
        "children":{}
    },
    "ip":{
        "v":0,
        "children":{}
    },
    "ipinip":{
        "v":4,
        "children":{}
    },
    "ipsec":{
        "v":50,
        "children":{}
    },
    "nos":{
        "v":94,
        "children":{}
    },
    "ospf":{
        "v":89,
        "children":{}
    },
    "pcp":{
        "v":108,
        "children":{}
    },
    "pim":{
        "v":103,
        "children":{}
    },
    "pptp":{
        "v":47,
        "children":{}
    },
    "snp":{
        "v":109,
        "children":{}
    },
    "tcp":{
        "v":6,
        "children":{
            "aol": { "v":5190},
            "bgp": { "v":179},
            "chargen": { "v":19},
            "cifs": { "v":3020},
            "citrix-ica": { "v":1494},
            "cmd": { "v":514},
            "ctiqbe": { "v":2748},
            "daytime": { "v":13},
            "discard": { "v":9},
            "domain": { "v":53},
            "echo": { "v":7},
            "exec": { "v":512},
            "finger": { "v":79},
            "ftp": { "v":21},
            "ftp-data": { "v":20},
            "gopher": { "v":70},
            "h323": { "v":1720},
            "hostname": { "v":101},
            "http": { "v":80},
            "https": { "v":443},
            "ident": { "v":113},
            "imap4": { "v":143},
            "irc": { "v":194},
            "kerberos": { "v":750},
            "klogin": { "v":543},
            "kshell": { "v":544},
            "ldap": { "v":389},
            "ldaps": { "v":636},
            "login": { "v":513},
            "lotusnotes": { "v":1352},
            "lpd": { "v":515},
            "netbios-ssn": { "v":139},
            "nfs": { "v":2049},
            "nntp": { "v":119},
            "pcanywhere-data": { "v":5631},
            "pim-auto-rp": { "v":496},
            "pop2": { "v":109},
            "pop3": { "v":110},
            "pptp": { "v":1723},
            "rsh": { "v":514},
            "rtsp": { "v":554},
            "sip": { "v":5060},
            "smtp": { "v":25},
            "sqlnet": { "v":1521},
            "ssh": { "v":22},
            "sunrpc": { "v":111},
            "tacacs": { "v":49},
            "talk": { "v":517},
            "telnet": { "v":23},
            "uucp": { "v":540},
            "whois": { "v":43},
            "www": { "v":80}
        }
    },
    "udp":{
        "v":17,
        "children":{
            "biff": {"v":512},
            "bootpc": {"v":68},
            "bootps": {"v":67},
            "cifs": {"v":3020},
            "discard": {"v":9},
            "dnsix": {"v":195},
            "domain": {"v":53},
            "echo": {"v":7},
            "http": {"v":80},
            "isakmp": {"v":500},
            "kerberos": {"v":750},
            "mobile-ip": {"v":434},
            "nameserver": {"v":42},
            "netbios-dgm": {"v":138},
            "netbios-ns": {"v":137},
            "nfs": {"v":2049},
            "ntp": {"v":123},
            "pcanywhere-status": {"v":5632},
            "pim-auto-rp": {"v":496},
            "radius": {"v":1645},
            "radius-acct": {"v":1646},
            "rip": {"v":520},
            "secureid-udp": {"v":5510},
            "sip": {"v":5060},
            "snmp": {"v":161},
            "snmptrap": {"v":162},
            "sunrpc": {"v":111},
            "syslog": {"v":514},
            "tacacs": {"v":49},
            "talk": {"v":517},
            "tftp": {"v":69},
            "time": {"v":37},
            "vxlan": {"v":4789},
            "who": {"v":513},
            "www": {"v":80},
            "xdmcp": {"v":177},
        }
    }
}

2.6 思科ASA防火墙策略格式

2.6.1 object的配置格式

以下是ASA防火墙中object的定义方式的分类整理(暂不包含wildcardmask的情况)。

ASA object定义方式
ASA object定义方式

2.6.2 object-group的配置格式

以下是ASA防火墙中object-group的定义方式的分类。

ASA object-group定义方式
ASA object-group定义方式

2.6.3 access-list的配置格式

以下是ASA防火墙中access-list的定义方式的分类,共分为标准和拓展两种

ASA access-list定义方式
ASA access-list定义方式

因上图没有对acl规则进行详细描述,附上用于匹配的正则表达式作为补充。

代码语言:python
复制
# 对标准acl进行匹配,如access-list out permit tcp any host 192.168.0.10 eq http
aclstandard = re.compile(r'^access-list ([^\s]+) (permit|deny) ([^\s]+) '
                         r'(any|host [0-9.]+|subnet [0-9. ]+|range [0-9. ]+) '
                         r'(any|host [0-9.]+|subnet [0-9. ]+|range [0-9. ]+)'
                         r'( eq [^\s]+)?')
# 对拓展acl进行匹配,如access-list Web_access_in extended permit object TCP8888 host 10.2.101.13 object 10.2.94.13
aclextend = re.compile(r'^access-list ([\w\W]+) extended (permit|deny) '
                       r'(any|object [^\s]+|object-group [^\s]+|[^\s]+) '
                       r'(any|host [0-9.]+|subnet [0-9. ]+|range [0-9. ]+|object [^\s]+|object-group [^\s]+) '
                       r'(any|host [0-9.]+|subnet [0-9. ]+|range [0-9. ]+|object [^\s]+|object-group [^\s]+)'
                       r'( eq [^\s]+)?')

2.6.4 NAT的配置格式

本文暂不对NAT配置进行分类,后续会有nat的专题进行讲解。

2.7 Juniper防火墙策略格式

2.7.1 address-book的配置格式

以下为Juniper防火墙中address-book的整理分类示例(暂不包含wildcardmask的情况)。

代码语言:javascript
复制
#初始IP地址/短子网掩码
set security zones security-zone INTERNET address-book address 135.224.178.133/32 35.224.178.133/32
set security zones security-zone INTERNET address-book address 121.7.106.0/24 111.7.106.0/24

#初始IP地址/末尾IP地址
set security zones security-zone INTERNET address-book DNS10 range-address 192.168.1.10 to 192.168.1.100 

#地址集,用于嵌套多个地址
set security zones security-zone INTERNET address-book address-set DNSGROUP address DNS10
set security zones security-zone INTERNET address-book address-set DNSGROUP address 135.224.178.133/32 
set security zones security-zone INTERNET address-book address-set DNSGROUP address 121.7.106.0/24

2.7.2 application的配置格式

以下为juniper防火墙中application的整理分类示例。

代码语言:javascript
复制
#指定单个端口
set applications application TCP8102 protocol tcp
set applications application TCP8102 destination-port 8102

#指定多个端口
set applications application tcp12140-12144 protocol tcp
set applications application tcp12140-12144 destination-port 12140-12144

#指定应用集
set applications application-set ChinaPay-Port application tcp12140-12144
set applications application-set ChinaPay-Port application TCP8102

2.7.3 security policy的配置格式

以下为juniper防火墙中policy的整理分类示例。

代码语言:javascript
复制
#通用的配置格式需要包含source-address,destination-address,application,action(then)四部分  
set security policies from-zone exd to-zone exb policy 3771 match source-address 202.104.148.138/32
set security policies from-zone exd to-zone exb policy 3771 match destination-address 10.14.196.83/32
set security policies from-zone exd to-zone exb policy 3771 match application junos-icmp-all
set security policies from-zone exd to-zone exb policy 3771 match application tcp32003
set security policies from-zone exd to-zone exb policy 3771 then permit

2.7.4 NAT的配置格式

本文暂不对NAT配置进行分类,后续会有nat的专题进行讲解。

2.8 H3C防火墙策略格式

2.8.1 object-group的配置格式

以下为H3C防火墙中object-group的整理分类示例。

代码语言:javascript
复制
#以下演示了三种IP网段的定义形式,都可以放在object-group中
object-group ip address Yesnet
 security-zone Untrust
 0 network subnet 45.51.32.0 255.255.255.0
 10 network range 70.67.45.11 70.67.45.12
 20 network subnet 15.48.128.0 wildcard 0.15.126.255
 30 network subnet 15.48.64.64 wildcard 0.15.31.63
 
#以下演示了两种服务的定义形式,也可以放在object-group中
object-group service 管理端口
 0 service tcp destination eq 9300
 10 service udp destination range 10162 10181

2.8.2 rule的配置格式

以下为H3C防火墙中rule的示例。

代码语言:javascript
复制
##通用的配置格式需要包含source-ip,destination-ip,service,action四部分  
rule 149 name 某安一账通
 action pass
 source-zone Trust
 destination-zone Untrust
 source-ip 一账通测试机
 destination-ip 一账通入口地址
 service 管理端口
 service 8006
 service http

2.8.3 NAT的配置格式

本文暂不对NAT配置进行分类,后续会有nat的专题进行讲解。

3. 防火墙配置解析代码

3.1 防火墙配置解析需要的输出

一般地,不考虑做NAT的情况,防火墙的配置会包含以下的信息。

输出项名称

CISCO防火墙

Juniper防火墙

H3C防火墙

action

源地址

目地址

源端口

x

x

目端口

策略名

其中策略名、action、源地址、目标地址、目标端口/协议都会被包含,下面的解析代码也会输出这五项数据。IP地址全部用的是int数的形式

代码语言:javascript
复制
#输出项
#每一行的列表内容分别为策略名称、action、源地址、目标地址、目标端口/协议
#针对源目地址的ip网段,此处不考虑wildcard mask的情况,每一个都为 (开始地址,目标地址-开始地址)
#int数不易人工读取,可以自行将之转化成易读的形式,参考3.2的代码进行转化
['GLOBAL', 'permit', [(167945216, 255), (167945728, 255)], [(167940236, 2)], [('tcp', (24019, 24019))]]
['GLOBAL', 'permit', [(167945767, 1)], [(167927432, 0)], [('tcp', (9195, 9195))]]
['GLOBAL', 'permit', [(167945718, 0)], [(486610688, 255)], [('udp', (514, 514))]]
['GLOBAL', 'permit', [(167945728, 255)], [(487629436, 3)], [('tcp', (50883, 50883))]]

3.2 思科ASA防火墙策略解析代码

参考代码如下,该代码可以将一份ASA防火墙配置解析成3.1中给出的形式。

代码语言:python
复制
#encoding=utf-8
import re
import struct
import socket
import os
from collections import defaultdict
from functools import partial

#cisco官网搜集到的协议名到协议号的映射
protoSet={'ah':{'v':51,'children':{}},'eigrp':{'v':88,'children':{}},'esp':{'v':50,'children':{}},'gre':{'v':47,'children':{}},'icmp':{'v':1,'children':{'echo-reply':{'v':0},'unreachable':{'v':3},'source-quench':{'v':4},'redirect':{'v':5},'alternate-address':{'v':6},'echo':{'v':8},'router-advertisement':{'v':9},'router-solicitation':{'v':10},'time-exceeded':{'v':11},'parameter-problem':{'v':12},'timestamp-request':{'v':13},'timestamp-reply':{'v':14},'information-request':{'v':15},'information-reply':{'v':16},'mask-request':{'v':17},'mask-reply':{'v':18},'traceroute':{'v':30},'conversion-error':{'v':31},'mobile-redirect':{'v':32},}},'icmp6':{'v':58,'children':{}},'igmp':{'v':2,'children':{}},'igrp':{'v':9,'children':{}},'ip':{'v':0,'children':{}},'ipinip':{'v':4,'children':{}},'ipsec':{'v':50,'children':{}},'nos':{'v':94,'children':{}},'ospf':{'v':89,'children':{}},'pcp':{'v':108,'children':{}},'pim':{'v':103,'children':{}},'pptp':{'v':47,'children':{}},'snp':{'v':109,'children':{}},'tcp':{'v':6,'children':{'aol':{'v':5190},'bgp':{'v':179},'chargen':{'v':19},'cifs':{'v':3020},'citrix-ica':{'v':1494},'cmd':{'v':514},'ctiqbe':{'v':2748},'daytime':{'v':13},'discard':{'v':9},'domain':{'v':53},'echo':{'v':7},'exec':{'v':512},'finger':{'v':79},'ftp':{'v':21},'ftp-data':{'v':20},'gopher':{'v':70},'h323':{'v':1720},'hostname':{'v':101},'http':{'v':80},'https':{'v':443},'ident':{'v':113},'imap4':{'v':143},'irc':{'v':194},'kerberos':{'v':750},'klogin':{'v':543},'kshell':{'v':544},'ldap':{'v':389},'ldaps':{'v':636},'login':{'v':513},'lotusnotes':{'v':1352},'lpd':{'v':515},'netbios-ssn':{'v':139},'nfs':{'v':2049},'nntp':{'v':119},'pcanywhere-data':{'v':5631},'pim-auto-rp':{'v':496},'pop2':{'v':109},'pop3':{'v':110},'pptp':{'v':1723},'rsh':{'v':514},'rtsp':{'v':554},'sip':{'v':5060},'smtp':{'v':25},'sqlnet':{'v':1521},'ssh':{'v':22},'sunrpc':{'v':111},'tacacs':{'v':49},'talk':{'v':517},'telnet':{'v':23},'uucp':{'v':540},'whois':{'v':43},'www':{'v':80}}},'udp':{'v':17,'children':{'biff':{'v':512},'bootpc':{'v':68},'bootps':{'v':67},'cifs':{'v':3020},'discard':{'v':9},'dnsix':{'v':195},'domain':{'v':53},'echo':{'v':7},'http':{'v':80},'isakmp':{'v':500},'kerberos':{'v':750},'mobile-ip':{'v':434},'nameserver':{'v':42},'netbios-dgm':{'v':138},'netbios-ns':{'v':137},'nfs':{'v':2049},'ntp':{'v':123},'pcanywhere-status':{'v':5632},'pim-auto-rp':{'v':496},'radius':{'v':1645},'radius-acct':{'v':1646},'rip':{'v':520},'secureid-udp':{'v':5510},'sip':{'v':5060},'snmp':{'v':161},'snmptrap':{'v':162},'sunrpc':{'v':111},'syslog':{'v':514},'tacacs':{'v':49},'talk':{'v':517},'tftp':{'v':69},'time':{'v':37},'vxlan':{'v':4789},'who':{'v':513},'www':{'v':80},'xdmcp':{'v':177},}}}

# 简单对1.1.1.1这种ip做匹配
ip_host_regex =re.compile(r'\s*[0-9]+.[0-9]+.[0-9]+.[0-9]+\s*$')

# 简单对1.1.1.1 255.255.255.255这种ip做匹配
ip_subnet_regex =re.compile(r'\s*[0-9]+.[0-9]+.[0-9]+.[0-9]+\s*[0-9]+.[0-9]+.[0-9]+.[0-9]+\s*$')

# 简单对1.1.1.1/32这种ip做匹配
ip_short_regex =re.compile(r'\s*[0-9]+.[0-9]+.[0-9]+.[0-9]+/[0-9]+\s*$')

#建立短掩码到真实掩码的映射,如31->11111111111111111111111111111110
Short2num = {str(i):(1<<32)-(1<<(32-i)) for i in range(1,33)}

# 对标准acl进行匹配,如access-list out permit tcp any host 192.168.0.10 eq http
aclstandard = re.compile(r'^access-list ([^\s]+) (permit|deny) ([^\s]+) '
                         r'(any|host [0-9.]+|subnet [0-9. ]+|range [0-9. ]+) '
                         r'(any|host [0-9.]+|subnet [0-9. ]+|range [0-9. ]+)'
                         r'( eq [^\s]+)?')
# 对拓展acl进行匹配,如access-list Web_access_in extended permit object TCP8888 host 10.2.101.13 object 10.2.94.13
aclextend = re.compile(r'^access-list ([\w\W]+) extended (permit|deny) '
                       r'(any|object [^\s]+|object-group [^\s]+|[^\s]+) '
                       r'(any|host [0-9.]+|subnet [0-9. ]+|range [0-9. ]+|object [^\s]+|object-group [^\s]+) '
                       r'(any|host [0-9.]+|subnet [0-9. ]+|range [0-9. ]+|object [^\s]+|object-group [^\s]+)'
                       r'( eq [^\s]+)?')

#object语句的开头,用来判断是否是object/object-group语句的有效组成成分
ObjectConfigStart=["object", "host", "subnet", "range", "service", "object-group", "network-object", "protocol-object", "icmp-object",
 "port-object", "service-object", "group-object"]

def ip2num(ip):
    """将点分十进制ip转化成int数"""
    return struct.unpack("!L", socket.inet_aton(ip))[0]

def num2ip(num):
    """将int数转化成对应的点分十进制ip"""
    a, c, b, d = (num & 0xff000000) >> 24, (num & 0x0000ff00) >> 8, (num & 0x00ff0000) >> 16, (num & 0x000000ff) >> 0
    return "{}.{}.{}.{}".format(a, b, c, d)

def short2num(short):
    """短掩码转化成int格式的长掩码"""
    short = str(short)
    return Short2num[short]

def ipstr2num(ipstr):
    """将字符串形式的 ip、掩码 转化为两个int数 (开始,结束)"""
    if ip_host_regex.match(ipstr): #形如"192.168.1.1"格式进行转化,因为配置中的ip一般都无误,故编写正则表达式较简单,如用于校验请另外编写正则
        return ip2num(ipstr.strip()),0
    elif ip_subnet_regex.match(ipstr):  #形如"192.168.1.1 255.255.255.0"格式进行转化
        ip, subnet = ipstr.strip().split()
        ipn, shortn = ip2num(ip), ip2num(subnet)
        return ipn&shortn,short2num(32)-shortn
    elif ip_short_regex.match(ipstr):  #形如"192.168.1.1/31"格式进行转化
        ip, short = ipstr.split("/")
        ipn, shortn = ip2num(ip),short2num(short)
        return ipn&shortn,short2num(32)-shortn
    print("ipstr2num: '{}' can't be trans".format(ipstr))

def protoTransGetNum(proto3layer,key):
    """获取某一协议名对应的协议号"""
    if key in proto3layer["children"]:
        num = proto3layer["children"][key]["v"]
    else:
        num = int(key)
    return num

def protoTrans(cmdstr):
    """对形如以下的配置做解析:tcp destination range 6600 6699,tcp eq telnet,esp"""
    if cmdstr=="any":
        return "ip",(0,0)
    info = cmdstr.strip().split()
    if "destination" in info:
        info.remove("destination")
    proto3 = info[0]
    proto3layer = protoSet[info[0]]
    #protoNumIn3LayerHeader = proto3layer["v"]
    if len(info) >= 3:
        if info[1] == "eq":
            num = protoTransGetNum(proto3layer,info[2])
            return proto3,(num,num)
        elif info[1]=="lt": #有些防火墙的lt是<=,有些防火墙的lt是<,此处默认为<
            base = protoTransGetNum(proto3layer,info[2])
            return proto3,(0,base-1)
        elif info[1] == "lte":
            base = protoTransGetNum(proto3layer,info[2])
            return proto3,(0, base)
        elif info[1] == "gt":
            base = protoTransGetNum(proto3layer,info[2])
            return proto3,(base+1, 65535)
        elif info[1] == "gte":
            base = protoTransGetNum(proto3layer,info[2])
            return proto3,(base, 65535)
        elif info[1] == "range":
            if len(info)>=4:
                base,end = protoTransGetNum(proto3layer,info[2]),protoTransGetNum(proto3layer,info[3])
                return proto3, (base,end)
        print("protoTrans: '{}' can't be trans".format(cmdstr))
        return None
    else:
        if proto3=="tcp" or proto3=="udp":
            return proto3, (0, 65535)
        return proto3,(0,0)

def addressTrans(cmdstr):
    """对形如以下的配置做解析:host 202.100.1.1,subnet 202.100.1.0 255.255.255.0,range 202.100.2.10 202.100.2.20"""
    if cmdstr=="any":
        return 0,1<<32-1
    info = cmdstr.strip().split()
    if len(info)>=2:
        if info[0]=="range":
            if len(info)>=3:
                base,_ = ipstr2num(info[1])
                end, _  =  ipstr2num(info[2])
                return base,end-base
        else:
            cstr = cmdstr.replace("host","").replace("subnet","").strip()
            return ipstr2num(cstr)
    print("addressTrans: '{}' can't be trans".format(cmdstr))

def sectionInsert(discreteOrNot,subs,*addsubs):
    """
    用来合并subs和addsubs,subs是区间的集合,addsubs也是区间的集合
    discreteOrNot用于区分是否是离散数据,比如 subs=[(1,3)] addsubs=[(4,5)],
    在离散的做法中,3和4这两个数字是连续的,subs和addsubs可以合并成[(1,5)]
    但在连续的做法中,3和4之间还有大量空白,比如3.1、3.2、3.3..,是不连续的,最终结果为[(1,3),(4,5)]
    """
    for addsub in addsubs:
        subs.append(addsub)
    subs=list(set(subs)) #去重
    subs = sorted(subs,key=lambda k:(k[0],k[1]))
    subsextend = []
    for i in range(len(subs)):
        if discreteOrNot:
            subsextend.append([subs[i][0]-0.6, 2 * i])
            subsextend.append([subs[i][1]+0.6, 2 * i + 1])
        else:
            subsextend.append([subs[i][0],2*i])
            subsextend.append([subs[i][1],2*i+1])
    subsextend.sort(key=lambda k:k[0])
    start,end= 0,0
    final = []
    for i in range(len(subsextend)):
        target = subsextend[i]
        if i%2==0 and target[1]==i:
            start = target[0]
        elif i%2==1 and target[1]==i:
            end = target[0]
            if discreteOrNot:
                final.append((int(start+0.6),int(end-0.6)))
            else:
                final.append((start,end))
    return final

class ObjectBlock(object):
    """用来存放一个object/object-group配置块,并且对其解析,产生对应的数据,可以自行调用里面的属性做更多的操作(如压缩配置)"""
    def __init__(self,firstline):
        """用object-group network DM (第一行)这样的配置来初始化配置块"""
        self.config = [firstline]  #用来存放一般的配置语句
        self.quoteconfig = []  #用来存放引用到其他object/object-group的配置语句,完整的config=config+quoteconfig
        self.fulldata = []   #用来存放完整的数据
        self.simpleconfig = [] #用来存放简化的配置
        firstSplit= firstline.strip().split()
        self.FirstType = firstSplit[0]  #"object"或"object-group"
        self.SecondType = firstSplit[1] #"network","service","protocol"等
        self.name = firstSplit[2]       #配置块的名称,如"DM"
        self.extra = ""     #取出形似"object-group service udp.ser udp"中的"udp"
        self.data = []      #本配置块的数据,如"network-object host 10.2.104.96"里的"host 10.2.104.96"会转换成(base,offset)这种形式存在此中。
        self.quote = []     #本配置块引用到的其他的object/object-group名称
        self.quotedata = [] #本配置块引用到的数据
        if len(firstSplit)>3: 
            self.extra = firstSplit[3]
        self.discreteInsert = partial(sectionInsert,True)     #离散数据插入函数
        self.continuousInsert = partial(sectionInsert, False) #连续数据插入函数
        self.usedCount = 0  #本object/object-group被引用次数

    def __repr__(self):
        """返回打印出来的值"""
        return "{}/{}/{}".format(self.FirstType,self.SecondType,self.name)

    def dataMerge(self,*datas):
        """本地数据的归并,可以输入的data有self.data,self.quotedata"""
        originaldata = []
        tmpdata = []
        for data in datas:
            originaldata.extend(data)
        if self.SecondType=="network":
            finaldata = []
            for d in originaldata:
                if isinstance(d,tuple):
                    tmpdata.append((d[0],d[0]+d[1]))
            tmpdata = self.discreteInsert(tmpdata)
            for i in range(len(tmpdata)):
                d = tmpdata[i]
                nd = (d[0],d[1]-d[0])
                finaldata.append(nd)
        else:
            finaldata = []
            tmpdict = defaultdict(list)
            for d in originaldata:
                if isinstance(d,tuple):
                    protoName,r = d
                    tmpdict[protoName] = self.discreteInsert(tmpdict[protoName],(r[0],r[1]))
            for k in tmpdict:
                for i in range(len(tmpdict[k])):
                    d = tmpdict[k][i]
                    nd = (d[0], d[1])
                    finaldata.append((k,nd))
        return finaldata

    def objectCheck(self,liner):
        """检查配置块下面有没有嵌套的object或object-group"""
        if len(liner)>=2:
            if liner[0]=="group-object":
                return "/".join(("object-group",self.SecondType,liner[1]))
        if len(liner)>=3:
            if liner[1]=="object":
                return "/".join(("object",self.SecondType,liner[2]))
        return None

    def addLine(self,line):
        """添加一行如network-object object APP到配置块,并计算这一行引入的数据,添加到self.data"""
        liner = line.strip().split()
        if not liner:
            return
        key = "{}|{}|{}".format(self.FirstType,self.SecondType,liner[0])
        rets = self.objectCheck(liner)
        if rets:
            self.quote.append(rets)
            self.quoteconfig.append(line.strip())
            return
        self.config.append(line.strip())
        if key=="object-group|protocal|protocol-object":
            cmdstr = "".join(liner[1:])
            ret = protoTrans(cmdstr)
            self.data.append(ret)
        elif key=="object-group|service|port-object" or key=="object-group|service|service-object":
            if self.extra:
                cmdstr = "{} {}".format(self.extra," ".join(liner[1:]))
                ret = protoTrans(cmdstr)
                self.data.append(ret)
            else:
                cmdstr = " ".join(liner[1:])
                ret = protoTrans(cmdstr)
                self.data.append(ret)
        elif key=="object-group|icmp-type|icmp-object":
            cmdstr = "icmp eq {}".format(" ".join(liner[1:]))
            ret = protoTrans(cmdstr)
            self.data.append(ret)
        elif key=="object-group|network|network-object":
            cmdstr = " ".join(liner[1:])
            ret = addressTrans(cmdstr)
            self.data.append(ret)
        elif "object|network|" in key:
            if liner[0]=="host" or liner[0]=="subnet" or liner[0]=="range":
                cmdstr = line.strip()
                ret = addressTrans(cmdstr)
                self.data.append(ret)
        elif key=="object|service|service":
            cmdstr = " ".join(liner[1:])
            ret = protoTrans(cmdstr)
            self.data.append(ret)

class ObjectBlockOperation(object):
    """ObjectBlock的操作函数,写到一起方便查阅"""
    @classmethod
    def objectTraverse(cls,nowblock, blocks):
        """用来递归获取quote数据,避免因嵌套使用object-group造成的数据遗漏"""
        if not nowblock.quote:
            return nowblock.data
        retdata = []
        if not nowblock.quotedata:
            if nowblock.quote:
                for quo in nowblock.quote:
                    data = cls.objectTraverse(blocks[quo], blocks)
                    if data:
                        retdata.extend(data)
                return retdata
            return []
        return nowblock.quotedata

    @classmethod
    def readObjects(cls,lines):
        """用来读取所有objct/object-group配置,返回的是Block对象的集合"""
        currentBlock = None  #当前配置块
        Blocks = {}          #所有配置块的字典
        Released = True
        for l in lines:  #对于每行配置
            ler = l.split()
            if not ler or ler[0] not in ObjectConfigStart: #如果不是ObjectConfigStart中的作为开头,说明不是目标配置,直接跳出
                continue
            if l.startswith("object"):  #如果以"object"开头,说明是目标配置的第一行
                if currentBlock:        #如果已有上个配置块(吸收了足够的配置),就将这个代码块存入字典
                    Blocks[repr(currentBlock)] = currentBlock
                    Released = True
                currentBlock = ObjectBlock(l)  #用这行配置新建一个配置块
                continue
            if currentBlock:
                currentBlock.addLine(l)   #往当前配置块添加配置(这个操作会重复多次,直到下次遇到以"object"开头,再新建一个配置块)
                Released = False
        if not Released:  #将最后一个配置块放入字典
            Blocks[repr(currentBlock)] = currentBlock
        names = [k for k in Blocks]
        for name in names:
            nowblock = Blocks[name]
            if nowblock.quote:
                for quo in nowblock.quote:
                    Blocks[quo].usedCount +=1
                quetodata = cls.objectTraverse(nowblock,Blocks) #遍历获得引用的所有数据
                Blocks[name].quotedata = quetodata #赋值给原本为空的quotedata
        return Blocks

    @classmethod
    def mergeData(cls,blocks):
        """对block中的每一个block做数据合并"""
        names = [k for k in Blocks]
        for name in names:
            block = blocks[name]
            data =block.dataMerge(block.data,block.quotedata)
            blocks[name].fulldata = data
        return blocks


def aclAnalyze(lines,blocks):
    """拆解ACL语句,这段代码写得比较烂,因为对accest-list的所有组成情况有些不确定,如果有超出aclextend、aclstandard正则的情况请自行修改正则"""
    names = [k for k in Blocks]
    result = []
    for l in lines:
        ae = aclextend.findall(l.strip())
        if ae:
            pname,action,service,sour,dest,serviceadd = ae[0]
            if serviceadd:
                service = [protoTrans("{} {}".format(service, serviceadd))]
            elif "object" in service or "object-group" in service:
                se = service.split()
                key = ""
                for name in names:
                    if se[1] in name and "/network/" not in name:
                        key = name
                        break
                service = blocks[key].fulldata
            else:
                s = protoTrans(service)
                service = [s]
            if "object" in sour or "object-group" in sour:
                so = sour.split()
                key = ""
                for name in names:
                    if so[1] in name and "/network/" in name:
                        key = name
                        break
                sour = blocks[key].fulldata
            else:
                s = addressTrans(sour)
                sour = [s]
            if "object" in dest or "object-group" in dest:
                de = dest.split()
                key = ""
                for name in names:
                    if de[1] in name and "/network/" in name:
                        key = name
                        break
                dest = blocks[key].fulldata
            else:
                s = addressTrans(dest)
                dest = [s]
            result.append([pname, action, sour, dest,service])
        else:
            ast = aclstandard.findall(l.strip())
            if ast:
                pname, action, service, sour, dest ,serviceadd= ast[0]
                service=[protoTrans("{} {}".format(service,serviceadd))]
                sour = [addressTrans(sour)]
                dest = [addressTrans(dest)]
                result.append([pname, action,  sour, dest, service])
    return result

if __name__ == '__main__':
    with open('{}/asaconfcopy'.format(os.getcwd()),'r') as f: #请于此处修改配置文件名
        ls=f.readlines()
        OBO = ObjectBlockOperation()   #创建操作类,后续可以调用里面的函数
        Blocks = OBO.readObjects(ls)   #读入object/object-group
        Blocks = OBO.mergeData(Blocks) #对现有的数据进行整理合并(主要是有一些重合,将之消除)
        Result = aclAnalyze(ls,Blocks) #用Blocks去对所有acl进行解析,得出解析结果
        for s in Result:
            print(s)

3.3 Juniper防火墙策略解析代码

Juniper的策略结构比较简单易懂,可以参考2.7自行进行解析,参考代码后续补充。

3.4 H3C防火墙策略解析代码

H3C的策略结构在思科的基础上进行了简化,只使用了object-group,可以参考2.8自行进行解析。或者参考下面用python和c编写的防火墙命令行工具,因为篇幅限制,没办法直接把代码贴上来。代码已经打包放在下面的链接中了,里面包含1个python文件,1个c文件,均已详细注释出对应代码含义,并且有详细说明,有需要的同学可以自取。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 1. 需要明确的问题
    • 1.1 如何获取防火墙现有的信息?有哪些途径?
      • 1.2 上述获取信息的途径有哪些区别?
        • 1.3上述途径获取到的防火墙信息是什么形式?需不需要解析?
          •  1.4 获取防火墙策略的最佳方式是什么?为什么?
          • 2. 各种解析相关的知识
            • 2.1 点分十进制如何转化成int数
              • 2.2 何时用int数存储IP
                • 2.2.1 节省存储空间
                • 2.2.2 方便顺序查询
                • 2.2.3 IP网段快速匹配
              • 2.3 IP网段的三种常用表现形式
                • 2.3.1 初始IP地址+子网掩码 (base,subnet mask)
                • 2.3.2 初始IP地址+通配符掩码 (base,wildcard mask)
                • 2.3.3 初始IP地址+末尾IP地址 (base,end)
              • 2.4 如何对IP网段做合并
                • 2.5 常用协议以及对应的协议号
                  • 2.5.1 运行在三层的协议名以及协议号(tcp/ip协议族)
                  • 2.5.2 运行在四层上的协议名以及对应的协议号
                  • 2.5.3 常用协议的树状层级
                • 2.6 思科ASA防火墙策略格式
                  • 2.6.1 object的配置格式
                    • 2.6.2 object-group的配置格式
                    • 2.6.3 access-list的配置格式
                    • 2.6.4 NAT的配置格式
                  • 2.7 Juniper防火墙策略格式
                    • 2.7.1 address-book的配置格式
                    • 2.7.2 application的配置格式
                    • 2.7.3 security policy的配置格式
                    • 2.7.4 NAT的配置格式
                  • 2.8 H3C防火墙策略格式
                    • 2.8.1 object-group的配置格式
                    • 2.8.2 rule的配置格式
                    • 2.8.3 NAT的配置格式
                • 3. 防火墙配置解析代码
                  • 3.1 防火墙配置解析需要的输出
                    • 3.2 思科ASA防火墙策略解析代码
                      • 3.3 Juniper防火墙策略解析代码
                        • 3.4 H3C防火墙策略解析代码
                        相关产品与服务
                        命令行工具
                        腾讯云命令行工具 TCCLI 是管理腾讯云资源的统一工具。使用腾讯云命令行工具,您可以快速调用腾讯云 API 来管理您的腾讯云资源。此外,您还可以基于腾讯云的命令行工具来做自动化和脚本处理,以更多样的方式进行组合和重用。
                        领券
                        问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档