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