关于 aegis-firewall-edge
aegis-firewall-edge 是用于检查单台 Linux 主机网络边界或其配置代码的审计技能,面向主机与基础设施安全审查。它覆盖监听暴露、防火墙状态、出站流量、IPv6、管理面隔离及 Docker 与云安全组的交互,并要求检查命令只读。
主要功能
- 01盘点 TCP 与 UDP 监听端口
- 02核验生效的防火墙规则
- 03评估默认拒绝与出站过滤
- 04检查 IPv6 规则一致性
- 05审查限速与管理面隔离
- 06检查 Docker 与云安全组交互
适用场景
- 01检查 Linux 主机是否暴露管理端口
- 02发现 IPv6 或 Docker 端口绕过
- 03评估出站流量与暴力破解防护
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ as-foss/ aegis-skills --skill aegis-firewall-edge安装后在 Claude Code 中输入/aegis-firewall-edge确认已加载。
附带文件references/background.mdreferences/further-reading.md
详细介绍
范围与目标
审计一台正在运行的 Linux 主机的网络边界(或其配置即代码)。按优先级排列的八个领域:
- 暴露面清单 — 列出每个 TCP/UDP 监听端口及其绑定地址、服务和用户;将其分类为互联网可访问、仅限环回或链路本地;识别应绑定到环回地址的通配符(
0.0.0.0/[::])绑定。 - 防火墙状态 — 确认实际启用的是 nftables、iptables、ufw、firewalld 中的哪一个(或均未启用),以及默认策略和各链的规则密度。
- 默认拒绝设计 — 入站策略设为丢弃并显式允许所需流量;启用带速率限制的丢弃日志记录;确保三个管理层的语法正确。
- 出站过滤 — 控制出站流量,以防止 SSRF 数据外传、反向 Shell 和数据窃取;分阶段以仅记录模式推出。
- IPv6 一致性 — IPv6 的过滤至少要与 IPv4 同样严格;允许 NDP/RA。
- L4 速率限制与暴力破解防护 — 在防火墙处设置连接/速率计量器,并为 sshd 和 nginx 身份验证失败配置 fail2ban 监狱。
- 管理平面隔离 — SSH、数据库管理和监控 Agent 仅允许从 VPN/堡垒机 CIDR 访问;变更流程须防止管理员被锁在主机外。
- 各层交互 — Docker DNAT 绕过 INPUT 规则;云安全组与主机防火墙造成的双重暴露或虚假安全感。
范围之外(交叉引用):内核 sysctl 参数(SYN cookies、tcp_syncookies、conntrack 限制)→ BASE/linux-baseline;TLS 终止和代理标头 → TLS;容器运行时沙箱 → SANDBOX;日志转发 → LOGMON。
操作规则:
- 所有检查命令均为只读;仅在获批后,才可在修复部分使用会修改状态的命令。
- 优先采用有效状态证据(
ss -tlnp、nft list ruleset),而非配置文件——磁盘上的规则集文件无法说明当前加载了什么。 - 需要 root 的命令会标记为
[ROOT]。没有 root 权限时,审计所有用户可读的状态和配置仓库,并使用 Patterns & Signatures 评估渲染后的配置。 - 外部探测(从主机外部连接服务)须获得书面授权,并注明源主机和目标范围。若未获授权,则按 Exploitation & Reproduction 中的规定,依赖主机内证据。
先决条件与术语
零背景知识入门:本模块使用的术语,每个术语用一行说明。每个类别的详细通俗解释见仓库的 GUIDE.md 术语表。
- 默认拒绝:丢弃所有流量,仅放行明确允许的流量
- 监听器绑定:服务接受连接的地址;
0.0.0.0表示所有接口 - 安全组(SG):位于主机前方的云端防火墙
- Docker DNAT 绕过:已发布的容器端口绕过主机的常规防火墙规则进行转发
- 出站过滤:限制出站目标,防止被盗用的访问权限回连或外传数据
- IPv6 一致性:IPv6 的过滤至少要与 IPv4 同样严格
- fail2ban 监狱:多次登录失败后自动封禁 IP
- 污点流 / 源→汇:不可信数据从入口流向危险函数的路径
- 发现项状态:已确认 > 较可能 > 需复核;证据规则见 templates/finding-report.md
心智模型
暴露情况由四个相互独立的层决定;任何一层存在漏洞就足以造成暴露:
internet ──▶ [1] cloud SG/NLB filters by CIDR/port BEFORE the host
──▶ [2] host firewall nftables/iptables INPUT (+ FORWARD for docker)
──▶ [3] listener binding which address the process accepts on
──▶ [4] app auth passwords, ACLs, TLS client certs
本模块利用的关键不对称性:
- 绑定地址是判断“如果所有过滤都消失,谁能够访问它”的事实依据。 通配符绑定(
0.0.0.0:6379)意味着第 3 层会将服务开放给整个互联网;只有第 1–2 层能起到阻挡作用。绑定到回环地址(127.0.0.1:6379)则无论防火墙配置有何错误,都无法从外部访问。这就是为什么通配符绑定——而非缺少防火墙规则——是云安全漏洞的头号攻击途径(Redis/Memcached/MongoDB 被公开访问后引发的勒索软件浪潮)。 - Docker 会绕过第 2 层。 发布的端口(
-p 6379:6379)会在 nat 表中进行 DNAT,并通过 FORWARD 链转发;ufw/nftables 的 INPUT 规则看不到这类流量。 - 主机防火墙是 4 层中的第 2 层,不能替代 SG。 SG 开放 + 主机开放 = 双重暴露;SG 关闭 + 主机开放 = 虚假的安全感,拓扑一旦变化(新增子网、对等 VPC、连接 LB)就会消失。
- 入站流量备受关注;出站流量则是被忽视的另一半。 一旦取得立足点(SSRF、WebShell),未过滤的出站路径就会将访问转化为数据外泄、反向 Shell 和横向移动。
- IPv6 是第二个入口。 仅基于
ip地址族构建的规则集,会让缺少table inet的主机在[::]监听器上门户大开;攻击者扫描 IPv6 的方式与扫描 IPv4 完全相同。
按最坏情况下的可达性对每个监听器分类,然后检查第 1–2 层是否真的限制了访问。不要在未核实这两层的情况下,只凭“SG 会拦截”就认为安全。
检查内容
1. 暴露面清单(首先执行)
在 [ROOT] 下运行以下可直接粘贴执行的扫描,然后为 ss -tlnp 和 ss -ulnp 的每一行建立表格:绑定地址:端口 → 进程名称 → 所属 UID → 分类。
# ===== exposure sweep (read-only; run as root) =====
echo "== TCP listeners =="; ss -tlnp
echo "== UDP listeners =="; ss -ulnp
echo "== wildcard-bound TCP listeners (suspect) =="
ss -tlnp | awk 'NR==1 || $4 ~ /^0\.0\.0\.0:/ || $4 ~ /^\[::\]:/ || $4 ~ /^\*:/'
echo "== firewall backend detection =="
for s in nftables ufw firewalld iptables netfilter-persistent; do
printf '%-24s %s\n' "$s:" "$(systemctl is-active "$s" 2>/dev/null)"
done
command -v nft >/dev/null && nft list tables
command -v iptables >/dev/null && iptables -V
command -v ufw >/dev/null && ufw status verbose
command -v firewall-cmd >/dev/null && firewall-cmd --state
echo "== iptables chain policies + INPUT rule count =="
iptables -S 2>/dev/null | grep '^-P'
printf 'INPUT rules: %s\n' "$(iptables -S INPUT 2>/dev/null | grep -c '^-A')"
echo "== ipv6 parity quick check =="
ip6tables -S 2>/dev/null | grep '^-P'
printf 'ip6tables INPUT rules: %s\n' "$(ip6tables -S INPUT 2>/dev/null | grep -c '^-A')"
nft list tables 2>/dev/null | grep -qE '(^| )(inet|ip6) ' || echo "no inet/ip6 nft table -> IPv6 likely unfiltered"
echo "== docker presence =="
[ -S /var/run/docker.sock ] && echo "docker socket present"
iptables -t nat -S DOCKER 2>/dev/null | head -5
分类规则:
- 绑定到
127.0.0.1/[::1]→ 仅限回环访问,天然安全。 - 绑定到
169.254.*或 IPv6 链路本地地址 → 仅限链路本地访问;记录下来,但不要视为可从互联网访问。 - 绑定到
10.*、172.16-31.*、192.168.*或私有接口 IP → 内部可访问;仍要审核这些网段由谁管理(VPN?整个 VPC?共享办公 LAN?)。 - 绑定到
0.0.0.0、[::]、*或公共接口 IP → 除非上层有阻挡,否则可从互联网访问;每个此类监听器都需要明确说明理由。
对于每个通配符绑定,都要问:它必须允许从回环地址之外访问吗?Web 服务器(80/443)、公共 API 端口和负载均衡后端是合理的;数据库、缓存、队列、管理面板和开发服务器则不是。通过 users:(("name",pid=N,...)) 字段交叉核对进程身份——在 0.0.0.0 上监听的“node”或“python”可能是被遗忘的开发服务器。
2. 防火墙状态评估
确定当前加载的规则实际由哪个后端管理——不同发行版各不相同,多个管理器还可能在无提示的情况下相互冲突:
nft list tables[ROOT]— 如果有活动监听器但输出为空,则表示完全没有防火墙(这本身就是一项发现)。iptables -V— 版本后缀(nf_tables)= iptables-nft 转换后端;(legacy)= 旧版 xtables。一个后端加载的规则对另一个后端不可见;在信任iptables -S前,先确认使用的是哪个后端。ufw status verbose[ROOT]—Status: inactive= 一项发现;记下Default:行。firewall-cmd --state和firewall-cmd --list-all[ROOT]— 记下区域、接口、服务和丰富规则。- 旧版后端检测的佐证:
update-alternatives --display iptables 2>/dev/null | grep value(Debian/Ubuntu);lsmod | grep -E '^ip_tables|^nf_tables'。
评估策略和规则数量:
- 链策略:
iptables -S | grep '^-P';nft:查看nft list ruleset中每条基链对应行里的policy关键字。 - 每条链的规则数:
iptables -S INPUT | grep -c '^-A'。对于可从互联网访问的主机,如果策略为 ACCEPT 且规则数为 0,则表明防火墙规则为空,这是一个发现,即使已安装 ufw/firewalld 软件包也不例外(已安装 ≠ 已启用)。 - 是否存在丢弃日志:检查
nft list ruleset中drop判定前是否有log prefix规则,或检查iptables -S INPUT中是否有-j LOG。
3. 默认拒绝设计
确认已加载(而非磁盘上的)规则集实现了:允许回环流量 → 允许已建立/相关连接 → 仅允许管理 CIDR 范围内的 SSH → 允许公网 80/443 → 对其他所有流量限速记录日志并丢弃,且 FORWARD 也默认丢弃。对照“模式与特征”和“修复”中的参考模式。任何超出预期范围的 accept(例如没有来源限制的 tcp dport 22 accept)都属于发现项。
4. 出站流量状况
检查 output 链方向:iptables -S OUTPUT / nft output 链策略。如今没有任何限制的 policy ACCEPT 是常见情况——将其记录为基线发现项(Medium 锚点),并评估已取得立足点的攻击者能够访问哪些目标:解析器、软件包镜像、应用已知的外部 API,尤其是云元数据端点。元数据可达性探测(无害 GET,仅限授权环境):
curl -sS --max-time 3 http://169.254.169.254/latest/meta-data/ami-id && echo # AWS/GCP shape
curl -sS --max-time 3 -H 'Metadata:true' 'http://169.254.169.254/metadata/instance?api-version=2021-02-01' -o /dev/null -w '%{http_code}\n' # Azure shape
从工作负载上下文内部收到响应,即可证明元数据可达——这是从 SSRF 转向实例凭据的目标。记录是否启用了 IMDSv2/令牌强制要求(仅启用 AWS IMDSv2 的实例会对盲目 GET 返回 401)。
5. IPv6 一致性
- 是否存在任何
table inet(nft list tables)?inet系列同时涵盖 v4 和 v6;只有table ip filter则仅涵盖 v4。 - 如果使用旧版规则:对比
iptables -S INPUT | grep -c '^-A'与ip6tables -S INPUT | grep -c '^-A'。 - 重新扫描监听器表,查找
[::]:PORT行——如果 v6 没有防火墙,每一行都是未过滤的 v6 入口。 - NDP/RA 要求:任何 v6 防火墙都必须允许 ICMPv6 类型 neighbor-solicit/advert 和 router-advert,否则 v6 连接会悄然失效。隐私扩展(临时地址)与此无关——服务器使用静态地址;不要尝试据此设置过滤规则。
6. 速率限制与 L4 防护
检查任意层是否存在以下措施:
- SSH 限流:
ufw limit ssh风格的规则、nft 计量器(meter ... { ip saddr ... })、针对端口 22 NEW 连接的 iptablesrecent/hashlimit匹配。 - 公共服务的 SYN/新连接速率上限(nft 规则中针对 dport 80/443 的
limit rate)。 - ICMP 速率限制:echo-request 位于
limit rate子句下;仍允许 destination-unreachable/time-exceeded。 - fail2ban:
systemctl is-active fail2ban,然后运行fail2ban-client status[ROOT]——启用了哪些 jail?是否存在 sshd jail?在 nginx 主机上,是否有监控身份验证失败的 jail?
对于可从互联网访问且 SSH 使用密码的主机,如果以上四项全部缺失,则属于发现项(与 BASE/linux-baseline sshd 检查项叠加)。
7. 管理平面隔离
针对每个管理监听器(sshd 22、数据库管理端口、node_exporter 9100、phpMyAdmin、RabbitMQ 管理端口 15672、监控 Agent):
- 从清单表中记录其绑定地址。
- 在已加载的规则集中找到允许其访问的规则,并检查来源选择器:管理 CIDR / VPN 范围还是不受限制。
- 将观察到的覆盖网络/敲门选项(tailscale/wireguard 接口
ip link show type wireguard、knockd 单元)作为背景记录,但不要将其视为充分措施。
如果仅堡垒机子网需要访问,但管理端口允许来自 0.0.0.0/0 或整个 VPC CIDR 的访问,则属于发现项。
8. Docker 交互
如果 /var/run/docker.sock 存在,或 docker ps --format '{{.Names}}\t{{.Ports}}' 返回容器:
- 列出已发布端口及其主机侧绑定:
-p 127.0.0.1:5432:5432与-p 5432:5432。 [ROOT]iptables -t nat -S DOCKER | grep DNAT——每个已发布的通配地址端口都会在此显示为 DNAT,完全绕过 INPUT。- 检查 DOCKER-USER 链是否存在并包含限制性规则:
iptables -S DOCKER-USER。默认情况是除末尾的RETURN外没有规则——这意味着 ufw INPUT 规则不会保护容器发布的端口。 - 交叉核对 compose 文件 / 单元参数:裸
"8080:8080"端口映射与带127.0.0.1:前缀的映射。
9. 云安全组协调
主机无法直接查看自己的 SG。可通过以下方式进行协调:
- 读取代码即配置仓库(Terraform
aws_security_group/google_compute_firewall/azurerm_network_security_group块;如果有访问权限,也可查看云控制台),检查适用于此实例 NIC/LB 目标组的 SG 规则。 - 构建组合矩阵:针对每个端口,分别核对 SG 状态、主机防火墙状态和绑定地址。发现的问题:
- SG 开放 + 主机开放 + 通配符绑定 → 双重暴露;如果是未经身份验证的服务,则为严重级别。
- SG 关闭 + 主机开放 → 造成虚假的安全感;应标记此问题,因为拓扑变更(新增对等连接、附加 LB、某些 VPC 中 IPv6 默认开放)会悄然激活开放的主机层。
- SG 开放 + 主机关闭 → 通常是正确的设计(由 SG 充当边缘防护);请确认主机层并非因意外而关闭(没有人维护的空链)。
查看位置
证据收集:tools/sweeps/sweep-firewall.sh 会逐字捕获 [FW-nn] 部分;应根据本模块的评估标准进行判断,切勿只依据原始输出。
应先检查有效状态,再检查文件(文件内容可能与当前加载的配置不符):
| 项目 | 路径 / 命令 | 备注 |
|---|---|---|
| 已加载的 nftables 规则集 | nft list ruleset [ROOT] | nft 后端的真实状态 |
| nftables 持久化配置 | /etc/nftables.conf;drop-ins /etc/nftables.d/*.nft | 由 nftables.service 加载 |
| iptables-nft/legacy 实时规则 | iptables -S、iptables -t nat -S、ip6tables -S [ROOT] | Docker 审计必须检查 nat 表 |
| iptables 持久化配置 | Debian:/etc/iptables/rules.v4、rules.v6;RHEL legacy:/etc/sysconfig/iptables、ip6tables | iptables-persistent/netfilter-persistent.service 与 iptables.service |
| ufw 状态 | ufw status verbose [ROOT] | 文件:/etc/default/ufw(IPV6=yes?)、生成的规则 /etc/ufw/{before,user,after}.rules |
| firewalld | firewall-cmd --list-all --permanent [ROOT] | 区域:/etc/firewalld/zones/*.xml,发行版默认配置 /usr/lib/firewalld/zones/ |
| fail2ban | fail2ban-client status [ROOT];/etc/fail2ban/jail.local、jail.d/*.conf | 通过客户端查看有效 jail,而非文件文本 |
| Docker 发布的端口 | docker ps --format '{{.Names}}\t{{.Ports}}';iptables -t nat -S DOCKER [ROOT] | Compose 文件:全仓库中的 ports: 块 |
| 监听器/进程映射 | ss -tlnp、ss -ulnp [ROOT] | 等效命令:lsof -nP -iTCP -sTCP:LISTEN |
| 代码即配置的边缘规则 | Terraform *.tf 安全组块;Ansible ufw/iptables/firewalld 任务;cloud-init 用户数据 | 根据 Patterns & Signatures 中的内容评估渲染结果 |
在确认路径可靠之前,先检测发行版:cat /etc/os-release。Debian/Ubuntu 通常使用 ufw+nftables;RHEL/Fedora 通常使用 firewalld;Alpine 使用不带管理器的纯 iptables/nftables。
模式与特征
监听器分类表
| 监听器 | 高风险绑定 | 预期绑定 | 原因 |
|---|---|---|---|
| Redis 6379 | 0.0.0.0:6379、[::]:6379 | 127.0.0.1:6379 | 默认未启用身份验证;曾发生大规模失陷,进而通过写入 crontab 实现 RCE 的攻击链 |
| Memcached 11211 | 0.0.0.0:11211、启用 UDP | 127.0.0.1:11211、-U 0 | 数据窃取 + 有记录以来规模最大的 UDP 放大 DDoS 攻击途径 |
| PostgreSQL 5432 | 0.0.0.0:5432、listen_addresses='*' | 127.0.0.1:5432 + Unix 套接字 | 易遭凭据填充攻击;应用运行在同一主机时,绝不需要对外暴露 |
| MySQL/MariaDB 3306 | bind-address=0.0.0.0 | 127.0.0.1 或私有 CIDR | 暴力破解目标;历史上存在预身份验证 CVE |
| MongoDB 27017 | 未启用身份验证时使用 bindIp: 0.0.0.0 | 127.0.0.1 / VPC 内部 + 身份验证 | 勒索软件暴露事件的典型攻击目标 |
| RabbitMQ 管理界面 15672 / AMQP 5672 | 0.0.0.0:15672 | 环回地址或仅限 VPN | 管理面板;历史上默认凭据 guest/guest 一直未更改 |
| Elasticsearch 9200 / Kibana 5601 | network.host: 0.0.0.0 | 环回地址/私有网络 + 身份验证 | 可导致全部数据被窃取;旧版本曾存在脚本执行 RCE 问题 |
| 开发/管理服务器(Jupyter 8888、Werkzeug 5000、Vite/webpack 3000–8080) | 0.0.0.0 或 --host=0.0.0.0 | 127.0.0.1 | 调试器控制台、文件上传端点,且完全没有安全加固 |
| 监控 Agent(node_exporter 9100、dcgm 等) | 0.0.0.0:9100 | 私有接口或环回地址 + 代理 | 内部指标泄露;可枚举采集端点 |
特征:暴露的通配监听器
# VULNERABLE — ss -tlnp shape: wildcard binds on sensitive services
LISTEN 0 511 0.0.0.0:6379 0.0.0.0:* users:(("redis-server",pid=812,fd=7))
LISTEN 0 4096 [::]:27017 [::]:* users:(("mongod",pid=903,fd=21))
LISTEN 0 128 *:8888 *:* users:(("python",pid=1204,fd=4))
# FIXED — same services loopback-bound
LISTEN 0 511 127.0.0.1:6379 0.0.0.0:* users:(("redis-server",pid=812,fd=7))
LISTEN 0 4096 127.0.0.1:27017 127.0.0.1:* users:(("mongod",pid=903,fd=21))
LISTEN 0 128 127.0.0.1:8888 0.0.0.0:* users:(("python",pid=1204,fd=4))
特征:运行中的主机没有防火墙规则
# VULNERABLE — iptables -S on an internet-reachable host: policies ACCEPT, zero rules
-P INPUT ACCEPT
-P FORWARD ACCEPT
-P OUTPUT ACCEPT
# FIXED — default-deny shape with explicit allows present (excerpt)
-P INPUT DROP
-P FORWARD DROP
-A INPUT -i lo -j ACCEPT
-A INPUT -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT
-A INPUT -s 203.0.113.0/24 -p tcp --dport 22 -j ACCEPT
-A INPUT -j LOGDROP
特征:IPv6 缺口
# VULNERABLE — v4 locked down but ip6tables empty/wide open; listeners on [::]
$ ip6tables -S INPUT
-P INPUT ACCEPT # zero '-A' lines follow
$ nft list tables
table ip filter # family 'ip' only -> v6 unfiltered
# FIXED
table inet filter { ... } # single inet table governs both families
ip6tables -P INPUT DROP # legacy path equivalent, mirrored rule set
入站规则参考设计(三种语法)
应用服务器的意图:允许环回流量,允许已建立/相关连接,仅允许来自管理员 CIDR 203.0.113.0/24 的 SSH,向公众开放 HTTP(S),对 ICMP 限速,对其他所有流量进行日志记录(限制速率)并丢弃。规范的完整文件位于 Remediation;此处列出特征:
# FIXED — nftables (native)
tcp dport { 80, 443 } ct state new accept
ip saddr 203.0.113.0/24 tcp dport 22 ct state new accept
icmp type echo-request limit rate 5/second accept
meta l4proto ipv6-icmp accept
jump logdrop # chain: log under rate limit, then drop everything
# FIXED — ufw (same intent, command sequence)
ufw default deny incoming
ufw allow from 203.0.113.0/24 to any port 22 proto tcp
ufw allow 80/tcp
ufw allow 443/tcp
ufw logging on
ufw enable
# FIXED — firewalld rich rules (same intent)
firewall-cmd --permanent --zone=public --add-rich-rule='rule family="ipv4" source address="203.0.113.0/24" service name="ssh" accept'
firewall-cmd --permanent --zone=public --remove-service=ssh
firewall-cmd --permanent --zone=public --add-service=http --add-service=https
firewall-cmd --set-log-denied=all
firewall-cmd --reload
污点追踪指南
将“暴露”视为从配置源到互联网可达汇点的数据流:
源模式(危险值的来源):
- 通配绑定字面值:
0.0.0.0、[::]、*、--host 0.0.0.0、listen_addresses = '*'、bind 0.0.0.0、bindIpAll: true、network.host: 0.0.0.0。 - 解析为通配地址的默认值:未设置的
LISTEN_HOST、Composeports: "8080:8080"、Docker-p 8080:8080、KuberneteshostPort:/nodePort:。 - 宽泛的边缘规则:非 Web 端口上的 SG
cidr_blocks = ["0.0.0.0/0"]或"::/0";缺少源地址选择器的 nft/iptables 接受规则。 - 缺少出站限制:输出策略为 ACCEPT,且没有按目标地址限定的允许规则。
在配置即代码仓库中追踪传播路径:
- 变量默认值:入口点、systemd 单元(
Environment=)、Helm values.yaml 中的${LISTEN_HOST:-0.0.0.0}— 追踪变量最终进入的配置位置。 - 模板渲染:会插入绑定地址的 Ansible/Jinja 模板 — 审查渲染后的文件,而非模板常量。
- Compose/K8s 端口映射:每个
ports:/hostPort条目 → 它会开放哪些主机接口,然后检查主机防火墙/SG 是否覆盖这些接口。 - SG 继承关系:实例 → 网卡 → SG 集合;LB/目标组 SG 会添加隐式允许路径 — 在判定端口已关闭之前,追踪整个链路。
- Docker DNAT:无论 INPUT 是否配置妥当,已发布端口都会污染 FORWARD 路径 — 将
-p <pub>:<pub>视为自动发现项,除非 DOCKER-USER 对其进行了限制。
**汇点分类:**互联网可达(公网接口、::/0 SG)、合作方/VPC 共享、仅堡垒机可达、环回地址。严重性取决于汇点:污点路径终止于互联网汇点时,按 Severity Assessment 列为严重/高;相同的错误配置若终止于仅堡垒机可达的汇点,则为中等。
全仓库 grep 命令示例见 Verification & Validation;即使无法进行实时检查,也应运行这些命令。
漏洞利用与复现
只读演示。仅从授权中指定的位置连接服务;否则,依据主机内证据进行判读。
1. 未经身份验证且绑定到通配地址的 Redis
外部验证(仅限从授权的源位置进行):
$ redis-cli -h 198.51.100.10 ping
PONG # no AUTH required -> unauthenticated reachability proven
$ redis-cli -h 198.51.100.10 CONFIG GET dir
1) "dir"
2) "/var/lib/redis" # writable data dir confirmed -> RCE chain viable
未经授权进行外部测试时,仅根据主机状态证明同一问题:ss -tlnp 中的 0.0.0.0:6379 ... redis-server 表明服务绑定到通配地址;空白或未配置的防火墙(iptables -S 全部为 ACCEPT,或 nft list tables 输出为空)以及 0.0.0.0/0 SG 规则,表明互联网与该绑定之间没有任何阻隔。从逻辑上说,这些证据组合等同于外部探测。
攻击路径说明(典型攻击链——在调查结果中描述;不要执行):可访问的未授权 Redis → 攻击者运行 CONFIG SET dir /var/spool/cron,将 cron 载荷写入一个键,并触发 SAVE → Redis 将该键序列化到 root 的 crontab 文件中 → cron 每分钟执行一次 → 以 root 身份建立反向 shell。真实的大规模入侵行动的公开分析文章中记录了每个步骤。
2. 空白防火墙证明
对完全开放的主机运行 iptables -L -n -v [ROOT],输出格式如下——策略均为 ACCEPT、规则数为零,只有计数器:
# VULNERABLE
Chain INPUT (policy ACCEPT 15432 packets, 892134 bytes)
pkts bytes target prot opt in out source destination
Chain FORWARD (policy ACCEPT 0 packets, 0 bytes)
pkts bytes target prot opt in out source destination
Chain OUTPUT (policy ACCEPT 12098 packets, 745112 bytes)
pkts bytes target prot opt in out source destination
使用 ufw status verbose 进行交叉验证,确认输出显示 Status: inactive,同时 systemctl is-active ufw 也显示 inactive——已安装但未启用的管理器常常只是摆设。
3. IPv6 防护缺口证明
# VULNERABLE shape
$ iptables -S INPUT | head -3
-P INPUT DROP
-A INPUT -i lo -j ACCEPT
# ...remaining v4 allow rules (established/related, admin ssh, 80/443)...
$ ip6tables -S INPUT
-P INPUT ACCEPT # <- entire v6 plane open
$ ss -tlnp | grep ':::'
LISTEN 0 511 [::]:6379 [::]:* users:(("redis-server",pid=812,fd=7))
说明:攻击者解析主机的 AAAA 记录,通过 v6 连接,并成功访问恰好是 v4 过滤规则试图隐藏的服务。仅配置 ip 地址族规则集的双栈主机上,所有 [::] 监听器在 v6 上都完全暴露。
4. Docker 绕过演示
# Evidence chain [ROOT]
$ docker ps --format '{{.Names}}\t{{.Ports}}'
cache 0.0.0.0:6379->6379/tcp # published wildcard
$ ufw status verbose | grep 'deny\|Default'
Default: deny (incoming)
$ iptables -t nat -S DOCKER | grep 6379
-A DOCKER ! -i docker0 -p tcp -m tcp --dport 6379 -j DNAT --to-destination 172.17.0.2:6379
解读:入站 :6379 流量在 nat PREROUTING 中被 DNAT,并通过 FORWARD 的 docker 链获准通过——ufw 的 deny-incoming INPUT 策略根本不会对其进行检查。尽管“防火墙拒绝一切”,该容器仍可从互联网访问。攻击路径:与上述未授权 Redis 攻击链相同,但相信 ufw 能保护自己的运维人员对此毫不知情。
修复
存在锁定风险——远程应用任何规则前请先阅读。 通过 SSH 推送限制性规则集之前,请确认带外访问可用且正常:在浏览器标签页中打开云串行控制台会话、确保可访问 IPMI/iDRAC/iLO,或确保可访问虚拟机管理程序控制台。然后在应用规则前安排自动回滚:
echo 'nft flush ruleset && nft -f /etc/nftables.conf' | at now +5 minutes atq # 确认任务已加入队列 # ... 应用新规则集,并从第二个 shell 测试 ... atrm <jobid> # 确认访问正常后才取消回滚需要
atd:检查systemctl is-active atd;若未安装,请使用发行版的软件包管理器安装(apt install at/dnf install at),或使用事后删除的 root crontab 单行命令。如果既没有 at 也没有 cron:保持两个 SSH 会话处于连接状态,从一个会话应用规则,从第二个会话验证后再关闭第一个会话,并以带外控制台作为后备。firewalld 方案:仅应用运行时变更(省略--permanent),这样systemctl restart firewalld就会恢复原状。ufw 方案:先使用ufw --dry-run enable进行检查(仅打印规则,不会应用)。
加固后的 nftables 规则集(/etc/nftables.conf)
# FIXED — complete reference ruleset, app server, dual stack
#!/usr/sbin/nft -f
flush ruleset
table inet filter {
chain logdrop {
limit rate 5/second burst 20 packets log prefix "nft-drop " level warn
counter drop
}
chain input {
type filter hook input priority 0; policy drop;
iifname "lo" accept
ct state invalid drop
ct state established,related accept
ip protocol icmp icmp type { destination-unreachable, time-exceeded, parameter-problem } accept
icmp type echo-request limit rate 5/second accept
meta l4proto ipv6-icmp accept
ip saddr 203.0.113.0/24 tcp dport 22 ct state new meter ssh-brute { ip saddr limit rate over 15/minute } drop
ip saddr 203.0.113.0/24 tcp dport 22 accept
tcp dport { 80, 443 } ct state new limit rate 50/second burst 100 packets accept
jump logdrop
}
chain forward {
type filter hook forward priority 0; policy drop;
# docker-managed forwarding may require explicit allows here; see Docker subsection
}
chain output {
type filter hook output priority 0; policy accept;
}
}
应用并持久化:
echo 'nft flush ruleset && nft -f /etc/nftables.conf' | at now +5 minutes # rollback guard
nft -f /etc/nftables.conf
nft list ruleset # verify loaded
systemctl is-enabled nftables || systemctl enable nftables
systemctl cat nftables.service # ExecStart must be 'nft -f /etc/nftables.conf'
atrm <jobid>
等效的传统 iptables 配置(适用于没有 nft 的主机):通过 -P INPUT DROP、-A INPUT -i lo -j ACCEPT、允许 conntrack ESTABLISHED,RELATED、按 CIDR 限定 dport 22、使用 hashlimit/recent 限制 SSH 速率,以及使用 LOGDROP 链来实现相同逻辑;该链使用 -m limit --limit 5/s -j LOG --log-prefix "ipt-drop " 记录日志,然后执行 -j DROP。使用 netfilter-persistent save 持久化。
等效的 ufw 配置步骤
# FIXED — run from console-backed session; dry-run first
ufw --dry-run enable
ufw default deny incoming
ufw default allow outgoing
ufw allow from 203.0.113.0/24 to any port 22 proto tcp
ufw allow 80/tcp
ufw allow 443/tcp
ufw logging on
ufw enable
ufw status verbose numbered
确保 /etc/default/ufw 中包含 IPV6=yes(默认值),以便规则应用于 v6。回滚:ufw disable。
等效的 firewalld 配置
# FIXED — runtime-only first (self-reverting via restart), then permanent
firewall-cmd --zone=public --add-rich-rule='rule family="ipv4" source address="203.0.113.0/24" service name="ssh" accept'
firewall-cmd --zone=public --remove-service=ssh
firewall-cmd --zone=public --add-service=http --add-service=https
firewall-cmd --set-log-denied=all
# verify SSH still works from admin range, then persist:
firewall-cmd --runtime-to-permanent
firewall-cmd --reload
firewall-cmd --list-all
fail2ban 监狱
/etc/fail2ban/jail.local:
# FIXED
[DEFAULT]
banaction = nftables-multiport
findtime = 10m
bantime = 1h
maxretry = 5
ignoreip = 127.0.0.1/8 ::1 203.0.113.0/24
[sshd]
enabled = true
port = 22
backend = systemd
[nginx-auth]
enabled = true
port = http,https
filter = nginx-auth
logpath = /var/log/nginx/error.log
maxretry = 5
启用:systemctl enable --now fail2ban && fail2ban-client status sshd。自带的 nginx-auth 过滤器会匹配 HTTP 基本身份验证失败;如果还想检测针对 nginx 的凭据填充攻击,请添加一个自定义过滤器,用正则表达式匹配 nginx 访问日志中持续出现的 401/403 响应。新版 fail2ban 支持使用 bantime.increment = true 逐步延长封禁时间。
分阶段推出出站流量限制
郑重提醒:一次性严格限制出站流量,造成的问题可能比收益还多——DNS 解析器、apt 镜像、NTP、监控出站流量和基于 CDN 的 API 都需要逐一列出。分阶段实施:
- 阶段 0——观察(无风险)。 保持
policy accept,添加计数和日志规则,记录尚未明确允许的所有流量:# FIXED — nft output additions, observe-only oifname "lo" counter accept ip daddr 203.0.113.53 udp dport 53 counter accept # resolver ip daddr 203.0.113.53 tcp dport 53 counter accept udp dport 123 counter accept # NTP tcp dport { 80, 443 } counter accept # mirrors/APIs, narrow later by CIDR where feasible counter log prefix "egress-would-drop " # watch this for days before Phase 1 - 阶段 1——强制执行。 将 output 的
policy改为drop,保留相同的允许规则,并将其余流量导入限速的出站 logdrop 链。 - 阶段 2——收紧规则。 将软件包镜像和已知 API 目标限定为特定 CIDR/FQDN 解析集合;在业务允许的情况下,丢弃宽泛允许的 80/443 流量。
- 始终包括:允许已建立/相关连接、回环流量、仅允许到指定解析器的 DNS,以及明确决定是否设置
ip daddr 169.254.169.254 drop(对于永远不需要元数据的工作负载应予以阻止;云端 Agent/云初始化可能需要该流量——阻止前请先协调)。
修复 Docker 交互问题
主要控制措施——仅在回环地址上发布端口(compose):
# VULNERABLE
ports:
- "6379:6379"
# FIXED
ports:
- "127.0.0.1:6379:6379"
深度防御——在 DOCKER-USER 中限制转发流量:
# FIXED [ROOT] — block off-host reach of sensitive published ports at FORWARD stage
iptables -I DOCKER-USER -m conntrack --ctstate ESTABLISHED,RELATED -j RETURN
iptables -I DOCKER-USER -i eth0 -s 127.0.0.0/8 -j RETURN
iptables -I DOCKER-USER -i eth0 -p tcp -m multiport --dports 3306,5432,6379,27017 -j DROP
netfilter-persistent save # or iptables-save > /etc/iptables/rules.v4; DOCKER-USER survives docker restarts but NOT reboots without persistence
ufw-docker 工具(github.com/chaifeng/ufw-docker)可以自动配置等效的 ufw 侧 FORWARD 规则;请评估它与手动维护的 DOCKER-USER 规则是否适用。切勿将 /etc/docker/daemon.json 中的 "iptables": false 当作“修复方案”——这会禁用 Docker 的 NAT 记录功能,并破坏容器网络。
验证与确认
每次修复后,重新执行「检查内容」中的暴露面扫描;每项修复的预期变化如下。
| 修复 | 验证命令 | 预期输出 |
|---|---|---|
| 重新绑定到回环地址 | ss -tlnp | grep -E '6379|27017|5432' | 仅有 127.0.0.1: 前缀,不应出现 0.0.0.0:/[::]: |
| nftables 默认拒绝 | nft list ruleset | grep policy | input 和 forward 上均为 policy drop |
| 已启用 ufw | ufw status verbose | Status: active,Default: deny (incoming) |
| 已应用 firewalld 配置 | firewall-cmd --list-all | 不包含 ssh 服务;包含 http/https;存在针对管理员 CIDR 的 rich rule |
| IPv6 规则一致 | ip6tables -S INPUT | grep '^-A' | wc -l; nft list tables | 计数非零,或存在 inet/ip6 表 |
| fail2ban | fail2ban-client status sshd | jail 列表中包含 Currently banned 字段 |
| Docker 隔离 | iptables -S DOCKER-USER; docker ps --format '{{.Ports}}' | 存在 DROP 规则;发布端口仅带回环地址前缀 |
负向测试(合法流量必须仍能通过——请从授权位置运行):
curl -sSI --max-time 10 https://YOUR-HOST | head -1 # HTTP/1.1 200 or 301 -> web path intact
ssh -o ConnectTimeout=5 -o BatchMode=yes HOST true # succeeds from admin IP
getent hosts archive.ubuntu.com # DNS resolution works under egress rules
ping -c1 -W3 HOST # v6 NDP/ICMP not over-filtered (dual-stack hosts)
阻断路径测试:从外部授权来源运行 nc -zv -w3 HOST 6379 和 nc -zv -w3 HOST 22,必须超时或拒绝连接;再针对 AAAA 地址重复测试 IPv6 网络。
回归问题观察清单(已知故障特征):
- 严格限制出站流量但未允许解析器 → apt/日志中出现
Temporary failure resolving,systemd 定时器不断重试。修复:先允许到已配置解析器的 UDP+TCP 53 流量(通过resolvectl status//etc/resolv.conf查找解析器)。 - 缺少 ESTABLISHED,RELATED 允许规则 → 下载过程中途停滞,长时间运行的 SSH 会话冻结。
- DOCKER-USER 丢弃规则过于宽泛 → LB 健康检查失败,容器无法访问互联网;检查计数器
iptables -L DOCKER-USER -n -v。 - 完全过滤 ICMPv6 → IPv6 短暂可用后失效(ND 缓存过期);症状是间歇性 IPv6 单独故障。
- fail2ban 未设置 bastion 网段的
ignoreip→ 共享 NAT 流量激增时,运维人员会被封禁。
配置即代码的 grep 命令示例:
grep -RInE '"0\.0\.0\.0"|0\.0\.0\.0/0|::/0|--host[= ]0\.0\.0\.0|listen_addresses\s*=\s*.{0,3}\*' .
grep -RInE 'bind[- ]address|bindIp|network.host' --include='*.cnf' --include='*.conf' --include='*.yml' . | grep -vE '127\.0\.0\.1'
grep -RInE 'ports:\s*$' --include='docker-compose*.yml' -A2 . | grep -E '[0-9]+:[0-9]+' | grep -v '127\.0\.0\.1:'
grep -RInE 'ufw.*allow.*(22\|3389)\b' . ; grep -RInE 'cidr_blocks?\s*=?.*(0\.0\.0\.0/0)' .
grep -RIlE 'policy accept|-P INPUT ACCEPT' . # ruleset files shipping default-accept
严重性评估
锚点(针对范围变更的变体,可在区间内调整;S:C 会推高严重性):
| 发现 | 严重性 | CVSS v3.1 向量 |
|---|---|---|
| 未经身份验证的服务绑定到通配地址且可从互联网访问(Redis/Memcached/MongoDB 类型) | 严重(10.0) | CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:C/C:H/I:H/A:H |
| 可从互联网访问的主机上未加载防火墙/所有策略均为 ACCEPT | 高(9.3) | CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:L/A:L |
| IPv6 完全开放,但 IPv4 受过滤(或 Docker DNAT 绕过 INPUT) | 高(8.8) | CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:L/A:N |
| 所有暴露主机均未进行出口流量过滤 | 中(5.8) | CVSS:3.1/AV:N/AC:H/PR:L/UI:N/S:U/C:H/I:L/A:N |
| 已配置默认拒绝,但缺少丢弃日志/日志速率不受限 | 低(3.6) | CVSS:3.1/AV:L/AC:H/PR:L/UI:N/S:U/C:L/I:L/A:N |
修正因素:已通过身份验证但绑定到通配地址的服务,严重性降低一个等级(身份验证是有效的控制措施);管理端口向整个 VPC 开放,而非仅向堡垒机 CIDR 开放 = 根据资产重要性,严重性为中至高;绑定到通配地址的服务即使当前处于有效且安全组封闭的网络拓扑中,仍为高危,因为拓扑变更会在不知情的情况下使其暴露。
常见误报
- Kubernetes 工作节点/控制平面节点 — kube-proxy 会配置数千条 KUBE-* iptables/nft 规则链,并自行设置 FORWARD 行为。手动收紧规则会与控制器冲突并破坏服务路由。可通过
iptables -S | grep -c KUBE-或 kube-proxy 进程进行检测;应在 NetworkPolicy/云安全组层面审计暴露情况。 - 云负载均衡器健康检查 — ALB/GCP 健康检查范围(例如 VPC CIDR、GCP
100.64.0.0/10探测范围)必须能够访问目标,因此主机防火墙允许这些范围访问是功能所需。标记问题前,先确认其后的监听器是否确实需要保护;发现应仅针对真正的公网访问路径。 - 使用
--network=host的容器 — 其监听器会作为普通进程出现在主机的ss -tlnp输出中;应在容器启动参数/compose 中修复,主机防火墙规则仍照常适用(不会进行 DNAT)。当一个根本原因同时解释两个层面的问题时,不要将两者作为独立发现重复报告。 - 集群互连(Galera、etcd 对等端口、NFS)通常会在私有接口上进行较宽泛的绑定;应根据接口的可达性分类,而非仅根据端口分类。
参考资料
- Netfilter/nftables wiki — https://wiki.nftables.org/(规则集语法、计量器、日志);netfilter 项目文档 — https://netfilter.org/documentation/
- 手册页:
nft(8)、iptables(8)、ip6tables(8)、ufw(8)、firewalld(1)、firewall-cmd(1)、fail2ban-client(1)、jail.conf(5)(fail2ban jail 配置手册)、ss(8)、at(1) - Docker 发布端口与 iptables 的交互 — https://docs.docker.com/network/iptables/
- ufw-docker 工具 — https://github.com/chaifeng/ufw-docker
- fail2ban wiki(过滤器、操作)— https://github.com/fail2ban/fail2ban/wiki
- CWE-16 配置 — https://cwe.mitre.org/data/definitions/16.html
- CWE-284 访问控制不当 — https://cwe.mitre.org/data/definitions/284.html
- OWASP Top 10 A05:2021 安全配置错误 — https://owasp.org/Top10/A05_2021-Security_Misconfiguration/
- CIS Distribution Independent Linux Benchmark(防火墙部分的基线命名)