Rename to hkt.sh
This commit is contained in:
112
scripts/mac-guard.js
Normal file
112
scripts/mac-guard.js
Normal file
@@ -0,0 +1,112 @@
|
||||
/**
|
||||
* Mac 登录失败监控 & 自动封禁脚本
|
||||
* 检测 SSH/登录失败,同一 IP 失败 5 次自动封禁
|
||||
*/
|
||||
|
||||
const { spawn } = require('child_process');
|
||||
const https = require('https');
|
||||
const fs = require('fs');
|
||||
|
||||
// ========== 配置 ==========
|
||||
const CONFIG = {
|
||||
maxFailures: 5,
|
||||
banDurationMin: 60,
|
||||
tgBotToken: '8300905342:AAH3Q78FuR5Exrw2zWYJHFRyVeLlws3xnww',
|
||||
tgChatId: '165067365',
|
||||
dataFile: '/Users/jianzhang/.openclaw/workspace/scripts/ban-data.json',
|
||||
whitelistIPs: ['127.0.0.1', '192.168.1.1']
|
||||
};
|
||||
|
||||
let failedAttempts = {};
|
||||
let bannedIPs = {};
|
||||
|
||||
// ========== Telegram 通知 ==========
|
||||
function sendTG(message) {
|
||||
const data = JSON.stringify({ chat_id: CONFIG.tgChatId, text: message, parse_mode: 'HTML' });
|
||||
const req = https.request({
|
||||
hostname: 'api.telegram.org',
|
||||
path: `/bot${CONFIG.tgBotToken}/sendMessage`,
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' }
|
||||
});
|
||||
req.write(data);
|
||||
req.end();
|
||||
}
|
||||
|
||||
// ========== 数据持久化 ==========
|
||||
function saveData() {
|
||||
fs.writeFileSync(CONFIG.dataFile, JSON.stringify({ failedAttempts, bannedIPs }, null, 2));
|
||||
}
|
||||
|
||||
function loadData() {
|
||||
try {
|
||||
if (fs.existsSync(CONFIG.dataFile)) {
|
||||
const d = JSON.parse(fs.readFileSync(CONFIG.dataFile, 'utf8'));
|
||||
failedAttempts = d.failedAttempts || {};
|
||||
bannedIPs = d.bannedIPs || {};
|
||||
}
|
||||
} catch (e) {}
|
||||
}
|
||||
|
||||
// ========== 封禁/解封 IP ==========
|
||||
function banIP(ip) {
|
||||
if (CONFIG.whitelistIPs.includes(ip) || bannedIPs[ip]) return;
|
||||
console.log(`[BAN] ${ip}`);
|
||||
bannedIPs[ip] = Date.now();
|
||||
|
||||
spawn('sudo', ['pfctl', '-t', 'bruteforce', '-T', 'add', ip]).on('close', (code) => {
|
||||
if (code === 0) {
|
||||
sendTG(`🚫 <b>IP 已封禁</b>\n\nIP: <code>${ip}</code>\n原因: 登录失败 ${CONFIG.maxFailures} 次\n时长: ${CONFIG.banDurationMin} 分钟`);
|
||||
}
|
||||
});
|
||||
|
||||
setTimeout(() => unbanIP(ip), CONFIG.banDurationMin * 60 * 1000);
|
||||
saveData();
|
||||
}
|
||||
|
||||
function unbanIP(ip) {
|
||||
if (!bannedIPs[ip]) return;
|
||||
console.log(`[UNBAN] ${ip}`);
|
||||
delete bannedIPs[ip];
|
||||
delete failedAttempts[ip];
|
||||
spawn('sudo', ['pfctl', '-t', 'bruteforce', '-T', 'delete', ip]);
|
||||
saveData();
|
||||
}
|
||||
|
||||
// ========== 记录失败 ==========
|
||||
function recordFailure(ip) {
|
||||
if (CONFIG.whitelistIPs.includes(ip) || bannedIPs[ip]) return;
|
||||
if (!failedAttempts[ip]) failedAttempts[ip] = { count: 0, firstTime: Date.now() };
|
||||
failedAttempts[ip].count++;
|
||||
console.log(`[FAIL] ${ip} x${failedAttempts[ip].count}`);
|
||||
if (failedAttempts[ip].count >= CONFIG.maxFailures) banIP(ip);
|
||||
saveData();
|
||||
}
|
||||
|
||||
// ========== 解析日志 ==========
|
||||
function parseLine(line) {
|
||||
let m = line.match(/Failed password for .* from ([\d.]+)/);
|
||||
if (m) return m[1];
|
||||
m = line.match(/Invalid user .* from ([\d.]+)/);
|
||||
if (m) return m[1];
|
||||
m = line.match(/authentication failure.*rhost=([\d.]+)/);
|
||||
if (m) return m[1];
|
||||
return null;
|
||||
}
|
||||
|
||||
// ========== 启动监控 ==========
|
||||
function start() {
|
||||
console.log('[START] Mac Guard 启动');
|
||||
loadData();
|
||||
|
||||
const log = spawn('log', ['stream', '--predicate', 'process == "sshd"', '--style', 'syslog']);
|
||||
log.stdout.on('data', (d) => {
|
||||
d.toString().split('\n').forEach(line => {
|
||||
const ip = parseLine(line);
|
||||
if (ip) recordFailure(ip);
|
||||
});
|
||||
});
|
||||
log.on('close', () => { console.log('[RESTART]'); setTimeout(start, 5000); });
|
||||
}
|
||||
|
||||
start();
|
||||
Reference in New Issue
Block a user