目录

对特定端口进行封禁:

#!/bin/bash
#
# 安全性修复
  
current_script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" >/dev/null 2>&1 && pwd)"
  
read -p "请输入 ssh 登录密码: " -s password
echo ""
#保存iptables配置
if [ ! -f "/etc/iptables" ]; then
    echo ${password} | sudo -S bash -c 'iptables-save > /etc/iptables'
    echo ${password} | sudo -S bash -c 'chmod 755 /etc/iptables'
fi
  
# Log the given message at the given level. All logs are written to stderr with a timestamp.
function log() {
    local -r level="$1"
    local -r message="$2"
    local -r timestamp=$(date +"%Y-%m-%d %H:%M:%S")
    local -r script_name="$(basename "$0")"
    echo >&2 -e "${timestamp} [${level}] [$script_name] ${message}"
}
  
# Log the given message at INFO level. All logs are written to stderr with a timestamp.
function log_info() {
    local -r message="$1"
    log "\033[1mINFO\033[0m" "$message"
}
  
# Log the given message at WARN level. All logs are written to stderr with a timestamp.
function log_warn() {
    local -r message="$1"
    log "\033[33;1mWARN\033[0m" "$message"
}
  
# Log the given message at ERROR level. All logs are written to stderr with a timestamp.
function log_error() {
    local -r message="$1"
    log "\033[31;1mERROR\033[0m" "$message"
}
  
# 获取 /etc/iptabls *raw 第一行规则
function get_first_raw_rule_row_number() {
    mapfile rows < /etc/iptables
    local matched=0
    for (( i=0; i<=${#rows[@]}; i++)); do
        if [[ $matched -eq 1 ]]; then
            if [[ ! ${rows[i]} =~ ^: ]] && [[ ! ${rows[i]} =~ ^# ]]; then
                echo $((i+1))
                return 0
            fi
        elif [[ "${rows[i]}" =~ ^\*raw ]]; then
            matched=1
        fi
    done
}
# 封禁端口
function ban_port() {
    port=$1
    rule_line=$(echo ${password} | sudo -S iptables -t raw -nvL PREROUTING --line-number | grep "dpt:${port} " | grep "DROP" | wc -l)
    if_raw=$(echo ${password} | sudo -S  cat /etc/iptables | grep "*raw" | wc -l)
    if [[ $if_raw == 0 ]]; then
        echo ${password} | sudo -S sed -i "1i\*raw\n:PREROUTING" /etc/iptables
    fi
  
    if [[ $rule_line == "0" ]]; then
        # 找到需要封禁的对外网卡名
        echo ${password} | sudo -S sed -i "/dport ${port}/d" /etc/iptables
        devs=$(cat /proc/net/dev | awk '{i++; if(i>2){print $1}}' | sed 's/^[\t]*//g' | sed 's/[:]*$//g' | grep -v -E '^veth|^br|^lo|hurricane|^flannel|^docker|^cni')
        for dev in ${devs[@]}
        do
            echo ${password} | sudo -S iptables -w 10 -t raw -I PREROUTING -i ${dev} -p tcp -m tcp --dport ${port} -j DROP
            local rule="-A PREROUTING -i ${dev} -p tcp -m tcp --dport ${port} -j DROP"
            echo ${password} | sudo -S sed -i "`get_first_raw_rule_row_number`i\\${rule}" /etc/iptables
        done
        log_info "[${port}端口] 添加禁止外部地址访问规则"
    else
        log_warn "[${port}端口] 禁止外部地址访问规则已存在"
    fi
}
# 开放端口
function open_port() {
    port=$1
    echo ${password} | sudo -S iptables -w 10 -t raw -F
    echo ${password} | sudo -S sed -i "/dport ${port}/d" /etc/iptables
    log_info "[${port}端口] 清除禁止外部访问规则"
}
 
# ==================================================
  
args=${1:-"fix"}
port_list=("80" "8080") #操作的端口号
if [[ $args == "fix" ]]
then
    for port in ${port_list[@]}
    do
        ban_port $port
    done
elif [[ $args == "reset" ]]
then
    for port in ${port_list[@]}
    do
        open_port $port
    done
else
    echo "unsupport operation $args"
fi
  1. #登录目标机器
  2. vi port.sh  #复制脚本代码  并修改需要操作的端口号 port_list
  3. bash port.sh fix #执行封禁脚本 
  4. exit #退出远程
  5. nc -vz {IP} {PORT} #测试端口是否成功封禁

这里采用的drop方式封禁端口,所以nc不会返回

使用iptables禁用端口时,nc(netcat)不会返回“连接拒绝”,而是直接卡住在终端上,这与iptables的规则配置和TCP协议的工作方式有关。具体来说,当你使用iptables封禁端口时,如果是DROP操作,TCP连接的行为会有所不同。

TCP 连接的工作方式

  1. 端口关闭但可达: 当目标端口是关闭的且防火墙允许ICMP不可达消息时,nc会立即收到一个TCP RST(重置)包,这会导致连接被拒绝(Connection refused)。
  2. 端口被防火墙DROP: 当目标端口被防火墙DROP规则封禁时,数据包被直接丢弃,不会生成任何响应。这样,nc无法收到任何回应,表现为连接卡住等待超时。

iptables 的处理方式

iptables可以通过两种方式封禁端口:REJECT和DROP。

    DROP:丢弃数据包,不发送任何响应。对于TCP连接,意味着客户端会一直等待直到连接超时。

    iptables -A INPUT -p tcp --dport 8080 -j DROP
    
  1. iptables -A INPUT -p tcp --dport 8080 -j REJECT --reject-with tcp-reset
    

为什么会卡住

当使用DROP规则时,数据包被直接丢弃,客户端(nc)没有收到任何响应,导致它会一直尝试等待连接超时。这就是为什么连接会卡住而不是立即返回“连接拒绝”。

解决方法

如果你希望nc在端口被封禁时能够立即返回“连接拒绝”而不是卡住,可以将iptables规则设置为REJECT而不是DROP:

这样,尝试连接被封禁端口时,客户端会收到TCP RST包,立即返回“连接拒绝”。

示例对比

使用 DROP 封禁端口

sudo iptables -A INPUT -p tcp --dport 8080 -j DROP

nc -v localhost 8080
# 卡住,没有任何输出,直到超时

使用 REJECT 封禁端口

此时,nc会立即返回连接拒绝:

nc -v localhost 8080
# 输出
nc: connect to localhost port 8080 (tcp) failed: Connection refused

参考资料

drop还是reject

在决定使用DROP还是REJECT来封禁端口时,需要考虑安全需求和实际场景。这两种方法各有优缺点,具体选择取决于你对安全性、性能和用户体验的要求。

DROP 与 REJECT 的比较

DROP

  • 行为:直接丢弃数据包,不发送任何响应。
  • 优点:
    • 隐蔽性:攻击者无法确定端口是否存在,因为没有任何响应返回。提高了系统的隐蔽性。
    • 减少负载:没有响应包返回,节省了系统资源。
  • 缺点:
    • 用户体验:合法用户可能会感到困惑,因为连接会一直挂起直到超时。
    • 诊断困难:排查网络问题时,不容易区分端口未开放还是被防火墙封禁。

REJECT

  • 行为:拒绝数据包,并发送适当的响应回客户端。对于TCP连接,通常会返回TCP RST(重置)包。
  • 优点:
    • 用户体验:合法用户会立即知道端口被封禁,不会长时间等待。
    • 便于诊断:更容易区分端口被封禁和网络其他问题。
  • 缺点:
    • 可识别性:攻击者可以通过收到的响应,确认端口被封禁。
    • 系统负载:需要生成并发送响应包,略微增加系统负载。

选择 DROP 还是 REJECT 的建议

  1. 高安全性场景:
    • 使用DROP:如果你希望最大程度隐藏系统的存在,避免被扫描器和潜在攻击者发现,可以选择DROP。这种方式可以使攻击者无法确定端口是否存在。
  2. 用户体验和诊断优先:
    • 使用REJECT:如果你更关注合法用户的体验,或者需要更方便地进行网络问题诊断,可以选择REJECT。这种方式可以立即通知用户端口不可用。

实践建议

  • 公共服务端口:对于公开的服务端口,例如Web服务器的80/443端口,建议使用REJECT。这有助于用户和管理员快速确定端口被封禁的原因。
  • 内部或敏感端口:对于内部服务或敏感端口,例如数据库端口或SSH端口,建议使用DROP。这样可以减少被扫描和攻击的可能性。

配置示例

使用 DROP

sudo iptables -A INPUT -p tcp --dport 8080 -j DROP

使用 REJECT

sudo iptables -A INPUT -p tcp --dport 8080 -j REJECT --reject-with tcp-reset

总结

选择DROP还是REJECT取决于你的具体需求和安全策略。如果希望提高系统隐蔽性,避免暴露给攻击者,使用DROP是更好的选择。如果需要提高用户体验和诊断效率,使用REJECT会更合适。

参考资料

通过以上的分析和建议,你可以根据具体需求选择合适的策略来封禁端口。