ACL在企业内网安全中发挥着不可或缺的作用,它可以有效防止很多外部和内部的信息安全问题。例如开发人员有权限登录生产环境的某些机器,ACL可以防止他们利用这些机器跳转访问到其他不属于其权限范围的机器。又例如黑客通过网站获取到内网服务器的控制权限,ACL可以限制黑客在内网中横向移动的范围,降低危害。
上图是一个简单的企业内网拓扑示意图,内网ACL配置在位于中心位置的网络设备,可能是交换机、路由器或防火墙。ACL管控内网中不同区域之间的数据往来,且应该遵循默认拒绝原则,即跨区域传输的数据包,只有当ACL中有明确的匹配规则时才可通过,否则一律拒绝。按照该原则,每当企业需要新增设备、服务或者需要临时调试网络、测试业务时,都需要开通新的ACL规则。但在一些中小型企业中,ACL规则只开不关的情况普遍存在。当设备或服务下线、IP地址变更或者临时用途结束后,没有及时删除对应的ACL规则。日积月累,网络设备上堆积了大量的ACL规则,其中可能存在一些开放了危险端口(如3389、3306、1433等)甚至是网段全通的规则,也可能存在一些冗余或者互相冲突的规则,这些情况都存在安全隐患,并且体积太大的配置文件也不利于网络设备的健康运行。相关负责人可能有意识到这种情况,但是由于网络设备数量多,如需要人工审计所有设备上的ACL规则将耗费大量的时间精力,实施难度较大。目前业内暂时没有针对ACL安全审计的解决方案,网络上也没有相关的开源工具,因此笔者尝试通过Python脚本实现ACL的批量化安全审计,弥补纯人工审计的弊端。
在实际的企业内网中,基本上每个区域的边界都会有路由交换或防火墙等设备,这些设备上都会配置ACL规则用于管控所属区域的数据进出。因此脚本首先需要具备批量审计的功能,将所有待审计的完整网络配置文件放置在一个文件夹,脚本能一次性审计完成。再者,脚本需要基于预先设定好的审计规则发现这些配置文件的ACL列表中存在安全隐患的规则,最后,为了方便审计结果的保存和传阅,脚本需能够输出可读性较高的审计日志,并按网络配置文件名生成对应的日志文件。日志内容应包括配置文件名、ACL列表名称、规则数量、存在隐患的ACL规则和对应的隐患类型及发现该ACL存在的隐患总数等信息。另外,有些企业内网中会使用不同品牌的网络设备,基本上可分为两大派系:一是华为系,包括华为和华三,二是思科系,包括思科和锐捷,其他品牌的设备大多也是基于这两大派系的底层系统。因此,审计脚本至少必须支持华为系和思科系品牌的配置文件格式。并且笔者希望脚本可以实现不同品牌设备配置文件的一键批量审计,而不需要用户在审计时将不同品牌的配置文件放在不同的文件夹或是执行脚本时利用参数指定品牌。
前面提到所有待审的配置文件会放置在一个文件夹中,因此脚本执行的第一步就是从这个文件夹中逐个读取配置文件内容。配置文件保存了设备上的所有配置,如接口地址、设备登录凭据、路由协议、ACL、Vlan等等,以上每一个部分之间都有特定的符号进行分割,如下图所示,华为系设备是“#”,而思科系设备是“!”。
为了方便审计规则的匹配,需要将所有ACL配置提取出来,保存为一个列表,后续对这个列表进行审计。这样做的原因是因为有些配置文件内容较多且复杂,把ACL配置单独提取出来的目的是细化审计范围,避免其他无关的配置内容影响审计进行。
ACL配置提取出来后,还需要进一步进细化。因为提取出来的配置中包含了多个互相独立的ACL列表,在上图中便能看到“3999”和“4000”两个列表。最终需要审计的目标是每个ACL列表中的每一条ACL规则。因此需要将提取出来的ACL配置再分割为单独的ACL列表。
整个提取过程如下图所示。
作为一个审计脚本,最核心的就是审计规则,即判断怎么样的ACL规则存在安全隐患,再根据这些规则编写具体的匹配规则。笔者根据以往在甲方企业中兼任网络管理员的经验,整理出以下存在安全隐患的规则类型。
注:因不同品牌的ACL规则格式不同,下列有部分规则仅针对特定品牌,已作备注。
类型 | 描述 |
危险规则 | 开放UDP协议 | UDP是无连接协议,数据传输速度快,但可被利用于UDP Flood攻击,建议及时关闭不必要的UDP规则。 |
开放危险端口 |
21、23、3306、3389等敏感端口,黑客进入内网后可能利用这些端口进行横向移动,建议及时关闭包含这些端口的不必要的规则。 |
宽松规则 | 源地址范围过大 | ACL动作为允许,且源地址范围过大,例如开放了整个网段。 |
目的地址范围过大 | ACL动作为允许,且目的地址范围过大,例如开放了整个网段。 |
未指定源地址 (仅适用华为系) | ACL动作为允许,但未指定源地址,任意源地址可以访问指定目的地址。 |
指定了ANY源地址 (仅适用思科系) | ACL动作为允许,且指定了ANY源地址,任意源地址可以访问指定目的地址。 |
未指定目的地址 (仅适用华为系) | ACL动作为允许,但未指定目的地址,指定源地址可以访问任意目的地址。 |
指定了ANY目的地址 (仅适用思科系) | ACL动作为允许,且指定了ANY目的地址,指定源地址可以访问任意目的地址。 |
指定了ANY地址 (仅适用华为系) | ACL动作为允许,且源或目的地址为ANY,即允许指定源地址访问任意目的地址,或允许任意源地址访问指定目的地址。 |
未指定端口 | ACL动作为允许,且未指定具体放通的端口,即放通所有端口。 |
覆盖规则 | 源地址被覆盖 | A和B规则的动作均为允许,目的地址和端口相同,且A规则的源地址覆盖了B规则的源地址,例如A规则源地址为1.1.1.0整个网段,而B规则源地址为1.1.1.1单个地址。 |
目的地址被覆盖 | A和B规则的动作均为允许,源地址和端口相同,且A规则的目的地址覆盖了B规则的目的地址,例如A规则目的地址为2.2.2.0整个网段,而B规则目的地址为2.2.2.2单个地址。 |
冲突规则 | 相同地址和端口同时存在允许和拒绝规则 | A和B规则的动作分别是允许和拒绝,但源地址、目的地址和端口都相同。例如ACL中已经存在A规则允许某地址访问,后面又新增了一条B规则拒绝该地址访问,但由于ACL是按照从上到下的优先级顺序进行匹配,即只匹配A规则,B规则将不会匹配。 |
源地址冲突 | A和B规则的动作分别是允许和拒绝,目的地址和端口相同,但A规则的源地址覆盖了B规则的源地址。例如ACL已存在A规则允许源地址1.1.1.0访问,后面又新增了一条B规则拒绝源地址1.1.1.1访问。 |
目的地址冲突 | A和B规则的动作分别是允许和拒绝,源地址和端口相同,但A规则的目的地址覆盖了B规则的目的地址。例如ACL已存在A规则允许访问目的地址2.2.2.0,后面又新增了一条B规则拒绝访问目的地址2.2.2.2。 |
冗余规则 | 动作、地址和端口号均重复 (仅适用华为系) | A和B规则完全一致。管理员在开通ACL规则时,未核查是否拟开通的规则是否已存在,导致重复开通。 |
未配置默认拒绝规则 | 即每个ACL最后一条规则需为deny any。部分品牌的网络设备对于ACL有隐式拒绝规则,不需要用户自行配置,但有些品牌没有。因此,在不确定所用设备的ACL是否有隐式拒绝规则时,笔者建议统一添加默认拒绝规则。 |
笔者将以上审计规则划分为两大类:1类包含危险规则和宽松规则,通过输入单条ACL规则便可判断其是否匹配;2类包含覆盖规则、冲突规则和冗余规则,需要对比两条ACL规则才能判断是否互相覆盖、冲突或冗余。而最后一个默认拒绝规则的匹配方式较为特殊,后文会详细讲解。
笔者已经将ACL列表从配置文件中提取出来,执行审计时需要依次用上文定义的规则去匹配每个ACL列表中的每一条规则,而默认拒绝规则是直接从单个ACL列表中匹配。匹配成功后打印结果并记录日志,日志中包含匹配的审计规则和对应的ACL规则。脚本的整体结构和执行流程如下图所示。
不同品牌网络设备的ACL配置格式相差较大。其中思科系设备的ACL配置格式比较多样。相同意义的ACL规则,华为系只存在一种格式,而思科系可以用四种不同的格式表达,如下表所示。
允许主机1.1.1.1访问2.2.2.2的80端口 |
华为系 |
rule 0 permit source 1.1.1.1 0,0,0,255 destination 2.2.2.2 0.0.0.255 destination-port eq 80 |
思科系 |
permit host 1.1.1.1 host 2.2.2.2 eq 80 |
permit host 1.1.1.1 2.2.2.2 0.0.0.255 eq 80 |
permit 1.1.1.1 0.0.0.255 host 2.2.2.2 eq 80 |
permit 1.1.1.1 0.0.0.255 2.2.2.2 0.0.0.255 eq 80 |
从上表可以看出,华为系ACL规则的特点是源地址、目的地址和端口都有明确的关键字作标识。而思科系ACL规则没有以上关键字,默认前面的地址为源,后面的地址为目的。且地址的编写方式有两种,一是host+IP,二是IP+掩码。在企业网络中,由于不同网络管理员的操作习惯,同一台设备的ACL规则可能存在不同的格式。这就给脚本匹配规则的编写带来了较大的难度。
根据上述设计思路,笔者编写了审计脚本,其中包含的6个函数及主要功能描述如下表所示。ruleXX()和ruleXX()函数即是根据4.2章节表格中所列举的审计规则利用正则表达式实现。
变量dport | 以列表的形式定义危险端口,用于后面的匹配规则 |
变量path | 网络设备配置文件所在目录 |
变量output | 审计日志输出目录 |
pretreat() | 预处理函数,用于将所有ACL配置从整个配置文件中提取出来,并识别设备品牌 |
ruleA1() | 适用于华为系设备的1类审计规则,输入单条ACL规则进行审计 |
ruleA2() | 适用于华为系设备的2类审计规则,输入两条ACL规则进行对比审计 |
ruleB1() | 适用于思科系设备的1类审计规则,输入单条ACL规则进行审计 |
ruleB2() |
适用于思科系设备的2类审计规则,输入单条ACL规则进行对比审计 |
main() | 主函数,用于将所有ACL配置分割为单独了ACL列表,再从每个列表中读取ACL规则,调用审计函数进行审计 |
该函数用于提取ACL列表并识别设备品牌,如下图。
首先通过思科系设备ACL配置中的关键字符串’ip access-list’来识别设备类型是华为系还是思科系,并利用变量device_type作为标识,便于后续调用相应的审计规则。华为系和思科系设备的配置文件中不同类型的配置之间会分别以“#”号和”!”号进行分割,基于这个特点本函数利用find()函数和字符串切片方法来提取ACL配置部分,提取后再利用split()函数将其分割为单独的ACL列表。
以华为系为例,首先利用find()函数查找字符串‘acl’并将索引值赋予给变量first_index,再利用rfind()函数从文件末尾反向查找最后一个’acl’字符串并将索引值赋予变量second_index,查找到最后一个‘acl’字符串所在的索引值后find()函数则可以从该索引值的位置往后查找第第一个‘#’符号的索引值,将该索引值与变量second_index对应的索引值相加后则是整个ACL配置部分最后的一个’#’符号的索引值。最后用字符切片方法将截取索引值fisrt_index和third_index之间的内容即为上图红框所示的ACL配置,截取后同时利用split()函数以’#’符号分割并赋值予列表alist。最终执行结果如下图所示。
Main()函数将进一步处理列表alist,将其提取为单个ACL列表。执行审计时,首先在整个ACL列表中匹配是否存在默认拒绝规则,再逐条提取ACL规则,调用1类规则函数进行审计,最后按两条一组提取ACL规则,调用2类规则函数进行审计。主要代码如下图。
下面以表格形式对部分关键代码作讲解。
行数 | 作用 |
5 | 利用listdir()函数列出目录下所有配置文件名赋值予列表file_list。 |
6,13 | 利用for循环从flist中逐个读取配置文件名并调用pretreat()函数提取ACL配置,即alist |
14-17 | 利用count()函数计算配置文件中ACL的数量。 |
19-21 | alist是一个列表,里面包含了所有的ACL,因此利用while循环结合索引值逐个读取ACL列表,并以’\n’换行符进行分割,生成一个包含只单个ACL的列表并赋值予列表acl。 |
29-33 52-55 | 变量deny用于帮助判断ACL是否存在默认拒绝规则,默认拒绝规则格式一般为”deny any”,因此这里利用正则表达式对单个ACL进行匹配,如匹配到则将变量denyn赋值为true,后续通过判断dn的值来确定ACL中是否存在默认拒绝规则。这里也可以不利用变量deny,可以用if not条件语句判断当ACL不包含默认拒绝规则时输出日志,但是笔者希望把这个日志输出到整个日志的末尾,因此利用了变量deny来控制这个规则输出日志的位置。 |
34-51 | 利用for循环从列表acl中逐条读取acl规则为变量i,首先通过device_type判断配置文件对应的设备品牌,再利用if语句判断所读取的是否为正确的acl规则,因为在列表acl中,第一个值不是ACL规则,而是该ACL的名称,这类值不参与审计。传入变量i作为参数调用品牌对应的ruleX1()函数对其进行审计。随后再用一个嵌套for循环再次从acl中读取ACL规则为变量o,同样判断其是否为正确的ACL规则。最后传入变量i和o作为参数调用ruleX2()函数进行审计。这里由于for循环嵌套的特性,在进行2类规则审计时,可能会出现重复的匹配结果。以华为系为例,匹配冗余规则时,匹配到rule 5和rule 10冗余之后,又会再次匹配rule 10和rule 5冗余,笔者暂时没有思路解决这个bug,但是并不影响最终的审计结果。 |
56 | 在审计开始之前先定义一个变量match_count,在审计函数中,每匹配一次则match_count递增一次,单个ACL审计完成后变量match_count则记录了其中存在隐患的总数。 |
63 | 变量file_name是被审计的配置文件名。 变量acl_count是配置文件中包含的ACL总数,直接利用count()函数得出。 变量global_count是配置文件中发现的安全隐患总是,通过合计变量match_count得出。 |
ruleXX()函数族包含了4个函数,分别为对应华为系ACL格式的ruleA1()和ruleA2(),对应思科系ACL格式的ruleB1()和ruleB2()。这几个函数中包含了脚本的所有审计规则(除默认拒绝规则),均使用正则表达式进行匹配。下面各列举A1类和A2类的一个匹配规则作讲解。
ruleA()函数中通过正则表达式匹配动作为允许的ACL规则中是否存在放通整个C段甚至是B段的作为源地址的情况,如有则将该ACL规则判断为源地址范围过大的宽松规则。这种匹配方式存在的不足是当网段不是正常C段或B段而是非常规的子网时就无法匹配,但由于这种子网不便于管理,因此大多数中小型企业内网很少使用这种子网。
ruleB()函数首先通过对比规则号判断两条传入的ACL规则是否为同一条,再判断动作是否一致,如一致则由后面的冗余和覆盖规则进行匹配,否则进行冲突规则的匹配。
下图中的冲突规则是对比两条ACL的源地址、目的地址和端口号,在完全一致的情况下,由于两条规则的动作不一致,即一条为允许另一条为拒绝,由此把这两条ACL规则判断为互相冲突。
下图中分割线以下的规则为冗余和覆盖规则,原理与上述规则一致。由于两条ACL的动作、源地址、目的地址和端口号均一致,因此把这两条规则判断为互相冗余。
本文介绍了笔者基于目前市场和网络上难以找到ACL安全审计解决方案和工具的情况,根据以往在甲方企业的网络管理工作经验编写了一个安全审计脚本。该脚本可以批量发现新主流品牌的网络设备配置文件中存在安全隐患的ACL规则并输出具备较高可读性的审计日志。随着网络安全形势发展,笔者认为往后会有越来越多的企业产生ACL安全审计这个需求。
目前该脚本支持华为、华三、思科、锐捷等主流品牌及其他基于以上品牌底层系统的设备,但因为笔者持有的测试样本较少。无法保证该脚本没有BUG,希望有ACL安全审计这方面需求的朋友们下载试用。如有任何问题可以在Github提交Issue,帮助笔者逐步完善该脚本。
最后附上Github地址:https://github.com/cahi1l1yn/aclAuditor/