超进化 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:
Peijun Zhao
2026-05-10 22:02:42 +08:00
parent 1d297ba095
commit b271eda8d5
38 changed files with 2316 additions and 15 deletions
+10 -4
View File
@@ -77,7 +77,7 @@
</td> </td>
<td align="center" width="140"> <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> <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> <sub>安全模块</sub>
</td> </td>
<td align="center" width="140"> <td align="center" width="140">
@@ -148,7 +148,7 @@
</td> </td>
<td width="50%"> <td width="50%">
<h3>🛡️ 全栈纵深防御</h3> <h3>🛡️ 全栈纵深防御</h3>
<p>从 MC 协议层 → 应用 WAF → 系统防火墙 → Cloudflare 边缘节点。<b>30+ 安全模块</b>覆盖 L3-L7,零信任架构、蜜罐诱捕一应俱全。</p> <p>从 MC 协议层 → 应用 WAF → 系统防火墙 → Cloudflare 边缘节点。<b>85+ 安全模块</b>覆盖 L3-L7,零信任架构、蜜罐诱捕一应俱全。</p>
</td> </td>
</tr> </tr>
<tr> <tr>
@@ -261,7 +261,7 @@ cell.pressure = cell.pressure × 0.55 + pulse.heat × 0.75
│ │ Process │ │ DDoS/IDS │ │ DeepSeek │ │ │ │ Process │ │ DDoS/IDS │ │ DeepSeek │ │
│ │ Resource │ │ WAF/Honey│ │ Anomaly │ │ │ │ Resource │ │ WAF/Honey│ │ Anomaly │ │
│ │ Connect │ │ ZeroTrust│ │ Predict │ │ │ │ Connect │ │ ZeroTrust│ │ Predict │ │
│ │ Log │ │ 30+ svc │ │ Adaptive │ │ │ │ Log │ │ 85+ svc │ │ Adaptive │ │
│ └────┬─────┘ └────┬─────┘ └────┬─────┘ │ │ └────┬─────┘ └────┬─────┘ └────┬─────┘ │
│ │ │ │ │ │ │ │ │ │
│ └─────────┬───┴─────────────┘ │ │ └─────────┬───┴─────────────┘ │
@@ -810,7 +810,7 @@ serverguard
### 安全防护层 (`com.aluer.security`) ### 安全防护层 (`com.aluer.security`)
这是系统中规模最大的包,包含 65+ 个安全服务(含 25 个超进化 v3.2 新增模块): 这是系统中规模最大的包,包含 85+ 个安全服务(含 31 个超进化 v3.1 / v3.2 / v3.3 新增模块):
| 类别 | 类 | 功能 | | 类别 | 类 | 功能 |
|------|-----|------| |------|-----|------|
@@ -885,6 +885,12 @@ serverguard
| | `AntiDupeDetectionService` | 物品复制检测:堆叠溢出、高价值物品暴涨、9种已知复制方法监测 | | | `AntiDupeDetectionService` | 物品复制检测:堆叠溢出、高价值物品暴涨、9种已知复制方法监测 |
| | `CrashExploitProtectionService` | 崩溃漏洞防护:超大包/负长度/NBT嵌套/书与笔/Sign炸弹/Tile Entity洪水 | | | `CrashExploitProtectionService` | 崩溃漏洞防护:超大包/负长度/NBT嵌套/书与笔/Sign炸弹/Tile Entity洪水 |
| | `LagMachineDetectionService` | 卡服机检测:Observer链/TNT堆叠/红石密度/实体刷怪/掉落方块洪流 | | | `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`) ### 运营控制层 (`com.aluer.console`)
+10 -4
View File
@@ -83,7 +83,7 @@
</td> </td>
<td align="center" width="140"> <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> <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> <sub>Security Modules</sub>
</td> </td>
<td align="center" width="140"> <td align="center" width="140">
@@ -154,7 +154,7 @@
</td> </td>
<td width="50%"> <td width="50%">
<h3>🛡️ Full-Stack Defense</h3> <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> </td>
</tr> </tr>
<tr> <tr>
@@ -269,7 +269,7 @@ cell.pressure = cell.pressure × 0.55 + pulse.heat × 0.75
│ │ Process │ │ DDoS/IDS │ │ DeepSeek │ │ │ │ Process │ │ DDoS/IDS │ │ DeepSeek │ │
│ │ Resource │ │ WAF/Honey│ │ Anomaly │ │ │ │ Resource │ │ WAF/Honey│ │ Anomaly │ │
│ │ Connect │ │ ZeroTrust│ │ Predict │ │ │ │ Connect │ │ ZeroTrust│ │ Predict │ │
│ │ Log │ │ 30+ svc │ │ Adaptive │ │ │ │ Log │ │ 85+ svc │ │ Adaptive │ │
│ └────┬─────┘ └────┬─────┘ └────┬─────┘ │ │ └────┬─────┘ └────┬─────┘ └────┬─────┘ │
│ │ │ │ │ │ │ │ │ │
│ └─────────┬───┴─────────────┘ │ │ └─────────┬───┴─────────────┘ │
@@ -667,7 +667,7 @@ serverguard
### Security Defense Layer (`com.aluer.security`) ### 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 | | 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 | | | `AntiDupeDetectionService` | Item duplication detection: stack overflow, high-value item surges, 9 known methods |
| | `CrashExploitProtectionService` | Crash exploit protection: oversized packets, NBT bombs, book/sign exploits | | | `CrashExploitProtectionService` | Crash exploit protection: oversized packets, NBT bombs, book/sign exploits |
| | `LagMachineDetectionService` | Lag machine detection: observer chains, TNT stacks, entity spam, falling blocks | | | `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`) ### Operations Control Layer (`com.aluer.console`)
+61
View File
@@ -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/crash-exploit/status` | GET | 崩溃漏洞防护状态(超大包、NBT炸弹、书与笔攻击) |
| `/api/security/lag-machine/status` | GET | 卡服机检测状态(Observer链、TNT堆、红石密度) | | `/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 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 KernelConfig kernel = new KernelConfig();
private TaskBusConfig taskBus = new TaskBusConfig(); private TaskBusConfig taskBus = new TaskBusConfig();
private SelfHealingConfig selfHealing = new SelfHealingConfig(); private SelfHealingConfig selfHealing = new SelfHealingConfig();
private SuperEvolutionConfig superEvolution = new SuperEvolutionConfig();
public boolean isEnabled() { return enabled; } public boolean isEnabled() { return enabled; }
public void setEnabled(boolean enabled) { this.enabled = 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 void setTaskBus(TaskBusConfig taskBus) { this.taskBus = taskBus; }
public SelfHealingConfig getSelfHealing() { return selfHealing; } public SelfHealingConfig getSelfHealing() { return selfHealing; }
public void setSelfHealing(SelfHealingConfig selfHealing) { this.selfHealing = 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 { public static class MinecraftDefenseConfig {
@@ -865,6 +868,103 @@ public class ServerGuardConfig {
public void setHandshakeTtlSeconds(int handshakeTtlSeconds) { this.handshakeTtlSeconds = handshakeTtlSeconds; } 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 { public static class AnnouncementConfig {
private boolean enabled = false; private boolean enabled = false;
private int intervalSeconds = 300; private int intervalSeconds = 300;
@@ -1,5 +1,7 @@
package com.aluer.security; package com.aluer.security;
import com.aluer.config.ServerGuardConfig;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import java.io.BufferedReader; import java.io.BufferedReader;
@@ -15,6 +17,7 @@ import java.util.concurrent.atomic.AtomicLong;
@Service @Service
public class ARPSpoofDetectionService { public class ARPSpoofDetectionService {
private final ServerGuardConfig config;
private final Map<String, String> arpCache = new ConcurrentHashMap<>(); private final Map<String, String> arpCache = new ConcurrentHashMap<>();
private final Map<String, List<ARPEvent>> arpEvents = new ConcurrentHashMap<>(); private final Map<String, List<ARPEvent>> arpEvents = new ConcurrentHashMap<>();
private final ScheduledExecutorService scheduler = Executors.newSingleThreadScheduledExecutor(); private final ScheduledExecutorService scheduler = Executors.newSingleThreadScheduledExecutor();
@@ -23,8 +26,16 @@ public class ARPSpoofDetectionService {
private static final int BASELINE_SAMPLES = 3; private static final int BASELINE_SAMPLES = 3;
public ARPSpoofDetectionService() { public ARPSpoofDetectionService() {
buildBaseline(); this(new ServerGuardConfig());
scheduler.scheduleAtFixedRate(this::scanARPTable, 30, 60, TimeUnit.SECONDS); }
@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() { private void buildBaseline() {
@@ -35,6 +46,10 @@ public class ARPSpoofDetectionService {
} }
public ARPScanResult scanARPTable() { public ARPScanResult scanARPTable() {
if (!config.getSecurity().getSuperEvolution().isArpSpoof()) {
return new ARPScanResult(List.of(), arpCache.size(), totalSpoofDetections.get());
}
Map<String, String> current = readARPTable(); Map<String, String> current = readARPTable();
List<ARPEvent> alerts = new ArrayList<>(); List<ARPEvent> alerts = new ArrayList<>();
@@ -1,5 +1,7 @@
package com.aluer.security; package com.aluer.security;
import com.aluer.config.ServerGuardConfig;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import java.time.Instant; import java.time.Instant;
@@ -13,6 +15,7 @@ import java.util.concurrent.atomic.AtomicLong;
@Service @Service
public class AntiBotDetectionService { public class AntiBotDetectionService {
private final ServerGuardConfig config;
private final Map<String, PlayerJoinRecord> joinHistory = new ConcurrentHashMap<>(); private final Map<String, PlayerJoinRecord> joinHistory = new ConcurrentHashMap<>();
private final Map<String, List<Instant>> ipJoinTimeline = new ConcurrentHashMap<>(); private final Map<String, List<Instant>> ipJoinTimeline = new ConcurrentHashMap<>();
private final Map<String, Instant> blockedBots = new ConcurrentHashMap<>(); private final Map<String, Instant> blockedBots = new ConcurrentHashMap<>();
@@ -47,10 +50,20 @@ public class AntiBotDetectionService {
private final AtomicLong totalBotsBlocked = new AtomicLong(0); private final AtomicLong totalBotsBlocked = new AtomicLong(0);
public AntiBotDetectionService() { public AntiBotDetectionService() {
this(new ServerGuardConfig());
}
@Autowired
public AntiBotDetectionService(ServerGuardConfig config) {
this.config = config;
scheduler.scheduleAtFixedRate(this::cleanupExpired, 120, 300, TimeUnit.SECONDS); scheduler.scheduleAtFixedRate(this::cleanupExpired, 120, 300, TimeUnit.SECONDS);
} }
public BotCheckResult checkPlayerJoin(String playerName, String ip, String clientBrand) { public BotCheckResult checkPlayerJoin(String playerName, String ip, String clientBrand) {
if (!config.getSecurity().getSuperEvolution().isAntiBot()) {
return BotCheckResult.clean();
}
if (blockedBots.containsKey(ip)) { if (blockedBots.containsKey(ip)) {
Instant until = blockedBots.get(ip); Instant until = blockedBots.get(ip);
if (Instant.now().isAfter(until)) { if (Instant.now().isAfter(until)) {
@@ -1,5 +1,6 @@
package com.aluer.security; package com.aluer.security;
import com.aluer.config.ServerGuardConfig;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import java.time.Instant; import java.time.Instant;
@@ -13,6 +14,8 @@ import java.util.concurrent.atomic.AtomicLong;
@Service @Service
public class AntiDupeDetectionService { public class AntiDupeDetectionService {
private final ServerGuardConfig config;
private final Map<String, PlayerInventorySnapshot> inventorySnapshots = new ConcurrentHashMap<>(); private final Map<String, PlayerInventorySnapshot> inventorySnapshots = new ConcurrentHashMap<>();
private final Map<String, List<DupeEvent>> dupeEvents = new ConcurrentHashMap<>(); private final Map<String, List<DupeEvent>> dupeEvents = new ConcurrentHashMap<>();
private final Map<String, Instant> flaggedPlayers = new ConcurrentHashMap<>(); private final Map<String, Instant> flaggedPlayers = new ConcurrentHashMap<>();
@@ -48,11 +51,20 @@ public class AntiDupeDetectionService {
} }
public AntiDupeDetectionService() { public AntiDupeDetectionService() {
this(new ServerGuardConfig());
}
public AntiDupeDetectionService(ServerGuardConfig config) {
this.config = config;
scheduler.scheduleAtFixedRate(this::cleanupOldData, 300, 600, TimeUnit.SECONDS); scheduler.scheduleAtFixedRate(this::cleanupOldData, 300, 600, TimeUnit.SECONDS);
} }
public DupeCheckResult checkInventoryChange(String playerName, String itemType, int newCount, int oldCount, public DupeCheckResult checkInventoryChange(String playerName, String itemType, int newCount, int oldCount,
String containerType, int x, int y, int z) { String containerType, int x, int y, int z) {
if (!config.getSecurity().getSuperEvolution().isAntiDupe()) {
return DupeCheckResult.clean();
}
if (flaggedPlayers.containsKey(playerName)) { if (flaggedPlayers.containsKey(playerName)) {
if (Instant.now().isAfter(flaggedPlayers.get(playerName))) { if (Instant.now().isAfter(flaggedPlayers.get(playerName))) {
flaggedPlayers.remove(playerName); flaggedPlayers.remove(playerName);
@@ -1,5 +1,6 @@
package com.aluer.security; package com.aluer.security;
import com.aluer.config.ServerGuardConfig;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import java.time.Instant; import java.time.Instant;
@@ -13,6 +14,8 @@ import java.util.concurrent.atomic.AtomicLong;
@Service @Service
public class AntiFlyDetectionService { public class AntiFlyDetectionService {
private final ServerGuardConfig config;
private final Map<String, PlayerMovementData> movementData = new ConcurrentHashMap<>(); private final Map<String, PlayerMovementData> movementData = new ConcurrentHashMap<>();
private final Map<String, Instant> flaggedPlayers = new ConcurrentHashMap<>(); private final Map<String, Instant> flaggedPlayers = new ConcurrentHashMap<>();
private final ScheduledExecutorService scheduler = Executors.newSingleThreadScheduledExecutor(); private final ScheduledExecutorService scheduler = Executors.newSingleThreadScheduledExecutor();
@@ -29,11 +32,20 @@ public class AntiFlyDetectionService {
private static final long FLAG_DURATION_SECONDS = 3600; private static final long FLAG_DURATION_SECONDS = 3600;
public AntiFlyDetectionService() { public AntiFlyDetectionService() {
this(new ServerGuardConfig());
}
public AntiFlyDetectionService(ServerGuardConfig config) {
this.config = config;
scheduler.scheduleAtFixedRate(this::cleanupOldData, 120, 300, TimeUnit.SECONDS); scheduler.scheduleAtFixedRate(this::cleanupOldData, 120, 300, TimeUnit.SECONDS);
} }
public FlyCheckResult checkMovement(String playerName, double fromX, double fromY, double fromZ, public FlyCheckResult checkMovement(String playerName, double fromX, double fromY, double fromZ,
double toX, double toY, double toZ, boolean onGround, String gameMode) { double toX, double toY, double toZ, boolean onGround, String gameMode) {
if (!config.getSecurity().getSuperEvolution().isAntiFly()) {
return FlyCheckResult.clean();
}
if (flaggedPlayers.containsKey(playerName)) { if (flaggedPlayers.containsKey(playerName)) {
if (Instant.now().isAfter(flaggedPlayers.get(playerName))) { if (Instant.now().isAfter(flaggedPlayers.get(playerName))) {
flaggedPlayers.remove(playerName); flaggedPlayers.remove(playerName);
@@ -1,5 +1,6 @@
package com.aluer.security; package com.aluer.security;
import com.aluer.config.ServerGuardConfig;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import java.time.Instant; import java.time.Instant;
@@ -13,6 +14,8 @@ import java.util.concurrent.atomic.AtomicLong;
@Service @Service
public class AntiGriefDetectionService { public class AntiGriefDetectionService {
private final ServerGuardConfig config;
private final Map<String, PlayerActivity> playerActivities = new ConcurrentHashMap<>(); private final Map<String, PlayerActivity> playerActivities = new ConcurrentHashMap<>();
private final Map<String, List<GriefEvent>> griefEvents = new ConcurrentHashMap<>(); private final Map<String, List<GriefEvent>> griefEvents = new ConcurrentHashMap<>();
private final Map<String, Instant> flaggedPlayers = new ConcurrentHashMap<>(); private final Map<String, Instant> flaggedPlayers = new ConcurrentHashMap<>();
@@ -39,10 +42,19 @@ public class AntiGriefDetectionService {
); );
public AntiGriefDetectionService() { public AntiGriefDetectionService() {
this(new ServerGuardConfig());
}
public AntiGriefDetectionService(ServerGuardConfig config) {
this.config = config;
scheduler.scheduleAtFixedRate(this::cleanupOldData, 120, 300, TimeUnit.SECONDS); scheduler.scheduleAtFixedRate(this::cleanupOldData, 120, 300, TimeUnit.SECONDS);
} }
public GriefCheckResult checkBlockBreak(String playerName, String blockType, int x, int y, int z, String world) { 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()); PlayerActivity activity = playerActivities.computeIfAbsent(playerName, k -> new PlayerActivity());
activity.recordBlockBreak(blockType, x, y, z, world); 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; package com.aluer.security;
import com.aluer.config.ServerGuardConfig;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import java.time.Instant; import java.time.Instant;
@@ -13,6 +14,8 @@ import java.util.concurrent.atomic.AtomicLong;
@Service @Service
public class AntiXrayDetectionService { public class AntiXrayDetectionService {
private final ServerGuardConfig config;
private final Map<String, PlayerMiningData> miningData = new ConcurrentHashMap<>(); private final Map<String, PlayerMiningData> miningData = new ConcurrentHashMap<>();
private final Map<String, Instant> flaggedPlayers = new ConcurrentHashMap<>(); private final Map<String, Instant> flaggedPlayers = new ConcurrentHashMap<>();
private final ScheduledExecutorService scheduler = Executors.newSingleThreadScheduledExecutor(); private final ScheduledExecutorService scheduler = Executors.newSingleThreadScheduledExecutor();
@@ -47,10 +50,19 @@ public class AntiXrayDetectionService {
); );
public AntiXrayDetectionService() { public AntiXrayDetectionService() {
this(new ServerGuardConfig());
}
public AntiXrayDetectionService(ServerGuardConfig config) {
this.config = config;
scheduler.scheduleAtFixedRate(this::cleanupOldData, 120, 300, TimeUnit.SECONDS); scheduler.scheduleAtFixedRate(this::cleanupOldData, 120, 300, TimeUnit.SECONDS);
} }
public XrayCheckResult checkBlockBreak(String playerName, String blockType, int x, int y, int z, String world) { 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 (flaggedPlayers.containsKey(playerName)) {
if (Instant.now().isAfter(flaggedPlayers.get(playerName))) { if (Instant.now().isAfter(flaggedPlayers.get(playerName))) {
flaggedPlayers.remove(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; package com.aluer.security;
import com.aluer.config.ServerGuardConfig;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import java.time.Instant; import java.time.Instant;
@@ -13,6 +15,7 @@ import java.util.concurrent.atomic.AtomicLong;
@Service @Service
public class BruteForceProtectionService { public class BruteForceProtectionService {
private final ServerGuardConfig config;
private final Map<String, FailureRecord> failureTracker = new ConcurrentHashMap<>(); private final Map<String, FailureRecord> failureTracker = new ConcurrentHashMap<>();
private final Map<String, Instant> blockedAccounts = new ConcurrentHashMap<>(); private final Map<String, Instant> blockedAccounts = new ConcurrentHashMap<>();
private final Map<String, List<Instant>> ipAttemptTimeline = 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); private final AtomicLong totalBruteForceDetections = new AtomicLong(0);
public BruteForceProtectionService() { public BruteForceProtectionService() {
this(new ServerGuardConfig());
}
@Autowired
public BruteForceProtectionService(ServerGuardConfig config) {
this.config = config;
scheduler.scheduleAtFixedRate(this::cleanupExpired, 60, 120, TimeUnit.SECONDS); scheduler.scheduleAtFixedRate(this::cleanupExpired, 60, 120, TimeUnit.SECONDS);
} }
public AuthResult checkLoginAttempt(String username, String ip, String source) { public AuthResult checkLoginAttempt(String username, String ip, String source) {
if (!config.getSecurity().getSuperEvolution().isBruteForce()) {
return AuthResult.allowed(0);
}
if (isBlocked(username)) { if (isBlocked(username)) {
return AuthResult.blocked("Account " + username + " is temporarily locked due to excessive failed attempts"); return AuthResult.blocked("Account " + username + " is temporarily locked due to excessive failed attempts");
} }
@@ -1,5 +1,6 @@
package com.aluer.security; package com.aluer.security;
import com.aluer.config.ServerGuardConfig;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import java.time.Instant; import java.time.Instant;
@@ -10,9 +11,18 @@ import java.util.concurrent.atomic.AtomicLong;
@Service @Service
public class CSPEnforcementService { public class CSPEnforcementService {
private final ServerGuardConfig config;
private final Map<String, List<CSPViolation>> violations = new ConcurrentHashMap<>(); private final Map<String, List<CSPViolation>> violations = new ConcurrentHashMap<>();
private final AtomicLong totalViolations = new AtomicLong(0); 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("; ", private static final String DEFAULT_CSP_HEADER = String.join("; ",
"default-src 'self'", "default-src 'self'",
"script-src 'self' 'unsafe-inline' 'unsafe-eval'", "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) { public CSPCheckResult checkRequest(String uri, Map<String, String> headers, String body) {
if (!config.getSecurity().getSuperEvolution().isCsp()) return CSPCheckResult.clean();
List<String> matched = new ArrayList<>(); List<String> matched = new ArrayList<>();
int score = 0; int score = 0;
@@ -1,5 +1,6 @@
package com.aluer.security; package com.aluer.security;
import com.aluer.config.ServerGuardConfig;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import java.time.Instant; import java.time.Instant;
@@ -13,15 +14,26 @@ import java.util.concurrent.atomic.AtomicLong;
@Service @Service
public class ComplianceScannerService { public class ComplianceScannerService {
private final ServerGuardConfig config;
private final Map<String, List<ComplianceFinding>> findings = new ConcurrentHashMap<>(); private final Map<String, List<ComplianceFinding>> findings = new ConcurrentHashMap<>();
private final AtomicLong totalFindings = new AtomicLong(0); private final AtomicLong totalFindings = new AtomicLong(0);
private final ScheduledExecutorService scheduler = Executors.newSingleThreadScheduledExecutor(); private final ScheduledExecutorService scheduler = Executors.newSingleThreadScheduledExecutor();
public ComplianceScannerService() { public ComplianceScannerService() {
this(new ServerGuardConfig());
}
public ComplianceScannerService(ServerGuardConfig config) {
this.config = config;
scheduler.scheduleAtFixedRate(this::runComplianceScan, 120, 86400, TimeUnit.SECONDS); scheduler.scheduleAtFixedRate(this::runComplianceScan, 120, 86400, TimeUnit.SECONDS);
} }
public ComplianceReport runComplianceScan() { public ComplianceReport runComplianceScan() {
if (!config.getSecurity().getSuperEvolution().isCompliance()) {
return new ComplianceReport(Instant.now(), List.of(), 0.0);
}
List<ComplianceFinding> allFindings = new ArrayList<>(); List<ComplianceFinding> allFindings = new ArrayList<>();
Instant scanTime = Instant.now(); 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; package com.aluer.security;
import com.aluer.config.ServerGuardConfig;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import java.time.Instant; import java.time.Instant;
@@ -10,6 +11,8 @@ import java.util.concurrent.atomic.AtomicLong;
@Service @Service
public class CrashExploitProtectionService { public class CrashExploitProtectionService {
private final ServerGuardConfig config;
private final Map<String, List<CrashAttempt>> crashAttempts = new ConcurrentHashMap<>(); private final Map<String, List<CrashAttempt>> crashAttempts = new ConcurrentHashMap<>();
private final Map<String, Instant> blockedSources = new ConcurrentHashMap<>(); private final Map<String, Instant> blockedSources = new ConcurrentHashMap<>();
private final AtomicLong totalDetections = new AtomicLong(0); private final AtomicLong totalDetections = new AtomicLong(0);
@@ -46,7 +49,19 @@ public class CrashExploitProtectionService {
"Particle crash", "Particle flood causing GPU/CPU exhaustion", 75, false)); "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) { public CrashCheckResult checkPacket(String packetType, int packetSize, String sourceIP) {
if (!config.getSecurity().getSuperEvolution().isCrashExploit()) {
return CrashCheckResult.clean();
}
if (blockedSources.containsKey(sourceIP)) { if (blockedSources.containsKey(sourceIP)) {
if (Instant.now().isAfter(blockedSources.get(sourceIP))) { if (Instant.now().isAfter(blockedSources.get(sourceIP))) {
blockedSources.remove(sourceIP); blockedSources.remove(sourceIP);
@@ -1,5 +1,7 @@
package com.aluer.security; package com.aluer.security;
import com.aluer.config.ServerGuardConfig;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import java.time.Instant; import java.time.Instant;
@@ -13,6 +15,7 @@ import java.util.concurrent.atomic.AtomicLong;
@Service @Service
public class DNSTunnelDetectionService { public class DNSTunnelDetectionService {
private final ServerGuardConfig config;
private final Map<String, DNSQueryTracker> queryTrackers = new ConcurrentHashMap<>(); private final Map<String, DNSQueryTracker> queryTrackers = new ConcurrentHashMap<>();
private final Map<String, Instant> blockedSources = new ConcurrentHashMap<>(); private final Map<String, Instant> blockedSources = new ConcurrentHashMap<>();
private final ScheduledExecutorService scheduler = Executors.newSingleThreadScheduledExecutor(); private final ScheduledExecutorService scheduler = Executors.newSingleThreadScheduledExecutor();
@@ -31,10 +34,20 @@ public class DNSTunnelDetectionService {
".pw", ".club", ".work", ".date", ".bid", ".surf"); ".pw", ".club", ".work", ".date", ".bid", ".surf");
public DNSTunnelDetectionService() { public DNSTunnelDetectionService() {
this(new ServerGuardConfig());
}
@Autowired
public DNSTunnelDetectionService(ServerGuardConfig config) {
this.config = config;
scheduler.scheduleAtFixedRate(this::cleanupExpired, 120, 300, TimeUnit.SECONDS); scheduler.scheduleAtFixedRate(this::cleanupExpired, 120, 300, TimeUnit.SECONDS);
} }
public TunnelCheckResult checkDNSQuery(String sourceIP, String domain, String queryType) { public TunnelCheckResult checkDNSQuery(String sourceIP, String domain, String queryType) {
if (!config.getSecurity().getSuperEvolution().isDnsTunnel()) {
return TunnelCheckResult.clean();
}
if (blockedSources.containsKey(sourceIP)) { if (blockedSources.containsKey(sourceIP)) {
if (Instant.now().isAfter(blockedSources.get(sourceIP))) { if (Instant.now().isAfter(blockedSources.get(sourceIP))) {
blockedSources.remove(sourceIP); blockedSources.remove(sourceIP);
@@ -1,5 +1,6 @@
package com.aluer.security; package com.aluer.security;
import com.aluer.config.ServerGuardConfig;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import java.time.Instant; import java.time.Instant;
@@ -11,9 +12,18 @@ import java.util.regex.Pattern;
@Service @Service
public class DataLossPreventionService { public class DataLossPreventionService {
private final ServerGuardConfig config;
private final Map<String, List<DLPEvent>> detections = new ConcurrentHashMap<>(); private final Map<String, List<DLPEvent>> detections = new ConcurrentHashMap<>();
private final AtomicLong totalDetections = new AtomicLong(0); 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<>(); private static final List<DLPRule> RULES = new ArrayList<>();
static { static {
@@ -67,6 +77,7 @@ public class DataLossPreventionService {
} }
public DLPCheckResult scan(String content, String source, String channel) { 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(); if (content == null || content.trim().isEmpty()) return DLPCheckResult.clean();
Map<DLPLevel, List<String>> findings = new LinkedHashMap<>(); Map<DLPLevel, List<String>> findings = new LinkedHashMap<>();
@@ -1,5 +1,6 @@
package com.aluer.security; package com.aluer.security;
import com.aluer.config.ServerGuardConfig;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import java.time.Instant; import java.time.Instant;
@@ -10,10 +11,19 @@ import java.util.concurrent.atomic.AtomicLong;
@Service @Service
public class DatabaseFirewallService { public class DatabaseFirewallService {
private final ServerGuardConfig config;
private final Map<String, List<QueryEvent>> queryLog = new ConcurrentHashMap<>(); private final Map<String, List<QueryEvent>> queryLog = new ConcurrentHashMap<>();
private final AtomicLong totalBlocked = new AtomicLong(0); private final AtomicLong totalBlocked = new AtomicLong(0);
private final AtomicLong totalQueries = 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( private static final List<String> DANGEROUS_KEYWORDS = List.of(
"DROP TABLE", "DROP DATABASE", "TRUNCATE TABLE", "DELETE FROM", "DROP TABLE", "DROP DATABASE", "TRUNCATE TABLE", "DELETE FROM",
"ALTER TABLE", "ALTER DATABASE", "CREATE USER", "GRANT ALL", "ALTER TABLE", "ALTER DATABASE", "CREATE USER", "GRANT ALL",
@@ -28,6 +38,7 @@ public class DatabaseFirewallService {
private static final int MAX_JOIN_COUNT = 10; private static final int MAX_JOIN_COUNT = 10;
public QueryCheckResult checkQuery(String sql, String source, String database) { 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(); if (sql == null || sql.trim().isEmpty()) return QueryCheckResult.clean();
totalQueries.incrementAndGet(); totalQueries.incrementAndGet();
@@ -1,5 +1,7 @@
package com.aluer.security; package com.aluer.security;
import com.aluer.config.ServerGuardConfig;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import java.time.Instant; import java.time.Instant;
@@ -11,6 +13,7 @@ import java.util.regex.Pattern;
@Service @Service
public class ExploitSignatureService { public class ExploitSignatureService {
private final ServerGuardConfig config;
private final Map<String, List<ExploitEvent>> detections = new ConcurrentHashMap<>(); private final Map<String, List<ExploitEvent>> detections = new ConcurrentHashMap<>();
private final Map<String, Instant> blockedSources = new ConcurrentHashMap<>(); private final Map<String, Instant> blockedSources = new ConcurrentHashMap<>();
private final AtomicLong totalDetections = new AtomicLong(0); private final AtomicLong totalDetections = new AtomicLong(0);
@@ -75,7 +78,20 @@ public class ExploitSignatureService {
"Suspicious server.properties tampering")); "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) { 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 (content == null || content.trim().isEmpty()) return ExploitCheckResult.clean();
if (blockedSources.containsKey(sourceIP)) { if (blockedSources.containsKey(sourceIP)) {
@@ -1,5 +1,6 @@
package com.aluer.security; package com.aluer.security;
import com.aluer.config.ServerGuardConfig;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import java.io.*; import java.io.*;
@@ -12,18 +13,31 @@ import java.util.concurrent.atomic.AtomicLong;
@Service @Service
public class ForensicsCollectorService { public class ForensicsCollectorService {
private final ServerGuardConfig config;
private final Map<String, ForensicsCase> cases = new ConcurrentHashMap<>(); private final Map<String, ForensicsCase> cases = new ConcurrentHashMap<>();
private final Map<String, List<ForensicEvidence>> evidenceStore = new ConcurrentHashMap<>(); private final Map<String, List<ForensicEvidence>> evidenceStore = new ConcurrentHashMap<>();
private final AtomicLong totalEvidenceCollected = new AtomicLong(0); private final AtomicLong totalEvidenceCollected = new AtomicLong(0);
private final String evidenceDir = "./forensics"; private final String evidenceDir = "./forensics";
public ForensicsCollectorService() { public ForensicsCollectorService() {
this(new ServerGuardConfig());
}
public ForensicsCollectorService(ServerGuardConfig config) {
this.config = config;
try { try {
Files.createDirectories(Path.of(evidenceDir)); Files.createDirectories(Path.of(evidenceDir));
} catch (IOException ignored) {} } catch (IOException ignored) {}
} }
public ForensicsCase openCase(String caseName, String reason, String operator) { 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); String caseId = "CASE-" + Instant.now().getEpochSecond() + "-" + randomHex(6);
ForensicsCase fCase = new ForensicsCase(caseId, caseName, reason, operator, Instant.now()); ForensicsCase fCase = new ForensicsCase(caseId, caseName, reason, operator, Instant.now());
cases.put(caseId, fCase); 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; package com.aluer.security;
import com.aluer.config.ServerGuardConfig;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import java.time.Instant; import java.time.Instant;
@@ -10,6 +11,8 @@ import java.util.concurrent.atomic.AtomicLong;
@Service @Service
public class IncidentResponseService { public class IncidentResponseService {
private final ServerGuardConfig config;
private final Map<String, Incident> activeIncidents = new ConcurrentHashMap<>(); private final Map<String, Incident> activeIncidents = new ConcurrentHashMap<>();
private final Map<String, List<ResponseAction>> actionLog = new ConcurrentHashMap<>(); private final Map<String, List<ResponseAction>> actionLog = new ConcurrentHashMap<>();
private final AtomicLong totalIncidents = new AtomicLong(0); private final AtomicLong totalIncidents = new AtomicLong(0);
@@ -60,7 +63,21 @@ public class IncidentResponseService {
), IncidentSeverity.CRITICAL)); ), 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) { 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); String incidentId = "INC-" + Instant.now().getEpochSecond() + "-" + randomHex(4);
ResponsePlaybook playbook = PLAYBOOKS.getOrDefault(type, null); ResponsePlaybook playbook = PLAYBOOKS.getOrDefault(type, null);
Incident incident = new Incident(incidentId, type, description, source, severity, playbook, Instant.now()); Incident incident = new Incident(incidentId, type, description, source, severity, playbook, Instant.now());
@@ -1,5 +1,7 @@
package com.aluer.security; package com.aluer.security;
import com.aluer.config.ServerGuardConfig;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import javax.crypto.Mac; import javax.crypto.Mac;
@@ -17,12 +19,19 @@ public class JwtAuthService {
private static final long DEFAULT_TTL_SECONDS = 3600; private static final long DEFAULT_TTL_SECONDS = 3600;
private static final long MAX_TTL_SECONDS = 86400; private static final long MAX_TTL_SECONDS = 86400;
private final ServerGuardConfig config;
private final String secret; private final String secret;
private final Map<String, TokenEntry> tokenStore = new ConcurrentHashMap<>(); private final Map<String, TokenEntry> tokenStore = new ConcurrentHashMap<>();
private final Map<String, List<String>> userTokens = new ConcurrentHashMap<>(); private final Map<String, List<String>> userTokens = new ConcurrentHashMap<>();
private final Map<String, Instant> revokedTokens = new ConcurrentHashMap<>(); private final Map<String, Instant> revokedTokens = new ConcurrentHashMap<>();
public JwtAuthService() { public JwtAuthService() {
this(new ServerGuardConfig());
}
@Autowired
public JwtAuthService(ServerGuardConfig config) {
this.config = config;
byte[] keyBytes = new byte[64]; byte[] keyBytes = new byte[64];
SECURE_RANDOM.nextBytes(keyBytes); SECURE_RANDOM.nextBytes(keyBytes);
this.secret = Base64.getUrlEncoder().withoutPadding().encodeToString(keyBytes); this.secret = Base64.getUrlEncoder().withoutPadding().encodeToString(keyBytes);
@@ -64,6 +73,10 @@ public class JwtAuthService {
} }
public TokenValidationResult validateToken(String token) { 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(".")) { if (token == null || !token.contains(".")) {
return TokenValidationResult.invalid("Malformed token"); return TokenValidationResult.invalid("Malformed token");
} }
@@ -1,5 +1,6 @@
package com.aluer.security; package com.aluer.security;
import com.aluer.config.ServerGuardConfig;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import java.time.Instant; import java.time.Instant;
@@ -13,6 +14,8 @@ import java.util.concurrent.atomic.AtomicLong;
@Service @Service
public class LagMachineDetectionService { public class LagMachineDetectionService {
private final ServerGuardConfig config;
private final Map<String, List<LagEvent>> lagEvents = new ConcurrentHashMap<>(); private final Map<String, List<LagEvent>> lagEvents = new ConcurrentHashMap<>();
private final Map<String, Instant> flaggedPlayers = new ConcurrentHashMap<>(); private final Map<String, Instant> flaggedPlayers = new ConcurrentHashMap<>();
private final Map<String, ChunkActivity> chunkActivity = new ConcurrentHashMap<>(); private final Map<String, ChunkActivity> chunkActivity = new ConcurrentHashMap<>();
@@ -47,10 +50,19 @@ public class LagMachineDetectionService {
); );
public LagMachineDetectionService() { public LagMachineDetectionService() {
this(new ServerGuardConfig());
}
public LagMachineDetectionService(ServerGuardConfig config) {
this.config = config;
scheduler.scheduleAtFixedRate(this::cleanupOldData, 120, 300, TimeUnit.SECONDS); scheduler.scheduleAtFixedRate(this::cleanupOldData, 120, 300, TimeUnit.SECONDS);
} }
public LagCheckResult checkBlockPlace(String playerName, String blockType, int x, int y, int z, String world) { 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; String chunkKey = (x >> 4) + ":" + (z >> 4) + ":" + world;
ChunkActivity chunk = chunkActivity.computeIfAbsent(chunkKey, k -> new ChunkActivity()); ChunkActivity chunk = chunkActivity.computeIfAbsent(chunkKey, k -> new ChunkActivity());
chunk.recordBlockPlace(blockType, playerName); chunk.recordBlockPlace(blockType, playerName);
@@ -1,5 +1,6 @@
package com.aluer.security; package com.aluer.security;
import com.aluer.config.ServerGuardConfig;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import java.lang.management.ManagementFactory; import java.lang.management.ManagementFactory;
@@ -16,6 +17,7 @@ import java.util.concurrent.atomic.AtomicLong;
@Service @Service
public class MemoryProtectionService { public class MemoryProtectionService {
private final ServerGuardConfig config;
private final MemoryMXBean memoryMXBean = ManagementFactory.getMemoryMXBean(); private final MemoryMXBean memoryMXBean = ManagementFactory.getMemoryMXBean();
private final ScheduledExecutorService scheduler = Executors.newSingleThreadScheduledExecutor(); private final ScheduledExecutorService scheduler = Executors.newSingleThreadScheduledExecutor();
private final Map<String, List<MemoryAlert>> alerts = new ConcurrentHashMap<>(); private final Map<String, List<MemoryAlert>> alerts = new ConcurrentHashMap<>();
@@ -30,15 +32,25 @@ public class MemoryProtectionService {
private long lastGcTime; private long lastGcTime;
public MemoryProtectionService() { public MemoryProtectionService() {
this(new ServerGuardConfig());
}
public MemoryProtectionService(ServerGuardConfig config) {
this.config = config;
lastGcCount = ManagementFactory.getGarbageCollectorMXBeans().stream() lastGcCount = ManagementFactory.getGarbageCollectorMXBeans().stream()
.mapToLong(gc -> gc.getCollectionCount()).sum(); .mapToLong(gc -> gc.getCollectionCount()).sum();
lastGcTime = ManagementFactory.getGarbageCollectorMXBeans().stream() lastGcTime = ManagementFactory.getGarbageCollectorMXBeans().stream()
.mapToLong(gc -> gc.getCollectionTime()).sum(); .mapToLong(gc -> gc.getCollectionTime()).sum();
scheduler.scheduleAtFixedRate(this::checkMemory, MEMORY_CHECK_INTERVAL_SECONDS, if (config.getSecurity().getSuperEvolution().isMemoryProtection()) {
MEMORY_CHECK_INTERVAL_SECONDS, TimeUnit.SECONDS); scheduler.scheduleAtFixedRate(this::checkMemory, MEMORY_CHECK_INTERVAL_SECONDS,
MEMORY_CHECK_INTERVAL_SECONDS, TimeUnit.SECONDS);
}
} }
public MemoryCheckResult checkMemory() { public MemoryCheckResult checkMemory() {
if (!config.getSecurity().getSuperEvolution().isMemoryProtection()) {
return new MemoryCheckResult(0, 0, 0, 0, 0, 0, List.of());
}
List<String> warnings = new ArrayList<>(); List<String> warnings = new ArrayList<>();
MemoryUsage heap = memoryMXBean.getHeapMemoryUsage(); 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; package com.aluer.security;
import com.aluer.config.ServerGuardConfig;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import java.io.BufferedReader; import java.io.BufferedReader;
@@ -15,6 +16,7 @@ import java.util.concurrent.atomic.AtomicLong;
@Service @Service
public class ProcessInjectionDetectionService { public class ProcessInjectionDetectionService {
private final ServerGuardConfig config;
private final Map<Integer, ProcessSnapshot> processBaseline = new ConcurrentHashMap<>(); private final Map<Integer, ProcessSnapshot> processBaseline = new ConcurrentHashMap<>();
private final Map<String, List<InjectionEvent>> detections = new ConcurrentHashMap<>(); private final Map<String, List<InjectionEvent>> detections = new ConcurrentHashMap<>();
private final ScheduledExecutorService scheduler = Executors.newSingleThreadScheduledExecutor(); private final ScheduledExecutorService scheduler = Executors.newSingleThreadScheduledExecutor();
@@ -32,11 +34,21 @@ public class ProcessInjectionDetectionService {
); );
public ProcessInjectionDetectionService() { public ProcessInjectionDetectionService() {
captureBaseline(); this(new ServerGuardConfig());
scheduler.scheduleAtFixedRate(this::scanProcesses, 60, 120, TimeUnit.SECONDS); }
public ProcessInjectionDetectionService(ServerGuardConfig config) {
this.config = config;
if (config.getSecurity().getSuperEvolution().isProcessInjection()) {
captureBaseline();
scheduler.scheduleAtFixedRate(this::scanProcesses, 60, 120, TimeUnit.SECONDS);
}
} }
public InjectionCheckResult scanProcesses() { public InjectionCheckResult scanProcesses() {
if (!config.getSecurity().getSuperEvolution().isProcessInjection()) {
return new InjectionCheckResult(List.of(), 0, totalDetections.get());
}
List<String> alerts = new ArrayList<>(); List<String> alerts = new ArrayList<>();
Map<Integer, ProcessSnapshot> current = captureProcessSnapshots(); Map<Integer, ProcessSnapshot> current = captureProcessSnapshots();
@@ -1,5 +1,7 @@
package com.aluer.security; package com.aluer.security;
import com.aluer.config.ServerGuardConfig;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import java.time.Instant; import java.time.Instant;
@@ -13,6 +15,7 @@ import java.util.concurrent.atomic.AtomicLong;
@Service @Service
public class ReverseShellDetectionService { public class ReverseShellDetectionService {
private final ServerGuardConfig config;
private final Map<String, List<ShellDetectionEvent>> detections = new ConcurrentHashMap<>(); private final Map<String, List<ShellDetectionEvent>> detections = new ConcurrentHashMap<>();
private final ScheduledExecutorService scheduler = Executors.newSingleThreadScheduledExecutor(); private final ScheduledExecutorService scheduler = Executors.newSingleThreadScheduledExecutor();
private final AtomicLong totalDetections = new AtomicLong(0); private final AtomicLong totalDetections = new AtomicLong(0);
@@ -67,10 +70,20 @@ public class ReverseShellDetectionService {
); );
public ReverseShellDetectionService() { public ReverseShellDetectionService() {
this(new ServerGuardConfig());
}
@Autowired
public ReverseShellDetectionService(ServerGuardConfig config) {
this.config = config;
scheduler.scheduleAtFixedRate(this::cleanupOldDetections, 300, 600, TimeUnit.SECONDS); scheduler.scheduleAtFixedRate(this::cleanupOldDetections, 300, 600, TimeUnit.SECONDS);
} }
public DetectionResult scanCommand(String command, String user, String source) { public DetectionResult scanCommand(String command, String user, String source) {
if (!config.getSecurity().getSuperEvolution().isReverseShell()) {
return DetectionResult.clean();
}
if (command == null) return DetectionResult.clean(); if (command == null) return DetectionResult.clean();
String lower = command.toLowerCase().trim(); String lower = command.toLowerCase().trim();
@@ -1,5 +1,7 @@
package com.aluer.security; package com.aluer.security;
import com.aluer.config.ServerGuardConfig;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import java.net.*; import java.net.*;
@@ -11,6 +13,7 @@ import java.util.concurrent.atomic.AtomicLong;
@Service @Service
public class SSRFProtectionService { public class SSRFProtectionService {
private final ServerGuardConfig config;
private final Map<String, List<SSRFEvent>> detections = new ConcurrentHashMap<>(); private final Map<String, List<SSRFEvent>> detections = new ConcurrentHashMap<>();
private final AtomicLong totalBlocked = new AtomicLong(0); private final AtomicLong totalBlocked = new AtomicLong(0);
@@ -39,7 +42,20 @@ public class SSRFProtectionService {
"metadata.alibabacloud.com", "169.254.169.254" "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) { public SSRFCheckResult checkURL(String url, String sourceIP) {
if (!config.getSecurity().getSuperEvolution().isSsrf()) {
return SSRFCheckResult.clean();
}
if (url == null || url.trim().isEmpty()) return SSRFCheckResult.clean(); if (url == null || url.trim().isEmpty()) return SSRFCheckResult.clean();
List<String> reasons = new ArrayList<>(); List<String> reasons = new ArrayList<>();
@@ -1,5 +1,6 @@
package com.aluer.security; package com.aluer.security;
import com.aluer.config.ServerGuardConfig;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import java.io.File; import java.io.File;
@@ -13,17 +14,47 @@ import java.util.concurrent.atomic.AtomicLong;
@Service @Service
public class SecureFileDeletionService { public class SecureFileDeletionService {
private final ServerGuardConfig config;
private final SecureRandom secureRandom = new SecureRandom(); private final SecureRandom secureRandom = new SecureRandom();
private final Map<String, DeletionRecord> deletionLog = new ConcurrentHashMap<>(); private final Map<String, DeletionRecord> deletionLog = new ConcurrentHashMap<>();
private final AtomicLong totalFilesDeleted = new AtomicLong(0); private final AtomicLong totalFilesDeleted = new AtomicLong(0);
private final AtomicLong totalBytesWiped = 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 DEFAULT_PASSES = 3;
private static final int MAX_PASSES = 7; private static final int MAX_PASSES = 7;
private static final int BUFFER_SIZE = 8192; private static final int BUFFER_SIZE = 8192;
public DeletionResult secureDelete(String filePath, int passes) { public DeletionResult secureDelete(String filePath, int passes) {
File file = new File(filePath); 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()) { if (!file.exists()) {
return DeletionResult.error("File does not exist: " + filePath); return DeletionResult.error("File does not exist: " + filePath);
} }
@@ -1,5 +1,6 @@
package com.aluer.security; package com.aluer.security;
import com.aluer.config.ServerGuardConfig;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import java.time.Instant; import java.time.Instant;
@@ -13,6 +14,8 @@ import java.util.concurrent.atomic.AtomicLong;
@Service @Service
public class ThreatHuntingService { public class ThreatHuntingService {
private final ServerGuardConfig config;
private final Map<String, List<HuntResult>> huntResults = new ConcurrentHashMap<>(); private final Map<String, List<HuntResult>> huntResults = new ConcurrentHashMap<>();
private final Map<String, HuntDefinition> huntDefinitions = new ConcurrentHashMap<>(); private final Map<String, HuntDefinition> huntDefinitions = new ConcurrentHashMap<>();
private final AtomicLong totalHunts = new AtomicLong(0); private final AtomicLong totalHunts = new AtomicLong(0);
@@ -20,6 +23,11 @@ public class ThreatHuntingService {
private final ScheduledExecutorService scheduler = Executors.newSingleThreadScheduledExecutor(); private final ScheduledExecutorService scheduler = Executors.newSingleThreadScheduledExecutor();
public ThreatHuntingService() { public ThreatHuntingService() {
this(new ServerGuardConfig());
}
public ThreatHuntingService(ServerGuardConfig config) {
this.config = config;
registerDefaultHunts(); registerDefaultHunts();
scheduler.scheduleAtFixedRate(this::runAllHunts, 300, 1800, TimeUnit.SECONDS); scheduler.scheduleAtFixedRate(this::runAllHunts, 300, 1800, TimeUnit.SECONDS);
} }
@@ -68,6 +76,10 @@ public class ThreatHuntingService {
} }
public HuntResult runHunt(String huntId, List<String> dataSources) { 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); HuntDefinition def = huntDefinitions.get(huntId);
if (def == null) return null; if (def == null) return null;
@@ -1,5 +1,6 @@
package com.aluer.security; package com.aluer.security;
import com.aluer.config.ServerGuardConfig;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import java.time.Instant; import java.time.Instant;
@@ -10,9 +11,18 @@ import java.util.concurrent.atomic.AtomicLong;
@Service @Service
public class XXEProtectionService { public class XXEProtectionService {
private final ServerGuardConfig config;
private final Map<String, List<XXEEvent>> detections = new ConcurrentHashMap<>(); private final Map<String, List<XXEEvent>> detections = new ConcurrentHashMap<>();
private final AtomicLong totalDetections = new AtomicLong(0); 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( private static final List<String> XXE_PATTERNS = List.of(
"<!ENTITY", "<!DOCTYPE", "SYSTEM \"file://", "SYSTEM 'file://", "<!ENTITY", "<!DOCTYPE", "SYSTEM \"file://", "SYSTEM 'file://",
"SYSTEM \"http://", "SYSTEM 'http://", "SYSTEM \"https://", "SYSTEM 'https://", "SYSTEM \"http://", "SYSTEM 'http://", "SYSTEM \"https://", "SYSTEM 'https://",
@@ -30,6 +40,7 @@ public class XXEProtectionService {
); );
public XXECheckResult checkXML(String xmlContent, String sourceIP) { public XXECheckResult checkXML(String xmlContent, String sourceIP) {
if (!config.getSecurity().getSuperEvolution().isXxe()) return XXECheckResult.clean();
if (xmlContent == null || xmlContent.trim().isEmpty()) return XXECheckResult.clean(); if (xmlContent == null || xmlContent.trim().isEmpty()) return XXECheckResult.clean();
List<String> matched = new ArrayList<>(); List<String> matched = new ArrayList<>();
@@ -61,6 +61,13 @@ public class DashboardController {
private final AntiDupeDetectionService antiDupeDetectionService; private final AntiDupeDetectionService antiDupeDetectionService;
private final CrashExploitProtectionService crashExploitProtectionService; private final CrashExploitProtectionService crashExploitProtectionService;
private final LagMachineDetectionService lagMachineDetectionService; 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") @SuppressWarnings("java:S107")
public DashboardController( public DashboardController(
@@ -100,7 +107,13 @@ public class DashboardController {
AntiFlyDetectionService antiFlyDetectionService, AntiFlyDetectionService antiFlyDetectionService,
AntiDupeDetectionService antiDupeDetectionService, AntiDupeDetectionService antiDupeDetectionService,
CrashExploitProtectionService crashExploitProtectionService, CrashExploitProtectionService crashExploitProtectionService,
LagMachineDetectionService lagMachineDetectionService) { LagMachineDetectionService lagMachineDetectionService,
GeoBlockService geoBlockService,
PlayerSessionValidationService playerSessionValidationService,
PluginVerificationService pluginVerificationService,
ConnectionThrottleService connectionThrottleService,
BackupIntegrityService backupIntegrityService,
AntiSkinSpoofService antiSkinSpoofService) {
this.rconClient = rconClient; this.rconClient = rconClient;
this.profiler = profiler; this.profiler = profiler;
this.backupService = backupService; this.backupService = backupService;
@@ -138,6 +151,12 @@ public class DashboardController {
this.antiDupeDetectionService = antiDupeDetectionService; this.antiDupeDetectionService = antiDupeDetectionService;
this.crashExploitProtectionService = crashExploitProtectionService; this.crashExploitProtectionService = crashExploitProtectionService;
this.lagMachineDetectionService = lagMachineDetectionService; this.lagMachineDetectionService = lagMachineDetectionService;
this.geoBlockService = geoBlockService;
this.playerSessionValidationService = playerSessionValidationService;
this.pluginVerificationService = pluginVerificationService;
this.connectionThrottleService = connectionThrottleService;
this.backupIntegrityService = backupIntegrityService;
this.antiSkinSpoofService = antiSkinSpoofService;
} }
@GetMapping("/status") @GetMapping("/status")
@@ -467,4 +486,36 @@ public class DashboardController {
public Map<String, Object> getLagMachineStatus() { public Map<String, Object> getLagMachineStatus() {
return lagMachineDetectionService.getStatus(); 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();
}
} }
+36
View File
@@ -149,6 +149,42 @@ serverguard:
memory-emergency-threshold: 95.0 memory-emergency-threshold: 95.0
max-recovery-actions-per-hour: 8 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: dashboard:
enabled: true enabled: true
title: Aluer Nebula Console 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"));
}
}