栗子站

栗子站

HFish蜜罐联动多方UFW实践

2025-04-20
HFish蜜罐联动多方UFW实践

一、背景

1.1 要实现的效果

当主机A检测到攻击行为时,主机B自动在防火墙封禁攻击者IP。去年写过一个简单的,后来想想太幼稚就没再用了。

1.2 初步想法

服务端A+客户端BCD,服务端A监测攻击,通知客户端BCD封禁,客户端这些主机收到通知后,调防火墙封禁。

正好我有一台HFish蜜罐收集了很多情报,用WinPcap实时抓包可以最大化获取互联网扫描IP。

拿到情报后,可以用API实现联动封堵:

支持的设备我全都没有,但我UFW用得多,所以自己写一个适配UFW的脚本是可行的,先让ChatGPT-4o起草个流程图:

其中云主机A是蜜罐,蜜罐被攻击后记录攻击来源,并将攻击者信息分发给云主机BCD,云主机BCD通过API收到A通知的攻击者信息后,自动调用UFW防火墙封禁攻击者IP。

二、功能设计

  1. 攻击信息获取——HFish蜜罐已实现;

  2. 攻击信息API接口——HFish蜜罐已实现;

  3. 接收并格式化攻击者信息的脚本——交给ChatGPT;

  4. 黑名单IP发布服务——交给ChatGPT;

  5. 黑名单IP接收服务+防火墙脚本——交给ChatGPT

难度不大,都交给AI处理就行,我只管测试验证评估。

三、功能开发

3.1 蜜罐信息获取+IP格式化脚本

给GPT看了下微步的API开发文档,GPT说很简单,直接给写好了。

#!/bin/bash

# 定义API URL和API密钥

API_URL="https://云主机IP:端口/api/v1/attack/ip?api_key=你的KEY"

API_HEADER="Content-Type: application/json"

API_DATA='{

  "start_time": 0,

  "end_time": 0,

  "intranet": 0,

  "threat_label": []

}'

# 定义IP列表输出文件

IP_LIST_FILE="iplist.txt"

TARGET_FILE="一个路径/iplist.txt"

# 打印日志函数

log_message() {

    echo "$(date '+%Y-%m-%d %H:%M:%S') - $1"

}

# 定义一个函数,检查IP是否是内网地址

is_private_ip() {

    local ip=$1

    # 检查IP是否属于内网地址段

    if [[ "$ip" =~ ^10\..* ]] || [[ "$ip" =~ ^172\.(1[6-9]|2[0-9]|3[01])\..* ]] || [[ "$ip" =~ ^192\.168\..* ]]; then

        return 0  # 是内网IP,返回true

    else

        return 1  # 不是内网IP,返回false

    fi

}

# 定时每分钟执行

while true; do

    log_message "开始执行API请求..."

    # 执行curl命令获取API响应,忽略证书错误

    response=$(curl -k --location --request POST "$API_URL" --header "$API_HEADER" --data "$API_DATA")

    if [ $? -eq 0 ]; then

        log_message "API请求成功"

    else

        log_message "API请求失败"

        continue

    fi

    # 从响应中提取IP地址数组

    ip_list=$(echo "$response" | jq -r '.data.attack_ip[]')

    # 检查是否有IP地址

    if [ -z "$ip_list" ]; then

        log_message "没有提取到任何IP地址"

    else

        log_message "开始处理提取到的IP地址..."

        # 将新的IP地址追加到iplist.txt中,并去重和过滤内网IP

        for ip in $ip_list; do

            # 检查是否是内网地址

            if is_private_ip "$ip"; then

                log_message "IP $ip 被认为是内网地址,跳过"

            else

                # 去重并追加IP到文件

                grep -qxF "$ip" "$IP_LIST_FILE" || echo "$ip" >> "$IP_LIST_FILE"

            fi

        done

        log_message "IP地址添加完毕,去重和过滤内网地址完成"

        # 复制并覆盖目标文件

        cp "$IP_LIST_FILE" "$TARGET_FILE"

        log_message "已将IP列表复制到 $TARGET_FILE"

    fi

    # 等待1分钟

    log_message "等待1分钟后继续执行..."

    sleep 60

done

然后我们得到了一个每分钟自动更新的攻击者IP列表iplist.txt

3.2 IP 黑名单发布服务

我跟GPT说把这个iplist.txt弄个好看点的页面放互联网上,GPT二话不说,写了个差不多的。

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>栗子云 - 实时IP黑名单</title>
    <style>
        body {
            font-family: 'Arial', sans-serif;
            background: #1a1a1a;
            color: #f0f0f0;
            margin: 0;
            padding: 0;
            display: flex;
            flex-direction: column;
            align-items: center;
            justify-content: center;
            height: 100vh;
            text-align: center;
        }

        h1 {
            color: #66ccff;
            font-size: 3em;
        }

        #ip-list {
            background: #333;
            border-radius: 8px;
            padding: 20px;
            width: 80%;
            max-height: 400px;
            overflow-y: auto;
            margin-top: 20px;
            box-shadow: 0 4px 6px rgba(0, 0, 0, 0.2);
            font-size: 0.9em; /* Smaller font size for the IP list */
        }

        .ip-item {
            font-size: 1em; /* Smaller font size for each IP item */
            margin: 5px 0;
            background: #444;
            border-radius: 5px;
            padding: 10px;
            transition: background 0.3s ease;
        }

        .ip-item:hover {
            background: #666;
        }

        #last-updated {
            margin-top: 20px;
            font-size: 1.1em;
        }

        #ip-count {
            margin-top: 10px;
            font-size: 1.2em;
            color: #66ccff;
        }

        .download-link {
            margin-top: 20px;
            padding: 10px 20px;
            background: #66ccff;
            color: white;
            font-size: 1.2em;
            text-decoration: none;
            border-radius: 5px;
            transition: background 0.3s ease;
        }

        .download-link:hover {
            background: #4a9cb3;
        }

        /* Style for the checkbox and the label */
        .refresh-checkbox-container {
            position: fixed;
            right: 20px;
            top: 20px;
            display: flex;
            align-items: center;
            background: #333;
            padding: 10px;
            border-radius: 5px;
            box-shadow: 0 4px 6px rgba(0, 0, 0, 0.2);
        }

        .refresh-checkbox-container label {
            color: #f0f0f0;
            font-size: 1.2em;
            margin-right: 10px;
        }

        .refresh-checkbox-container input {
            width: 20px;
            height: 20px;
        }
    </style>
</head>
<body>
    <h1>实时IP黑名单</h1>
    
    <!-- Download IP list button above the IP list -->
    <a href="https://blackip.nsoc.tech/iplist.txt" class="download-link" download>下载全量IP黑名单</a>

    <!-- Last updated time displayed second -->
    <div id="last-updated">上一次更新时间: 加载中...</div>
    
    <!-- IP count displayed third -->
    <div id="ip-count">IP数量: 稍等,计算中...</div>

    <!-- IP list displayed last -->
    <div id="ip-list">正在等待IP加载...</div>

    <!-- Refresh Checkbox Section -->
    <div class="refresh-checkbox-container">
        <label for="refresh-checkbox">每5分钟刷新:</label>
        <input type="checkbox" id="refresh-checkbox">
    </div>

    <script>
        let refreshInterval = 60000; // Default refresh interval: 1 minute
        let refreshTimer;

        // Fetch the content of the iplist.txt file and update the page
        async function loadIpList() {
            const ipListContainer = document.getElementById('ip-list');
            const ipCountContainer = document.getElementById('ip-count');
            const lastUpdatedContainer = document.getElementById('last-updated');

            try {
                const response = await fetch('https://blackip.nsoc.tech/iplist.txt');
                if (!response.ok) {
                    throw new Error('Failed to fetch iplist.txt');
                }

                const text = await response.text();
                const ipList = text.split('\n').filter(ip => ip.trim() !== '');

                // Clear the current content
                ipListContainer.innerHTML = '';
                ipCountContainer.textContent = `当你的IP扫描全网时,就有可能出现在下表中,当前IP数量: ${ipList.length}`; // Display IP count

                // Append each IP address as a div
                ipList.forEach(ip => {
                    const ipDiv = document.createElement('div');
                    ipDiv.classList.add('ip-item');
                    ipDiv.textContent = ip;
                    ipListContainer.appendChild(ipDiv);
                });

                // Get file last modified time and display it
                const lastModified = new Date(response.headers.get('last-modified'));
                lastUpdatedContainer.textContent = `上一次更新时间: ${lastModified.toLocaleString()}`;

            } catch (error) {
                ipListContainer.textContent = 'IP黑名单加载失败,请联系站长。';
                lastUpdatedContainer.textContent = '上一次更新时间: N/A';
                ipCountContainer.textContent = 'IP数量: N/A';
                console.error(error);
            }
        }

        // Start or change the refresh interval based on checkbox
        function setRefreshInterval() {
            const refreshCheckbox = document.getElementById('refresh-checkbox');
            if (refreshCheckbox.checked) {
                refreshInterval = 300000; // 5 minutes in milliseconds
            } else {
                refreshInterval = 60000; // 1 minute in milliseconds
            }

            // Clear the previous interval and restart with the new interval
            clearInterval(refreshTimer);
            refreshTimer = setInterval(loadIpList, refreshInterval);
        }

        // Load the IP list when the page loads
        window.onload = () => {
            loadIpList();
            // Set up the refresh based on the initial checkbox state
            setRefreshInterval();

            // Attach event listener to the checkbox
            document.getElementById('refresh-checkbox').addEventListener('change', setRefreshInterval);
        };
    </script>
</body>
</html>

于是我们得到了一个黑名单发布服务:

3.3 黑名单IP接收服务+防火墙脚本

有了发布服务,我们接收下IP然后导入防火墙封禁就可以了。

ChatGPT直接给写了个一行一行导入的脚本,一运行我傻了,IP太多,用UFW导入一次要半小时。

动了动脑子,想起之前的黑名单,用ipset可能更快一点,于是让ChatGPT把脚本改成UFW+IPSET的逻辑,性能大增!

改后代码:

#!/usr/bin/env bash
# update_ufw_blacklist.sh  ——  每次运行把黑名单一次性刷新到 ipset
# 建议由 systemd‑timer 定时触发(如 6 小时一次)
#
# 前提:
#   1. 已安装 ipset:   sudo apt install ipset
#   2. 防火墙有 1 条规则引用该 set,例如(iptables 后端示例):
#        -A ufw-before-input -m set --match-set blackip src -j DROP
#
# 作者:ChatGPT  2025‑04‑19

set -Eeuo pipefail

#####################################
# 可自定义区
URL="https://blackip.nsoc.tech/iplist.txt"   # 黑名单下载地址
SET_NAME="blackip"                           # ipset 名称
LOG_FILE="/var/log/ufw-blacklist.log"        # 额外日志文件
MAXELEM=2000000                              # ipset 最大元素数
#####################################

log() {                                      # 同时记录到 journal 与文件
  local msg="[$(date '+%F %T')] $*"
  echo "$msg"
  echo "$msg" >> "$LOG_FILE"
}

log "====== 任务开始 ======"

# 若 set 不存在则创建(幂等)
if ! ipset list -n | grep -qx "$SET_NAME"; then
  log "ipset $SET_NAME 不存在,正在创建"
  ipset create "$SET_NAME" hash:ip maxelem "$MAXELEM" -exist
fi

# 下载 & 预处理
TMP_DIR=$(mktemp -d)
RAW_FILE="$TMP_DIR/raw.txt"
CLEAN_FILE="$TMP_DIR/clean.txt"
RESTORE_FILE="$TMP_DIR/restore.txt"

log "下载黑名单:$URL"
if ! curl -k -sSL --connect-timeout 20 --max-time 60 "$URL" -o "$RAW_FILE"; then
  log "下载失败,退出"
  rm -rf "$TMP_DIR"
  exit 1
fi
log "下载成功:$(wc -c < "$RAW_FILE") 字节"

log "去重、过滤私网地址"
grep -Eo '([0-9]{1,3}\.){3}[0-9]{1,3}' "$RAW_FILE" \
  | awk '!a[$0]++' \
  | grep -Ev '^(10\.|172\.(1[6-9]|2[0-9]|3[01])\.|192\.168\.)' \
  > "$CLEAN_FILE"

IP_CNT=$(wc -l < "$CLEAN_FILE")
log "处理后公网 IP 数量:$IP_CNT"

# 生成 ipset‑restore 文件:先 flush,再批量 add
{
  echo "flush $SET_NAME"
  awk -v S="$SET_NAME" '{print "add "S" "$1}' "$CLEAN_FILE"
} > "$RESTORE_FILE"

log "刷新 ipset(原子操作),开始导入"
if ipset restore -! < "$RESTORE_FILE"; then
  log "导入完毕,共 $IP_CNT 条;ipset 刷新成功"
else
  log "ipset restore 失败"
  rm -rf "$TMP_DIR"
  exit 1
fi

rm -rf "$TMP_DIR"
log "====== 任务结束 ======"
exit 0

然后手动测试了下,写成定时服务运行。

最后把ipset加到UFW或者iptables里面封禁即可。

四、总结

实现了:四台云上设备的防护联动。都是AI做的,我啥也没干。

本来还想再包装成安装脚本,但是GPT有点躺,写出来的没法一次过,这次就先不弄了。

另外把实时黑名单的站点贡献出来:https://blackip.nsoc.tech/ 大家都可以用我的这个方法实现联动防护。