/** * 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(`🚫 IP 已封禁\n\nIP: ${ip}\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();