iptables 结合敲门技术实现端口隐藏

敲门技术(Port Knocking)是一种通过特定顺序连接多个端口来动态打开防火墙端口的安全机制。以下是使用 iptables 实现端口隐藏的完整方案:

一、敲门技术原理

基本工作流程

客户端 → [发送秘密敲门序列] → 服务器
    1. 连接端口 1111 (失败)
    2. 连接端口 2222 (失败)
    3. 连接端口 3333 (失败)
    4. 服务器检测到正确序列,动态打开 SSH 端口
    5. 客户端连接 SSH 端口 22 (成功)

架构图

┌─────────┐   敲门序列    ┌─────────┐
│  客户端  │ ──────────> │  服务器  │
│         │  1111→2222→3333 │         │
└─────────┘               └─────────┘
        ↓                        ↓
    连接端口22             临时开放端口22

二、基于 iptables 的敲门实现

方法1:使用 iptables + 日志监控

1. 初始防火墙设置
#!/bin/bash
# knock-init.sh - 初始化防火墙规则

# 清除现有规则
iptables -F
iptables -t nat -F
iptables -t mangle -F

# 设置默认策略
iptables -P INPUT DROP
iptables -P FORWARD DROP
iptables -P OUTPUT ACCEPT

# 允许本地回环
iptables -A INPUT -i lo -j ACCEPT

# 允许已建立的连接
iptables -A INPUT -m state --state ESTABLISHED,RELATED -j ACCEPT

# 允许ICMP(ping)
iptables -A INPUT -p icmp -j ACCEPT

# 记录所有连接尝试(用于敲门检测)
iptables -A INPUT -p tcp --dport 1111 -j LOG --log-prefix "KNOCK1: "
iptables -A INPUT -p tcp --dport 2222 -j LOG --log-prefix "KNOCK2: "
iptables -A INPUT -p tcp --dport 3333 -j LOG --log-prefix "KNOCK3: "

# 默认拒绝并记录
iptables -A INPUT -p tcp -j LOG --log-prefix "DENIED: "
iptables -A INPUT -p udp -j LOG --log-prefix "DENIED: "

# 保存规则
iptables-save > /etc/iptables/knock.rules
2. 敲门检测脚本
#!/bin/bash
# knock-watch.sh - 监控敲门序列

LOG_FILE="/var/log/knock.log"
KNOCK_SEQUENCE="1111:2222:3333"
KNOCK_TIMEOUT=30  # 秒
SSH_PORT=22
ALLOW_IP=""
ALLOW_TIMEOUT=300  # 允许访问的时间(秒)

# 监控系统日志
tail -F /var/log/syslog | while read line
do
    # 检测第一个敲门
    if echo "$line" | grep -q "KNOCK1: "; then
        IP=$(echo "$line" | grep -o "SRC=[0-9.]*" | cut -d= -f2)
        TIME=$(date +%s)
        echo "$TIME:KNOCK1:$IP" >> $LOG_FILE
        
        # 启动敲门序列检测
        check_knock_sequence "$IP" &
    fi
done &

# 敲门序列检测函数
check_knock_sequence() {
    local ip=$1
    local start_time=$(date +%s)
    local sequence=""
    
    while [ $(($(date +%s) - start_time)) -lt $KNOCK_TIMEOUT ]; do
        # 检查日志中的敲门事件
        if tail -n 100 $LOG_FILE | grep "KNOCK1:$ip" > /dev/null; then
            sequence="1111"
            
            # 等待第二个敲门
            sleep 1
            if tail -n 100 $LOG_FILE | grep "KNOCK2:$ip" > /dev/null; then
                sequence="$sequence:2222"
                
                # 等待第三个敲门
                sleep 1
                if tail -n 100 $LOG_FILE | grep "KNOCK3:$ip" > /dev/null; then
                    sequence="$sequence:3333"
                    
                    # 验证敲门序列
                    if [ "$sequence" = "$KNOCK_SEQUENCE" ]; then
                        echo "$(date): Valid knock sequence from $ip" >> $LOG_FILE
                        open_port_for_ip "$ip"
                        break
                    fi
                fi
            fi
        fi
        sleep 1
    done
}

# 为特定IP开放端口
open_port_for_ip() {
    local ip=$1
    
    # 添加临时规则允许SSH访问
    iptables -I INPUT 1 -p tcp -s $ip --dport $SSH_PORT -j ACCEPT
    echo "$(date): Opened SSH port for $ip" >> $LOG_FILE
    
    # 设置定时删除规则
    (
        sleep $ALLOW_TIMEOUT
        iptables -D INPUT -p tcp -s $ip --dport $SSH_PORT -j ACCEPT
        echo "$(date): Closed SSH port for $ip" >> $LOG_FILE
    ) &
    
    # 记录允许的IP
    ALLOW_IP=$ip
}

方法2:使用 iptables + recent 模块(推荐)

1. 更高效的实现方案
#!/bin/bash
# knock-iptables.sh - 使用 iptables recent 模块

# 敲门端口序列
PORT1=1111
PORT2=2222
PORT3=3333
SSH_PORT=22
TIMEOUT=10  # 敲门序列必须在10秒内完成
ACCESS_TIMEOUT=300  # SSH访问超时时间

# 清除现有规则
iptables -F
iptables -X

# 创建自定义链
iptables -N KNOCKING
iptables -N KNOCK1_CHECK
iptables -N KNOCK2_CHECK
iptables -N KNOCK3_CHECK
iptables -N PASSED

# 设置默认策略
iptables -P INPUT DROP
iptables -P FORWARD DROP
iptables -P OUTPUT ACCEPT

# 允许本地回环和已建立连接
iptables -A INPUT -i lo -j ACCEPT
iptables -A INPUT -m state --state ESTABLISHED,RELATED -j ACCEPT
iptables -A INPUT -p icmp -j ACCEPT

# 将敲门端口的流量重定向到敲门链
iptables -A INPUT -p tcp --dport $PORT1 -j KNOCK1_CHECK
iptables -A INPUT -p tcp --dport $PORT2 -j KNOCK2_CHECK
iptables -A INPUT -p tcp --dport $PORT3 -j KNOCK3_CHECK

# === 敲门检查链 ===

# 第一步:检查端口1111
iptables -A KNOCK1_CHECK -m recent --name KNOCK1 --set -j DROP

# 第二步:检查端口2222(必须在KNOCK1列表中存在)
iptables -A KNOCK2_CHECK -m recent --name KNOCK1 --remove -j KNOCK2_CHECK_2
iptables -A KNOCK2_CHECK_2 -m recent --name KNOCK2 --set -j DROP

# 第三步:检查端口3333(必须在KNOCK2列表中存在)
iptables -A KNOCK3_CHECK -m recent --name KNOCK2 --remove -j KNOCK3_CHECK_2
iptables -A KNOCK3_CHECK_2 -m recent --name KNOCK3 --set -j DROP

# 第四步:敲门成功后处理
iptables -A KNOCK3_CHECK_2 -m recent --name KNOCK3 --remove -j PASSED

# === 通过验证后的处理 ===
iptables -A PASSED -m recent --name ALLOWED --set -j LOG --log-prefix "KNOCK_SUCCESS: "
iptables -A PASSED -j DROP

# === 允许通过验证的IP访问SSH ===
iptables -A INPUT -p tcp --dport $SSH_PORT -m recent --name ALLOWED --rcheck --seconds $ACCESS_TIMEOUT -j ACCEPT

# === 敲门超时处理(使用定时任务清理)===
# 添加定时清理任务
echo "*/1 * * * * root /usr/local/sbin/knock-cleanup" > /etc/cron.d/knock-cleanup
2. 清理脚本
#!/bin/bash
# knock-cleanup.sh - 清理过期的敲门记录

# 清除超过10秒的敲门记录
iptables-save | grep "recent.*KNOCK" | while read line; do
    # 提取IP地址
    ip=$(echo $line | grep -o '[0-9]\+\.[0-9]\+\.[0-9]\+\.[0-9]\+')
    if [ ! -z "$ip" ]; then
        # 从敲门列表中删除
        echo $ip > /proc/net/xt_recent/KNOCK1
        echo $ip > /proc/net/xt_recent/KNOCK2
        echo $ip > /proc/net/xt_recent/KNOCK3
    fi
done

# 清除超过5分钟的允许记录
iptables-save | grep "ALLOWED" | while read line; do
    # 这里可以添加更复杂的清理逻辑
    echo "Cleaning old knock entries"
done

方法3:完整的敲门服务

1. Python 敲门服务器
#!/usr/bin/env python3
# knock-server.py - 完整的敲门服务器

import sys
import socket
import time
import subprocess
import threading
from collections import defaultdict

# 配置
KNOCK_SEQUENCE = [1111, 2222, 3333]  # 敲门序列
SSH_PORT = 22
TIMEOUT = 10  # 整个序列必须在10秒内完成
ACCESS_DURATION = 300  # 允许访问的时间(秒)

# 记录每个IP的敲门状态
knock_status = defaultdict(list)

def run_iptables_command(cmd):
    """执行 iptables 命令"""
    subprocess.run(['iptables'] + cmd.split(), check=False)

def open_port_for_ip(ip):
    """为指定IP开放SSH端口"""
    # 检查是否已存在规则
    check_cmd = f"-C INPUT -p tcp -s {ip} --dport {SSH_PORT} -j ACCEPT 2>/dev/null"
    result = subprocess.run(f'iptables {check_cmd}', shell=True)
    
    if result.returncode != 0:
        # 添加规则
        run_iptables_command(f"-I INPUT -p tcp -s {ip} --dport {SSH_PORT} -j ACCEPT")
        print(f"[+] Opened SSH port for {ip}")
        
        # 设置定时关闭
        threading.Timer(ACCESS_DURATION, close_port_for_ip, args=[ip]).start()

def close_port_for_ip(ip):
    """关闭指定IP的SSH端口"""
    run_iptables_command(f"-D INPUT -p tcp -s {ip} --dport {SSH_PORT} -j ACCEPT")
    print(f"[-] Closed SSH port for {ip}")

def handle_knock(data, addr):
    """处理敲门请求"""
    ip = addr[0]
    current_time = time.time()
    
    try:
        port = int(data.decode().strip())
    except:
        return
    
    # 清理过期记录
    knock_status[ip] = [t for t in knock_status[ip] if current_time - t[1] < TIMEOUT]
    
    # 检查敲门序列
    expected_port = KNOCK_SEQUENCE[len(knock_status[ip])]
    
    if port == expected_port:
        knock_status[ip].append((port, current_time))
        print(f"[*] {ip} knocked port {port}")
        
        # 检查是否完成完整序列
        if len(knock_status[ip]) == len(KNOCK_SEQUENCE):
            # 验证序列是否正确
            received_ports = [p[0] for p in knock_status[ip]]
            if received_ports == KNOCK_SEQUENCE:
                print(f"[+] Valid knock sequence from {ip}")
                open_port_for_ip(ip)
            # 清理该IP的记录
            del knock_status[ip]
    else:
        # 错误的敲门,重置该IP的状态
        if ip in knock_status:
            del knock_status[ip]
        print(f"[-] Invalid knock from {ip}")

def main():
    # 初始化防火墙
    print("[*] Initializing firewall...")
    subprocess.run(['iptables', '-F', 'INPUT'])
    subprocess.run(['iptables', '-P', 'INPUT', 'DROP'])
    subprocess.run(['iptables', '-A', 'INPUT', '-i', 'lo', '-j', 'ACCEPT'])
    subprocess.run(['iptables', '-A', 'INPUT', '-m', 'state', '--state', 'ESTABLISHED,RELATED', '-j', 'ACCEPT'])
    
    # 创建UDP服务器监听敲门
    sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
    sock.bind(('0.0.0.0', 0))  # 随机端口
    
    print(f"[*] Knock server listening on port {sock.getsockname()[1]}")
    print(f"[*] Knock sequence: {KNOCK_SEQUENCE}")
    
    while True:
        try:
            data, addr = sock.recvfrom(1024)
            threading.Thread(target=handle_knock, args=(data, addr)).start()
        except KeyboardInterrupt:
            print("\n[*] Shutting down...")
            break
        except Exception as e:
            print(f"[!] Error: {e}")

if __name__ == "__main__":
    main()
2. 客户端敲门脚本
#!/bin/bash
# knock-client.sh - 客户端敲门脚本

SERVER_IP="your_server_ip"
KNOCK_PORTS="1111 2222 3333"
DELAY=1  # 敲门间隔(秒)

echo "Knocking on $SERVER_IP..."

for port in $KNOCK_PORTS; do
    echo "  Knocking port $port"
    # 使用nc连接(TCP敲门)
    nc -z -w 1 $SERVER_IP $port > /dev/null 2>&1
    
    # 或者使用nmap(更隐蔽)
    # nmap -Pn --host-timeout 201 --max-retries 0 -p $port $SERVER_IP > /dev/null 2>&1
    
    sleep $DELAY
done

echo "Knock sequence completed. You can now connect to SSH."
echo "ssh user@$SERVER_IP"

三、高级敲门技术

1. 加密敲门

# 使用加密的敲门序列
# 服务器端
KNOCK_KEY="secret_key"

# 客户端使用哈希生成动态端口
#!/bin/bash
# encrypted-knock-client.sh

SERVER_IP="192.168.1.100"
SECRET="my_secret_key"
TIMESTAMP=$(date +%s)
SEQUENCE=$(echo -n "${SECRET}${TIMESTAMP}" | md5sum | cut -c1-12)

# 将哈希值转换为端口序列
PORT1=$((0x${SEQUENCE:0:3} % 1000 + 2000))
PORT2=$((0x${SEQUENCE:3:3} % 1000 + 3000))
PORT3=$((0x${SEQUENCE:6:3} % 1000 + 4000))

# 执行敲门
for port in $PORT1 $PORT2 $PORT3; do
    nc -z -w 1 $SERVER_IP $port
    sleep 0.5
done

2. 单包授权(SPA)

# 使用单个加密包授权
# 服务器端使用 iptables 结合字符串匹配
iptables -A INPUT -p udp --dport 9999 -m string --string "SECRET_TOKEN" --algo bm -j ACCEPT

3. 使用 fwknop 工具

# 安装 fwknop
# Ubuntu/Debian
apt-get install fwknop-client fwknop-server

# CentOS/RHEL
yum install fwknop

# 服务器配置 /etc/fwknop/fwknopd.conf
PCAP_FILTER            "udp port 62201";
ENABLE_SPA_PACKET_AGING    Y;
REQUIRE_SOURCE_ADDRESS    Y;

# 客户端敲门
fwknop -A tcp/22 -a 192.168.1.100 -D server_ip

四、安全增强措施

1. 防暴力破解

# 使用 iptables limit 模块限制敲门尝试
iptables -A INPUT -p tcp -m multiport --dports 1111,2222,3333 -m recent --set --name KNOCK_ATTEMPT
iptables -A INPUT -p tcp -m multiport --dports 1111,2222,3333 -m recent --update --seconds 60 --hitcount 5 --name KNOCK_ATTEMPT -j DROP

2. IP黑名单

# 创建黑名单链
iptables -N BLACKLIST
iptables -A BLACKLIST -j LOG --log-prefix "BLACKLIST: "
iptables -A BLACKLIST -j DROP

# 可疑IP加入黑名单
iptables -I INPUT -s 192.168.1.100 -j BLACKLIST

3. 日志监控

# 详细的敲门日志
iptables -A INPUT -p tcp --dport 1111 -j LOG --log-prefix "KNOCK1: " --log-level 6
iptables -A INPUT -p tcp --dport 2222 -j LOG --log-prefix "KNOCK2: " --log-level 6
iptables -A INPUT -p tcp --dport 3333 -j LOG --log-prefix "KNOCK3: " --log-level 6

# 监控日志
tail -f /var/log/syslog | grep KNOCK

五、部署和测试

1. 部署步骤

# 1. 安装依赖
apt-get install iptables-persistent netcat

# 2. 配置 iptables 规则
chmod +x knock-init.sh
./knock-init.sh

# 3. 启动敲门监控
nohup ./knock-watch.sh > /var/log/knock.log 2>&1 &

# 4. 保存配置
iptables-save > /etc/iptables/rules.v4

# 5. 设置开机启动
echo "./knock-watch.sh" >> /etc/rc.local

2. 测试敲门

# 从客户端测试
./knock-client.sh

# 检查服务器日志
tail -f /var/log/knock.log

# 验证 iptables 规则
iptables -L INPUT -n --line-numbers

3. 故障排除

# 查看敲门状态
cat /proc/net/xt_recent/*

# 检查日志
dmesg | grep -i knock
journalctl -u knock-server

# 测试端口连通性
nc -zv server_ip 22

六、注意事项

  1. 备用访问方式:配置敲门时,务必保留一个备用访问方式(如控制台、第二个网络接口)
  2. 日志监控:定期检查敲门日志,检测异常行为
  3. 序列复杂性:使用复杂、随机、定期更换的敲门序列
  4. 超时设置:合理设置超时时间,避免过短或过长
  5. 端口选择:避免使用常见端口作为敲门端口
  6. 加密通信:考虑使用加密的敲门工具如 fwknop
  7. 多因素认证:结合其他认证方式增强安全性

敲门技术可以有效隐藏服务端口,显著增加攻击者发现和攻击服务的难度。但需要注意,这不能替代其他安全措施,应作为深度防御策略的一部分使用。

Logo

北京人形旗下天工造物具身智能开源社区,聚焦具身天工与慧思开物两大平台

更多推荐