Description
The default policy of a firewall applies to packets that match no rule. This control requires
the policy for incoming and forwarded packets to drop or reject them, while the SSH ports used by
sshd stay allowed so that administrators keep access:
- with ufw:
Default: deny (incoming)anddeny,rejectordisabled (routed)inufw status verbose, and a rule allowing each SSH port; - with nftables (when
nftables.serviceis enabled and ufw is not active): a base chain on theinputhook and one on theforwardhook withpolicy drop, and anacceptrule for each SSH port.
The automatic fix only handles ufw: it adds an allow rule for each SSH port that has none, then
sets the incoming and routed defaults to deny. nftables rulesets are written by hand.
The SSH rule check looks for the port number in an accept rule of nft list ruleset; complex
rulesets (sets, maps, jumps) may need a manual review.
Rationale
Default deny turns the firewall into an allow list: a service started by mistake, or a new one installed by a package, is not reachable until someone decides it should be. Dropping forwarded packets keeps the host from relaying traffic between networks. Checking that SSH is allowed avoids a policy that locks administrators out at the next reload.
Impact
Incoming connections to services without an allow rule are dropped. Containers and virtual machines that rely on forwarding through the host (Docker, libvirt) manage their own forward rules; check them after the change.
Audit
- ufw:
ufw status verboseshowsdeny (incoming)anddeny,rejectordisabled (routed), andufw statuslists anALLOWorLIMITrule for each port ofsshd -T | grep '^port '. - nftables:
nft list rulesethas a chain withhook inputandpolicy drop, a chain withhook forwardandpolicy drop, and a rule acceptingtcp dportfor each SSH port.
Automated: every check must pass. Scanners report each check by its id.
ufw-defaults
ufw default policies for incoming and routed traffic.
script: |
ufw status 2>/dev/null | grep -q '^Status: active' || { echo 'ufw is not active'; exit 2; }
d=$(ufw status verbose 2>/dev/null | sed -n 's/^Default: //p')
echo "$d"
printf '%s' "$d" | grep -Eq '(deny|reject) \(incoming\)' &&
printf '%s' "$d" | grep -Eq '(deny|reject|disabled) \(routed\)'
ufw-ssh
ufw rule allowing each SSH port.
script: |
s=$(ufw status 2>/dev/null)
printf '%s\n' "$s" | grep -q '^Status: active' || { echo 'ufw is not active'; exit 2; }
ports=$(PATH="$PATH:/usr/sbin" sshd -T 2>/dev/null | awk '$1 == "port" { print $2 }')
[ -n "$ports" ] || exit 3
rc=0
for p in $ports; do
if printf '%s\n' "$s" | grep -Eq "^($p(/tcp)?|OpenSSH)[[:space:]]+(ALLOW|LIMIT)"; then
echo "SSH port $p allowed"
else
echo "SSH port $p not allowed"; rc=1
fi
done
exit "$rc"
nft-defaults
nftables input and forward base chains with policy drop.
script: |
[ "$(systemctl is-enabled nftables.service 2>/dev/null)" = enabled ] || { echo 'nftables.service is not enabled'; exit 2; }
if command -v ufw >/dev/null 2>&1 && ufw status 2>/dev/null | grep -q '^Status: active'; then
echo 'ufw manages the firewall'; exit 2
fi
r=$(nft list ruleset 2>/dev/null) || exit 3
rc=0
for h in input forward; do
if printf '%s\n' "$r" | grep -Eq "hook $h .*policy drop"; then echo "$h: policy drop"; else echo "$h: no chain with policy drop"; rc=1; fi
done
exit "$rc"
nft-ssh
nftables accept rule for each SSH port.
script: |
[ "$(systemctl is-enabled nftables.service 2>/dev/null)" = enabled ] || { echo 'nftables.service is not enabled'; exit 2; }
if command -v ufw >/dev/null 2>&1 && ufw status 2>/dev/null | grep -q '^Status: active'; then
echo 'ufw manages the firewall'; exit 2
fi
r=$(nft list ruleset 2>/dev/null) || exit 3
ports=$(PATH="$PATH:/usr/sbin" sshd -T 2>/dev/null | awk '$1 == "port" { print $2 }')
[ -n "$ports" ] || exit 3
rc=0
for p in $ports; do
if printf '%s\n' "$r" | grep -E 'tcp dport .*accept' | grep -Eq "(^|[^0-9])$p([^0-9]|$)"; then
echo "SSH port $p accepted"
else
echo "SSH port $p not accepted"; rc=1
fi
done
exit "$rc"
Remediation
- ufw: allow SSH first, then set the defaults:
ufw allow 22/tcp ufw default deny incoming ufw default deny routed - nftables: write
/etc/nftables.confwith, at least:
Check it withtable inet filter { chain input { type filter hook input priority filter; policy drop; ct state established,related accept iif lo accept meta l4proto { icmp, ipv6-icmp } accept tcp dport 22 accept } chain forward { type filter hook forward priority filter; policy drop; } }nft -c -f /etc/nftables.conf, then runsystemctl reload nftablesfrom a console, or with a scheduled rollback, in case the SSH rule is wrong.
| Restart | none |
| Disruption | high |
| Lockout risk | yes: can cut off administrative access. Off by default in generated scripts and playbooks, opt in through a tailoring file. |
Rollback
ufw: ufw default allow incoming and ufw default allow routed (or restore the backed up
/etc/default/ufw and run ufw reload). nftables: restore the previous /etc/nftables.conf
and reload.
ufw-policy
# CB-DEB-NET-006: with ufw active, allow each SSH port that has no rule, then deny incoming and routed by default.
PATH="$PATH:/usr/sbin"
s=$(ufw status 2>/dev/null)
printf '%s\n' "$s" | grep -q '^Status: active' || exit 0
for f in /etc/default/ufw /etc/ufw/user.rules /etc/ufw/user6.rules; do cb_backup "$f"; done
for p in $(sshd -T 2>/dev/null | awk '$1 == "port" { print $2 }'); do
printf '%s\n' "$s" | grep -Eq "^($p(/tcp)?|OpenSSH)[[:space:]]+(ALLOW|LIMIT)" && continue
ufw allow "$p/tcp" comment 'SSH (CIRSEC Baselines)' >/dev/null || exit 1
done
ufw default deny incoming >/dev/null || exit 1
ufw default deny routed >/dev/null
Handwritten code reads parameters from CB_<NAME> environment variables. Each release turns these fixes into remediate.sh and an Ansible role per profile: dry run by default, a fix runs only when its check fails.
References
- ufw(8), Debian manual pages Debian, vendor-doc, accessed 2026-10-18
- nft(8), Debian manual pages Debian, vendor-doc, accessed 2026-10-18
- Debian Wiki - nftables Debian, vendor-doc, accessed 2026-10-18
- NIST SP 800-41 Rev. 1 - Guidelines on Firewalls and Firewall Policy NIST, guide, accessed 2026-10-18
Mappings
| Framework | ID | Relation |
|---|---|---|
| NIST SP 800-53 Rev. 55.1.1 | SC-7(5) | equivalent |
How this control relates to the other framework's: equivalent, stricter, looser, partial or related.
Threats mitigated
Related controls
- requires
CB-DEB-NET-005Run exactly one host firewall
People and lifecycle
| Authors |
|
| Verified | 2026-10-18 by Sn0wAlice on 12, Proxmox VM 921, Debian 12 genericcloud image, kernel 6.1.0-54-cloud-amd64. ufw defaults and SSH rule checked before and after the fix; nftables checks tested with accept, drop without SSH rule and drop with SSH rule rulesets; SSH by key works after fix and reboot. |
| Verified | 2026-10-18 by Sn0wAlice on 13, Proxmox VM 931, Debian 13 genericcloud image, kernel 6.12.113+deb13-cloud-amd64. ufw defaults and SSH rule checked before and after the fix; nftables checks tested with accept, drop without SSH rule and drop with SSH rule rulesets; SSH by key works after fix and reboot. |
| Introduced in | v0.1.0 |
| Created, updated | 2026-10-18, 2026-10-18 |
| Translations | fr |