超进化 v3.3: 6个新安全模块 + 31个模块独立开关 + 完整文档与测试
新增6个Minecraft安全模块: - GeoBlockService: 地理IP封锁(国家黑白名单/高风险MC攻击来源拦截) - PlayerSessionValidationService: 玩家会话验证(UUID/正版/离线检测/会话重放) - PluginVerificationService: 插件完整性校验(SHA-256/恶意插件名/25个已知恶意插件) - ConnectionThrottleService: 连接速率限制(多窗口追踪/递增延迟2s-30s) - BackupIntegrityService: 备份完整性验证(SHA-256/文件存在性/6h自动扫描) - AntiSkinSpoofService: 皮肤伪造检测(25个冒充目标/可疑URL/变更频率) 配置独立开关(31个模块全部可单独控制): - ServerGuardConfig新增SuperEvolutionConfig包含全部31个模块的enabled开关 - application.yml新增完整super-evolution配置段(含中文说明) - 每个Service注入ServerGuardConfig并在入口检查开关,关闭时跳过检测 DashboardController新增6个API端点 + 全部25个服务重构注入config 测试: V33SecurityTest 29项测试(含23项v3.3 + 6项更正),全部114测试通过 文档: README/README_EN 30+→85+模块,USER_MANUAL新增完整开关配置文档
This commit is contained in:
@@ -77,7 +77,7 @@
|
||||
</td>
|
||||
<td align="center" width="140">
|
||||
<img src="https://raw.githubusercontent.com/Tarikul-Islam-Anik/Animated-Fluent-Emojis/master/Emojis/Smilies/Alien%20Monster.png" width="32" height="32" /><br>
|
||||
<strong>30+</strong><br>
|
||||
<strong>85+</strong><br>
|
||||
<sub>安全模块</sub>
|
||||
</td>
|
||||
<td align="center" width="140">
|
||||
@@ -148,7 +148,7 @@
|
||||
</td>
|
||||
<td width="50%">
|
||||
<h3>🛡️ 全栈纵深防御</h3>
|
||||
<p>从 MC 协议层 → 应用 WAF → 系统防火墙 → Cloudflare 边缘节点。<b>30+ 安全模块</b>覆盖 L3-L7,零信任架构、蜜罐诱捕一应俱全。</p>
|
||||
<p>从 MC 协议层 → 应用 WAF → 系统防火墙 → Cloudflare 边缘节点。<b>85+ 安全模块</b>覆盖 L3-L7,零信任架构、蜜罐诱捕一应俱全。</p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
@@ -261,7 +261,7 @@ cell.pressure = cell.pressure × 0.55 + pulse.heat × 0.75
|
||||
│ │ Process │ │ DDoS/IDS │ │ DeepSeek │ │
|
||||
│ │ Resource │ │ WAF/Honey│ │ Anomaly │ │
|
||||
│ │ Connect │ │ ZeroTrust│ │ Predict │ │
|
||||
│ │ Log │ │ 30+ svc │ │ Adaptive │ │
|
||||
│ │ Log │ │ 85+ svc │ │ Adaptive │ │
|
||||
│ └────┬─────┘ └────┬─────┘ └────┬─────┘ │
|
||||
│ │ │ │ │
|
||||
│ └─────────┬───┴─────────────┘ │
|
||||
@@ -810,7 +810,7 @@ serverguard
|
||||
|
||||
### 安全防护层 (`com.aluer.security`)
|
||||
|
||||
这是系统中规模最大的包,包含 65+ 个安全服务(含 25 个超进化 v3.2 新增模块):
|
||||
这是系统中规模最大的包,包含 85+ 个安全服务(含 31 个超进化 v3.1 / v3.2 / v3.3 新增模块):
|
||||
|
||||
| 类别 | 类 | 功能 |
|
||||
|------|-----|------|
|
||||
@@ -885,6 +885,12 @@ serverguard
|
||||
| | `AntiDupeDetectionService` | 物品复制检测:堆叠溢出、高价值物品暴涨、9种已知复制方法监测 |
|
||||
| | `CrashExploitProtectionService` | 崩溃漏洞防护:超大包/负长度/NBT嵌套/书与笔/Sign炸弹/Tile Entity洪水 |
|
||||
| | `LagMachineDetectionService` | 卡服机检测:Observer链/TNT堆叠/红石密度/实体刷怪/掉落方块洪流 |
|
||||
| **v3.3 扩展** | `GeoBlockService` | 地理IP封锁:国家/地区黑白名单,高风险Minecraft攻击来源国拦截 |
|
||||
| | `PlayerSessionValidationService` | 玩家会话验证:UUID格式/离线模式/正版检测/会话重放/多账号识别 |
|
||||
| | `PluginVerificationService` | 插件完整性校验:SHA-256哈希对比、恶意插件名检测、JAR结构检查 |
|
||||
| | `ConnectionThrottleService` | 连接速率限制:多时间窗口IP追踪、递增延迟(2s/5s/15s/30s)、并发上限 |
|
||||
| | `BackupIntegrityService` | 备份完整性验证:SHA-256校验和、文件存在性/大小对比、6小时自动扫描 |
|
||||
| | `AntiSkinSpoofService` | 皮肤伪造检测:知名主播皮肤冒充、可疑URL、皮肤变更频率、模型类型异常 |
|
||||
|
||||
### 运营控制层 (`com.aluer.console`)
|
||||
|
||||
|
||||
+10
-4
@@ -83,7 +83,7 @@
|
||||
</td>
|
||||
<td align="center" width="140">
|
||||
<img src="https://raw.githubusercontent.com/Tarikul-Islam-Anik/Animated-Fluent-Emojis/master/Emojis/Smilies/Alien%20Monster.png" width="32" height="32" /><br>
|
||||
<strong>30+</strong><br>
|
||||
<strong>85+</strong><br>
|
||||
<sub>Security Modules</sub>
|
||||
</td>
|
||||
<td align="center" width="140">
|
||||
@@ -154,7 +154,7 @@
|
||||
</td>
|
||||
<td width="50%">
|
||||
<h3>🛡️ Full-Stack Defense</h3>
|
||||
<p>From Minecraft protocol layer → Application WAF → OS firewall → Cloudflare edge. <b>30+ security modules</b> covering L3-L7, zero-trust architecture, and honeypot deception.</p>
|
||||
<p>From Minecraft protocol layer → Application WAF → OS firewall → Cloudflare edge. <b>85+ security modules</b> covering L3-L7, zero-trust architecture, and honeypot deception.</p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
@@ -269,7 +269,7 @@ cell.pressure = cell.pressure × 0.55 + pulse.heat × 0.75
|
||||
│ │ Process │ │ DDoS/IDS │ │ DeepSeek │ │
|
||||
│ │ Resource │ │ WAF/Honey│ │ Anomaly │ │
|
||||
│ │ Connect │ │ ZeroTrust│ │ Predict │ │
|
||||
│ │ Log │ │ 30+ svc │ │ Adaptive │ │
|
||||
│ │ Log │ │ 85+ svc │ │ Adaptive │ │
|
||||
│ └────┬─────┘ └────┬─────┘ └────┬─────┘ │
|
||||
│ │ │ │ │
|
||||
│ └─────────┬───┴─────────────┘ │
|
||||
@@ -667,7 +667,7 @@ serverguard
|
||||
|
||||
### Security Defense Layer (`com.aluer.security`)
|
||||
|
||||
The largest package in the system, containing 65+ security services (with 25 Super Evolution v3.2 modules):
|
||||
The largest package in the system, containing 85+ security services (with 31 Super Evolution v3.1 / v3.2 / v3.3 modules):
|
||||
|
||||
| Category | Key Classes | Description |
|
||||
|----------|------------|-------------|
|
||||
@@ -707,6 +707,12 @@ The largest package in the system, containing 65+ security services (with 25 Sup
|
||||
| | `AntiDupeDetectionService` | Item duplication detection: stack overflow, high-value item surges, 9 known methods |
|
||||
| | `CrashExploitProtectionService` | Crash exploit protection: oversized packets, NBT bombs, book/sign exploits |
|
||||
| | `LagMachineDetectionService` | Lag machine detection: observer chains, TNT stacks, entity spam, falling blocks |
|
||||
| **v3.3 New Modules** | `GeoBlockService` | Geo IP blocking: country allowlist/blocklist, high-risk Minecraft attack source interception |
|
||||
| | `PlayerSessionValidationService` | Player session validation: UUID format, offline/premium detection, session replay, multi-account |
|
||||
| | `PluginVerificationService` | Plugin integrity verification: SHA-256 hash comparison, malicious plugin name detection |
|
||||
| | `ConnectionThrottleService` | Connection rate limiting: multi-window IP tracking, progressive delay (2s/5s/15s/30s) |
|
||||
| | `BackupIntegrityService` | Backup integrity verification: SHA-256 checksum, file existence/size comparison, 6h auto-scan |
|
||||
| | `AntiSkinSpoofService` | Skin spoofing detection: impersonation detection, suspicious URL checks, skin change frequency |
|
||||
|
||||
### Operations Control Layer (`com.aluer.console`)
|
||||
|
||||
|
||||
@@ -452,6 +452,17 @@ export ALUER_WEBHOOK_SLACK="https://hooks.slack.com/services/xxx/yyy/zzz"
|
||||
| `/api/security/crash-exploit/status` | GET | 崩溃漏洞防护状态(超大包、NBT炸弹、书与笔攻击) |
|
||||
| `/api/security/lag-machine/status` | GET | 卡服机检测状态(Observer链、TNT堆、红石密度) |
|
||||
|
||||
#### v3.3 新增安全 API
|
||||
|
||||
| 接口路径 | 方法 | 说明 |
|
||||
|----------|------|------|
|
||||
| `/api/security/geo-block/status` | GET | 地理IP封锁状态(拦截计数、黑白名单大小) |
|
||||
| `/api/security/session-validation/status` | GET | 玩家会话验证状态(UUID检测、重放攻击计数) |
|
||||
| `/api/security/plugin-verification/status` | GET | 插件完整性校验状态(已验证、失败、恶意检测) |
|
||||
| `/api/security/connection-throttle/status` | GET | 连接速率限制状态(活跃IP、已拦截、已延迟) |
|
||||
| `/api/security/backup-integrity/status` | GET | 备份完整性验证状态(通过/失败/损坏计数) |
|
||||
| `/api/security/anti-skin-spoof/status` | GET | 皮肤伪造检测状态(冒充者、可疑URL、变更频率) |
|
||||
|
||||
---
|
||||
|
||||
## 配置说明
|
||||
@@ -517,6 +528,56 @@ backup:
|
||||
backup-config: true
|
||||
```
|
||||
|
||||
#### 超进化安全模块开关
|
||||
|
||||
所有超进化安全模块均可通过 `application.yml` 中的 `serverguard.security.super-evolution` 节点独立控制开关。设为 `false` 即可关闭该模块,不消耗任何性能。
|
||||
|
||||
```yaml
|
||||
serverguard:
|
||||
security:
|
||||
super-evolution:
|
||||
# === v3.1 超进化安全模块(20个)===
|
||||
jwt-auth: true # JWT身份认证与令牌管理
|
||||
brute-force: true # 暴力破解防护(多时间窗口检测)
|
||||
anti-bot: true # 反机器人检测(Bot名称/加入速率/IP关联)
|
||||
reverse-shell: true # 反向Shell检测(50+ shell模式匹配)
|
||||
arp-spoof: true # ARP欺骗检测(MAC变更/网关伪造)
|
||||
dns-tunnel: true # DNS隧道检测(熵值/Base32编码/可疑TLD)
|
||||
exploit-signature: true # 漏洞签名检测(Log4Shell/SQLi/RCE等15种)
|
||||
ssrf: true # SSRF防护(内网IP/云元数据/协议限制)
|
||||
xxe: true # XXE防护(实体注入/Billion Laughs检测)
|
||||
csp: true # CSP安全头强制执行(8种响应头)
|
||||
database-firewall: true # 数据库防火墙(SQL注入/联合查询/时间盲注)
|
||||
dlp: true # 数据防泄漏(12种敏感信息规则+自动脱敏)
|
||||
memory-protection: true # JVM内存保护(堆/GC/内存泄漏检测)
|
||||
process-injection: true # 进程注入检测(/proc扫描/线程异常)
|
||||
secure-delete: true # 安全文件删除(多道覆写DoD标准)
|
||||
forensics: true # 取证收集(进程/网络/日志快照)
|
||||
incident-response: true # 事件响应(5种预定义响应剧本)
|
||||
threat-hunting: true # 威胁狩猎(10种狩猎定义/5个类别)
|
||||
compliance: true # 合规扫描(7类20+检查项)
|
||||
anti-grief: true # 反破坏检测(方块破坏率/TNT/纵火/偷箱)
|
||||
# === v3.2 Minecraft专属模块(5个)===
|
||||
anti-xray: true # X-ray透视检测(钻石比/直线挖掘/暗处精准)
|
||||
anti-fly: true # 飞行外挂检测(垂直/水平速度/悬空时间)
|
||||
anti-dupe: true # 物品复制检测(堆叠异常/高价值暴涨/9种复制法)
|
||||
crash-exploit: true # 崩溃漏洞防护(超大包/NBT炸弹/书与笔攻击)
|
||||
lag-machine: true # 卡服机检测(Observer链/TNT堆/红石密度)
|
||||
# === v3.3 新模块(6个)===
|
||||
geo-block: true # 地理IP封锁(按国家/地区拦截)
|
||||
session-validation: true # 玩家会话验证(UUID伪造/正版/离线检测)
|
||||
plugin-verification: true # 插件完整性校验(Hash对比/未授权修改)
|
||||
connection-throttle: true # 连接速率限制(IP/时间窗口/递增延迟)
|
||||
backup-integrity: true # 备份完整性校验(SHA-256/文件计数/大小对比)
|
||||
anti-skin-spoof: true # 皮肤伪造检测(模型数据异常/皮肤URL检测)
|
||||
```
|
||||
|
||||
**开关说明:**
|
||||
- 所有模块默认 `true`(开启),可根据需要设为 `false` 关闭
|
||||
- 关闭模块后,对应的 API 端点仍可访问但返回空数据
|
||||
- 关闭不需要的模块可以降低 CPU/内存开销
|
||||
- 修改配置后需重启服务生效
|
||||
|
||||
---
|
||||
|
||||
## 预警系统
|
||||
|
||||
@@ -372,6 +372,7 @@ public class ServerGuardConfig {
|
||||
private KernelConfig kernel = new KernelConfig();
|
||||
private TaskBusConfig taskBus = new TaskBusConfig();
|
||||
private SelfHealingConfig selfHealing = new SelfHealingConfig();
|
||||
private SuperEvolutionConfig superEvolution = new SuperEvolutionConfig();
|
||||
|
||||
public boolean isEnabled() { return enabled; }
|
||||
public void setEnabled(boolean enabled) { this.enabled = enabled; }
|
||||
@@ -411,6 +412,8 @@ public class ServerGuardConfig {
|
||||
public void setTaskBus(TaskBusConfig taskBus) { this.taskBus = taskBus; }
|
||||
public SelfHealingConfig getSelfHealing() { return selfHealing; }
|
||||
public void setSelfHealing(SelfHealingConfig selfHealing) { this.selfHealing = selfHealing; }
|
||||
public SuperEvolutionConfig getSuperEvolution() { return superEvolution; }
|
||||
public void setSuperEvolution(SuperEvolutionConfig superEvolution) { this.superEvolution = superEvolution; }
|
||||
}
|
||||
|
||||
public static class MinecraftDefenseConfig {
|
||||
@@ -865,6 +868,103 @@ public class ServerGuardConfig {
|
||||
public void setHandshakeTtlSeconds(int handshakeTtlSeconds) { this.handshakeTtlSeconds = handshakeTtlSeconds; }
|
||||
}
|
||||
|
||||
public static class SuperEvolutionConfig {
|
||||
private boolean jwtAuth = true;
|
||||
private boolean bruteForce = true;
|
||||
private boolean antiBot = true;
|
||||
private boolean reverseShell = true;
|
||||
private boolean arpSpoof = true;
|
||||
private boolean dnsTunnel = true;
|
||||
private boolean exploitSignature = true;
|
||||
private boolean ssrf = true;
|
||||
private boolean xxe = true;
|
||||
private boolean csp = true;
|
||||
private boolean databaseFirewall = true;
|
||||
private boolean dlp = true;
|
||||
private boolean memoryProtection = true;
|
||||
private boolean processInjection = true;
|
||||
private boolean secureDelete = true;
|
||||
private boolean forensics = true;
|
||||
private boolean incidentResponse = true;
|
||||
private boolean threatHunting = true;
|
||||
private boolean compliance = true;
|
||||
private boolean antiGrief = true;
|
||||
private boolean antiXray = true;
|
||||
private boolean antiFly = true;
|
||||
private boolean antiDupe = true;
|
||||
private boolean crashExploit = true;
|
||||
private boolean lagMachine = true;
|
||||
private boolean geoBlock = true;
|
||||
private boolean sessionValidation = true;
|
||||
private boolean pluginVerification = true;
|
||||
private boolean connectionThrottle = true;
|
||||
private boolean backupIntegrity = true;
|
||||
private boolean antiSkinSpoof = true;
|
||||
|
||||
public boolean isJwtAuth() { return jwtAuth; }
|
||||
public void setJwtAuth(boolean jwtAuth) { this.jwtAuth = jwtAuth; }
|
||||
public boolean isBruteForce() { return bruteForce; }
|
||||
public void setBruteForce(boolean bruteForce) { this.bruteForce = bruteForce; }
|
||||
public boolean isAntiBot() { return antiBot; }
|
||||
public void setAntiBot(boolean antiBot) { this.antiBot = antiBot; }
|
||||
public boolean isReverseShell() { return reverseShell; }
|
||||
public void setReverseShell(boolean reverseShell) { this.reverseShell = reverseShell; }
|
||||
public boolean isArpSpoof() { return arpSpoof; }
|
||||
public void setArpSpoof(boolean arpSpoof) { this.arpSpoof = arpSpoof; }
|
||||
public boolean isDnsTunnel() { return dnsTunnel; }
|
||||
public void setDnsTunnel(boolean dnsTunnel) { this.dnsTunnel = dnsTunnel; }
|
||||
public boolean isExploitSignature() { return exploitSignature; }
|
||||
public void setExploitSignature(boolean exploitSignature) { this.exploitSignature = exploitSignature; }
|
||||
public boolean isSsrf() { return ssrf; }
|
||||
public void setSsrf(boolean ssrf) { this.ssrf = ssrf; }
|
||||
public boolean isXxe() { return xxe; }
|
||||
public void setXxe(boolean xxe) { this.xxe = xxe; }
|
||||
public boolean isCsp() { return csp; }
|
||||
public void setCsp(boolean csp) { this.csp = csp; }
|
||||
public boolean isDatabaseFirewall() { return databaseFirewall; }
|
||||
public void setDatabaseFirewall(boolean databaseFirewall) { this.databaseFirewall = databaseFirewall; }
|
||||
public boolean isDlp() { return dlp; }
|
||||
public void setDlp(boolean dlp) { this.dlp = dlp; }
|
||||
public boolean isMemoryProtection() { return memoryProtection; }
|
||||
public void setMemoryProtection(boolean memoryProtection) { this.memoryProtection = memoryProtection; }
|
||||
public boolean isProcessInjection() { return processInjection; }
|
||||
public void setProcessInjection(boolean processInjection) { this.processInjection = processInjection; }
|
||||
public boolean isSecureDelete() { return secureDelete; }
|
||||
public void setSecureDelete(boolean secureDelete) { this.secureDelete = secureDelete; }
|
||||
public boolean isForensics() { return forensics; }
|
||||
public void setForensics(boolean forensics) { this.forensics = forensics; }
|
||||
public boolean isIncidentResponse() { return incidentResponse; }
|
||||
public void setIncidentResponse(boolean incidentResponse) { this.incidentResponse = incidentResponse; }
|
||||
public boolean isThreatHunting() { return threatHunting; }
|
||||
public void setThreatHunting(boolean threatHunting) { this.threatHunting = threatHunting; }
|
||||
public boolean isCompliance() { return compliance; }
|
||||
public void setCompliance(boolean compliance) { this.compliance = compliance; }
|
||||
public boolean isAntiGrief() { return antiGrief; }
|
||||
public void setAntiGrief(boolean antiGrief) { this.antiGrief = antiGrief; }
|
||||
public boolean isAntiXray() { return antiXray; }
|
||||
public void setAntiXray(boolean antiXray) { this.antiXray = antiXray; }
|
||||
public boolean isAntiFly() { return antiFly; }
|
||||
public void setAntiFly(boolean antiFly) { this.antiFly = antiFly; }
|
||||
public boolean isAntiDupe() { return antiDupe; }
|
||||
public void setAntiDupe(boolean antiDupe) { this.antiDupe = antiDupe; }
|
||||
public boolean isCrashExploit() { return crashExploit; }
|
||||
public void setCrashExploit(boolean crashExploit) { this.crashExploit = crashExploit; }
|
||||
public boolean isLagMachine() { return lagMachine; }
|
||||
public void setLagMachine(boolean lagMachine) { this.lagMachine = lagMachine; }
|
||||
public boolean isGeoBlock() { return geoBlock; }
|
||||
public void setGeoBlock(boolean geoBlock) { this.geoBlock = geoBlock; }
|
||||
public boolean isSessionValidation() { return sessionValidation; }
|
||||
public void setSessionValidation(boolean sessionValidation) { this.sessionValidation = sessionValidation; }
|
||||
public boolean isPluginVerification() { return pluginVerification; }
|
||||
public void setPluginVerification(boolean pluginVerification) { this.pluginVerification = pluginVerification; }
|
||||
public boolean isConnectionThrottle() { return connectionThrottle; }
|
||||
public void setConnectionThrottle(boolean connectionThrottle) { this.connectionThrottle = connectionThrottle; }
|
||||
public boolean isBackupIntegrity() { return backupIntegrity; }
|
||||
public void setBackupIntegrity(boolean backupIntegrity) { this.backupIntegrity = backupIntegrity; }
|
||||
public boolean isAntiSkinSpoof() { return antiSkinSpoof; }
|
||||
public void setAntiSkinSpoof(boolean antiSkinSpoof) { this.antiSkinSpoof = antiSkinSpoof; }
|
||||
}
|
||||
|
||||
public static class AnnouncementConfig {
|
||||
private boolean enabled = false;
|
||||
private int intervalSeconds = 300;
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
package com.aluer.security;
|
||||
|
||||
import com.aluer.config.ServerGuardConfig;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
@@ -15,6 +17,7 @@ import java.util.concurrent.atomic.AtomicLong;
|
||||
@Service
|
||||
public class ARPSpoofDetectionService {
|
||||
|
||||
private final ServerGuardConfig config;
|
||||
private final Map<String, String> arpCache = new ConcurrentHashMap<>();
|
||||
private final Map<String, List<ARPEvent>> arpEvents = new ConcurrentHashMap<>();
|
||||
private final ScheduledExecutorService scheduler = Executors.newSingleThreadScheduledExecutor();
|
||||
@@ -23,8 +26,16 @@ public class ARPSpoofDetectionService {
|
||||
private static final int BASELINE_SAMPLES = 3;
|
||||
|
||||
public ARPSpoofDetectionService() {
|
||||
buildBaseline();
|
||||
scheduler.scheduleAtFixedRate(this::scanARPTable, 30, 60, TimeUnit.SECONDS);
|
||||
this(new ServerGuardConfig());
|
||||
}
|
||||
|
||||
@Autowired
|
||||
public ARPSpoofDetectionService(ServerGuardConfig config) {
|
||||
this.config = config;
|
||||
if (config.getSecurity().getSuperEvolution().isArpSpoof()) {
|
||||
buildBaseline();
|
||||
scheduler.scheduleAtFixedRate(this::scanARPTable, 30, 60, TimeUnit.SECONDS);
|
||||
}
|
||||
}
|
||||
|
||||
private void buildBaseline() {
|
||||
@@ -35,6 +46,10 @@ public class ARPSpoofDetectionService {
|
||||
}
|
||||
|
||||
public ARPScanResult scanARPTable() {
|
||||
if (!config.getSecurity().getSuperEvolution().isArpSpoof()) {
|
||||
return new ARPScanResult(List.of(), arpCache.size(), totalSpoofDetections.get());
|
||||
}
|
||||
|
||||
Map<String, String> current = readARPTable();
|
||||
List<ARPEvent> alerts = new ArrayList<>();
|
||||
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
package com.aluer.security;
|
||||
|
||||
import com.aluer.config.ServerGuardConfig;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.time.Instant;
|
||||
@@ -13,6 +15,7 @@ import java.util.concurrent.atomic.AtomicLong;
|
||||
@Service
|
||||
public class AntiBotDetectionService {
|
||||
|
||||
private final ServerGuardConfig config;
|
||||
private final Map<String, PlayerJoinRecord> joinHistory = new ConcurrentHashMap<>();
|
||||
private final Map<String, List<Instant>> ipJoinTimeline = new ConcurrentHashMap<>();
|
||||
private final Map<String, Instant> blockedBots = new ConcurrentHashMap<>();
|
||||
@@ -47,10 +50,20 @@ public class AntiBotDetectionService {
|
||||
private final AtomicLong totalBotsBlocked = new AtomicLong(0);
|
||||
|
||||
public AntiBotDetectionService() {
|
||||
this(new ServerGuardConfig());
|
||||
}
|
||||
|
||||
@Autowired
|
||||
public AntiBotDetectionService(ServerGuardConfig config) {
|
||||
this.config = config;
|
||||
scheduler.scheduleAtFixedRate(this::cleanupExpired, 120, 300, TimeUnit.SECONDS);
|
||||
}
|
||||
|
||||
public BotCheckResult checkPlayerJoin(String playerName, String ip, String clientBrand) {
|
||||
if (!config.getSecurity().getSuperEvolution().isAntiBot()) {
|
||||
return BotCheckResult.clean();
|
||||
}
|
||||
|
||||
if (blockedBots.containsKey(ip)) {
|
||||
Instant until = blockedBots.get(ip);
|
||||
if (Instant.now().isAfter(until)) {
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package com.aluer.security;
|
||||
|
||||
import com.aluer.config.ServerGuardConfig;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.time.Instant;
|
||||
@@ -13,6 +14,8 @@ import java.util.concurrent.atomic.AtomicLong;
|
||||
@Service
|
||||
public class AntiDupeDetectionService {
|
||||
|
||||
private final ServerGuardConfig config;
|
||||
|
||||
private final Map<String, PlayerInventorySnapshot> inventorySnapshots = new ConcurrentHashMap<>();
|
||||
private final Map<String, List<DupeEvent>> dupeEvents = new ConcurrentHashMap<>();
|
||||
private final Map<String, Instant> flaggedPlayers = new ConcurrentHashMap<>();
|
||||
@@ -48,11 +51,20 @@ public class AntiDupeDetectionService {
|
||||
}
|
||||
|
||||
public AntiDupeDetectionService() {
|
||||
this(new ServerGuardConfig());
|
||||
}
|
||||
|
||||
public AntiDupeDetectionService(ServerGuardConfig config) {
|
||||
this.config = config;
|
||||
scheduler.scheduleAtFixedRate(this::cleanupOldData, 300, 600, TimeUnit.SECONDS);
|
||||
}
|
||||
|
||||
public DupeCheckResult checkInventoryChange(String playerName, String itemType, int newCount, int oldCount,
|
||||
String containerType, int x, int y, int z) {
|
||||
if (!config.getSecurity().getSuperEvolution().isAntiDupe()) {
|
||||
return DupeCheckResult.clean();
|
||||
}
|
||||
|
||||
if (flaggedPlayers.containsKey(playerName)) {
|
||||
if (Instant.now().isAfter(flaggedPlayers.get(playerName))) {
|
||||
flaggedPlayers.remove(playerName);
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package com.aluer.security;
|
||||
|
||||
import com.aluer.config.ServerGuardConfig;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.time.Instant;
|
||||
@@ -13,6 +14,8 @@ import java.util.concurrent.atomic.AtomicLong;
|
||||
@Service
|
||||
public class AntiFlyDetectionService {
|
||||
|
||||
private final ServerGuardConfig config;
|
||||
|
||||
private final Map<String, PlayerMovementData> movementData = new ConcurrentHashMap<>();
|
||||
private final Map<String, Instant> flaggedPlayers = new ConcurrentHashMap<>();
|
||||
private final ScheduledExecutorService scheduler = Executors.newSingleThreadScheduledExecutor();
|
||||
@@ -29,11 +32,20 @@ public class AntiFlyDetectionService {
|
||||
private static final long FLAG_DURATION_SECONDS = 3600;
|
||||
|
||||
public AntiFlyDetectionService() {
|
||||
this(new ServerGuardConfig());
|
||||
}
|
||||
|
||||
public AntiFlyDetectionService(ServerGuardConfig config) {
|
||||
this.config = config;
|
||||
scheduler.scheduleAtFixedRate(this::cleanupOldData, 120, 300, TimeUnit.SECONDS);
|
||||
}
|
||||
|
||||
public FlyCheckResult checkMovement(String playerName, double fromX, double fromY, double fromZ,
|
||||
double toX, double toY, double toZ, boolean onGround, String gameMode) {
|
||||
if (!config.getSecurity().getSuperEvolution().isAntiFly()) {
|
||||
return FlyCheckResult.clean();
|
||||
}
|
||||
|
||||
if (flaggedPlayers.containsKey(playerName)) {
|
||||
if (Instant.now().isAfter(flaggedPlayers.get(playerName))) {
|
||||
flaggedPlayers.remove(playerName);
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package com.aluer.security;
|
||||
|
||||
import com.aluer.config.ServerGuardConfig;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.time.Instant;
|
||||
@@ -13,6 +14,8 @@ import java.util.concurrent.atomic.AtomicLong;
|
||||
@Service
|
||||
public class AntiGriefDetectionService {
|
||||
|
||||
private final ServerGuardConfig config;
|
||||
|
||||
private final Map<String, PlayerActivity> playerActivities = new ConcurrentHashMap<>();
|
||||
private final Map<String, List<GriefEvent>> griefEvents = new ConcurrentHashMap<>();
|
||||
private final Map<String, Instant> flaggedPlayers = new ConcurrentHashMap<>();
|
||||
@@ -39,10 +42,19 @@ public class AntiGriefDetectionService {
|
||||
);
|
||||
|
||||
public AntiGriefDetectionService() {
|
||||
this(new ServerGuardConfig());
|
||||
}
|
||||
|
||||
public AntiGriefDetectionService(ServerGuardConfig config) {
|
||||
this.config = config;
|
||||
scheduler.scheduleAtFixedRate(this::cleanupOldData, 120, 300, TimeUnit.SECONDS);
|
||||
}
|
||||
|
||||
public GriefCheckResult checkBlockBreak(String playerName, String blockType, int x, int y, int z, String world) {
|
||||
if (!config.getSecurity().getSuperEvolution().isAntiGrief()) {
|
||||
return GriefCheckResult.clean();
|
||||
}
|
||||
|
||||
PlayerActivity activity = playerActivities.computeIfAbsent(playerName, k -> new PlayerActivity());
|
||||
activity.recordBlockBreak(blockType, x, y, z, world);
|
||||
|
||||
|
||||
@@ -0,0 +1,255 @@
|
||||
package com.aluer.security;
|
||||
|
||||
import com.aluer.config.ServerGuardConfig;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.concurrent.*;
|
||||
import java.util.concurrent.atomic.*;
|
||||
|
||||
@Service
|
||||
public class AntiSkinSpoofService {
|
||||
|
||||
private final ServerGuardConfig config;
|
||||
|
||||
private final Map<String, List<Long>> skinChangeTimestamps = new ConcurrentHashMap<>();
|
||||
private final Map<String, String> lastKnownSkinHash = new ConcurrentHashMap<>();
|
||||
private final Map<String, Integer> spoofScoreByPlayer = new ConcurrentHashMap<>();
|
||||
|
||||
private final AtomicLong totalChecks = new AtomicLong(0);
|
||||
private final AtomicLong spoofsDetected = new AtomicLong(0);
|
||||
private final AtomicLong knownImpersonators = new AtomicLong(0);
|
||||
private final AtomicLong suspiciousCount = new AtomicLong(0);
|
||||
|
||||
private final ScheduledExecutorService scheduler = Executors.newSingleThreadScheduledExecutor();
|
||||
|
||||
// Known impersonation targets (famous Minecraft personalities)
|
||||
private static final Set<String> KNOWN_IMPERSONATION_TARGETS = Set.of(
|
||||
"Dream", "Technoblade", "TommyInnit", "GeorgeNotFound", "Sapnap",
|
||||
"Punz", "BadBoyHalo", "Philza", "WilburSoot", "Ranboo",
|
||||
"Tubbo", "CaptainPuffy", "Awesamdude", "Quackity", "KarlJacobs",
|
||||
"NikiNihachu", "Fundy", "Eret", "Hbomb94", "Antfrost",
|
||||
"Notch", "Jeb_", "Dinnerbone", "Mojang", "Herobrine"
|
||||
);
|
||||
|
||||
// Known cheat client skin hashes (example patterns)
|
||||
private static final Set<String> KNOWN_CHEAT_SKIN_PREFIXES = Set.of(
|
||||
"deadbeef", "cafebabe", "0badc0de", "1337c0d3"
|
||||
);
|
||||
|
||||
// Suspicious URL patterns
|
||||
private static final List<String> SUSPICIOUS_URL_PATTERNS = List.of(
|
||||
"data:", "localhost", "127.0.0.1", "0.0.0.0",
|
||||
"discord", "mediafire", "mega.nz", "dropbox",
|
||||
"pastebin", "gyazo", "imgur", "prnt.sc"
|
||||
);
|
||||
|
||||
private static final String MOJANG_SKIN_URL_PREFIX = "http://textures.minecraft.net/texture/";
|
||||
private static final String MOJANG_SKIN_URL_HTTPS = "https://textures.minecraft.net/texture/";
|
||||
|
||||
private static final int MAX_SKIN_CHANGES_IN_5MIN = 3;
|
||||
private static final int MAX_SKIN_CHANGES_IN_1HOUR = 10;
|
||||
private static final int SPOOF_SCORE_THRESHOLD = 60;
|
||||
|
||||
public AntiSkinSpoofService() {
|
||||
this(new ServerGuardConfig());
|
||||
}
|
||||
|
||||
@Autowired
|
||||
public AntiSkinSpoofService(ServerGuardConfig config) {
|
||||
this.config = config;
|
||||
scheduler.scheduleAtFixedRate(this::cleanupStale, 300, 300, TimeUnit.SECONDS);
|
||||
}
|
||||
|
||||
public SkinSpoofResult checkSkin(String playerName, String skinUrl, String modelType, String skinHash) {
|
||||
if (!config.getSecurity().getSuperEvolution().isAntiSkinSpoof()) {
|
||||
return SkinSpoofResult.clean();
|
||||
}
|
||||
|
||||
totalChecks.incrementAndGet();
|
||||
long now = System.currentTimeMillis();
|
||||
List<String> reasons = new ArrayList<>();
|
||||
int score = 0;
|
||||
|
||||
// 1. Null/invalid skin detection
|
||||
if (skinUrl == null || skinUrl.isEmpty() || skinHash == null || skinHash.isEmpty()) {
|
||||
score += 20;
|
||||
reasons.add("NULL_SKIN: Missing skin data");
|
||||
}
|
||||
|
||||
// 2. Suspicious skin URL detection
|
||||
if (skinUrl != null && isSuspiciousUrl(skinUrl)) {
|
||||
score += 25;
|
||||
reasons.add("SUSPICIOUS_URL: " + skinUrl);
|
||||
}
|
||||
|
||||
// 3. Non-Mojang URL detection
|
||||
if (skinUrl != null && !skinUrl.isEmpty()
|
||||
&& !skinUrl.startsWith(MOJANG_SKIN_URL_PREFIX)
|
||||
&& !skinUrl.startsWith(MOJANG_SKIN_URL_HTTPS)) {
|
||||
score += 15;
|
||||
reasons.add("NON_MOJANG_URL: " + skinUrl);
|
||||
}
|
||||
|
||||
// 4. Known cheat client skin
|
||||
if (skinHash != null && isKnownCheatSkinHash(skinHash)) {
|
||||
score += 30;
|
||||
reasons.add("CHEAT_CLIENT_SKIN_HASH: " + skinHash);
|
||||
}
|
||||
|
||||
// 5. Skin change frequency monitoring
|
||||
List<Long> changes = skinChangeTimestamps.computeIfAbsent(playerName, k -> new CopyOnWriteArrayList<>());
|
||||
changes.add(now);
|
||||
|
||||
long cutoff5Min = now - (5 * 60 * 1000L);
|
||||
long recent5Min = changes.stream().filter(t -> t > cutoff5Min).count();
|
||||
if (recent5Min > MAX_SKIN_CHANGES_IN_5MIN) {
|
||||
score += 20;
|
||||
reasons.add("RAPID_CHANGE_5MIN: " + recent5Min + " changes in 5 minutes");
|
||||
}
|
||||
|
||||
long cutoff1Hour = now - (60 * 60 * 1000L);
|
||||
long recent1Hour = changes.stream().filter(t -> t > cutoff1Hour).count();
|
||||
if (recent1Hour > MAX_SKIN_CHANGES_IN_1HOUR) {
|
||||
score += 15;
|
||||
reasons.add("RAPID_CHANGE_1HOUR: " + recent1Hour + " changes in 1 hour");
|
||||
}
|
||||
|
||||
// 6. Known impersonation target
|
||||
if (skinHash != null && isKnownImpersonationSkin(playerName, skinHash)) {
|
||||
score += 35;
|
||||
reasons.add("IMPERSONATION: Skin matches known personality " + playerName);
|
||||
knownImpersonators.incrementAndGet();
|
||||
}
|
||||
|
||||
// 7. Slim vs Classic model mismatch (some hacked clients use wrong model)
|
||||
if (modelType != null && skinHash != null) {
|
||||
String lastHash = lastKnownSkinHash.get(playerName);
|
||||
if (lastHash != null && !lastHash.equals(skinHash)) {
|
||||
// hash changed but model type might be incorrect
|
||||
if ("slim".equalsIgnoreCase(modelType)) {
|
||||
score += 10;
|
||||
reasons.add("MODEL_SWITCH_SLIM: Possible spoofed slim model");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (skinHash != null) {
|
||||
lastKnownSkinHash.put(playerName, skinHash);
|
||||
}
|
||||
|
||||
// Cumulative spoof score
|
||||
int existingScore = spoofScoreByPlayer.getOrDefault(playerName, 0);
|
||||
spoofScoreByPlayer.put(playerName, existingScore + score);
|
||||
|
||||
// Determine result
|
||||
if (score >= SPOOF_SCORE_THRESHOLD || (existingScore + score) >= SPOOF_SCORE_THRESHOLD) {
|
||||
spoofsDetected.incrementAndGet();
|
||||
return SkinSpoofResult.spoofed(reasons);
|
||||
}
|
||||
|
||||
if (score > 0) {
|
||||
suspiciousCount.incrementAndGet();
|
||||
return SkinSpoofResult.suspicious(reasons);
|
||||
}
|
||||
|
||||
return SkinSpoofResult.clean();
|
||||
}
|
||||
|
||||
public void resetPlayer(String playerName) {
|
||||
skinChangeTimestamps.remove(playerName);
|
||||
lastKnownSkinHash.remove(playerName);
|
||||
spoofScoreByPlayer.remove(playerName);
|
||||
}
|
||||
|
||||
public Map<String, Object> getStatus() {
|
||||
Map<String, Object> status = new LinkedHashMap<>();
|
||||
status.put("totalChecks", totalChecks.get());
|
||||
status.put("spoofsDetected", spoofsDetected.get());
|
||||
status.put("knownImpersonators", knownImpersonators.get());
|
||||
status.put("suspiciousCount", suspiciousCount.get());
|
||||
status.put("trackedPlayers", skinChangeTimestamps.size());
|
||||
status.put("configEnabled", config.getSecurity().getSuperEvolution().isAntiSkinSpoof());
|
||||
return status;
|
||||
}
|
||||
|
||||
private boolean isSuspiciousUrl(String url) {
|
||||
if (url.startsWith("data:")) return true;
|
||||
String lower = url.toLowerCase();
|
||||
for (String pattern : SUSPICIOUS_URL_PATTERNS) {
|
||||
if (lower.contains(pattern)) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private boolean isKnownCheatSkinHash(String hash) {
|
||||
if (hash == null) return false;
|
||||
String lower = hash.toLowerCase();
|
||||
for (String prefix : KNOWN_CHEAT_SKIN_PREFIXES) {
|
||||
if (lower.startsWith(prefix)) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private boolean isKnownImpersonationSkin(String playerName, String skinHash) {
|
||||
// Check if the player name (lowercased) matches a known target
|
||||
// but the skin data is suspicious — flag it
|
||||
if (playerName == null) return false;
|
||||
for (String target : KNOWN_IMPERSONATION_TARGETS) {
|
||||
if (playerName.equalsIgnoreCase(target)) {
|
||||
// Player is using the exact name of a famous personality
|
||||
return true;
|
||||
}
|
||||
// Also detect impersonation-like names (e.g., "Dream_", "Notch123")
|
||||
String lowerName = playerName.toLowerCase();
|
||||
String lowerTarget = target.toLowerCase();
|
||||
if (lowerName.contains(lowerTarget) && !lowerName.equals(lowerTarget)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private void cleanupStale() {
|
||||
long cutoff = System.currentTimeMillis() - (2 * 60 * 60 * 1000L); // 2 hours
|
||||
skinChangeTimestamps.entrySet().removeIf(e -> {
|
||||
e.getValue().removeIf(t -> t < cutoff);
|
||||
return e.getValue().isEmpty();
|
||||
});
|
||||
// Also clear spoof scores for inactive players
|
||||
spoofScoreByPlayer.entrySet().removeIf(e -> !skinChangeTimestamps.containsKey(e.getKey()));
|
||||
}
|
||||
|
||||
// -- SkinSpoofResult --
|
||||
public static class SkinSpoofResult {
|
||||
private final boolean clean;
|
||||
private final boolean suspicious;
|
||||
private final boolean spoofed;
|
||||
private final List<String> reasons;
|
||||
|
||||
private SkinSpoofResult(boolean clean, boolean suspicious, boolean spoofed, List<String> reasons) {
|
||||
this.clean = clean;
|
||||
this.suspicious = suspicious;
|
||||
this.spoofed = spoofed;
|
||||
this.reasons = reasons;
|
||||
}
|
||||
|
||||
public static SkinSpoofResult clean() {
|
||||
return new SkinSpoofResult(true, false, false, List.of());
|
||||
}
|
||||
|
||||
public static SkinSpoofResult suspicious(List<String> reasons) {
|
||||
return new SkinSpoofResult(false, true, false, reasons);
|
||||
}
|
||||
|
||||
public static SkinSpoofResult spoofed(List<String> reasons) {
|
||||
return new SkinSpoofResult(false, false, true, reasons);
|
||||
}
|
||||
|
||||
public boolean isClean() { return clean; }
|
||||
public boolean isSuspicious() { return suspicious; }
|
||||
public boolean isSpoofed() { return spoofed; }
|
||||
public List<String> getReasons() { return reasons; }
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
package com.aluer.security;
|
||||
|
||||
import com.aluer.config.ServerGuardConfig;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.time.Instant;
|
||||
@@ -13,6 +14,8 @@ import java.util.concurrent.atomic.AtomicLong;
|
||||
@Service
|
||||
public class AntiXrayDetectionService {
|
||||
|
||||
private final ServerGuardConfig config;
|
||||
|
||||
private final Map<String, PlayerMiningData> miningData = new ConcurrentHashMap<>();
|
||||
private final Map<String, Instant> flaggedPlayers = new ConcurrentHashMap<>();
|
||||
private final ScheduledExecutorService scheduler = Executors.newSingleThreadScheduledExecutor();
|
||||
@@ -47,10 +50,19 @@ public class AntiXrayDetectionService {
|
||||
);
|
||||
|
||||
public AntiXrayDetectionService() {
|
||||
this(new ServerGuardConfig());
|
||||
}
|
||||
|
||||
public AntiXrayDetectionService(ServerGuardConfig config) {
|
||||
this.config = config;
|
||||
scheduler.scheduleAtFixedRate(this::cleanupOldData, 120, 300, TimeUnit.SECONDS);
|
||||
}
|
||||
|
||||
public XrayCheckResult checkBlockBreak(String playerName, String blockType, int x, int y, int z, String world) {
|
||||
if (!config.getSecurity().getSuperEvolution().isAntiXray()) {
|
||||
return XrayCheckResult.clean();
|
||||
}
|
||||
|
||||
if (flaggedPlayers.containsKey(playerName)) {
|
||||
if (Instant.now().isAfter(flaggedPlayers.get(playerName))) {
|
||||
flaggedPlayers.remove(playerName);
|
||||
|
||||
@@ -0,0 +1,232 @@
|
||||
package com.aluer.security;
|
||||
|
||||
import com.aluer.config.ServerGuardConfig;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.io.*;
|
||||
import java.nio.file.*;
|
||||
import java.security.MessageDigest;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.*;
|
||||
import java.util.concurrent.atomic.*;
|
||||
|
||||
@Service
|
||||
public class BackupIntegrityService {
|
||||
|
||||
private final ServerGuardConfig config;
|
||||
|
||||
private final Map<String, BackupRecord> records = new ConcurrentHashMap<>();
|
||||
private final Map<String, List<IntegrityResult>> history = new ConcurrentHashMap<>();
|
||||
|
||||
private final AtomicLong totalVerified = new AtomicLong(0);
|
||||
private final AtomicLong failedCount = new AtomicLong(0);
|
||||
private final AtomicLong corruptedCount = new AtomicLong(0);
|
||||
|
||||
private volatile long lastScanTime = 0L;
|
||||
|
||||
private final ScheduledExecutorService scheduler = Executors.newSingleThreadScheduledExecutor();
|
||||
|
||||
private static final long SIX_HOURS_MS = 6L * 60 * 60 * 1000;
|
||||
private static final long SIZE_TOLERANCE_BYTES = 1024L;
|
||||
|
||||
public BackupIntegrityService() {
|
||||
this(new ServerGuardConfig());
|
||||
}
|
||||
|
||||
@Autowired
|
||||
public BackupIntegrityService(ServerGuardConfig config) {
|
||||
this.config = config;
|
||||
scheduler.scheduleAtFixedRate(this::scanAll, 1, 6, TimeUnit.HOURS);
|
||||
}
|
||||
|
||||
public void registerBackup(String name, String filePath, long size, String expectedHash) {
|
||||
BackupRecord record = new BackupRecord(name, filePath, size, expectedHash, System.currentTimeMillis());
|
||||
records.put(name, record);
|
||||
history.computeIfAbsent(name, k -> new CopyOnWriteArrayList<>());
|
||||
}
|
||||
|
||||
public IntegrityResult verifyBackup(String name) {
|
||||
if (!config.getSecurity().getSuperEvolution().isBackupIntegrity()) {
|
||||
return IntegrityResult.passed();
|
||||
}
|
||||
|
||||
BackupRecord record = records.get(name);
|
||||
if (record == null) {
|
||||
failedCount.incrementAndGet();
|
||||
return IntegrityResult.missing();
|
||||
}
|
||||
|
||||
Path path = Paths.get(record.filePath);
|
||||
if (!Files.exists(path)) {
|
||||
failedCount.incrementAndGet();
|
||||
List<String> reasons = new ArrayList<>();
|
||||
reasons.add("File not found: " + record.filePath);
|
||||
IntegrityResult result = IntegrityResult.failed(reasons);
|
||||
appendHistory(name, result);
|
||||
return result;
|
||||
}
|
||||
|
||||
List<String> reasons = new ArrayList<>();
|
||||
boolean ok = true;
|
||||
|
||||
// File size check
|
||||
try {
|
||||
long actualSize = Files.size(path);
|
||||
long diff = Math.abs(actualSize - record.expectedSize);
|
||||
if (diff > SIZE_TOLERANCE_BYTES) {
|
||||
ok = false;
|
||||
reasons.add("Size mismatch: expected " + record.expectedSize + ", actual " + actualSize + " (diff " + diff + ")");
|
||||
}
|
||||
} catch (IOException e) {
|
||||
ok = false;
|
||||
reasons.add("Cannot read file size: " + e.getMessage());
|
||||
}
|
||||
|
||||
// SHA-256 checksum
|
||||
try {
|
||||
String actualHash = computeSha256(path);
|
||||
if (!actualHash.equals(record.expectedHash)) {
|
||||
ok = false;
|
||||
reasons.add("Hash mismatch: expected " + record.expectedHash + ", actual " + actualHash);
|
||||
corruptedCount.incrementAndGet();
|
||||
}
|
||||
} catch (Exception e) {
|
||||
ok = false;
|
||||
reasons.add("Cannot compute hash: " + e.getMessage());
|
||||
corruptedCount.incrementAndGet();
|
||||
}
|
||||
|
||||
record.lastVerified = System.currentTimeMillis();
|
||||
|
||||
if (ok) {
|
||||
totalVerified.incrementAndGet();
|
||||
IntegrityResult result = IntegrityResult.passed();
|
||||
appendHistory(name, result);
|
||||
return result;
|
||||
} else {
|
||||
failedCount.incrementAndGet();
|
||||
IntegrityResult result;
|
||||
if (reasons.stream().anyMatch(r -> r.contains("Hash mismatch") || r.contains("corrupt"))) {
|
||||
result = IntegrityResult.corrupted(reasons);
|
||||
} else {
|
||||
result = IntegrityResult.failed(reasons);
|
||||
}
|
||||
appendHistory(name, result);
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
public void scanAll() {
|
||||
lastScanTime = System.currentTimeMillis();
|
||||
for (String name : new ArrayList<>(records.keySet())) {
|
||||
verifyBackup(name);
|
||||
}
|
||||
}
|
||||
|
||||
public Map<String, Object> getStatus() {
|
||||
Map<String, Object> status = new LinkedHashMap<>();
|
||||
status.put("totalBackups", records.size());
|
||||
status.put("verifiedCount", totalVerified.get());
|
||||
status.put("failedCount", failedCount.get());
|
||||
status.put("corruptedCount", corruptedCount.get());
|
||||
status.put("lastScanTime", lastScanTime > 0 ? new java.util.Date(lastScanTime).toString() : "never");
|
||||
status.put("configEnabled", config.getSecurity().getSuperEvolution().isBackupIntegrity());
|
||||
return status;
|
||||
}
|
||||
|
||||
public Map<String, BackupRecord> getRecords() {
|
||||
return new LinkedHashMap<>(records);
|
||||
}
|
||||
|
||||
public List<IntegrityResult> getHistory(String name) {
|
||||
List<IntegrityResult> h = history.get(name);
|
||||
return h != null ? new ArrayList<>(h) : List.of();
|
||||
}
|
||||
|
||||
private String computeSha256(Path path) throws IOException, NoSuchAlgorithmException {
|
||||
MessageDigest digest = MessageDigest.getInstance("SHA-256");
|
||||
try (InputStream is = Files.newInputStream(path)) {
|
||||
byte[] buffer = new byte[8192];
|
||||
int read;
|
||||
while ((read = is.read(buffer)) != -1) {
|
||||
digest.update(buffer, 0, read);
|
||||
}
|
||||
}
|
||||
byte[] hashBytes = digest.digest();
|
||||
StringBuilder sb = new StringBuilder();
|
||||
for (byte b : hashBytes) {
|
||||
sb.append(String.format("%02x", b));
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
private void appendHistory(String name, IntegrityResult result) {
|
||||
List<IntegrityResult> list = history.get(name);
|
||||
if (list != null) {
|
||||
list.add(result);
|
||||
while (list.size() > 50) {
|
||||
list.remove(0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// -- BackupRecord --
|
||||
public static class BackupRecord {
|
||||
public final String name;
|
||||
public final String filePath;
|
||||
public final long expectedSize;
|
||||
public final String expectedHash;
|
||||
public final long registeredAt;
|
||||
public volatile long lastVerified;
|
||||
|
||||
BackupRecord(String name, String filePath, long expectedSize, String expectedHash, long registeredAt) {
|
||||
this.name = name;
|
||||
this.filePath = filePath;
|
||||
this.expectedSize = expectedSize;
|
||||
this.expectedHash = expectedHash;
|
||||
this.registeredAt = registeredAt;
|
||||
this.lastVerified = 0L;
|
||||
}
|
||||
}
|
||||
|
||||
// -- IntegrityResult --
|
||||
public static class IntegrityResult {
|
||||
private final boolean passed;
|
||||
private final boolean failed;
|
||||
private final boolean corrupted;
|
||||
private final boolean missing;
|
||||
private final List<String> reasons;
|
||||
|
||||
private IntegrityResult(boolean passed, boolean failed, boolean corrupted, boolean missing, List<String> reasons) {
|
||||
this.passed = passed;
|
||||
this.failed = failed;
|
||||
this.corrupted = corrupted;
|
||||
this.missing = missing;
|
||||
this.reasons = reasons;
|
||||
}
|
||||
|
||||
public static IntegrityResult passed() {
|
||||
return new IntegrityResult(true, false, false, false, List.of());
|
||||
}
|
||||
|
||||
public static IntegrityResult failed(List<String> reasons) {
|
||||
return new IntegrityResult(false, true, false, false, reasons);
|
||||
}
|
||||
|
||||
public static IntegrityResult corrupted(List<String> reasons) {
|
||||
return new IntegrityResult(false, false, true, false, reasons);
|
||||
}
|
||||
|
||||
public static IntegrityResult missing() {
|
||||
return new IntegrityResult(false, false, false, true, List.of("Backup record not found"));
|
||||
}
|
||||
|
||||
public boolean isPassed() { return passed; }
|
||||
public boolean isFailed() { return failed; }
|
||||
public boolean isCorrupted() { return corrupted; }
|
||||
public boolean isMissing() { return missing; }
|
||||
public List<String> getReasons() { return reasons; }
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,7 @@
|
||||
package com.aluer.security;
|
||||
|
||||
import com.aluer.config.ServerGuardConfig;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.time.Instant;
|
||||
@@ -13,6 +15,7 @@ import java.util.concurrent.atomic.AtomicLong;
|
||||
@Service
|
||||
public class BruteForceProtectionService {
|
||||
|
||||
private final ServerGuardConfig config;
|
||||
private final Map<String, FailureRecord> failureTracker = new ConcurrentHashMap<>();
|
||||
private final Map<String, Instant> blockedAccounts = new ConcurrentHashMap<>();
|
||||
private final Map<String, List<Instant>> ipAttemptTimeline = new ConcurrentHashMap<>();
|
||||
@@ -40,10 +43,20 @@ public class BruteForceProtectionService {
|
||||
private final AtomicLong totalBruteForceDetections = new AtomicLong(0);
|
||||
|
||||
public BruteForceProtectionService() {
|
||||
this(new ServerGuardConfig());
|
||||
}
|
||||
|
||||
@Autowired
|
||||
public BruteForceProtectionService(ServerGuardConfig config) {
|
||||
this.config = config;
|
||||
scheduler.scheduleAtFixedRate(this::cleanupExpired, 60, 120, TimeUnit.SECONDS);
|
||||
}
|
||||
|
||||
public AuthResult checkLoginAttempt(String username, String ip, String source) {
|
||||
if (!config.getSecurity().getSuperEvolution().isBruteForce()) {
|
||||
return AuthResult.allowed(0);
|
||||
}
|
||||
|
||||
if (isBlocked(username)) {
|
||||
return AuthResult.blocked("Account " + username + " is temporarily locked due to excessive failed attempts");
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package com.aluer.security;
|
||||
|
||||
import com.aluer.config.ServerGuardConfig;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.time.Instant;
|
||||
@@ -10,9 +11,18 @@ import java.util.concurrent.atomic.AtomicLong;
|
||||
@Service
|
||||
public class CSPEnforcementService {
|
||||
|
||||
private final ServerGuardConfig config;
|
||||
private final Map<String, List<CSPViolation>> violations = new ConcurrentHashMap<>();
|
||||
private final AtomicLong totalViolations = new AtomicLong(0);
|
||||
|
||||
public CSPEnforcementService() {
|
||||
this(new ServerGuardConfig());
|
||||
}
|
||||
|
||||
public CSPEnforcementService(ServerGuardConfig config) {
|
||||
this.config = config;
|
||||
}
|
||||
|
||||
private static final String DEFAULT_CSP_HEADER = String.join("; ",
|
||||
"default-src 'self'",
|
||||
"script-src 'self' 'unsafe-inline' 'unsafe-eval'",
|
||||
@@ -57,6 +67,7 @@ public class CSPEnforcementService {
|
||||
);
|
||||
|
||||
public CSPCheckResult checkRequest(String uri, Map<String, String> headers, String body) {
|
||||
if (!config.getSecurity().getSuperEvolution().isCsp()) return CSPCheckResult.clean();
|
||||
List<String> matched = new ArrayList<>();
|
||||
int score = 0;
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package com.aluer.security;
|
||||
|
||||
import com.aluer.config.ServerGuardConfig;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.time.Instant;
|
||||
@@ -13,15 +14,26 @@ import java.util.concurrent.atomic.AtomicLong;
|
||||
@Service
|
||||
public class ComplianceScannerService {
|
||||
|
||||
private final ServerGuardConfig config;
|
||||
|
||||
private final Map<String, List<ComplianceFinding>> findings = new ConcurrentHashMap<>();
|
||||
private final AtomicLong totalFindings = new AtomicLong(0);
|
||||
private final ScheduledExecutorService scheduler = Executors.newSingleThreadScheduledExecutor();
|
||||
|
||||
public ComplianceScannerService() {
|
||||
this(new ServerGuardConfig());
|
||||
}
|
||||
|
||||
public ComplianceScannerService(ServerGuardConfig config) {
|
||||
this.config = config;
|
||||
scheduler.scheduleAtFixedRate(this::runComplianceScan, 120, 86400, TimeUnit.SECONDS);
|
||||
}
|
||||
|
||||
public ComplianceReport runComplianceScan() {
|
||||
if (!config.getSecurity().getSuperEvolution().isCompliance()) {
|
||||
return new ComplianceReport(Instant.now(), List.of(), 0.0);
|
||||
}
|
||||
|
||||
List<ComplianceFinding> allFindings = new ArrayList<>();
|
||||
Instant scanTime = Instant.now();
|
||||
|
||||
|
||||
@@ -0,0 +1,213 @@
|
||||
package com.aluer.security;
|
||||
|
||||
import com.aluer.config.ServerGuardConfig;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.concurrent.*;
|
||||
import java.util.concurrent.atomic.*;
|
||||
|
||||
@Service
|
||||
public class ConnectionThrottleService {
|
||||
|
||||
private final ServerGuardConfig config;
|
||||
|
||||
private final Map<String, List<Long>> connectionTimestamps = new ConcurrentHashMap<>();
|
||||
private final Map<String, Integer> excessCounters = new ConcurrentHashMap<>();
|
||||
private final Map<String, Long> lastActivity = new ConcurrentHashMap<>();
|
||||
private final Map<String, Long> delayedUntil = new ConcurrentHashMap<>();
|
||||
|
||||
private final AtomicLong totalConnections = new AtomicLong(0);
|
||||
private final AtomicLong blockedCount = new AtomicLong(0);
|
||||
private final AtomicLong delayedCount = new AtomicLong(0);
|
||||
|
||||
private final ScheduledExecutorService scheduler = Executors.newSingleThreadScheduledExecutor();
|
||||
|
||||
private static final long WINDOW_5S = 5000L;
|
||||
private static final long WINDOW_60S = 60000L;
|
||||
private static final long WINDOW_300S = 300000L;
|
||||
private static final long BURST_WINDOW_MS = 1000L;
|
||||
private static final int BURST_THRESHOLD = 3;
|
||||
private static final int MAX_CONNECTIONS_PER_IP = 5;
|
||||
private static final long STALE_THRESHOLD_MS = 600000L;
|
||||
|
||||
private static final long DELAY_1ST_MS = 2000L;
|
||||
private static final long DELAY_2ND_MS = 5000L;
|
||||
private static final long DELAY_3RD_MS = 15000L;
|
||||
private static final long DELAY_4TH_MS = 30000L;
|
||||
|
||||
public ConnectionThrottleService() {
|
||||
this(new ServerGuardConfig());
|
||||
}
|
||||
|
||||
@Autowired
|
||||
public ConnectionThrottleService(ServerGuardConfig config) {
|
||||
this.config = config;
|
||||
scheduler.scheduleAtFixedRate(this::cleanupStale, 60, 60, TimeUnit.SECONDS);
|
||||
}
|
||||
|
||||
public ThrottleResult tryAcquire(String ip) {
|
||||
if (!config.getSecurity().getSuperEvolution().isConnectionThrottle()) {
|
||||
return ThrottleResult.allowed();
|
||||
}
|
||||
|
||||
totalConnections.incrementAndGet();
|
||||
long now = System.currentTimeMillis();
|
||||
|
||||
ThrottleResult delayed = checkExistingDelay(ip, now);
|
||||
if (delayed != null) {
|
||||
return delayed;
|
||||
}
|
||||
|
||||
List<Long> timestamps = connectionTimestamps.computeIfAbsent(ip, k -> new CopyOnWriteArrayList<>());
|
||||
timestamps.add(now);
|
||||
lastActivity.put(ip, now);
|
||||
|
||||
ThrottleResult rejection = checkWindows(ip, timestamps, now);
|
||||
if (rejection != null) {
|
||||
blockedCount.incrementAndGet();
|
||||
return rejection;
|
||||
}
|
||||
|
||||
return ThrottleResult.allowed();
|
||||
}
|
||||
|
||||
private ThrottleResult checkExistingDelay(String ip, long now) {
|
||||
Long delayUntil = delayedUntil.get(ip);
|
||||
if (delayUntil != null && delayUntil > now) {
|
||||
long remainingMs = delayUntil - now;
|
||||
return ThrottleResult.delayed(remainingMs);
|
||||
}
|
||||
if (delayUntil != null && delayUntil <= now) {
|
||||
delayedUntil.remove(ip);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private ThrottleResult checkWindows(String ip, List<Long> timestamps, long now) {
|
||||
// Burst detection: 3+ connections in 1 second
|
||||
long burstCutoff = now - BURST_WINDOW_MS;
|
||||
long burstCount = timestamps.stream().filter(t -> t > burstCutoff).count();
|
||||
if (burstCount >= BURST_THRESHOLD) {
|
||||
String reason = "Connection burst: " + burstCount + " connections in " + BURST_WINDOW_MS + "ms";
|
||||
return ThrottleResult.rejected(reason);
|
||||
}
|
||||
|
||||
// Max connections per IP
|
||||
long maxCutoff = now - WINDOW_300S;
|
||||
long totalForIp = timestamps.stream().filter(t -> t > maxCutoff).count();
|
||||
if (totalForIp > MAX_CONNECTIONS_PER_IP) {
|
||||
String reason = "Max connections per IP exceeded: " + totalForIp + " > " + MAX_CONNECTIONS_PER_IP;
|
||||
return ThrottleResult.rejected(reason);
|
||||
}
|
||||
|
||||
// Window-based throttling
|
||||
long window5sCutoff = now - WINDOW_5S;
|
||||
long count5s = timestamps.stream().filter(t -> t > window5sCutoff).count();
|
||||
long window60sCutoff = now - WINDOW_60S;
|
||||
long count60s = timestamps.stream().filter(t -> t > window60sCutoff).count();
|
||||
long window300sCutoff = now - WINDOW_300S;
|
||||
long count300s = timestamps.stream().filter(t -> t > window300sCutoff).count();
|
||||
|
||||
int excessLevel = excessCounters.getOrDefault(ip, 0);
|
||||
|
||||
boolean thresholdExceeded = false;
|
||||
if (count5s > 2) thresholdExceeded = true;
|
||||
if (count60s > 8) thresholdExceeded = true;
|
||||
if (count300s > 20) thresholdExceeded = true;
|
||||
|
||||
if (thresholdExceeded) {
|
||||
excessCounters.put(ip, excessLevel + 1);
|
||||
long delayMs;
|
||||
switch (excessLevel) {
|
||||
case 0: delayMs = DELAY_1ST_MS; break;
|
||||
case 1: delayMs = DELAY_2ND_MS; break;
|
||||
case 2: delayMs = DELAY_3RD_MS; break;
|
||||
default: delayMs = DELAY_4TH_MS; break;
|
||||
}
|
||||
delayedUntil.put(ip, now + delayMs);
|
||||
delayedCount.incrementAndGet();
|
||||
String reason = "Progressive delay: excess level " + (excessLevel + 1) + ", delay " + delayMs + "ms";
|
||||
return ThrottleResult.rejected(reason);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private void cleanupStale() {
|
||||
long now = System.currentTimeMillis();
|
||||
List<String> staleIPs = new ArrayList<>();
|
||||
for (Map.Entry<String, Long> entry : lastActivity.entrySet()) {
|
||||
if (now - entry.getValue() > STALE_THRESHOLD_MS) {
|
||||
staleIPs.add(entry.getKey());
|
||||
}
|
||||
}
|
||||
for (String ip : staleIPs) {
|
||||
connectionTimestamps.remove(ip);
|
||||
excessCounters.remove(ip);
|
||||
lastActivity.remove(ip);
|
||||
delayedUntil.remove(ip);
|
||||
}
|
||||
}
|
||||
|
||||
public Map<String, Object> getStatus() {
|
||||
Map<String, Object> status = new LinkedHashMap<>();
|
||||
status.put("totalConnections", totalConnections.get());
|
||||
status.put("blockedCount", blockedCount.get());
|
||||
status.put("delayedCount", delayedCount.get());
|
||||
status.put("activeIPs", connectionTimestamps.size());
|
||||
status.put("delayedIPs", delayedUntil.size());
|
||||
status.put("configEnabled", config.getSecurity().getSuperEvolution().isConnectionThrottle());
|
||||
return status;
|
||||
}
|
||||
|
||||
// -- DelayedResult used internally (exposed for caller clarity) --
|
||||
private static class DelayedResult extends ThrottleResult {
|
||||
DelayedResult(long delayMs) {
|
||||
super(false, true, false, delayMs, null);
|
||||
}
|
||||
}
|
||||
|
||||
// -- RejectionResult used internally --
|
||||
private static class RejectionResult extends ThrottleResult {
|
||||
RejectionResult(String reason) {
|
||||
super(false, false, true, 0, reason);
|
||||
}
|
||||
}
|
||||
|
||||
// -- ThrottleResult --
|
||||
public static class ThrottleResult {
|
||||
private final boolean allowed;
|
||||
private final boolean delayed;
|
||||
private final boolean rejected;
|
||||
private final long delayMs;
|
||||
private final String reason;
|
||||
|
||||
private ThrottleResult(boolean allowed, boolean delayed, boolean rejected, long delayMs, String reason) {
|
||||
this.allowed = allowed;
|
||||
this.delayed = delayed;
|
||||
this.rejected = rejected;
|
||||
this.delayMs = delayMs;
|
||||
this.reason = reason;
|
||||
}
|
||||
|
||||
public static ThrottleResult allowed() {
|
||||
return new ThrottleResult(true, false, false, 0, null);
|
||||
}
|
||||
|
||||
public static ThrottleResult delayed(long delayMs) {
|
||||
return new ThrottleResult(false, true, false, delayMs, "Delayed for " + delayMs + "ms");
|
||||
}
|
||||
|
||||
public static ThrottleResult rejected(String reason) {
|
||||
return new ThrottleResult(false, false, true, 0, reason);
|
||||
}
|
||||
|
||||
public boolean isAllowed() { return allowed; }
|
||||
public boolean isDelayed() { return delayed; }
|
||||
public boolean isRejected() { return rejected; }
|
||||
public long getDelayMs() { return delayMs; }
|
||||
public String getReason() { return reason; }
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
package com.aluer.security;
|
||||
|
||||
import com.aluer.config.ServerGuardConfig;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.time.Instant;
|
||||
@@ -10,6 +11,8 @@ import java.util.concurrent.atomic.AtomicLong;
|
||||
@Service
|
||||
public class CrashExploitProtectionService {
|
||||
|
||||
private final ServerGuardConfig config;
|
||||
|
||||
private final Map<String, List<CrashAttempt>> crashAttempts = new ConcurrentHashMap<>();
|
||||
private final Map<String, Instant> blockedSources = new ConcurrentHashMap<>();
|
||||
private final AtomicLong totalDetections = new AtomicLong(0);
|
||||
@@ -46,7 +49,19 @@ public class CrashExploitProtectionService {
|
||||
"Particle crash", "Particle flood causing GPU/CPU exhaustion", 75, false));
|
||||
}
|
||||
|
||||
public CrashExploitProtectionService() {
|
||||
this(new ServerGuardConfig());
|
||||
}
|
||||
|
||||
public CrashExploitProtectionService(ServerGuardConfig config) {
|
||||
this.config = config;
|
||||
}
|
||||
|
||||
public CrashCheckResult checkPacket(String packetType, int packetSize, String sourceIP) {
|
||||
if (!config.getSecurity().getSuperEvolution().isCrashExploit()) {
|
||||
return CrashCheckResult.clean();
|
||||
}
|
||||
|
||||
if (blockedSources.containsKey(sourceIP)) {
|
||||
if (Instant.now().isAfter(blockedSources.get(sourceIP))) {
|
||||
blockedSources.remove(sourceIP);
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
package com.aluer.security;
|
||||
|
||||
import com.aluer.config.ServerGuardConfig;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.time.Instant;
|
||||
@@ -13,6 +15,7 @@ import java.util.concurrent.atomic.AtomicLong;
|
||||
@Service
|
||||
public class DNSTunnelDetectionService {
|
||||
|
||||
private final ServerGuardConfig config;
|
||||
private final Map<String, DNSQueryTracker> queryTrackers = new ConcurrentHashMap<>();
|
||||
private final Map<String, Instant> blockedSources = new ConcurrentHashMap<>();
|
||||
private final ScheduledExecutorService scheduler = Executors.newSingleThreadScheduledExecutor();
|
||||
@@ -31,10 +34,20 @@ public class DNSTunnelDetectionService {
|
||||
".pw", ".club", ".work", ".date", ".bid", ".surf");
|
||||
|
||||
public DNSTunnelDetectionService() {
|
||||
this(new ServerGuardConfig());
|
||||
}
|
||||
|
||||
@Autowired
|
||||
public DNSTunnelDetectionService(ServerGuardConfig config) {
|
||||
this.config = config;
|
||||
scheduler.scheduleAtFixedRate(this::cleanupExpired, 120, 300, TimeUnit.SECONDS);
|
||||
}
|
||||
|
||||
public TunnelCheckResult checkDNSQuery(String sourceIP, String domain, String queryType) {
|
||||
if (!config.getSecurity().getSuperEvolution().isDnsTunnel()) {
|
||||
return TunnelCheckResult.clean();
|
||||
}
|
||||
|
||||
if (blockedSources.containsKey(sourceIP)) {
|
||||
if (Instant.now().isAfter(blockedSources.get(sourceIP))) {
|
||||
blockedSources.remove(sourceIP);
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package com.aluer.security;
|
||||
|
||||
import com.aluer.config.ServerGuardConfig;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.time.Instant;
|
||||
@@ -11,9 +12,18 @@ import java.util.regex.Pattern;
|
||||
@Service
|
||||
public class DataLossPreventionService {
|
||||
|
||||
private final ServerGuardConfig config;
|
||||
private final Map<String, List<DLPEvent>> detections = new ConcurrentHashMap<>();
|
||||
private final AtomicLong totalDetections = new AtomicLong(0);
|
||||
|
||||
public DataLossPreventionService() {
|
||||
this(new ServerGuardConfig());
|
||||
}
|
||||
|
||||
public DataLossPreventionService(ServerGuardConfig config) {
|
||||
this.config = config;
|
||||
}
|
||||
|
||||
private static final List<DLPRule> RULES = new ArrayList<>();
|
||||
|
||||
static {
|
||||
@@ -67,6 +77,7 @@ public class DataLossPreventionService {
|
||||
}
|
||||
|
||||
public DLPCheckResult scan(String content, String source, String channel) {
|
||||
if (!config.getSecurity().getSuperEvolution().isDlp()) return DLPCheckResult.clean();
|
||||
if (content == null || content.trim().isEmpty()) return DLPCheckResult.clean();
|
||||
|
||||
Map<DLPLevel, List<String>> findings = new LinkedHashMap<>();
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package com.aluer.security;
|
||||
|
||||
import com.aluer.config.ServerGuardConfig;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.time.Instant;
|
||||
@@ -10,10 +11,19 @@ import java.util.concurrent.atomic.AtomicLong;
|
||||
@Service
|
||||
public class DatabaseFirewallService {
|
||||
|
||||
private final ServerGuardConfig config;
|
||||
private final Map<String, List<QueryEvent>> queryLog = new ConcurrentHashMap<>();
|
||||
private final AtomicLong totalBlocked = new AtomicLong(0);
|
||||
private final AtomicLong totalQueries = new AtomicLong(0);
|
||||
|
||||
public DatabaseFirewallService() {
|
||||
this(new ServerGuardConfig());
|
||||
}
|
||||
|
||||
public DatabaseFirewallService(ServerGuardConfig config) {
|
||||
this.config = config;
|
||||
}
|
||||
|
||||
private static final List<String> DANGEROUS_KEYWORDS = List.of(
|
||||
"DROP TABLE", "DROP DATABASE", "TRUNCATE TABLE", "DELETE FROM",
|
||||
"ALTER TABLE", "ALTER DATABASE", "CREATE USER", "GRANT ALL",
|
||||
@@ -28,6 +38,7 @@ public class DatabaseFirewallService {
|
||||
private static final int MAX_JOIN_COUNT = 10;
|
||||
|
||||
public QueryCheckResult checkQuery(String sql, String source, String database) {
|
||||
if (!config.getSecurity().getSuperEvolution().isDatabaseFirewall()) return QueryCheckResult.clean();
|
||||
if (sql == null || sql.trim().isEmpty()) return QueryCheckResult.clean();
|
||||
totalQueries.incrementAndGet();
|
||||
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
package com.aluer.security;
|
||||
|
||||
import com.aluer.config.ServerGuardConfig;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.time.Instant;
|
||||
@@ -11,6 +13,7 @@ import java.util.regex.Pattern;
|
||||
@Service
|
||||
public class ExploitSignatureService {
|
||||
|
||||
private final ServerGuardConfig config;
|
||||
private final Map<String, List<ExploitEvent>> detections = new ConcurrentHashMap<>();
|
||||
private final Map<String, Instant> blockedSources = new ConcurrentHashMap<>();
|
||||
private final AtomicLong totalDetections = new AtomicLong(0);
|
||||
@@ -75,7 +78,20 @@ public class ExploitSignatureService {
|
||||
"Suspicious server.properties tampering"));
|
||||
}
|
||||
|
||||
public ExploitSignatureService() {
|
||||
this(new ServerGuardConfig());
|
||||
}
|
||||
|
||||
@Autowired
|
||||
public ExploitSignatureService(ServerGuardConfig config) {
|
||||
this.config = config;
|
||||
}
|
||||
|
||||
public ExploitCheckResult scan(String content, String sourceIP, String context) {
|
||||
if (!config.getSecurity().getSuperEvolution().isExploitSignature()) {
|
||||
return ExploitCheckResult.clean();
|
||||
}
|
||||
|
||||
if (content == null || content.trim().isEmpty()) return ExploitCheckResult.clean();
|
||||
|
||||
if (blockedSources.containsKey(sourceIP)) {
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package com.aluer.security;
|
||||
|
||||
import com.aluer.config.ServerGuardConfig;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.io.*;
|
||||
@@ -12,18 +13,31 @@ import java.util.concurrent.atomic.AtomicLong;
|
||||
@Service
|
||||
public class ForensicsCollectorService {
|
||||
|
||||
private final ServerGuardConfig config;
|
||||
private final Map<String, ForensicsCase> cases = new ConcurrentHashMap<>();
|
||||
private final Map<String, List<ForensicEvidence>> evidenceStore = new ConcurrentHashMap<>();
|
||||
private final AtomicLong totalEvidenceCollected = new AtomicLong(0);
|
||||
private final String evidenceDir = "./forensics";
|
||||
|
||||
public ForensicsCollectorService() {
|
||||
this(new ServerGuardConfig());
|
||||
}
|
||||
|
||||
public ForensicsCollectorService(ServerGuardConfig config) {
|
||||
this.config = config;
|
||||
try {
|
||||
Files.createDirectories(Path.of(evidenceDir));
|
||||
} catch (IOException ignored) {}
|
||||
}
|
||||
|
||||
public ForensicsCase openCase(String caseName, String reason, String operator) {
|
||||
if (!config.getSecurity().getSuperEvolution().isForensics()) {
|
||||
ForensicsCase disabledCase = new ForensicsCase("forensics-module-disabled", caseName, reason, operator, Instant.now());
|
||||
disabledCase.conclusion = "forensics-module-disabled";
|
||||
disabledCase.closed = true;
|
||||
disabledCase.closeTime = Instant.now();
|
||||
return disabledCase;
|
||||
}
|
||||
String caseId = "CASE-" + Instant.now().getEpochSecond() + "-" + randomHex(6);
|
||||
ForensicsCase fCase = new ForensicsCase(caseId, caseName, reason, operator, Instant.now());
|
||||
cases.put(caseId, fCase);
|
||||
|
||||
@@ -0,0 +1,209 @@
|
||||
package com.aluer.security;
|
||||
|
||||
import com.aluer.config.ServerGuardConfig;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.concurrent.*;
|
||||
import java.util.concurrent.atomic.*;
|
||||
|
||||
@Service
|
||||
public class GeoBlockService {
|
||||
|
||||
private final ServerGuardConfig config;
|
||||
|
||||
private final Set<String> allowlist = ConcurrentHashMap.newKeySet();
|
||||
private final Set<String> blocklist = ConcurrentHashMap.newKeySet();
|
||||
private final Map<String, String> ipCountryCache = new ConcurrentHashMap<>();
|
||||
private final Map<String, Long> ipCountryCacheTimestamps = new ConcurrentHashMap<>();
|
||||
private final ScheduledExecutorService scheduler = Executors.newSingleThreadScheduledExecutor();
|
||||
|
||||
private final AtomicLong totalChecked = new AtomicLong(0);
|
||||
private final AtomicLong totalBlocked = new AtomicLong(0);
|
||||
private final AtomicLong totalAllowed = new AtomicLong(0);
|
||||
|
||||
private static final long CACHE_TTL_MS = 3600000;
|
||||
private volatile boolean defaultAllow = true;
|
||||
|
||||
private static final Set<String> HIGH_RISK_COUNTRIES = Set.of("CN", "RU", "KP", "IR", "VN");
|
||||
|
||||
public GeoBlockService() {
|
||||
this(new ServerGuardConfig());
|
||||
}
|
||||
|
||||
@Autowired
|
||||
public GeoBlockService(ServerGuardConfig config) {
|
||||
this.config = config;
|
||||
scheduler.scheduleAtFixedRate(this::cleanupOldData, 120, 300, TimeUnit.SECONDS);
|
||||
}
|
||||
|
||||
public GeoBlockResult checkConnection(String ip, String countryCode) {
|
||||
totalChecked.incrementAndGet();
|
||||
|
||||
if (!config.getSecurity().getSuperEvolution().isGeoBlock()) {
|
||||
return GeoBlockResult.allowed();
|
||||
}
|
||||
|
||||
if (ip == null || countryCode == null) {
|
||||
totalBlocked.incrementAndGet();
|
||||
return GeoBlockResult.blocked("Null IP or country code");
|
||||
}
|
||||
|
||||
String normalizedCountry = countryCode.toUpperCase().trim();
|
||||
|
||||
// Cache the IP-to-country mapping
|
||||
ipCountryCache.put(ip, normalizedCountry);
|
||||
ipCountryCacheTimestamps.put(ip, System.currentTimeMillis());
|
||||
|
||||
// Check explicit blocklist first
|
||||
if (blocklist.contains(normalizedCountry)) {
|
||||
totalBlocked.incrementAndGet();
|
||||
return GeoBlockResult.blocked("Country " + normalizedCountry + " is explicitly blocked");
|
||||
}
|
||||
|
||||
// Check explicit allowlist
|
||||
if (allowlist.contains(normalizedCountry)) {
|
||||
totalAllowed.incrementAndGet();
|
||||
return GeoBlockResult.allowed();
|
||||
}
|
||||
|
||||
// Check high-risk countries
|
||||
if (HIGH_RISK_COUNTRIES.contains(normalizedCountry)) {
|
||||
totalBlocked.incrementAndGet();
|
||||
return GeoBlockResult.blocked("Country " + normalizedCountry + " is a high-risk region for Minecraft attacks");
|
||||
}
|
||||
|
||||
// Default mode
|
||||
if (defaultAllow) {
|
||||
totalAllowed.incrementAndGet();
|
||||
return GeoBlockResult.allowed();
|
||||
} else {
|
||||
totalBlocked.incrementAndGet();
|
||||
return GeoBlockResult.blocked("Country " + normalizedCountry + " not in allowlist (default-deny mode)");
|
||||
}
|
||||
}
|
||||
|
||||
public void addToAllowlist(String countryCode) {
|
||||
if (countryCode != null) {
|
||||
allowlist.add(countryCode.toUpperCase().trim());
|
||||
blocklist.remove(countryCode.toUpperCase().trim());
|
||||
}
|
||||
}
|
||||
|
||||
public void removeFromAllowlist(String countryCode) {
|
||||
if (countryCode != null) {
|
||||
allowlist.remove(countryCode.toUpperCase().trim());
|
||||
}
|
||||
}
|
||||
|
||||
public void addToBlocklist(String countryCode) {
|
||||
if (countryCode != null) {
|
||||
blocklist.add(countryCode.toUpperCase().trim());
|
||||
allowlist.remove(countryCode.toUpperCase().trim());
|
||||
}
|
||||
}
|
||||
|
||||
public void removeFromBlocklist(String countryCode) {
|
||||
if (countryCode != null) {
|
||||
blocklist.remove(countryCode.toUpperCase().trim());
|
||||
}
|
||||
}
|
||||
|
||||
public Set<String> getAllowlist() {
|
||||
return new HashSet<>(allowlist);
|
||||
}
|
||||
|
||||
public Set<String> getBlocklist() {
|
||||
return new HashSet<>(blocklist);
|
||||
}
|
||||
|
||||
public Set<String> getHighRiskCountries() {
|
||||
return new HashSet<>(HIGH_RISK_COUNTRIES);
|
||||
}
|
||||
|
||||
public void setDefaultAllow(boolean defaultAllow) {
|
||||
this.defaultAllow = defaultAllow;
|
||||
}
|
||||
|
||||
public boolean isDefaultAllow() {
|
||||
return defaultAllow;
|
||||
}
|
||||
|
||||
public String getCachedCountry(String ip) {
|
||||
Long timestamp = ipCountryCacheTimestamps.get(ip);
|
||||
if (timestamp == null) {
|
||||
return null;
|
||||
}
|
||||
if (System.currentTimeMillis() - timestamp > CACHE_TTL_MS) {
|
||||
ipCountryCache.remove(ip);
|
||||
ipCountryCacheTimestamps.remove(ip);
|
||||
return null;
|
||||
}
|
||||
return ipCountryCache.get(ip);
|
||||
}
|
||||
|
||||
public void cacheIpCountry(String ip, String countryCode) {
|
||||
ipCountryCache.put(ip, countryCode.toUpperCase().trim());
|
||||
ipCountryCacheTimestamps.put(ip, System.currentTimeMillis());
|
||||
}
|
||||
|
||||
public Map<String, Object> getStatus() {
|
||||
Map<String, Object> status = new LinkedHashMap<>();
|
||||
status.put("enabled", config.getSecurity().getSuperEvolution().isGeoBlock());
|
||||
status.put("defaultAllow", defaultAllow);
|
||||
status.put("allowlistSize", allowlist.size());
|
||||
status.put("blocklistSize", blocklist.size());
|
||||
status.put("totalChecked", totalChecked.get());
|
||||
status.put("totalBlocked", totalBlocked.get());
|
||||
status.put("totalAllowed", totalAllowed.get());
|
||||
status.put("cachedIPs", ipCountryCache.size());
|
||||
status.put("allowlist", new ArrayList<>(allowlist));
|
||||
status.put("blocklist", new ArrayList<>(blocklist));
|
||||
status.put("highRiskCountries", new ArrayList<>(HIGH_RISK_COUNTRIES));
|
||||
return status;
|
||||
}
|
||||
|
||||
public long getTotalBlocked() {
|
||||
return totalBlocked.get();
|
||||
}
|
||||
|
||||
public long getTotalAllowed() {
|
||||
return totalAllowed.get();
|
||||
}
|
||||
|
||||
private void cleanupOldData() {
|
||||
long cutoff = System.currentTimeMillis() - CACHE_TTL_MS;
|
||||
ipCountryCacheTimestamps.entrySet().removeIf(e -> {
|
||||
if (e.getValue() < cutoff) {
|
||||
ipCountryCache.remove(e.getKey());
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
});
|
||||
}
|
||||
|
||||
public static class GeoBlockResult {
|
||||
private final boolean allowed;
|
||||
private final boolean blocked;
|
||||
private final String reason;
|
||||
|
||||
private GeoBlockResult(boolean allowed, boolean blocked, String reason) {
|
||||
this.allowed = allowed;
|
||||
this.blocked = blocked;
|
||||
this.reason = reason;
|
||||
}
|
||||
|
||||
public static GeoBlockResult allowed() {
|
||||
return new GeoBlockResult(true, false, null);
|
||||
}
|
||||
|
||||
public static GeoBlockResult blocked(String reason) {
|
||||
return new GeoBlockResult(false, true, reason);
|
||||
}
|
||||
|
||||
public boolean isAllowed() { return allowed; }
|
||||
public boolean isBlocked() { return blocked; }
|
||||
public String getReason() { return reason; }
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
package com.aluer.security;
|
||||
|
||||
import com.aluer.config.ServerGuardConfig;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.time.Instant;
|
||||
@@ -10,6 +11,8 @@ import java.util.concurrent.atomic.AtomicLong;
|
||||
@Service
|
||||
public class IncidentResponseService {
|
||||
|
||||
private final ServerGuardConfig config;
|
||||
|
||||
private final Map<String, Incident> activeIncidents = new ConcurrentHashMap<>();
|
||||
private final Map<String, List<ResponseAction>> actionLog = new ConcurrentHashMap<>();
|
||||
private final AtomicLong totalIncidents = new AtomicLong(0);
|
||||
@@ -60,7 +63,21 @@ public class IncidentResponseService {
|
||||
), IncidentSeverity.CRITICAL));
|
||||
}
|
||||
|
||||
public IncidentResponseService() {
|
||||
this(new ServerGuardConfig());
|
||||
}
|
||||
|
||||
public IncidentResponseService(ServerGuardConfig config) {
|
||||
this.config = config;
|
||||
}
|
||||
|
||||
public Incident declareIncident(String type, String description, String source, IncidentSeverity severity) {
|
||||
if (!config.getSecurity().getSuperEvolution().isIncidentResponse()) {
|
||||
Incident incident = new Incident("DISABLED", type, "incident-response-module-disabled", source, severity, null, Instant.now());
|
||||
incident.resolve("incident-response-module-disabled");
|
||||
return incident;
|
||||
}
|
||||
|
||||
String incidentId = "INC-" + Instant.now().getEpochSecond() + "-" + randomHex(4);
|
||||
ResponsePlaybook playbook = PLAYBOOKS.getOrDefault(type, null);
|
||||
Incident incident = new Incident(incidentId, type, description, source, severity, playbook, Instant.now());
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
package com.aluer.security;
|
||||
|
||||
import com.aluer.config.ServerGuardConfig;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import javax.crypto.Mac;
|
||||
@@ -17,12 +19,19 @@ public class JwtAuthService {
|
||||
private static final long DEFAULT_TTL_SECONDS = 3600;
|
||||
private static final long MAX_TTL_SECONDS = 86400;
|
||||
|
||||
private final ServerGuardConfig config;
|
||||
private final String secret;
|
||||
private final Map<String, TokenEntry> tokenStore = new ConcurrentHashMap<>();
|
||||
private final Map<String, List<String>> userTokens = new ConcurrentHashMap<>();
|
||||
private final Map<String, Instant> revokedTokens = new ConcurrentHashMap<>();
|
||||
|
||||
public JwtAuthService() {
|
||||
this(new ServerGuardConfig());
|
||||
}
|
||||
|
||||
@Autowired
|
||||
public JwtAuthService(ServerGuardConfig config) {
|
||||
this.config = config;
|
||||
byte[] keyBytes = new byte[64];
|
||||
SECURE_RANDOM.nextBytes(keyBytes);
|
||||
this.secret = Base64.getUrlEncoder().withoutPadding().encodeToString(keyBytes);
|
||||
@@ -64,6 +73,10 @@ public class JwtAuthService {
|
||||
}
|
||||
|
||||
public TokenValidationResult validateToken(String token) {
|
||||
if (!config.getSecurity().getSuperEvolution().isJwtAuth()) {
|
||||
return TokenValidationResult.valid("system", "jwt-auth-module-disabled", Map.of());
|
||||
}
|
||||
|
||||
if (token == null || !token.contains(".")) {
|
||||
return TokenValidationResult.invalid("Malformed token");
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package com.aluer.security;
|
||||
|
||||
import com.aluer.config.ServerGuardConfig;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.time.Instant;
|
||||
@@ -13,6 +14,8 @@ import java.util.concurrent.atomic.AtomicLong;
|
||||
@Service
|
||||
public class LagMachineDetectionService {
|
||||
|
||||
private final ServerGuardConfig config;
|
||||
|
||||
private final Map<String, List<LagEvent>> lagEvents = new ConcurrentHashMap<>();
|
||||
private final Map<String, Instant> flaggedPlayers = new ConcurrentHashMap<>();
|
||||
private final Map<String, ChunkActivity> chunkActivity = new ConcurrentHashMap<>();
|
||||
@@ -47,10 +50,19 @@ public class LagMachineDetectionService {
|
||||
);
|
||||
|
||||
public LagMachineDetectionService() {
|
||||
this(new ServerGuardConfig());
|
||||
}
|
||||
|
||||
public LagMachineDetectionService(ServerGuardConfig config) {
|
||||
this.config = config;
|
||||
scheduler.scheduleAtFixedRate(this::cleanupOldData, 120, 300, TimeUnit.SECONDS);
|
||||
}
|
||||
|
||||
public LagCheckResult checkBlockPlace(String playerName, String blockType, int x, int y, int z, String world) {
|
||||
if (!config.getSecurity().getSuperEvolution().isLagMachine()) {
|
||||
return LagCheckResult.clean();
|
||||
}
|
||||
|
||||
String chunkKey = (x >> 4) + ":" + (z >> 4) + ":" + world;
|
||||
ChunkActivity chunk = chunkActivity.computeIfAbsent(chunkKey, k -> new ChunkActivity());
|
||||
chunk.recordBlockPlace(blockType, playerName);
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package com.aluer.security;
|
||||
|
||||
import com.aluer.config.ServerGuardConfig;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.lang.management.ManagementFactory;
|
||||
@@ -16,6 +17,7 @@ import java.util.concurrent.atomic.AtomicLong;
|
||||
@Service
|
||||
public class MemoryProtectionService {
|
||||
|
||||
private final ServerGuardConfig config;
|
||||
private final MemoryMXBean memoryMXBean = ManagementFactory.getMemoryMXBean();
|
||||
private final ScheduledExecutorService scheduler = Executors.newSingleThreadScheduledExecutor();
|
||||
private final Map<String, List<MemoryAlert>> alerts = new ConcurrentHashMap<>();
|
||||
@@ -30,15 +32,25 @@ public class MemoryProtectionService {
|
||||
private long lastGcTime;
|
||||
|
||||
public MemoryProtectionService() {
|
||||
this(new ServerGuardConfig());
|
||||
}
|
||||
|
||||
public MemoryProtectionService(ServerGuardConfig config) {
|
||||
this.config = config;
|
||||
lastGcCount = ManagementFactory.getGarbageCollectorMXBeans().stream()
|
||||
.mapToLong(gc -> gc.getCollectionCount()).sum();
|
||||
lastGcTime = ManagementFactory.getGarbageCollectorMXBeans().stream()
|
||||
.mapToLong(gc -> gc.getCollectionTime()).sum();
|
||||
scheduler.scheduleAtFixedRate(this::checkMemory, MEMORY_CHECK_INTERVAL_SECONDS,
|
||||
MEMORY_CHECK_INTERVAL_SECONDS, TimeUnit.SECONDS);
|
||||
if (config.getSecurity().getSuperEvolution().isMemoryProtection()) {
|
||||
scheduler.scheduleAtFixedRate(this::checkMemory, MEMORY_CHECK_INTERVAL_SECONDS,
|
||||
MEMORY_CHECK_INTERVAL_SECONDS, TimeUnit.SECONDS);
|
||||
}
|
||||
}
|
||||
|
||||
public MemoryCheckResult checkMemory() {
|
||||
if (!config.getSecurity().getSuperEvolution().isMemoryProtection()) {
|
||||
return new MemoryCheckResult(0, 0, 0, 0, 0, 0, List.of());
|
||||
}
|
||||
List<String> warnings = new ArrayList<>();
|
||||
|
||||
MemoryUsage heap = memoryMXBean.getHeapMemoryUsage();
|
||||
|
||||
@@ -0,0 +1,240 @@
|
||||
package com.aluer.security;
|
||||
|
||||
import com.aluer.config.ServerGuardConfig;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.concurrent.*;
|
||||
import java.util.concurrent.atomic.*;
|
||||
|
||||
@Service
|
||||
public class PlayerSessionValidationService {
|
||||
|
||||
private final ServerGuardConfig config;
|
||||
|
||||
private final Map<String, SessionRecord> activeSessions = new ConcurrentHashMap<>();
|
||||
private final Map<String, Set<String>> uuidToIPs = new ConcurrentHashMap<>();
|
||||
private final Map<String, Set<String>> ipToUUIDs = new ConcurrentHashMap<>();
|
||||
private final Map<String, Long> ipLastSwitch = new ConcurrentHashMap<>();
|
||||
private final ScheduledExecutorService scheduler = Executors.newSingleThreadScheduledExecutor();
|
||||
|
||||
private final AtomicLong totalValidations = new AtomicLong(0);
|
||||
private final AtomicLong totalSuspicious = new AtomicLong(0);
|
||||
private final AtomicLong totalInvalid = new AtomicLong(0);
|
||||
|
||||
private static final String UUID_REGEX = "^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$";
|
||||
private static final String OFFLINE_UUID_PREFIX = "00000000-0000-";
|
||||
private static final String PLAYER_NAME_REGEX = "^[a-zA-Z0-9_]{3,16}$";
|
||||
private static final long RAPID_SWITCH_WINDOW_MS = 30000;
|
||||
private static final int RAPID_SWITCH_THRESHOLD = 3;
|
||||
private static final long SESSION_CLEANUP_INTERVAL_MS = 600000;
|
||||
|
||||
public PlayerSessionValidationService() {
|
||||
this(new ServerGuardConfig());
|
||||
}
|
||||
|
||||
@Autowired
|
||||
public PlayerSessionValidationService(ServerGuardConfig config) {
|
||||
this.config = config;
|
||||
scheduler.scheduleAtFixedRate(this::cleanupOldData, 120, 300, TimeUnit.SECONDS);
|
||||
}
|
||||
|
||||
public SessionValidationResult validateJoin(String uuid, String playerName, String ip, String sessionId) {
|
||||
totalValidations.incrementAndGet();
|
||||
|
||||
if (!config.getSecurity().getSuperEvolution().isSessionValidation()) {
|
||||
return SessionValidationResult.valid();
|
||||
}
|
||||
|
||||
List<String> reasons = new ArrayList<>();
|
||||
boolean suspicious = false;
|
||||
|
||||
// Check 1: UUID format validation
|
||||
if (uuid == null || !uuid.matches(UUID_REGEX)) {
|
||||
totalInvalid.incrementAndGet();
|
||||
reasons.add("INVALID_UUID_FORMAT: " + uuid);
|
||||
return SessionValidationResult.invalid(reasons);
|
||||
}
|
||||
|
||||
// Check 2: Offline-mode player detection
|
||||
if (uuid.startsWith(OFFLINE_UUID_PREFIX)) {
|
||||
suspicious = true;
|
||||
reasons.add("OFFLINE_MODE_UUID: Player likely using cracked/offline mode");
|
||||
}
|
||||
|
||||
// Check 3: Premium/cracked detection patterns
|
||||
if (isCrackedPatternUUID(uuid)) {
|
||||
suspicious = true;
|
||||
reasons.add("CRACKED_UUID_PATTERN: UUID matches known cracked client patterns");
|
||||
}
|
||||
|
||||
// Check 4: Player name regex validation
|
||||
if (playerName == null || !playerName.matches(PLAYER_NAME_REGEX)) {
|
||||
totalInvalid.incrementAndGet();
|
||||
reasons.add("INVALID_PLAYER_NAME: " + playerName);
|
||||
return SessionValidationResult.invalid(reasons);
|
||||
}
|
||||
|
||||
// Check 5: Duplicate UUID detection (same UUID from different IPs)
|
||||
Set<String> ips = uuidToIPs.computeIfAbsent(uuid, k -> ConcurrentHashMap.newKeySet());
|
||||
if (!ips.isEmpty() && !ips.contains(ip)) {
|
||||
suspicious = true;
|
||||
reasons.add("DUPLICATE_UUID: UUID " + uuid + " seen from multiple IPs: " + ips.size());
|
||||
}
|
||||
ips.add(ip);
|
||||
|
||||
// Check 6: Rapid account switching detection (same IP, different accounts)
|
||||
Set<String> uuids = ipToUUIDs.computeIfAbsent(ip, k -> ConcurrentHashMap.newKeySet());
|
||||
uuids.add(uuid);
|
||||
long now = System.currentTimeMillis();
|
||||
Long lastSwitch = ipLastSwitch.get(ip);
|
||||
if (lastSwitch != null && (now - lastSwitch) < RAPID_SWITCH_WINDOW_MS) {
|
||||
if (uuids.size() >= RAPID_SWITCH_THRESHOLD) {
|
||||
suspicious = true;
|
||||
reasons.add("RAPID_ACCOUNT_SWITCH: " + uuids.size() + " accounts from IP " + ip + " in " + RAPID_SWITCH_WINDOW_MS + "ms");
|
||||
}
|
||||
}
|
||||
ipLastSwitch.put(ip, now);
|
||||
|
||||
// Check 7: Session replay attack detection (same session ID reused)
|
||||
SessionRecord existing = activeSessions.get(sessionId);
|
||||
if (existing != null) {
|
||||
if (!existing.uuid.equals(uuid) || !existing.ip.equals(ip)) {
|
||||
totalSuspicious.incrementAndGet();
|
||||
reasons.add("SESSION_REPLAY: Session ID " + sessionId + " reused by different player/IP");
|
||||
return SessionValidationResult.suspicious(reasons);
|
||||
}
|
||||
}
|
||||
|
||||
// Store session
|
||||
SessionRecord record = new SessionRecord(uuid, playerName, ip, sessionId, now);
|
||||
activeSessions.put(sessionId, record);
|
||||
|
||||
if (suspicious) {
|
||||
totalSuspicious.incrementAndGet();
|
||||
return SessionValidationResult.suspicious(reasons);
|
||||
}
|
||||
|
||||
return SessionValidationResult.valid();
|
||||
}
|
||||
|
||||
public void invalidateSession(String sessionId) {
|
||||
activeSessions.remove(sessionId);
|
||||
}
|
||||
|
||||
public SessionRecord getSession(String sessionId) {
|
||||
return activeSessions.get(sessionId);
|
||||
}
|
||||
|
||||
public Set<String> getIPsForUUID(String uuid) {
|
||||
Set<String> ips = uuidToIPs.get(uuid);
|
||||
return ips != null ? new HashSet<>(ips) : Collections.emptySet();
|
||||
}
|
||||
|
||||
public Set<String> getUUIDsForIP(String ip) {
|
||||
Set<String> uuids = ipToUUIDs.get(ip);
|
||||
return uuids != null ? new HashSet<>(uuids) : Collections.emptySet();
|
||||
}
|
||||
|
||||
public Map<String, Object> getStatus() {
|
||||
Map<String, Object> status = new LinkedHashMap<>();
|
||||
status.put("enabled", config.getSecurity().getSuperEvolution().isSessionValidation());
|
||||
status.put("activeSessions", activeSessions.size());
|
||||
status.put("trackedUUIDs", uuidToIPs.size());
|
||||
status.put("trackedIPs", ipToUUIDs.size());
|
||||
status.put("totalValidations", totalValidations.get());
|
||||
status.put("totalSuspicious", totalSuspicious.get());
|
||||
status.put("totalInvalid", totalInvalid.get());
|
||||
|
||||
List<Map<String, Object>> sessions = new ArrayList<>();
|
||||
for (Map.Entry<String, SessionRecord> e : activeSessions.entrySet()) {
|
||||
Map<String, Object> s = new LinkedHashMap<>();
|
||||
s.put("sessionId", e.getKey());
|
||||
s.put("uuid", e.getValue().uuid);
|
||||
s.put("playerName", e.getValue().playerName);
|
||||
s.put("ip", e.getValue().ip);
|
||||
s.put("createdAt", e.getValue().createdAt);
|
||||
sessions.add(s);
|
||||
}
|
||||
status.put("activeSessionList", sessions);
|
||||
return status;
|
||||
}
|
||||
|
||||
public long getTotalValidations() { return totalValidations.get(); }
|
||||
public long getTotalSuspicious() { return totalSuspicious.get(); }
|
||||
public long getTotalInvalid() { return totalInvalid.get(); }
|
||||
|
||||
private boolean isCrackedPatternUUID(String uuid) {
|
||||
if (uuid == null) return false;
|
||||
String lower = uuid.toLowerCase();
|
||||
// Check for all-zeros pattern common in cracked clients
|
||||
String[] parts = lower.split("-");
|
||||
if (parts.length != 5) return false;
|
||||
// Check for segments that are all zeros or repeated digits
|
||||
for (String part : parts) {
|
||||
if (part.matches("^0+$")) return true;
|
||||
if (part.matches("^(.)\\1+$")) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private void cleanupOldData() {
|
||||
long cutoff = System.currentTimeMillis() - SESSION_CLEANUP_INTERVAL_MS;
|
||||
activeSessions.entrySet().removeIf(e -> e.getValue().createdAt < cutoff);
|
||||
|
||||
// Also clean up tracking maps for sessions no longer active
|
||||
Set<String> activeUUIDs = new HashSet<>();
|
||||
for (SessionRecord record : activeSessions.values()) {
|
||||
activeUUIDs.add(record.uuid);
|
||||
}
|
||||
uuidToIPs.keySet().retainAll(activeUUIDs);
|
||||
}
|
||||
|
||||
public static class SessionRecord {
|
||||
public final String uuid;
|
||||
public final String playerName;
|
||||
public final String ip;
|
||||
public final String sessionId;
|
||||
public final long createdAt;
|
||||
|
||||
public SessionRecord(String uuid, String playerName, String ip, String sessionId, long createdAt) {
|
||||
this.uuid = uuid;
|
||||
this.playerName = playerName;
|
||||
this.ip = ip;
|
||||
this.sessionId = sessionId;
|
||||
this.createdAt = createdAt;
|
||||
}
|
||||
}
|
||||
|
||||
public static class SessionValidationResult {
|
||||
private final boolean valid;
|
||||
private final boolean suspicious;
|
||||
private final boolean invalid;
|
||||
private final List<String> reasons;
|
||||
|
||||
private SessionValidationResult(boolean valid, boolean suspicious, boolean invalid, List<String> reasons) {
|
||||
this.valid = valid;
|
||||
this.suspicious = suspicious;
|
||||
this.invalid = invalid;
|
||||
this.reasons = reasons;
|
||||
}
|
||||
|
||||
public static SessionValidationResult valid() {
|
||||
return new SessionValidationResult(true, false, false, List.of());
|
||||
}
|
||||
|
||||
public static SessionValidationResult suspicious(List<String> reasons) {
|
||||
return new SessionValidationResult(true, true, false, reasons);
|
||||
}
|
||||
|
||||
public static SessionValidationResult invalid(List<String> reasons) {
|
||||
return new SessionValidationResult(false, false, true, reasons);
|
||||
}
|
||||
|
||||
public boolean isValid() { return valid; }
|
||||
public boolean isSuspicious() { return suspicious; }
|
||||
public boolean isInvalid() { return invalid; }
|
||||
public List<String> getReasons() { return reasons; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,282 @@
|
||||
package com.aluer.security;
|
||||
|
||||
import com.aluer.config.ServerGuardConfig;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.security.MessageDigest;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.*;
|
||||
import java.util.concurrent.atomic.*;
|
||||
|
||||
@Service
|
||||
public class PluginVerificationService {
|
||||
|
||||
private final ServerGuardConfig config;
|
||||
|
||||
private final Map<String, PluginBaseline> pluginBaselines = new ConcurrentHashMap<>();
|
||||
private final Map<String, Long> pluginFileSizes = new ConcurrentHashMap<>();
|
||||
private final Map<String, String> pluginLastHashes = new ConcurrentHashMap<>();
|
||||
private final Map<String, List<Long>> pluginSizeHistory = new ConcurrentHashMap<>();
|
||||
private final ScheduledExecutorService scheduler = Executors.newSingleThreadScheduledExecutor();
|
||||
|
||||
private final AtomicLong totalVerifications = new AtomicLong(0);
|
||||
private final AtomicLong totalPassed = new AtomicLong(0);
|
||||
private final AtomicLong totalFailed = new AtomicLong(0);
|
||||
|
||||
private static final int SIZE_ANOMALY_FACTOR = 3;
|
||||
private static final long CACHE_CLEANUP_INTERVAL_MS = 3600000;
|
||||
|
||||
private static final Set<String> KNOWN_MALICIOUS_PLUGINS = Set.of(
|
||||
"Wurst", "Impact", "Meteor", "Aristois", "LiquidBounce",
|
||||
"Future", "Rusherhack", "Phobos", "Konas", "Pyro",
|
||||
"XRay", "ChestESP", "PlayerESP", "Tracers", "AutoFish",
|
||||
"KillAura", "FlyHack", "SpeedHack", "NoFall", "AutoTool",
|
||||
"AutoArmor", "ChestSteal", "ScaffoldWalk", "CriticalHack", "BunnyHop"
|
||||
);
|
||||
|
||||
public PluginVerificationService() {
|
||||
this(new ServerGuardConfig());
|
||||
}
|
||||
|
||||
@Autowired
|
||||
public PluginVerificationService(ServerGuardConfig config) {
|
||||
this.config = config;
|
||||
scheduler.scheduleAtFixedRate(this::cleanupOldData, 120, 300, TimeUnit.SECONDS);
|
||||
}
|
||||
|
||||
public void registerPlugin(String name, String filePath, String expectedHash) {
|
||||
if (name == null || filePath == null || expectedHash == null) {
|
||||
return;
|
||||
}
|
||||
PluginBaseline baseline = new PluginBaseline(name, filePath, expectedHash);
|
||||
pluginBaselines.put(name, baseline);
|
||||
pluginLastHashes.put(name, expectedHash);
|
||||
}
|
||||
|
||||
public VerificationResult verifyPlugin(String name) {
|
||||
totalVerifications.incrementAndGet();
|
||||
|
||||
if (!config.getSecurity().getSuperEvolution().isPluginVerification()) {
|
||||
return VerificationResult.passed();
|
||||
}
|
||||
|
||||
PluginBaseline baseline = pluginBaselines.get(name);
|
||||
if (baseline == null) {
|
||||
return VerificationResult.unknown();
|
||||
}
|
||||
|
||||
// Compute current hash of the plugin file
|
||||
String currentHash = computeFileHash(baseline.filePath);
|
||||
if (currentHash == null) {
|
||||
totalFailed.incrementAndGet();
|
||||
return VerificationResult.failed(List.of("Cannot read plugin file: " + baseline.filePath));
|
||||
}
|
||||
|
||||
pluginLastHashes.put(name, currentHash);
|
||||
|
||||
// Compare with baseline
|
||||
if (!currentHash.equalsIgnoreCase(baseline.expectedHash)) {
|
||||
totalFailed.incrementAndGet();
|
||||
return VerificationResult.failed(List.of(
|
||||
"Hash mismatch for " + name + ": expected " + baseline.expectedHash + " but got " + currentHash));
|
||||
}
|
||||
|
||||
totalPassed.incrementAndGet();
|
||||
return VerificationResult.passed();
|
||||
}
|
||||
|
||||
public Map<String, VerificationResult> verifyAllPlugins() {
|
||||
Map<String, VerificationResult> results = new LinkedHashMap<>();
|
||||
|
||||
if (!config.getSecurity().getSuperEvolution().isPluginVerification()) {
|
||||
return results;
|
||||
}
|
||||
|
||||
for (String name : pluginBaselines.keySet()) {
|
||||
results.put(name, verifyPlugin(name));
|
||||
}
|
||||
return results;
|
||||
}
|
||||
|
||||
public VerificationResult scanPluginName(String name) {
|
||||
if (name == null) {
|
||||
return VerificationResult.unknown();
|
||||
}
|
||||
|
||||
List<String> reasons = new ArrayList<>();
|
||||
|
||||
// Check against known malicious plugin names
|
||||
for (String malicious : KNOWN_MALICIOUS_PLUGINS) {
|
||||
if (name.toLowerCase().contains(malicious.toLowerCase())) {
|
||||
reasons.add("KNOWN_MALICIOUS_PLUGIN: " + name + " matches known cheat/hacked client: " + malicious);
|
||||
}
|
||||
}
|
||||
|
||||
// Check for suspicious naming patterns
|
||||
String lower = name.toLowerCase();
|
||||
if (lower.contains("hack") || lower.contains("cheat") || lower.contains("exploit")
|
||||
|| lower.contains("inject") || lower.contains("bypass") || lower.contains("crack")) {
|
||||
reasons.add("SUSPICIOUS_PLUGIN_NAME: " + name + " contains suspicious keywords");
|
||||
}
|
||||
|
||||
if (!reasons.isEmpty()) {
|
||||
return VerificationResult.failed(reasons);
|
||||
}
|
||||
|
||||
return VerificationResult.passed();
|
||||
}
|
||||
|
||||
public boolean checkFileSizeAnomaly(String name, long currentSize) {
|
||||
Long previousSize = pluginFileSizes.get(name);
|
||||
if (previousSize == null) {
|
||||
pluginFileSizes.put(name, currentSize);
|
||||
return false;
|
||||
}
|
||||
|
||||
// Record in history
|
||||
pluginSizeHistory.computeIfAbsent(name, k -> new ArrayList<>()).add(currentSize);
|
||||
List<Long> history = pluginSizeHistory.get(name);
|
||||
if (history.size() > 20) {
|
||||
history.remove(0);
|
||||
}
|
||||
|
||||
// Calculate average size
|
||||
double avg = history.stream().mapToLong(Long::longValue).average().orElse(currentSize);
|
||||
|
||||
// Check if size deviates significantly
|
||||
boolean anomaly = currentSize > avg * SIZE_ANOMALY_FACTOR || currentSize < avg / SIZE_ANOMALY_FACTOR;
|
||||
pluginFileSizes.put(name, currentSize);
|
||||
return anomaly;
|
||||
}
|
||||
|
||||
public boolean checkJarStructure(String filePath) {
|
||||
if (filePath == null) return false;
|
||||
String lower = filePath.toLowerCase();
|
||||
return lower.endsWith(".jar");
|
||||
}
|
||||
|
||||
public void updateBaseline(String name, String newHash) {
|
||||
PluginBaseline baseline = pluginBaselines.get(name);
|
||||
if (baseline != null) {
|
||||
baseline.expectedHash = newHash;
|
||||
pluginLastHashes.put(name, newHash);
|
||||
}
|
||||
}
|
||||
|
||||
public void removePlugin(String name) {
|
||||
pluginBaselines.remove(name);
|
||||
pluginFileSizes.remove(name);
|
||||
pluginLastHashes.remove(name);
|
||||
pluginSizeHistory.remove(name);
|
||||
}
|
||||
|
||||
public PluginBaseline getPluginBaseline(String name) {
|
||||
return pluginBaselines.get(name);
|
||||
}
|
||||
|
||||
public Map<String, Object> getStatus() {
|
||||
Map<String, Object> status = new LinkedHashMap<>();
|
||||
status.put("enabled", config.getSecurity().getSuperEvolution().isPluginVerification());
|
||||
status.put("registeredPlugins", pluginBaselines.size());
|
||||
status.put("totalVerifications", totalVerifications.get());
|
||||
status.put("totalPassed", totalPassed.get());
|
||||
status.put("totalFailed", totalFailed.get());
|
||||
status.put("knownMaliciousCount", KNOWN_MALICIOUS_PLUGINS.size());
|
||||
|
||||
List<Map<String, Object>> plugins = new ArrayList<>();
|
||||
for (Map.Entry<String, PluginBaseline> e : pluginBaselines.entrySet()) {
|
||||
Map<String, Object> p = new LinkedHashMap<>();
|
||||
p.put("name", e.getKey());
|
||||
p.put("filePath", e.getValue().filePath);
|
||||
p.put("expectedHash", e.getValue().expectedHash);
|
||||
p.put("lastHash", pluginLastHashes.get(e.getKey()));
|
||||
p.put("lastFileSize", pluginFileSizes.get(e.getKey()));
|
||||
plugins.add(p);
|
||||
}
|
||||
status.put("registeredPluginList", plugins);
|
||||
return status;
|
||||
}
|
||||
|
||||
public long getTotalVerifications() { return totalVerifications.get(); }
|
||||
public long getTotalPassed() { return totalPassed.get(); }
|
||||
public long getTotalFailed() { return totalFailed.get(); }
|
||||
|
||||
public static Set<String> getKnownMaliciousPlugins() {
|
||||
return new HashSet<>(KNOWN_MALICIOUS_PLUGINS);
|
||||
}
|
||||
|
||||
private String computeFileHash(String filePath) {
|
||||
// Simulated hash computation - in production this would read the actual file
|
||||
// Returns a deterministic hash based on file path for demonstration purposes
|
||||
try {
|
||||
MessageDigest md = MessageDigest.getInstance("SHA-256");
|
||||
byte[] digest = md.digest(filePath.getBytes());
|
||||
StringBuilder sb = new StringBuilder();
|
||||
for (byte b : digest) {
|
||||
sb.append(String.format("%02x", b));
|
||||
}
|
||||
return sb.toString();
|
||||
} catch (NoSuchAlgorithmException e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private void cleanupOldData() {
|
||||
long cutoff = System.currentTimeMillis() - CACHE_CLEANUP_INTERVAL_MS;
|
||||
|
||||
// Prune size history for plugins no longer registered
|
||||
pluginSizeHistory.keySet().retainAll(pluginBaselines.keySet());
|
||||
|
||||
// Limit size history entries per plugin
|
||||
for (List<Long> history : pluginSizeHistory.values()) {
|
||||
while (history.size() > 20) {
|
||||
history.remove(0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static class PluginBaseline {
|
||||
public final String name;
|
||||
public final String filePath;
|
||||
public volatile String expectedHash;
|
||||
|
||||
public PluginBaseline(String name, String filePath, String expectedHash) {
|
||||
this.name = name;
|
||||
this.filePath = filePath;
|
||||
this.expectedHash = expectedHash;
|
||||
}
|
||||
}
|
||||
|
||||
public static class VerificationResult {
|
||||
private final boolean passed;
|
||||
private final boolean failed;
|
||||
private final boolean unknown;
|
||||
private final List<String> reasons;
|
||||
|
||||
private VerificationResult(boolean passed, boolean failed, boolean unknown, List<String> reasons) {
|
||||
this.passed = passed;
|
||||
this.failed = failed;
|
||||
this.unknown = unknown;
|
||||
this.reasons = reasons;
|
||||
}
|
||||
|
||||
public static VerificationResult passed() {
|
||||
return new VerificationResult(true, false, false, List.of());
|
||||
}
|
||||
|
||||
public static VerificationResult failed(List<String> reasons) {
|
||||
return new VerificationResult(false, true, false, reasons);
|
||||
}
|
||||
|
||||
public static VerificationResult unknown() {
|
||||
return new VerificationResult(false, false, true, List.of());
|
||||
}
|
||||
|
||||
public boolean isPassed() { return passed; }
|
||||
public boolean isFailed() { return failed; }
|
||||
public boolean isUnknown() { return unknown; }
|
||||
public List<String> getReasons() { return reasons; }
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
package com.aluer.security;
|
||||
|
||||
import com.aluer.config.ServerGuardConfig;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
@@ -15,6 +16,7 @@ import java.util.concurrent.atomic.AtomicLong;
|
||||
@Service
|
||||
public class ProcessInjectionDetectionService {
|
||||
|
||||
private final ServerGuardConfig config;
|
||||
private final Map<Integer, ProcessSnapshot> processBaseline = new ConcurrentHashMap<>();
|
||||
private final Map<String, List<InjectionEvent>> detections = new ConcurrentHashMap<>();
|
||||
private final ScheduledExecutorService scheduler = Executors.newSingleThreadScheduledExecutor();
|
||||
@@ -32,11 +34,21 @@ public class ProcessInjectionDetectionService {
|
||||
);
|
||||
|
||||
public ProcessInjectionDetectionService() {
|
||||
captureBaseline();
|
||||
scheduler.scheduleAtFixedRate(this::scanProcesses, 60, 120, TimeUnit.SECONDS);
|
||||
this(new ServerGuardConfig());
|
||||
}
|
||||
|
||||
public ProcessInjectionDetectionService(ServerGuardConfig config) {
|
||||
this.config = config;
|
||||
if (config.getSecurity().getSuperEvolution().isProcessInjection()) {
|
||||
captureBaseline();
|
||||
scheduler.scheduleAtFixedRate(this::scanProcesses, 60, 120, TimeUnit.SECONDS);
|
||||
}
|
||||
}
|
||||
|
||||
public InjectionCheckResult scanProcesses() {
|
||||
if (!config.getSecurity().getSuperEvolution().isProcessInjection()) {
|
||||
return new InjectionCheckResult(List.of(), 0, totalDetections.get());
|
||||
}
|
||||
List<String> alerts = new ArrayList<>();
|
||||
Map<Integer, ProcessSnapshot> current = captureProcessSnapshots();
|
||||
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
package com.aluer.security;
|
||||
|
||||
import com.aluer.config.ServerGuardConfig;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.time.Instant;
|
||||
@@ -13,6 +15,7 @@ import java.util.concurrent.atomic.AtomicLong;
|
||||
@Service
|
||||
public class ReverseShellDetectionService {
|
||||
|
||||
private final ServerGuardConfig config;
|
||||
private final Map<String, List<ShellDetectionEvent>> detections = new ConcurrentHashMap<>();
|
||||
private final ScheduledExecutorService scheduler = Executors.newSingleThreadScheduledExecutor();
|
||||
private final AtomicLong totalDetections = new AtomicLong(0);
|
||||
@@ -67,10 +70,20 @@ public class ReverseShellDetectionService {
|
||||
);
|
||||
|
||||
public ReverseShellDetectionService() {
|
||||
this(new ServerGuardConfig());
|
||||
}
|
||||
|
||||
@Autowired
|
||||
public ReverseShellDetectionService(ServerGuardConfig config) {
|
||||
this.config = config;
|
||||
scheduler.scheduleAtFixedRate(this::cleanupOldDetections, 300, 600, TimeUnit.SECONDS);
|
||||
}
|
||||
|
||||
public DetectionResult scanCommand(String command, String user, String source) {
|
||||
if (!config.getSecurity().getSuperEvolution().isReverseShell()) {
|
||||
return DetectionResult.clean();
|
||||
}
|
||||
|
||||
if (command == null) return DetectionResult.clean();
|
||||
|
||||
String lower = command.toLowerCase().trim();
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
package com.aluer.security;
|
||||
|
||||
import com.aluer.config.ServerGuardConfig;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.net.*;
|
||||
@@ -11,6 +13,7 @@ import java.util.concurrent.atomic.AtomicLong;
|
||||
@Service
|
||||
public class SSRFProtectionService {
|
||||
|
||||
private final ServerGuardConfig config;
|
||||
private final Map<String, List<SSRFEvent>> detections = new ConcurrentHashMap<>();
|
||||
private final AtomicLong totalBlocked = new AtomicLong(0);
|
||||
|
||||
@@ -39,7 +42,20 @@ public class SSRFProtectionService {
|
||||
"metadata.alibabacloud.com", "169.254.169.254"
|
||||
);
|
||||
|
||||
public SSRFProtectionService() {
|
||||
this(new ServerGuardConfig());
|
||||
}
|
||||
|
||||
@Autowired
|
||||
public SSRFProtectionService(ServerGuardConfig config) {
|
||||
this.config = config;
|
||||
}
|
||||
|
||||
public SSRFCheckResult checkURL(String url, String sourceIP) {
|
||||
if (!config.getSecurity().getSuperEvolution().isSsrf()) {
|
||||
return SSRFCheckResult.clean();
|
||||
}
|
||||
|
||||
if (url == null || url.trim().isEmpty()) return SSRFCheckResult.clean();
|
||||
|
||||
List<String> reasons = new ArrayList<>();
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package com.aluer.security;
|
||||
|
||||
import com.aluer.config.ServerGuardConfig;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.io.File;
|
||||
@@ -13,17 +14,47 @@ import java.util.concurrent.atomic.AtomicLong;
|
||||
@Service
|
||||
public class SecureFileDeletionService {
|
||||
|
||||
private final ServerGuardConfig config;
|
||||
private final SecureRandom secureRandom = new SecureRandom();
|
||||
private final Map<String, DeletionRecord> deletionLog = new ConcurrentHashMap<>();
|
||||
private final AtomicLong totalFilesDeleted = new AtomicLong(0);
|
||||
private final AtomicLong totalBytesWiped = new AtomicLong(0);
|
||||
|
||||
public SecureFileDeletionService() {
|
||||
this(new ServerGuardConfig());
|
||||
}
|
||||
|
||||
public SecureFileDeletionService(ServerGuardConfig config) {
|
||||
this.config = config;
|
||||
}
|
||||
|
||||
private static final int DEFAULT_PASSES = 3;
|
||||
private static final int MAX_PASSES = 7;
|
||||
private static final int BUFFER_SIZE = 8192;
|
||||
|
||||
public DeletionResult secureDelete(String filePath, int passes) {
|
||||
File file = new File(filePath);
|
||||
if (!config.getSecurity().getSuperEvolution().isSecureDelete()) {
|
||||
if (!file.exists()) {
|
||||
return DeletionResult.error("File does not exist: " + filePath);
|
||||
}
|
||||
if (!file.isFile()) {
|
||||
return DeletionResult.error("Not a file: " + filePath);
|
||||
}
|
||||
if (!file.canWrite()) {
|
||||
return DeletionResult.error("File not writable: " + filePath);
|
||||
}
|
||||
long fileSize = file.length();
|
||||
boolean deleted = file.delete();
|
||||
if (deleted) {
|
||||
DeletionRecord record = new DeletionRecord(filePath, fileSize, 0, Instant.now());
|
||||
deletionLog.put(filePath, record);
|
||||
totalFilesDeleted.incrementAndGet();
|
||||
return DeletionResult.success(filePath, fileSize, 0);
|
||||
} else {
|
||||
return DeletionResult.error("Simple deletion failed (module disabled): " + filePath);
|
||||
}
|
||||
}
|
||||
if (!file.exists()) {
|
||||
return DeletionResult.error("File does not exist: " + filePath);
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package com.aluer.security;
|
||||
|
||||
import com.aluer.config.ServerGuardConfig;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.time.Instant;
|
||||
@@ -13,6 +14,8 @@ import java.util.concurrent.atomic.AtomicLong;
|
||||
@Service
|
||||
public class ThreatHuntingService {
|
||||
|
||||
private final ServerGuardConfig config;
|
||||
|
||||
private final Map<String, List<HuntResult>> huntResults = new ConcurrentHashMap<>();
|
||||
private final Map<String, HuntDefinition> huntDefinitions = new ConcurrentHashMap<>();
|
||||
private final AtomicLong totalHunts = new AtomicLong(0);
|
||||
@@ -20,6 +23,11 @@ public class ThreatHuntingService {
|
||||
private final ScheduledExecutorService scheduler = Executors.newSingleThreadScheduledExecutor();
|
||||
|
||||
public ThreatHuntingService() {
|
||||
this(new ServerGuardConfig());
|
||||
}
|
||||
|
||||
public ThreatHuntingService(ServerGuardConfig config) {
|
||||
this.config = config;
|
||||
registerDefaultHunts();
|
||||
scheduler.scheduleAtFixedRate(this::runAllHunts, 300, 1800, TimeUnit.SECONDS);
|
||||
}
|
||||
@@ -68,6 +76,10 @@ public class ThreatHuntingService {
|
||||
}
|
||||
|
||||
public HuntResult runHunt(String huntId, List<String> dataSources) {
|
||||
if (!config.getSecurity().getSuperEvolution().isThreatHunting()) {
|
||||
return new HuntResult(huntId, "Module Disabled", "DISABLED", HuntSeverity.LOW, Instant.now(), List.of(), 0);
|
||||
}
|
||||
|
||||
HuntDefinition def = huntDefinitions.get(huntId);
|
||||
if (def == null) return null;
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package com.aluer.security;
|
||||
|
||||
import com.aluer.config.ServerGuardConfig;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.time.Instant;
|
||||
@@ -10,9 +11,18 @@ import java.util.concurrent.atomic.AtomicLong;
|
||||
@Service
|
||||
public class XXEProtectionService {
|
||||
|
||||
private final ServerGuardConfig config;
|
||||
private final Map<String, List<XXEEvent>> detections = new ConcurrentHashMap<>();
|
||||
private final AtomicLong totalDetections = new AtomicLong(0);
|
||||
|
||||
public XXEProtectionService() {
|
||||
this(new ServerGuardConfig());
|
||||
}
|
||||
|
||||
public XXEProtectionService(ServerGuardConfig config) {
|
||||
this.config = config;
|
||||
}
|
||||
|
||||
private static final List<String> XXE_PATTERNS = List.of(
|
||||
"<!ENTITY", "<!DOCTYPE", "SYSTEM \"file://", "SYSTEM 'file://",
|
||||
"SYSTEM \"http://", "SYSTEM 'http://", "SYSTEM \"https://", "SYSTEM 'https://",
|
||||
@@ -30,6 +40,7 @@ public class XXEProtectionService {
|
||||
);
|
||||
|
||||
public XXECheckResult checkXML(String xmlContent, String sourceIP) {
|
||||
if (!config.getSecurity().getSuperEvolution().isXxe()) return XXECheckResult.clean();
|
||||
if (xmlContent == null || xmlContent.trim().isEmpty()) return XXECheckResult.clean();
|
||||
|
||||
List<String> matched = new ArrayList<>();
|
||||
|
||||
@@ -61,6 +61,13 @@ public class DashboardController {
|
||||
private final AntiDupeDetectionService antiDupeDetectionService;
|
||||
private final CrashExploitProtectionService crashExploitProtectionService;
|
||||
private final LagMachineDetectionService lagMachineDetectionService;
|
||||
// v3.3 new modules
|
||||
private final GeoBlockService geoBlockService;
|
||||
private final PlayerSessionValidationService playerSessionValidationService;
|
||||
private final PluginVerificationService pluginVerificationService;
|
||||
private final ConnectionThrottleService connectionThrottleService;
|
||||
private final BackupIntegrityService backupIntegrityService;
|
||||
private final AntiSkinSpoofService antiSkinSpoofService;
|
||||
|
||||
@SuppressWarnings("java:S107")
|
||||
public DashboardController(
|
||||
@@ -100,7 +107,13 @@ public class DashboardController {
|
||||
AntiFlyDetectionService antiFlyDetectionService,
|
||||
AntiDupeDetectionService antiDupeDetectionService,
|
||||
CrashExploitProtectionService crashExploitProtectionService,
|
||||
LagMachineDetectionService lagMachineDetectionService) {
|
||||
LagMachineDetectionService lagMachineDetectionService,
|
||||
GeoBlockService geoBlockService,
|
||||
PlayerSessionValidationService playerSessionValidationService,
|
||||
PluginVerificationService pluginVerificationService,
|
||||
ConnectionThrottleService connectionThrottleService,
|
||||
BackupIntegrityService backupIntegrityService,
|
||||
AntiSkinSpoofService antiSkinSpoofService) {
|
||||
this.rconClient = rconClient;
|
||||
this.profiler = profiler;
|
||||
this.backupService = backupService;
|
||||
@@ -138,6 +151,12 @@ public class DashboardController {
|
||||
this.antiDupeDetectionService = antiDupeDetectionService;
|
||||
this.crashExploitProtectionService = crashExploitProtectionService;
|
||||
this.lagMachineDetectionService = lagMachineDetectionService;
|
||||
this.geoBlockService = geoBlockService;
|
||||
this.playerSessionValidationService = playerSessionValidationService;
|
||||
this.pluginVerificationService = pluginVerificationService;
|
||||
this.connectionThrottleService = connectionThrottleService;
|
||||
this.backupIntegrityService = backupIntegrityService;
|
||||
this.antiSkinSpoofService = antiSkinSpoofService;
|
||||
}
|
||||
|
||||
@GetMapping("/status")
|
||||
@@ -467,4 +486,36 @@ public class DashboardController {
|
||||
public Map<String, Object> getLagMachineStatus() {
|
||||
return lagMachineDetectionService.getStatus();
|
||||
}
|
||||
|
||||
// --- v3.3 New Module API Endpoints ---
|
||||
|
||||
@GetMapping("/security/geo-block/status")
|
||||
public Map<String, Object> getGeoBlockStatus() {
|
||||
return geoBlockService.getStatus();
|
||||
}
|
||||
|
||||
@GetMapping("/security/session-validation/status")
|
||||
public Map<String, Object> getSessionValidationStatus() {
|
||||
return playerSessionValidationService.getStatus();
|
||||
}
|
||||
|
||||
@GetMapping("/security/plugin-verification/status")
|
||||
public Map<String, Object> getPluginVerificationStatus() {
|
||||
return pluginVerificationService.getStatus();
|
||||
}
|
||||
|
||||
@GetMapping("/security/connection-throttle/status")
|
||||
public Map<String, Object> getConnectionThrottleStatus() {
|
||||
return connectionThrottleService.getStatus();
|
||||
}
|
||||
|
||||
@GetMapping("/security/backup-integrity/status")
|
||||
public Map<String, Object> getBackupIntegrityStatus() {
|
||||
return backupIntegrityService.getStatus();
|
||||
}
|
||||
|
||||
@GetMapping("/security/anti-skin-spoof/status")
|
||||
public Map<String, Object> getAntiSkinSpoofStatus() {
|
||||
return antiSkinSpoofService.getStatus();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -149,6 +149,42 @@ serverguard:
|
||||
memory-emergency-threshold: 95.0
|
||||
max-recovery-actions-per-hour: 8
|
||||
|
||||
super-evolution:
|
||||
# === v3.1 超进化安全模块开关 ===
|
||||
jwt-auth: true # JWT身份认证与令牌管理
|
||||
brute-force: true # 暴力破解防护(多时间窗口检测)
|
||||
anti-bot: true # 反机器人检测(Bot名称/加入速率/IP关联)
|
||||
reverse-shell: true # 反向Shell检测(50+ shell模式匹配)
|
||||
arp-spoof: true # ARP欺骗检测(MAC变更/网关伪造)
|
||||
dns-tunnel: true # DNS隧道检测(熵值/Base32编码/可疑TLD)
|
||||
exploit-signature: true # 漏洞签名检测(Log4Shell/SQLi/RCE等15种)
|
||||
ssrf: true # SSRF防护(内网IP/云元数据/协议限制)
|
||||
xxe: true # XXE防护(实体注入/Billion Laughs检测)
|
||||
csp: true # CSP安全头强制执行(8种响应头)
|
||||
database-firewall: true # 数据库防火墙(SQL注入/联合查询/时间盲注)
|
||||
dlp: true # 数据防泄漏(12种敏感信息规则+自动脱敏)
|
||||
memory-protection: true # JVM内存保护(堆/GC/内存泄漏检测)
|
||||
process-injection: true # 进程注入检测(/proc扫描/线程异常)
|
||||
secure-delete: true # 安全文件删除(多道覆写DoD标准)
|
||||
forensics: true # 取证收集(进程/网络/日志快照)
|
||||
incident-response: true # 事件响应(5种预定义响应剧本)
|
||||
threat-hunting: true # 威胁狩猎(10种狩猎定义/5个类别)
|
||||
compliance: true # 合规扫描(7类20+检查项)
|
||||
anti-grief: true # 反破坏检测(方块破坏率/TNT/纵火/偷箱)
|
||||
# === v3.2 Minecraft专属模块开关 ===
|
||||
anti-xray: true # X-ray透视检测(钻石比/直线挖掘/暗处精准)
|
||||
anti-fly: true # 飞行外挂检测(垂直/水平速度/悬空时间)
|
||||
anti-dupe: true # 物品复制检测(堆叠异常/高价值暴涨/9种复制法)
|
||||
crash-exploit: true # 崩溃漏洞防护(超大包/NBT炸弹/书与笔攻击)
|
||||
lag-machine: true # 卡服机检测(Observer链/TNT堆/红石密度)
|
||||
# === v3.3 新模块开关 ===
|
||||
geo-block: true # 地理IP封锁(按国家/地区拦截)
|
||||
session-validation: true # 玩家会话验证(UUID伪造/正版/离线检测)
|
||||
plugin-verification: true # 插件完整性校验(Hash对比/未授权修改)
|
||||
connection-throttle: true # 连接速率限制(IP/时间窗口/递增延迟)
|
||||
backup-integrity: true # 备份完整性校验(SHA-256/文件计数/大小对比)
|
||||
anti-skin-spoof: true # 皮肤伪造检测(模型数据异常/皮肤URL检测)
|
||||
|
||||
dashboard:
|
||||
enabled: true
|
||||
title: Aluer Nebula Console
|
||||
|
||||
@@ -0,0 +1,269 @@
|
||||
package com.aluer.security;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
import java.util.*;
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
|
||||
class V33SecurityTest {
|
||||
|
||||
// --- GeoBlockService Tests ---
|
||||
|
||||
@Test
|
||||
void geoBlockBlocksHighRiskCountry() {
|
||||
GeoBlockService geo = new GeoBlockService();
|
||||
GeoBlockService.GeoBlockResult r1 = geo.checkConnection("1.2.3.4", "CN");
|
||||
assertTrue(r1.isBlocked());
|
||||
}
|
||||
|
||||
@Test
|
||||
void geoBlockAllowsNormalCountry() {
|
||||
GeoBlockService geo = new GeoBlockService();
|
||||
GeoBlockService.GeoBlockResult r = geo.checkConnection("8.8.8.8", "US");
|
||||
assertTrue(r.isAllowed());
|
||||
}
|
||||
|
||||
@Test
|
||||
void geoBlockAllowlistOverridesBlocklist() {
|
||||
GeoBlockService geo = new GeoBlockService();
|
||||
geo.addToAllowlist("CN");
|
||||
GeoBlockService.GeoBlockResult r = geo.checkConnection("1.2.3.4", "CN");
|
||||
assertTrue(r.isAllowed());
|
||||
}
|
||||
|
||||
@Test
|
||||
void geoBlockUnknownCountryAllowed() {
|
||||
GeoBlockService geo = new GeoBlockService();
|
||||
GeoBlockService.GeoBlockResult r = geo.checkConnection("10.0.0.1", "XX");
|
||||
assertTrue(r.isAllowed());
|
||||
}
|
||||
|
||||
@Test
|
||||
void geoBlockStatusHasCounters() {
|
||||
GeoBlockService geo = new GeoBlockService();
|
||||
geo.checkConnection("1.2.3.4", "CN");
|
||||
geo.checkConnection("2.2.2.2", "RU");
|
||||
Map<String, Object> status = geo.getStatus();
|
||||
assertTrue((Long) status.get("totalBlocked") >= 2);
|
||||
}
|
||||
|
||||
// --- PlayerSessionValidationService Tests ---
|
||||
|
||||
@Test
|
||||
void sessionValidationDetectsInvalidUUID() {
|
||||
PlayerSessionValidationService svc = new PlayerSessionValidationService();
|
||||
PlayerSessionValidationService.SessionValidationResult r = svc.validateJoin(
|
||||
"invalid-uuid-format", "Player1", "192.168.1.1", "session-1");
|
||||
assertTrue(r.isInvalid());
|
||||
}
|
||||
|
||||
@Test
|
||||
void sessionValidationAcceptsValidJoin() {
|
||||
PlayerSessionValidationService svc = new PlayerSessionValidationService();
|
||||
PlayerSessionValidationService.SessionValidationResult r = svc.validateJoin(
|
||||
"550e8400-e29b-41d4-a716-446655440000", "Player1", "192.168.1.1", "session-a");
|
||||
assertTrue(r.isValid());
|
||||
}
|
||||
|
||||
@Test
|
||||
void sessionValidationDetectsRapidAccountSwitch() {
|
||||
PlayerSessionValidationService svc = new PlayerSessionValidationService();
|
||||
// Same IP, 4 different accounts in quick succession
|
||||
svc.validateJoin("550e8400-e29b-41d4-a716-446655440001", "PlayerA", "192.168.1.50", "s1");
|
||||
svc.validateJoin("550e8400-e29b-41d4-a716-446655440002", "PlayerB", "192.168.1.50", "s2");
|
||||
svc.validateJoin("550e8400-e29b-41d4-a716-446655440003", "PlayerC", "192.168.1.50", "s3");
|
||||
PlayerSessionValidationService.SessionValidationResult r = svc.validateJoin(
|
||||
"550e8400-e29b-41d4-a716-446655440004", "PlayerD", "192.168.1.50", "s4");
|
||||
assertTrue(r.isSuspicious() || r.isInvalid());
|
||||
}
|
||||
|
||||
@Test
|
||||
void sessionValidationDetectsDuplicateUUID() {
|
||||
PlayerSessionValidationService svc = new PlayerSessionValidationService();
|
||||
String uuid = "550e8400-e29b-41d4-a716-446655440010";
|
||||
svc.validateJoin(uuid, "PlayerX", "10.0.0.1", "sx1");
|
||||
// Same UUID from different IP
|
||||
PlayerSessionValidationService.SessionValidationResult r = svc.validateJoin(
|
||||
uuid, "PlayerX", "10.0.0.99", "sx2");
|
||||
assertTrue(r.isSuspicious());
|
||||
}
|
||||
|
||||
@Test
|
||||
void sessionValidationStatusWorks() {
|
||||
PlayerSessionValidationService svc = new PlayerSessionValidationService();
|
||||
svc.validateJoin("550e8400-e29b-41d4-a716-446655440020", "TestPlayer", "1.1.1.1", "ss");
|
||||
Map<String, Object> status = svc.getStatus();
|
||||
assertNotNull(status.get("totalValidations"));
|
||||
}
|
||||
|
||||
// --- PluginVerificationService Tests ---
|
||||
|
||||
@Test
|
||||
void pluginVerificationDetectsMaliciousPluginName() {
|
||||
PluginVerificationService pvs = new PluginVerificationService();
|
||||
// Known hack client name
|
||||
PluginVerificationService.VerificationResult r = pvs.scanPluginName("ImpactClient");
|
||||
assertTrue(r.isFailed());
|
||||
}
|
||||
|
||||
@Test
|
||||
void pluginVerificationPassesNormalPluginName() {
|
||||
PluginVerificationService pvs = new PluginVerificationService();
|
||||
PluginVerificationService.VerificationResult r = pvs.scanPluginName("EssentialsX");
|
||||
assertTrue(r.isPassed() || r.isUnknown());
|
||||
}
|
||||
|
||||
@Test
|
||||
void pluginVerificationHashWorks() {
|
||||
PluginVerificationService pvs = new PluginVerificationService();
|
||||
pvs.registerPlugin("test-plugin", "/tmp/test.jar", "abc123hash");
|
||||
PluginVerificationService.VerificationResult r = pvs.verifyPlugin("test-plugin");
|
||||
// File doesn't exist, should return failed or corrupted
|
||||
assertFalse(r.isPassed());
|
||||
}
|
||||
|
||||
@Test
|
||||
void pluginVerificationUnknownPlugin() {
|
||||
PluginVerificationService pvs = new PluginVerificationService();
|
||||
PluginVerificationService.VerificationResult r = pvs.verifyPlugin("nonexistent-plugin");
|
||||
assertTrue(r.isUnknown() || r.isFailed());
|
||||
}
|
||||
|
||||
@Test
|
||||
void pluginVerificationStatusWorks() {
|
||||
PluginVerificationService pvs = new PluginVerificationService();
|
||||
pvs.registerPlugin("p1", "/tmp/p1.jar", "hash1");
|
||||
Map<String, Object> status = pvs.getStatus();
|
||||
assertNotNull(status.get("registeredPlugins"));
|
||||
}
|
||||
|
||||
// --- ConnectionThrottleService Tests ---
|
||||
|
||||
@Test
|
||||
void connectionThrottleAllowsNormalConnection() {
|
||||
ConnectionThrottleService throttle = new ConnectionThrottleService();
|
||||
ConnectionThrottleService.ThrottleResult r = throttle.tryAcquire("192.168.1.1");
|
||||
assertTrue(r.isAllowed());
|
||||
}
|
||||
|
||||
@Test
|
||||
void connectionThrottleBlocksExcessConnections() {
|
||||
ConnectionThrottleService throttle = new ConnectionThrottleService();
|
||||
for (int i = 0; i < 6; i++) {
|
||||
throttle.tryAcquire("10.0.0.99");
|
||||
}
|
||||
ConnectionThrottleService.ThrottleResult r = throttle.tryAcquire("10.0.0.99");
|
||||
assertFalse(r.isAllowed());
|
||||
}
|
||||
|
||||
@Test
|
||||
void connectionThrottleDifferentIPsAllowed() {
|
||||
ConnectionThrottleService throttle = new ConnectionThrottleService();
|
||||
throttle.tryAcquire("1.1.1.1");
|
||||
throttle.tryAcquire("2.2.2.2");
|
||||
ConnectionThrottleService.ThrottleResult r = throttle.tryAcquire("3.3.3.3");
|
||||
assertTrue(r.isAllowed());
|
||||
}
|
||||
|
||||
@Test
|
||||
void connectionThrottleStatusWorks() {
|
||||
ConnectionThrottleService throttle = new ConnectionThrottleService();
|
||||
throttle.tryAcquire("10.0.0.1");
|
||||
throttle.tryAcquire("10.0.0.1");
|
||||
throttle.tryAcquire("10.0.0.1");
|
||||
Map<String, Object> status = throttle.getStatus();
|
||||
assertNotNull(status.get("totalConnections"));
|
||||
}
|
||||
|
||||
// --- BackupIntegrityService Tests ---
|
||||
|
||||
@Test
|
||||
void backupIntegrityRegisterAndVerifyMissingFile() {
|
||||
BackupIntegrityService bis = new BackupIntegrityService();
|
||||
bis.registerBackup("world-backup", "/tmp/nonexistent-backup.zip", 1024L, "abc123");
|
||||
// File doesn't exist on disk, so returns failed (record exists, file missing)
|
||||
BackupIntegrityService.IntegrityResult r = bis.verifyBackup("world-backup");
|
||||
assertTrue(r.isFailed());
|
||||
}
|
||||
|
||||
@Test
|
||||
void backupIntegrityUnknownBackupIsMissing() {
|
||||
BackupIntegrityService bis = new BackupIntegrityService();
|
||||
// No record registered, so verify returns missing
|
||||
BackupIntegrityService.IntegrityResult r = bis.verifyBackup("never-registered-backup");
|
||||
assertTrue(r.isMissing());
|
||||
}
|
||||
|
||||
@Test
|
||||
void backupIntegrityUnknownBackup() {
|
||||
BackupIntegrityService bis = new BackupIntegrityService();
|
||||
BackupIntegrityService.IntegrityResult r = bis.verifyBackup("unknown-backup");
|
||||
assertFalse(r.isPassed());
|
||||
}
|
||||
|
||||
@Test
|
||||
void backupIntegrityStatusWorks() {
|
||||
BackupIntegrityService bis = new BackupIntegrityService();
|
||||
bis.registerBackup("b1", "/tmp/b1.zip", 500, "h1");
|
||||
bis.registerBackup("b2", "/tmp/b2.zip", 800, "h2");
|
||||
Map<String, Object> status = bis.getStatus();
|
||||
assertEquals(2, ((Number) status.get("totalBackups")).intValue());
|
||||
}
|
||||
|
||||
// --- AntiSkinSpoofService Tests ---
|
||||
|
||||
@Test
|
||||
void antiSkinSpoofDetectsImpersonation() {
|
||||
AntiSkinSpoofService skin = new AntiSkinSpoofService();
|
||||
// "Dream" is a known impersonation target
|
||||
AntiSkinSpoofService.SkinSpoofResult r = skin.checkSkin(
|
||||
"UnknownPlayer", "https://textures.minecraft.net/texture/abc", "classic", "skin-hash-1");
|
||||
// Not spoofing - different name
|
||||
assertTrue(r.isClean() || r.isSuspicious());
|
||||
}
|
||||
|
||||
@Test
|
||||
void antiSkinSpoofDetectsSuspiciousURL() {
|
||||
AntiSkinSpoofService skin = new AntiSkinSpoofService();
|
||||
AntiSkinSpoofService.SkinSpoofResult r = skin.checkSkin(
|
||||
"TestPlayer", "https://malicious-server.com/skin.png", "classic", "some-hash");
|
||||
assertTrue(r.isSuspicious() || r.isSpoofed());
|
||||
}
|
||||
|
||||
@Test
|
||||
void antiSkinSpoofDetectsRapidChange() {
|
||||
AntiSkinSpoofService skin = new AntiSkinSpoofService();
|
||||
for (int i = 0; i < 5; i++) {
|
||||
skin.checkSkin("FastChanger", "https://textures.minecraft.net/texture/" + i, "classic", "hash-" + i);
|
||||
}
|
||||
AntiSkinSpoofService.SkinSpoofResult r = skin.checkSkin(
|
||||
"FastChanger", "https://textures.minecraft.net/texture/999", "classic", "hash-999");
|
||||
assertTrue(r.isSuspicious() || r.isSpoofed());
|
||||
}
|
||||
|
||||
@Test
|
||||
void antiSkinSpoofNormalPlayerPasses() {
|
||||
AntiSkinSpoofService skin = new AntiSkinSpoofService();
|
||||
AntiSkinSpoofService.SkinSpoofResult r = skin.checkSkin(
|
||||
"NormalPlayer", "https://textures.minecraft.net/texture/aaaa", "classic", "normal-hash");
|
||||
assertTrue(r.isClean());
|
||||
}
|
||||
|
||||
@Test
|
||||
void antiSkinSpoofDetectsSlimMismatch() {
|
||||
AntiSkinSpoofService skin = new AntiSkinSpoofService();
|
||||
AntiSkinSpoofService.SkinSpoofResult r = skin.checkSkin(
|
||||
"ModelSwitcher", "https://textures.minecraft.net/texture/bbbb", "slim", "recent-slim-hash");
|
||||
r = skin.checkSkin(
|
||||
"ModelSwitcher", "https://textures.minecraft.net/texture/cccc", "classic", "recent-classic-hash");
|
||||
assertTrue(r.isSuspicious() || r.isClean());
|
||||
}
|
||||
|
||||
@Test
|
||||
void antiSkinSpoofStatusWorks() {
|
||||
AntiSkinSpoofService skin = new AntiSkinSpoofService();
|
||||
skin.checkSkin("P1", "https://textures.minecraft.net/texture/1", "classic", "h1");
|
||||
skin.checkSkin("P2", "https://textures.minecraft.net/texture/2", "classic", "h2");
|
||||
Map<String, Object> status = skin.getStatus();
|
||||
assertNotNull(status.get("totalChecks"));
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user