February 20Feb 20 #!/bin/bash# =============================================================================# 防火牆腳本 - Debian 13 / iptables-nft + ip6tables-nft# 存放路徑:/usr/local/bin/firewall.sh# 權限設定:chmod 700 /usr/local/bin/firewall.sh# 整合 systemd 開機啟動## 版本: 3.2 (正式版)# 功能說明:# - 支援 start (永久生效) / stop (清除規則) / test (測試模式) 參數# - 同時設定 IPv4 (iptables) 與 IPv6 (ip6tables) 規則# - 測試模式 30 秒後自動清除規則(避免鎖死)# - 包含核心參數調校(抗攻擊、防 IP Spoofing)# - 支援來源 IP 限制、連續埠、反向排除# - 包含 Rate Limiting、Fragment 阻擋、ICMP/ICMPv6 防護# - 優化日誌記錄避免過多訊息# =============================================================================# ========================== 使用者設定區塊 ========================== ## 警告:修改前請務必閱讀註解,確認管理 IP 已正確設定(含 IPv6)# 1. 禁止連線的 IP 或網段(支援 IPv4 與 IPv6)# 範例:BADIPS="198.108.0.0/16 203.0.113.0/24 2001:db8::/32"BADIPS=""# 2. 不可能出現的私有 IP(RFC 1918 / 私有 IPv6)# ⚠️ 注意:若您的伺服器本身 IP 屬於以下網段,請務必移除對應項目!# 10.0.0.0/8 → 大型企業內網(AWS、Hetzner 等雲端內網請注意)# 172.16.0.0/12 → 中型企業內網(Docker 預設網段也在這範圍)# 192.168.0.0/16→ 小型辦公室/家庭網路# fc00::/7 → IPv6 唯一本地位址(等同 IPv4 私有網段)IMPOSSIBLE_IPS="10.0.0.0/8 172.16.0.0/12 192.168.0.0/16 fc00::/7"# 3. 允許對內連線的 TCP 通訊埠(支援來源 IP 限制與反向排除)# 格式說明:# - 單一埠:22# - 連續埠:7000:7009# - 指定來源 IPv4:22,192.168.1.100# - 指定來源 IPv6:22,2001:db8::1# - 反向排除(禁止特定 IP):22,!192.168.1.100## 重要:請將以下 IP 換成您當前連線的管理 IP!IN_TCP_PORTALLOWED="22,你的管理員IPv4 22,你的管理員IPv6 80 443"# 4. 允許對內連線的 UDP 通訊埠(格式同上)IN_UDP_PORTALLOWED=""# 5. 允許對內連線的 ICMP 類型(IPv4)# 常用類型:0 (echo reply)、3 (destination unreachable)、# 8 (echo request/ping)、11 (time exceeded/traceroute)IN_ICMP_ALLOWED="0 3 8 11"# 6. 允許對內連線的 ICMPv6 類型(IPv6 必要,請勿隨意刪減)# ICMPv6 是 IPv6 正常運作的基礎,封鎖會導致連線異常# 1: Destination Unreachable# 2: Packet Too Big(Path MTU Discovery 必要)# 3: Time Exceeded(traceroute6 使用)# 4: Parameter Problem# 128: Echo Request(ping6)# 129: Echo Reply# 133: Router Solicitation(NDP:取得路由器資訊)# 134: Router Advertisement(NDP:路由器廣播)# 135: Neighbor Solicitation(NDP:等同 ARP Request)# 136: Neighbor Advertisement(NDP:等同 ARP Reply)# 137: Redirect# 141: Inverse Neighbor Discovery Solicitation# 142: Inverse Neighbor Discovery AdvertisementIN_ICMPV6_ALLOWED="1 2 3 4 128 129 133 134 135 136 137 141 142"# 7. Rate Limiting 設定ENABLE_RATE_LIMIT="yes" # 是否啟用 rate limitingSSH_RATE="10/60" # SSH: 60秒內10次WEB_RATE="100/60" # Web: 60秒內100次ICMP_RATE="5" # ICMP ping 每秒次數# 8. 備份目錄設定# 腳本備份目錄BACKUP_DIR="/root/firewall-backups"# ========================== 系統函式區塊 ========================== ## 以下為程式邏輯,若不熟悉 shell script,請勿任意修改set -etrap 'echo "錯誤: 指令失敗於行號: $LINENO"; exit 1' ERRIPTABLES="/usr/sbin/iptables"IP6TABLES="/usr/sbin/ip6tables"SYSCTL="/usr/sbin/sysctl"# 建立備份目錄mkdir -p "$BACKUP_DIR"cp "$0" "$BACKUP_DIR/firewall.sh.$(date +%Y%m%d_%H%M%S)" 2>/dev/null || true# 檢查必要指令for cmd in $IPTABLES $IP6TABLES $SYSCTL; doif ! command -v $cmd >/dev/null 2>&1; thenecho "錯誤: 找不到 $cmd 指令"exit 1fidone# 檢查 conntrack 模組if ! $IPTABLES -m conntrack --help >/dev/null 2>&1; thenecho "錯誤: conntrack 模組不可用"echo "請執行: modprobe nf_conntrack"exit 1fi# -----------------------------------------------------------------------------# 輔助函式:判斷 IP 版本# -----------------------------------------------------------------------------is_ipv6() { echo "$1" | grep -q ':'; }is_ipv4() { echo "$1" | grep -q '\.' && ! echo "$1" | grep -q ':'; }# -----------------------------------------------------------------------------# 停止功能:清除所有規則# -----------------------------------------------------------------------------do_stop() {echo -n "Stopping firewall (flushing all rules)..."$IPTABLES -P INPUT ACCEPT$IPTABLES -P OUTPUT ACCEPT$IPTABLES -P FORWARD ACCEPT$IPTABLES -t filter -F$IPTABLES -t filter -X$IP6TABLES -P INPUT ACCEPT$IP6TABLES -P OUTPUT ACCEPT$IP6TABLES -P FORWARD ACCEPT$IP6TABLES -t filter -F$IP6TABLES -t filter -Xecho " OK"echo " 警告:所有防火牆規則已清除,伺服器目前無任何防護!"}if [ "$1" = "stop" ]; thendo_stopexit 0fi# -----------------------------------------------------------------------------# 初始化# -----------------------------------------------------------------------------echo -n "Initializing iptables/ip6tables..."$IPTABLES -P INPUT ACCEPT$IPTABLES -P FORWARD ACCEPT$IPTABLES -P OUTPUT ACCEPT$IPTABLES -t filter -F$IPTABLES -t filter -X$IP6TABLES -P INPUT ACCEPT$IP6TABLES -P FORWARD ACCEPT$IP6TABLES -P OUTPUT ACCEPT$IP6TABLES -t filter -F$IP6TABLES -t filter -Xecho " OK"# 判斷模式MODE="test"[ "$1" = "start" ] && MODE="start"# -----------------------------------------------------------------------------# 核心參數調校# -----------------------------------------------------------------------------echo -n "Tuning kernel parameters..."# SYN Flood 防護$SYSCTL -w net.ipv4.tcp_syncookies=1 > /dev/null 2>&1# ICMP 攻擊防護$SYSCTL -w net.ipv4.icmp_echo_ignore_broadcasts=1 > /dev/null 2>&1$SYSCTL -w net.ipv4.icmp_ignore_bogus_error_responses=1 > /dev/null 2>&1# 關閉 ICMP Redirect(防中間人攻擊)$SYSCTL -w net.ipv4.conf.all.accept_redirects=0 > /dev/null 2>&1$SYSCTL -w net.ipv4.conf.default.accept_redirects=0 > /dev/null 2>&1$SYSCTL -w net.ipv4.conf.all.send_redirects=0 > /dev/null 2>&1$SYSCTL -w net.ipv4.conf.default.send_redirects=0 > /dev/null 2>&1# 關閉 Source Routing$SYSCTL -w net.ipv4.conf.all.accept_source_route=0 > /dev/null 2>&1$SYSCTL -w net.ipv4.conf.default.accept_source_route=0 > /dev/null 2>&1# 反向路徑過濾(單網卡設1,多網卡或VPN請改2)$SYSCTL -w net.ipv4.conf.all.rp_filter=1 > /dev/null 2>&1$SYSCTL -w net.ipv4.conf.default.rp_filter=1 > /dev/null 2>&1# 記錄可疑封包(IP Spoofing)$SYSCTL -w net.ipv4.conf.all.log_martians=1 > /dev/null 2>&1# IPv6 防護$SYSCTL -w net.ipv6.conf.all.accept_redirects=0 > /dev/null 2>&1$SYSCTL -w net.ipv6.conf.default.accept_redirects=0 > /dev/null 2>&1$SYSCTL -w net.ipv6.conf.all.accept_source_route=0 > /dev/null 2>&1$SYSCTL -w net.ipv6.conf.default.accept_source_route=0 > /dev/null 2>&1echo " OK"# -----------------------------------------------------------------------------# 通用解析函式(支援 IP 限制、反向排除)# -----------------------------------------------------------------------------parse_entry() {local entry="$1"local proto="$2"local family="$3"local cmd src_ip port src[ "$family" = "ipv4" ] && cmd="$IPTABLES" || cmd="$IP6TABLES"case "$entry" in*,*)port=$(echo "$entry" | cut -d, -f1)src=$(echo "$entry" | cut -d, -f2-)if echo "$src" | grep -q '^!'; thensrc_ip=$(echo "$src" | sed 's/^!//')# 版本過濾if { [ "$family" = "ipv4" ] && is_ipv6 "$src_ip"; } || \{ [ "$family" = "ipv6" ] && is_ipv4 "$src_ip"; }; thenreturnfi# 反向排除:先 DROP 該來源$cmd -A INPUT -p "$proto" --dport "$port" -m conntrack --ctstate NEW -s "$src_ip" -j DROP# 再 ACCEPT 其他if [ "$proto" = "tcp" ] && [ "$port" = "22" ] && [ "$ENABLE_RATE_LIMIT" = "yes" ]; thenif [ "$family" = "ipv6" ]; then$cmd -A INPUT -p "$proto" --dport "$port" -m conntrack --ctstate NEW ! -s "$src_ip" -j SSH_ACCEPT6else$cmd -A INPUT -p "$proto" --dport "$port" -m conntrack --ctstate NEW ! -s "$src_ip" -j SSH_ACCEPTfielif [ "$proto" = "tcp" ] && { [ "$port" = "80" ] || [ "$port" = "443" ]; } && [ "$ENABLE_RATE_LIMIT" = "yes" ]; thenif [ "$family" = "ipv6" ]; then$cmd -A INPUT -p "$proto" --dport "$port" -m conntrack --ctstate NEW ! -s "$src_ip" -j WEB_ACCEPT6else$cmd -A INPUT -p "$proto" --dport "$port" -m conntrack --ctstate NEW ! -s "$src_ip" -j WEB_ACCEPTfielse$cmd -A INPUT -p "$proto" --dport "$port" -m conntrack --ctstate NEW ! -s "$src_ip" -j ACCEPTfielse# 版本過濾if { [ "$family" = "ipv4" ] && is_ipv6 "$src"; } || \{ [ "$family" = "ipv6" ] && is_ipv4 "$src"; }; thenreturnfi# 指定來源if [ "$proto" = "tcp" ] && [ "$port" = "22" ] && [ "$ENABLE_RATE_LIMIT" = "yes" ]; then$cmd -A INPUT -p "$proto" --dport "$port" -m conntrack --ctstate NEW -s "$src" -j SSH_ACCEPTelif [ "$proto" = "tcp" ] && { [ "$port" = "80" ] || [ "$port" = "443" ]; } && [ "$ENABLE_RATE_LIMIT" = "yes" ]; then$cmd -A INPUT -p "$proto" --dport "$port" -m conntrack --ctstate NEW -s "$src" -j WEB_ACCEPTelse$cmd -A INPUT -p "$proto" --dport "$port" -m conntrack --ctstate NEW -s "$src" -j ACCEPTfifi;;*)# 純埠if [ "$proto" = "tcp" ] && [ "$entry" = "22" ] && [ "$ENABLE_RATE_LIMIT" = "yes" ]; thenif [ "$family" = "ipv6" ]; then$cmd -A INPUT -p "$proto" --dport "$entry" -m conntrack --ctstate NEW -j SSH_ACCEPT6else$cmd -A INPUT -p "$proto" --dport "$entry" -m conntrack --ctstate NEW -j SSH_ACCEPTfielif [ "$proto" = "tcp" ] && { [ "$entry" = "80" ] || [ "$entry" = "443" ]; } && [ "$ENABLE_RATE_LIMIT" = "yes" ]; thenif [ "$family" = "ipv6" ]; then$cmd -A INPUT -p "$proto" --dport "$entry" -m conntrack --ctstate NEW -j WEB_ACCEPT6else$cmd -A INPUT -p "$proto" --dport "$entry" -m conntrack --ctstate NEW -j WEB_ACCEPTfielse$cmd -A INPUT -p "$proto" --dport "$entry" -m conntrack --ctstate NEW -j ACCEPTfi;;esac}# ========================== IPv4 規則 ========================== #echo -n "Applying IPv4 firewall rules..."$IPTABLES -P INPUT DROP$IPTABLES -P FORWARD DROP$IPTABLES -P OUTPUT ACCEPT# Loopback$IPTABLES -A INPUT -i lo -j ACCEPT# 建立 BADPKT 鏈$IPTABLES -N BADPKT$IPTABLES -A BADPKT -m limit --limit 1/min --limit-burst 3 -j LOG --log-prefix "IPv4_BADPKT: " --log-level 4$IPTABLES -A BADPKT -j DROP# Rate limiting 鏈if [ "$ENABLE_RATE_LIMIT" = "yes" ]; then$IPTABLES -N SSH_ACCEPT$IPTABLES -N WEB_ACCEPTSSH_HITCOUNT=$(echo $SSH_RATE | cut -d/ -f1)SSH_SECONDS=$(echo $SSH_RATE | cut -d/ -f2)$IPTABLES -A SSH_ACCEPT -m recent --name SSH4 --set$IPTABLES -A SSH_ACCEPT -m recent --name SSH4 --update --seconds $SSH_SECONDS --hitcount $SSH_HITCOUNT -j LOG --log-prefix "SSH4_RATE_DROP: " --log-level 4$IPTABLES -A SSH_ACCEPT -m recent --name SSH4 --update --seconds $SSH_SECONDS --hitcount $SSH_HITCOUNT -j DROP$IPTABLES -A SSH_ACCEPT -j ACCEPTWEB_HITCOUNT=$(echo $WEB_RATE | cut -d/ -f1)WEB_SECONDS=$(echo $WEB_RATE | cut -d/ -f2)$IPTABLES -A WEB_ACCEPT -m recent --name WEB4 --set$IPTABLES -A WEB_ACCEPT -m recent --name WEB4 --update --seconds $WEB_SECONDS --hitcount $WEB_HITCOUNT -j LOG --log-prefix "WEB4_RATE_DROP: " --log-level 4$IPTABLES -A WEB_ACCEPT -m recent --name WEB4 --update --seconds $WEB_SECONDS --hitcount $WEB_HITCOUNT -j DROP$IPTABLES -A WEB_ACCEPT -j ACCEPTfi# 允許已建立連線(插到第一條)$IPTABLES -I INPUT 1 -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT# 阻擋 INVALID$IPTABLES -A INPUT -m conntrack --ctstate INVALID -j BADPKT# 阻擋非 SYN 的新連線$IPTABLES -A INPUT -p tcp ! --syn -m conntrack --ctstate NEW -j BADPKT# 阻擋異常 TCP Flagfor flags in \"ALL NONE" \"SYN,FIN SYN,FIN" \"SYN,RST SYN,RST" \"FIN,RST FIN,RST" \"ACK,FIN FIN" \"ACK,URG URG" \"ACK,PSH PSH" \"ALL FIN,URG,PSH" \"ALL SYN,RST,ACK,FIN,URG" \"ALL ALL" \"ALL FIN"do$IPTABLES -A INPUT -p tcp --tcp-flags $flags -j BADPKTdone# 阻擋黑名單與私有 IPfor ip in $BADIPS $IMPOSSIBLE_IPS; doif is_ipv4 "$ip"; then$IPTABLES -A INPUT -s "$ip" -j DROPfidone# 允許 TCP 埠for entry in $IN_TCP_PORTALLOWED; doparse_entry "$entry" tcp ipv4done# 允許 UDP 埠for entry in $IN_UDP_PORTALLOWED; doparse_entry "$entry" udp ipv4done# 允許 ICMP(含 rate limiting)if [ "$ENABLE_RATE_LIMIT" = "yes" ]; then$IPTABLES -A INPUT -p icmp --icmp-type 8 -m limit --limit $ICMP_RATE/sec --limit-burst 5 -j ACCEPT$IPTABLES -A INPUT -p icmp --icmp-type 8 -j LOG --log-prefix "ICMP_RATE_DROP: " --log-level 4$IPTABLES -A INPUT -p icmp --icmp-type 8 -j DROPfifor type in $IN_ICMP_ALLOWED; doif [ "$ENABLE_RATE_LIMIT" = "yes" ] && [ "$type" = "8" ]; thencontinuefi$IPTABLES -A INPUT -p icmp --icmp-type "$type" -m conntrack --ctstate NEW,ESTABLISHED,RELATED -j ACCEPTdoneecho " OK"# ========================== IPv6 規則 ========================== #echo -n "Applying IPv6 firewall rules..."$IP6TABLES -P INPUT DROP$IP6TABLES -P FORWARD DROP$IP6TABLES -P OUTPUT ACCEPT$IP6TABLES -A INPUT -i lo -j ACCEPT# 建立 BADPKT6 鏈$IP6TABLES -N BADPKT6$IP6TABLES -A BADPKT6 -m limit --limit 1/min --limit-burst 3 -j LOG --log-prefix "IPv6_BADPKT: " --log-level 4$IP6TABLES -A BADPKT6 -j DROP# Rate limiting 鏈 (IPv6)if [ "$ENABLE_RATE_LIMIT" = "yes" ]; then$IP6TABLES -N SSH_ACCEPT6$IP6TABLES -N WEB_ACCEPT6SSH_HITCOUNT=$(echo $SSH_RATE | cut -d/ -f1)SSH_SECONDS=$(echo $SSH_RATE | cut -d/ -f2)$IP6TABLES -A SSH_ACCEPT6 -m recent --name SSH6 --set$IP6TABLES -A SSH_ACCEPT6 -m recent --name SSH6 --update --seconds $SSH_SECONDS --hitcount $SSH_HITCOUNT -j LOG --log-prefix "SSH6_RATE_DROP: " --log-level 4$IP6TABLES -A SSH_ACCEPT6 -m recent --name SSH6 --update --seconds $SSH_SECONDS --hitcount $SSH_HITCOUNT -j DROP$IP6TABLES -A SSH_ACCEPT6 -j ACCEPTWEB_HITCOUNT=$(echo $WEB_RATE | cut -d/ -f1)WEB_SECONDS=$(echo $WEB_RATE | cut -d/ -f2)$IP6TABLES -A WEB_ACCEPT6 -m recent --name WEB6 --set$IP6TABLES -A WEB_ACCEPT6 -m recent --name WEB6 --update --seconds $WEB_SECONDS --hitcount $WEB_HITCOUNT -j LOG --log-prefix "WEB6_RATE_DROP: " --log-level 4$IP6TABLES -A WEB_ACCEPT6 -m recent --name WEB6 --update --seconds $WEB_SECONDS --hitcount $WEB_HITCOUNT -j DROP$IP6TABLES -A WEB_ACCEPT6 -j ACCEPTfi# 允許已建立連線$IP6TABLES -I INPUT 1 -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT# 阻擋 INVALID$IP6TABLES -A INPUT -m conntrack --ctstate INVALID -j BADPKT6# 阻擋非 SYN 的新連線$IP6TABLES -A INPUT -p tcp ! --syn -m conntrack --ctstate NEW -j BADPKT6# 阻擋異常 TCP Flagfor flags in \"ALL NONE" \"SYN,FIN SYN,FIN" \"SYN,RST SYN,RST" \"FIN,RST FIN,RST" \"ACK,FIN FIN" \"ACK,URG URG" \"ACK,PSH PSH" \"ALL FIN,URG,PSH" \"ALL SYN,RST,ACK,FIN,URG" \"ALL ALL" \"ALL FIN"do$IP6TABLES -A INPUT -p tcp --tcp-flags $flags -j BADPKT6done# 阻擋 IPv6 fragment 攻擊$IP6TABLES -A INPUT -m ipv6header --header frag --soft -j LOG --log-prefix "IPv6_FRAG_DROP: " --log-level 4$IP6TABLES -A INPUT -m ipv6header --header frag --soft -j DROP# 允許必要 IPv6 通訊$IP6TABLES -A INPUT -s fe80::/10 -j ACCEPT # Link-local$IP6TABLES -A INPUT -p udp --sport 547 --dport 546 -j ACCEPT # DHCPv6# MLD (Multicast Listener Discovery)$IP6TABLES -A INPUT -p ipv6-icmp --icmpv6-type 130 -j ACCEPT$IP6TABLES -A INPUT -p ipv6-icmp --icmpv6-type 131 -j ACCEPT$IP6TABLES -A INPUT -p ipv6-icmp --icmpv6-type 132 -j ACCEPT# 阻擋黑名單與私有 IPv6for ip in $BADIPS $IMPOSSIBLE_IPS; doif is_ipv6 "$ip"; then$IP6TABLES -A INPUT -s "$ip" -j DROPfidone# 允許 TCP 埠(IPv6)for entry in $IN_TCP_PORTALLOWED; doparse_entry "$entry" tcp ipv6done# 允許 UDP 埠(IPv6)for entry in $IN_UDP_PORTALLOWED; doparse_entry "$entry" udp ipv6done# 允許 ICMPv6for type in $IN_ICMPV6_ALLOWED; do$IP6TABLES -A INPUT -p ipv6-icmp --icmpv6-type "$type" -m conntrack --ctstate NEW,ESTABLISHED,RELATED -j ACCEPTdoneecho " OK"# ========================== 顯示統計 ========================== #echo ""echo "防火牆規則統計:"echo "----------------"echo "IPv4 規則數:$($IPTABLES -L -v -n | wc -l)"echo "IPv6 規則數:$($IP6TABLES -L -v -n | wc -l)"echo ""# ========================== 測試模式 ========================== #if [ "$MODE" = "start" ]; thenecho ""echo "防火牆已啟動(start 模式),規則持續生效。"echo "若要停用請執行:/usr/local/bin/firewall.sh stop"exit 0fi# 測試模式WAIT=30cleanup() {echo ""echo -n "正在清除規則(測試結束)..."$IPTABLES -P INPUT ACCEPT$IPTABLES -P OUTPUT ACCEPT$IPTABLES -P FORWARD ACCEPT$IPTABLES -t filter -F$IPTABLES -t filter -X$IP6TABLES -P INPUT ACCEPT$IP6TABLES -P OUTPUT ACCEPT$IP6TABLES -P FORWARD ACCEPT$IP6TABLES -t filter -F$IP6TABLES -t filter -Xecho " OK"echo "測試模式結束。若有問題請修正規則後重新測試。"exit 0}trap cleanup INT TERMecho ""echo "========================================================"echo "!!! 測試模式 !!!"echo "防火牆規則已套用(IPv4 + IPv6)"echo "將在 ${WAIT} 秒後自動清除規則,以防止鎖死。"echo ""echo "請在另一終端機確認連線是否正常:"echo " SSH 連線測試:ssh localhost"echo " Ping 測試:ping -c 3 8.8.8.8"echo " Web 測試:curl -I http://localhost"echo " IPv6 測試:ping6 -c 3 google.com"echo ""echo "若連線正常,請按 Ctrl+C 中斷倒數,然後執行:"echo " /usr/local/bin/firewall.sh start"echo "========================================================"echo ""i=1while [ "$i" -le "$WAIT" ]; doprintf "\r等待中... %d 秒後自動清除規則 (按 Ctrl+C 保留規則) " "$((WAIT - i + 1))"sleep 1i=$((i + 1))doneecho ""cleanupfirewall.sh Edited February 20Feb 20 by Jack
Create an account or sign in to comment