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。
二、功能设计
攻击信息获取——HFish蜜罐已实现;
攻击信息API接口——HFish蜜罐已实现;
接收并格式化攻击者信息的脚本——交给ChatGPT;
黑名单IP发布服务——交给ChatGPT;
黑名单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/ 大家都可以用我的这个方法实现联动防护。
- 1
- 0
-
分享