人人都能学编程
English 开始学习
专项 · 网络安全第 9 课 / 共 9 课免费阅读

防火墙规则 · 网络安全入门

防火墙是怎么决定哪些流量能进来的?

课程目录

为什么需要它

互联网上的每台服务器都在被不停地扫描。防火墙是第一道过滤:让你预期的流量到达正确的端口,其余的全部丢弃。读写防火墙规则是安全和运维的核心技能,大多数错误都出在规则顺序和忘了默认规则上。

直觉理解

想象一个拿着规则清单的门卫,规则从上往下检查。第一条匹配的规则说了算,后面的都不再看。如果一条都不匹配,就按默认规则处理,安全的默认是「拒绝」。

地址按块来写,用的是 CIDR 记法:192.0.2.0/24 表示「前 24 位相同的所有地址」,也就是 192.0.2.0 到 192.0.2.255(256 个地址)。0.0.0.0/0 匹配所有地址。Python 的 ipaddress 模块帮你算这些二进制位:ip_address(x) in ip_network(block)。

端口说明访问的是机器上的哪个服务:22 是 SSH(远程登录),443 是 HTTPS(网站)。

定义

首条匹配规则

Rules are checked in order and the first matching rule decides. Order changes the result.
规则按顺序检查,第一条匹配的规则决定结果。顺序不同,结果就可能不同。

CIDR 地址块

An address range written as address/prefix length. A /24 block has 256 addresses; /32 is one address; /0 is all of them.
用「地址/前缀长度」写出的地址范围。/24 块有 256 个地址;/32 只有一个地址;/0 是全部地址。

示例

这个地址在地址块里吗?

动手试试

办公室网络是 192.0.2.0/24。

import ipaddress
office = ipaddress.ip_network("192.0.2.0/24")
for ip in ["192.0.2.10", "192.0.2.255", "192.0.3.10"]:
    print(ip, ipaddress.ip_address(ip) in office)
print(office.num_addresses)
逐步执行
  1. L2代表整个地址块的网络对象。
  2. L4in 检查地址是否落在这个块里。192.0.2.10 True
  3. L4192.0.3.x 是另一个块。192.0.3.10 False
  4. L5/24 留出 8 位给主机:2⁸ = 256。256

第一条匹配的说了算

动手试试

SSH 只允许办公室访问,HTTPS 谁都能访问,其余全部拒绝。

import ipaddress
rules = [
    ("allow", "192.0.2.0/24", 22),
    ("deny", "0.0.0.0/0", 22),
    ("allow", "0.0.0.0/0", 443),
]

def decide(ip, port):
    for action, net, p in rules:
        if port == p and ipaddress.ip_address(ip) in ipaddress.ip_network(net):
            return action
    return "deny"

print(decide("192.0.2.8", 22))
print(decide("203.0.113.9", 22))
print(decide("203.0.113.9", 443))
print(decide("203.0.113.9", 3306))
逐步执行
  1. L9规则从上往下检查。
  2. L11第一条匹配就立刻返回。
  3. L12没有规则匹配:默认拒绝。
  4. L14办公室地址,端口 22:第 1 条允许。allow
  5. L15外部地址,端口 22:第 1 条不匹配,第 2 条拒绝。deny
  6. L17端口 3306(数据库)谁都不匹配:默认拒绝。deny

常见错误

宽泛的规则放在最上面

如果 allow 0.0.0.0/0 22 排在第一条,下面「只允许办公室」的规则就永远用不上:谁都能 SSH。具体的规则要放在宽泛的规则前面。

默认放行

没有明确拒绝就放行的防火墙,会让每一个被遗忘的服务都暴露在外。默认拒绝,只放行你需要的。

小测验

1. 这段代码会输出什么?

import ipaddress
rules = [("allow", "0.0.0.0/0", 22), ("deny", "203.0.113.0/24", 22)]

def decide(ip, port):
    for action, net, p in rules:
        if port == p and ipaddress.ip_address(ip) in ipaddress.ip_network(net):
            return action
    return "deny"

print(decide("203.0.113.66", 22), decide("203.0.113.66", 80))
  1. A
    allow deny
  2. B
    deny deny
  3. C
    allow allow
查看答案

答案:A

放行所有人的规则排在第一,所以端口 22 永远轮不到下面的拒绝规则。端口 80 没有规则匹配:默认拒绝。

2. 10.0.0.0/30 里有多少个地址?

  1. A4 个
  2. B30 个
  3. C256 个
查看答案

答案:A

32 − 30 = 2 位留给主机,2² = 4。

动手练习

3 道练习,分三层:先预测程序的输出,再按新需求改程序,最后从零写一个。代码在浏览器里运行,提交马上判题。

  1. 预测输出判断每个数据包

    准确写出这段程序的输出。

  2. 改需求任意端口

    两处修改:

    • 规则里的端口可以是 None,表示任意端口
    • 没有规则匹配时返回 "deny"(现在的代码返回的是 "allow")

    列表最后那条新规则要在所有端口上封掉整个 198.51.100.0/24 网段,它必须排在最前面才会生效:把它移到列表顶部。

  3. 从零写从文本读规则

    防火墙规则常常写成文本。写一个函数 decide(ip, port, rules),其中 rules 是这样的字符串列表:

    allow 192.0.2.0/24 22
    deny 0.0.0.0/0 *

    每条规则是「动作 地址块 端口」,端口 * 表示任意端口。按顺序检查规则,返回第一条匹配规则的动作,都不匹配就返回 "deny"。port 是整数。

在互动课里练这一课

打开互动课,这些例子都能直接改、直接运行,练习题提交马上判题。免费开始,不用注册。

互动课按顺序解锁:前面的课还没学,会先带你到课程地图。

本课术语

术语中文英文定义
Firewall防火墙A filter that allows or blocks network traffic according to rules.
CIDR notationCIDR 记法Writing an address range as address/prefix, such as 192.0.2.0/24.
Port端口A number that selects a service on a machine, such as 22 for SSH or 443 for HTTPS.
Default deny默认拒绝Blocking everything that no rule explicitly allows.