113 lines
3.3 KiB
JavaScript
113 lines
3.3 KiB
JavaScript
/**
|
|
* 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();
|