1255 字
6 分钟
使用 iptables + ipset 屏蔽主流云厂商 ASN 与恶意扫描流量
本文记录利用 iptables 结合 ipset 屏蔽主流云服务商(AWS, DigitalOcean, Azure, Vultr, Oracle 等)扫描探针与探测流量的方案。
直接在 iptables 中处理大量 IP 网段会导致性能劣化。ipset 使用哈希结构维护 IP 集合,可在 时间复杂度内完成检索,并通过 ipset swap 实现增量无缝更新。
1. 防火墙初始化部署脚本
在部署前,禁用 ufw 以防规则冲突:
sudo ufw disablesudo systemctl stop ufwsudo systemctl disable ufw创建脚本 /usr/local/bin/deploy_firewall.sh:
#!/bin/bash# ============================================================================# 防火墙初始化部署脚本# ============================================================================
SSH_PORT=54321WEB_PORTS=(80 443)ENABLE_IPV6=1NEIGHBOR_V4="-"NEIGHBOR_V6="-"
export PATH="/usr/sbin:/sbin:$PATH"
check_v6() { if [ "$ENABLE_IPV6" != "1" ]; then echo 0; return; fi if [ -f /proc/net/if_inet6 ] && [ "$(wc -l < /proc/net/if_inet6)" -gt 0 ]; then command -v ip6tables &>/dev/null && echo 1 || echo 0 else echo 0 fi}
check_deps() { local missing=() for cmd in ipset iptables; do command -v "$cmd" &>/dev/null || missing+=("$cmd") done if [ "${#missing[@]}" -gt 0 ]; then echo "Missing dependencies: ${missing[*]}" echo "Run: sudo apt install -y ipset iptables" exit 1 fi}
install_persistent() { if dpkg -l iptables-persistent 2>/dev/null | grep -q '^ii' && dpkg -l ipset-persistent 2>/dev/null | grep -q '^ii'; then return 0 fi echo iptables-persistent iptables-persistent/autosave_v4 boolean true | debconf-set-selections 2>/dev/null echo iptables-persistent iptables-persistent/autosave_v6 boolean true | debconf-set-selections 2>/dev/null DEBIAN_FRONTEND=noninteractive apt-get install -y iptables-persistent ipset-persistent &>/dev/null}
create_ipsets() { ipset create bad_asn_v4 hash:net family inet hashsize 4096 maxelem 500000 -exist 2>/dev/null if [ "$HAS_V6" = "1" ]; then ipset create bad_asn_v6 hash:net family inet6 hashsize 4096 maxelem 500000 -exist 2>/dev/null fi}
deploy_v4() { iptables -P INPUT DROP iptables -P FORWARD DROP iptables -P OUTPUT ACCEPT iptables -F iptables -X
iptables -A INPUT -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT iptables -A INPUT -i lo -j ACCEPT
if [ "$NEIGHBOR_V4" != "-" ]; then iptables -A INPUT -s "$NEIGHBOR_V4" -j DROP fi
# 丢弃匹配 ipset 集合源的连接请求 iptables -A INPUT -m set --match-set bad_asn_v4 src -j DROP
iptables -A INPUT -p tcp --dport "$SSH_PORT" -j ACCEPT for port in "${WEB_PORTS[@]}"; do iptables -A INPUT -p tcp --dport "$port" -j ACCEPT done}
deploy_v6() { ip6tables -P INPUT DROP ip6tables -P FORWARD DROP ip6tables -P OUTPUT ACCEPT ip6tables -F ip6tables -X
ip6tables -A INPUT -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT ip6tables -A INPUT -i lo -j ACCEPT
if [ "$NEIGHBOR_V6" != "-" ]; then ip6tables -A INPUT -s "$NEIGHBOR_V6" -j DROP fi
ip6tables -A INPUT -m set --match-set bad_asn_v6 src -j DROP ip6tables -A INPUT -p tcp --dport "$SSH_PORT" -j ACCEPT for port in "${WEB_PORTS[@]}"; do ip6tables -A INPUT -p tcp --dport "$port" -j ACCEPT done}
persist() { if command -v netfilter-persistent &>/dev/null; then netfilter-persistent save 2>/dev/null else mkdir -p /etc/iptables ipset save > /etc/iptables/ipsets 2>/dev/null iptables-save > /etc/iptables/rules.v4 [ "$HAS_V6" = "1" ] && ip6tables-save > /etc/iptables/rules.v6 fi}
main() { if [ "$(id -u)" != "0" ]; then exit 1; fi HAS_V6=$(check_v6) check_deps create_ipsets deploy_v4 [ "$HAS_V6" = "1" ] && deploy_v6 install_persistent persist}
main "$@"赋予权限并运行:
sudo chmod +x /usr/local/bin/deploy_firewall.shsudo /usr/local/bin/deploy_firewall.sh2. 动态黑名单拉取更新脚本
创建脚本 /usr/local/bin/update_blacklists.sh:
#!/bin/bash# ============================================================================# 自治系统 IP 段同步脚本# ============================================================================
ASNS=( "14061|DigitalOcean" "16509|AWS" "15169|Google_Cloud" "8075|Azure" "20473|Vultr" "31898|Oracle")
CACHE_DIR="/var/lib/seedbox-firewall"export PATH="/usr/sbin:/sbin:$PATH"set -o pipefail
check_v6() { if [ -f /proc/net/if_inet6 ] && [ "$(wc -l < /proc/net/if_inet6)" -gt 0 ]; then command -v ip6tables &>/dev/null && echo 1 || echo 0 else echo 0 fi}
check_deps() { local missing=() for cmd in curl awk python3 ipset whois sha256sum; do command -v "$cmd" &>/dev/null || missing+=("$cmd") done if [ "${#missing[@]}" -gt 0 ]; then echo "Missing: ${missing[*]}. Run: sudo apt install -y curl gawk python3 ipset iproute2 whois" exit 1 fi}
init_cache() { mkdir -p "$CACHE_DIR" 2>/dev/null}
get_cached_hash() { [ -f "${CACHE_DIR}/${1}.sha256" ] && cat "${CACHE_DIR}/${1}.sha256" || echo ""}
set_cached_hash() { echo "$2" > "${CACHE_DIR}/${1}.sha256"}
is_changed() { if [ ! -s "$2" ]; then return 0; fi local new_hash=$(sha256sum "$2" | awk '{print $1}') local old_hash=$(get_cached_hash "$1") [ "$new_hash" != "$old_hash" ]}
update_cache() { local new_hash=$(sha256sum "$2" | awk '{print $1}') set_cached_hash "$1" "$new_hash"}
update_asn() { local work="/tmp/asn-update-$$" mkdir -p "$work" local v4_cidr="${CACHE_DIR}/asn_v4.cidr" local v6_cidr="${CACHE_DIR}/asn_v6.cidr" local v4_tmp="${work}/asn_v4.cidr" local v6_tmp="${work}/asn_v6.cidr"
true > "$v4_tmp" [ "$HAS_V6" = "1" ] && true > "$v6_tmp"
local v4_total=0 v6_total=0
for ITEM in "${ASNS[@]}"; do ASN="${ITEM%%|*}" local whois_out="${work}/whois_${ASN}.txt" if ! timeout 20 whois -h whois.radb.net -- "-i origin AS${ASN}" > "$whois_out" 2>/dev/null; then rm -f "$whois_out" continue fi
local v4_count=$(awk '/^route:/ {print $2}' "$whois_out" | grep -v '^$' | tee -a "$v4_tmp" | wc -l) v4_total=$((v4_total + v4_count))
if [ "$HAS_V6" = "1" ]; then local v6_count=$(awk '/^route6:/ {print $2}' "$whois_out" | grep -v '^$' | tee -a "$v6_tmp" | wc -l) v6_total=$((v6_total + v6_count)) fi rm -f "$whois_out" done
# 增量比对,利用 ipset swap 无缝替换 if [ "$v4_total" -ge 50 ] && is_changed "asn_v4" "$v4_tmp"; then local new_set="bad_asn_v4_new" ipset create "$new_set" hash:net family inet hashsize 4096 maxelem 500000 -exist 2>/dev/null { echo "create $new_set hash:net family inet hashsize 4096 maxelem 500000 -exist" awk '{print "add '"$new_set"' " $0}' "$v4_tmp" } | ipset restore -exist 2>/dev/null ipset swap "$new_set" bad_asn_v4 ipset destroy "$new_set" 2>/dev/null cp "$v4_tmp" "$v4_cidr" update_cache "asn_v4" "$v4_tmp" fi
if [ "$HAS_V6" = "1" ] && [ "$v6_total" -ge 50 ] && is_changed "asn_v6" "$v6_tmp"; then local new_set="bad_asn_v6_new" ipset create "$new_set" hash:net family inet6 hashsize 4096 maxelem 500000 -exist 2>/dev/null { echo "create $new_set hash:net family inet6 hashsize 4096 maxelem 500000 -exist" awk '{print "add '"$new_set"' " $0}' "$v6_tmp" } | ipset restore -exist 2>/dev/null ipset swap "$new_set" bad_asn_v6 ipset destroy "$new_set" 2>/dev/null cp "$v6_tmp" "$v6_cidr" update_cache "asn_v6" "$v6_tmp" fi
rm -rf "$work"}
main() { if [ "$(id -u)" != "0" ]; then exit 1; fi HAS_V6=$(check_v6) check_deps init_cache update_asn}
main "$@"赋予权限:
sudo chmod +x /usr/local/bin/update_blacklists.sh3. 首次加载与定时同步配置
手动运行脚本拉取数据并初始化 ipset 集合:
sudo /usr/local/bin/update_blacklists.sh添加 crontab 任务,设置每周日凌晨 4 点进行规则库同步:
sudo crontab -e# 写入以下配置0 4 * * 0 /usr/local/bin/update_blacklists.sh > /var/log/blacklist-update.log 2>&1执行以下命令查看拦截链状态:
sudo iptables -L INPUT -v -n --line-numbers在 INPUT 规则链中应存在 match-set bad_asn_v4 src DROP 相关的全局阻断项(位列 SSH 与 Web 端口放行规则之前)。
NOTEiptables 按自上而下的顺序处理规则,由于黑名单 DROP 规则位于开放端口 ACCEPT 规则之前,来自黑名单 ASN 的流量会在前半部分被直接丢弃,非黑名单流量才能匹配到后面的端口放行规则。
使用 iptables + ipset 屏蔽主流云厂商 ASN 与恶意扫描流量
https://blog.rokutosei.com/posts/linux/block_asn_using_iptables_and_ipset/