feat: super evolution — 20 new security modules with full test coverage

新增20个超进化安全模块 (17 new services + 1 test suite + Dashboard API):
- JWT认证服务 (JwtAuthService): 基于HMAC-SHA256的token管理
- 反暴力破解 (BruteForceProtectionService): 渐进延迟+三层时间窗口检测
- 反机器人检测 (AntiBotDetectionService): MC玩家名模式+加入速度+多账号检测
- 反向Shell检测 (ReverseShellDetectionService): 50+ reverse shell模式匹配
- ARP欺骗检测 (ARPSpoofDetectionService): MAC变更+网关欺骗+重复MAC检测
- DNS隧道检测 (DNSTunnelDetectionService): 熵值分析+子域名编码+频率监控
- 漏洞签名检测 (ExploitSignatureService): Log4Shell/SQL注入/XSS等15+签名
- SSRF防护 (SSRFProtectionService): 内网IP/云元数据/编码绕过检测
- XXE防护 (XXEProtectionService): XML实体注入+十亿笑攻击检测
- CSP强制执行 (CSPEnforcementService): XSS/Clickjack防御+安全响应头
- 数据库防火墙 (DatabaseFirewallService): SQL注入+危险关键字+堆叠查询检测
- 数据防泄漏 (DataLossPreventionService): 12种敏感数据模式(邮箱/API Key/SSH Key等)
- 内存保护 (MemoryProtectionService): JVM堆监控+GC过载+内存泄漏检测
- 进程注入检测 (ProcessInjectionDetectionService): 线程异常+内存映射+文件描述符
- 安全文件删除 (SecureFileDeletionService): DoD多pass覆写+擦除后删除
- 取证收集器 (ForensicsCollectorService): 进程/网络/文件/日志取证
- 事件响应 (IncidentResponseService): 5套Playbook(DDOS/爆破/入侵/MC漏洞/数据泄露)
- 威胁狩猎 (ThreatHuntingService): 10种主动狩猎规则
- 合规扫描 (ComplianceScannerService): 7大类20+项合规检查
- 反破坏检测 (AntiGriefDetectionService): MC方块破坏/放置/实体/容器监控

API端点: DashboardController新增20+个安全API端点
测试: 64个测试全部通过 (12 existing + 52 new)
This commit is contained in:
Peijun Zhao
2026-05-10 20:36:04 +08:00
parent 1fa3e96d0a
commit f7c590e34e
22 changed files with 4909 additions and 2 deletions
@@ -0,0 +1,212 @@
package com.aluer.security;
import org.springframework.stereotype.Service;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.time.Instant;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicLong;
@Service
public class ARPSpoofDetectionService {
private final Map<String, String> arpCache = new ConcurrentHashMap<>();
private final Map<String, List<ARPEvent>> arpEvents = new ConcurrentHashMap<>();
private final ScheduledExecutorService scheduler = Executors.newSingleThreadScheduledExecutor();
private final AtomicLong totalSpoofDetections = new AtomicLong(0);
private static final int BASELINE_SAMPLES = 3;
public ARPSpoofDetectionService() {
buildBaseline();
scheduler.scheduleAtFixedRate(this::scanARPTable, 30, 60, TimeUnit.SECONDS);
}
private void buildBaseline() {
Map<String, String> current = readARPTable();
for (Map.Entry<String, String> e : current.entrySet()) {
arpCache.put(e.getKey(), e.getValue());
}
}
public ARPScanResult scanARPTable() {
Map<String, String> current = readARPTable();
List<ARPEvent> alerts = new ArrayList<>();
for (Map.Entry<String, String> entry : current.entrySet()) {
String ip = entry.getKey();
String mac = entry.getValue();
if (mac.equals("00:00:00:00:00:00") || mac.equals("ff:ff:ff:ff:ff:ff")) {
continue;
}
String cachedMac = arpCache.get(ip);
if (cachedMac != null && !cachedMac.equalsIgnoreCase(mac)) {
ARPEvent event = new ARPEvent(
ARPEventType.MAC_CHANGE, ip, cachedMac, mac, Instant.now(),
"ARP spoofing: IP " + ip + " MAC changed from " + cachedMac + " to " + mac
);
alerts.add(event);
arpEvents.computeIfAbsent(ip, k -> new ArrayList<>()).add(event);
totalSpoofDetections.incrementAndGet();
}
// Check for duplicate MAC addresses (possible MITM)
for (Map.Entry<String, String> other : current.entrySet()) {
if (!other.getKey().equals(ip) && other.getValue().equalsIgnoreCase(mac)) {
ARPEvent event = new ARPEvent(
ARPEventType.DUPLICATE_MAC, ip, mac, other.getKey(), Instant.now(),
"Duplicate MAC: " + mac + " used by both " + ip + " and " + other.getKey()
);
alerts.add(event);
arpEvents.computeIfAbsent(ip, k -> new ArrayList<>()).add(event);
totalSpoofDetections.incrementAndGet();
}
}
}
// Check for gateway MAC spoofing
String gatewayIP = detectGateway();
if (gatewayIP != null) {
String gatewayMac = current.get(gatewayIP);
String cachedGatewayMac = arpCache.get(gatewayIP);
if (gatewayMac != null && cachedGatewayMac != null && !gatewayMac.equalsIgnoreCase(cachedGatewayMac)) {
ARPEvent event = new ARPEvent(
ARPEventType.GATEWAY_SPOOF, gatewayIP, cachedGatewayMac, gatewayMac, Instant.now(),
"Gateway ARP spoofing detected: " + gatewayIP
);
alerts.add(event);
arpEvents.computeIfAbsent(gatewayIP, k -> new ArrayList<>()).add(event);
totalSpoofDetections.incrementAndGet();
}
}
arpCache.clear();
arpCache.putAll(current);
return new ARPScanResult(alerts, current.size(), totalSpoofDetections.get());
}
public Map<String, Object> getStatus() {
Map<String, Object> status = new LinkedHashMap<>();
status.put("arpCacheSize", arpCache.size());
status.put("totalSpoofDetections", totalSpoofDetections.get());
status.put("monitoredIPs", new ArrayList<>(arpCache.keySet()));
List<Map<String, Object>> recentEvents = new ArrayList<>();
for (Map.Entry<String, List<ARPEvent>> e : arpEvents.entrySet()) {
for (ARPEvent event : e.getValue()) {
Map<String, Object> m = new LinkedHashMap<>();
m.put("ip", e.getKey());
m.put("type", event.type.name());
m.put("oldMac", event.oldMac);
m.put("newMac", event.newMac);
m.put("time", event.timestamp.toString());
m.put("message", event.message);
recentEvents.add(m);
}
}
recentEvents.sort((a, b) -> b.get("time").toString().compareTo(a.get("time").toString()));
status.put("recentEvents", recentEvents.subList(0, Math.min(recentEvents.size(), 20)));
return status;
}
public long getTotalSpoofDetections() { return totalSpoofDetections.get(); }
private Map<String, String> readARPTable() {
Map<String, String> table = new LinkedHashMap<>();
try {
Process proc = Runtime.getRuntime().exec(new String[]{"arp", "-a"});
try (BufferedReader reader = new BufferedReader(new InputStreamReader(proc.getInputStream()))) {
String line;
while ((line = reader.readLine()) != null) {
line = line.trim();
if (line.isEmpty() || line.startsWith("Interface") || line.startsWith("Internet")) continue;
// Windows: "192.168.1.1 00-11-22-33-44-55 dynamic"
// Linux: "gateway (192.168.1.1) at 00:11:22:33:44:55 [ether] on eth0"
String[] tokens = line.split("\\s+");
String ip = null, mac = null;
for (String token : tokens) {
token = token.replaceAll("[()]", "");
if (token.matches("\\d+\\.\\d+\\.\\d+\\.\\d+")) {
ip = token;
}
if (token.matches("[0-9A-Fa-f]{1,2}[-:][0-9A-Fa-f]{1,2}[-:][0-9A-Fa-f]{1,2}[-:][0-9A-Fa-f]{1,2}[-:][0-9A-Fa-f]{1,2}[-:][0-9A-Fa-f]{1,2}")) {
mac = token.replace('-', ':').toLowerCase();
}
}
if (ip != null && mac != null) {
table.put(ip, mac);
}
}
}
} catch (Exception ignored) {
// arp command not available, silent fail
}
return table;
}
private String detectGateway() {
try {
Process proc = Runtime.getRuntime().exec(new String[]{"sh", "-c", "ip route | grep default | awk '{print $3}'"});
try (BufferedReader reader = new BufferedReader(new InputStreamReader(proc.getInputStream()))) {
String line = reader.readLine();
if (line != null && !line.trim().isEmpty()) return line.trim();
}
// Fallback to Windows
proc = Runtime.getRuntime().exec(new String[]{"sh", "-c", "ipconfig | findstr /i \"Default Gateway\" | findstr /v \"::\""});
try (BufferedReader reader = new BufferedReader(new InputStreamReader(proc.getInputStream()))) {
String line = reader.readLine();
if (line != null) {
String[] parts = line.split(":");
if (parts.length >= 2) return parts[parts.length - 1].trim();
}
}
} catch (Exception ignored) {
// silent fail
}
return null;
}
public enum ARPEventType { MAC_CHANGE, DUPLICATE_MAC, GATEWAY_SPOOF }
public static class ARPEvent {
public final ARPEventType type;
public final String ip;
public final String oldMac;
public final String newMac;
public final Instant timestamp;
public final String message;
ARPEvent(ARPEventType type, String ip, String oldMac, String newMac, Instant timestamp, String message) {
this.type = type;
this.ip = ip;
this.oldMac = oldMac;
this.newMac = newMac;
this.timestamp = timestamp;
this.message = message;
}
}
public static class ARPScanResult {
public final List<ARPEvent> alerts;
public final int tableSize;
public final long totalDetections;
ARPScanResult(List<ARPEvent> alerts, int tableSize, long totalDetections) {
this.alerts = alerts;
this.tableSize = tableSize;
this.totalDetections = totalDetections;
}
public boolean hasAlerts() { return !alerts.isEmpty(); }
public List<ARPEvent> getAlerts() { return alerts; }
public int getTableSize() { return tableSize; }
public long getTotalDetections() { return totalDetections; }
}
}
@@ -0,0 +1,232 @@
package com.aluer.security;
import org.springframework.stereotype.Service;
import java.time.Instant;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicLong;
@Service
public class AntiBotDetectionService {
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<>();
private final Map<String, Integer> botScoreByIP = new ConcurrentHashMap<>();
private final ScheduledExecutorService scheduler = Executors.newSingleThreadScheduledExecutor();
// Minecraft bot behavior thresholds
private static final long JOIN_VELOCITY_WINDOW_MS = 3000;
private static final int MAX_JOINS_IN_VELOCITY_WINDOW = 5;
private static final int MAX_ACCOUNTS_PER_IP = 4;
private static final long IP_JOIN_WINDOW_SECONDS = 300;
private static final int MAX_JOINS_PER_IP_IN_WINDOW = 20;
private static final long BOT_BLOCK_SECONDS = 3600;
private static final int BOT_SCORE_THRESHOLD = 75;
// Known bot username patterns
private static final List<String> BOT_NAME_PATTERNS = List.of(
"^[A-Za-z]{2,4}\\d{4,8}$",
"^Bot_\\w+$",
"^\\w+Bot$",
"^[a-z]{3,6}[0-9]{3,6}$",
"^MC-\\w+$",
"^[A-Z]{2,4}_[a-z]{3,6}$"
);
private static final Set<String> KNOWN_BOT_PREFIXES = Set.of(
"Bot", "MCBot", "Headless", "Auto", "Cracked",
"Spambot", "Joinbot", "Pingbot", "Nullbot"
);
private final AtomicLong totalBotsDetected = new AtomicLong(0);
private final AtomicLong totalBotsBlocked = new AtomicLong(0);
public AntiBotDetectionService() {
scheduler.scheduleAtFixedRate(this::cleanupExpired, 120, 300, TimeUnit.SECONDS);
}
public BotCheckResult checkPlayerJoin(String playerName, String ip, String clientBrand) {
if (blockedBots.containsKey(ip)) {
Instant until = blockedBots.get(ip);
if (Instant.now().isAfter(until)) {
blockedBots.remove(ip);
} else {
return BotCheckResult.blocked("IP is bot-blocked until " + until);
}
}
int score = 0;
List<String> reasons = new ArrayList<>();
// Rule 1: Suspicious username patterns
if (matchesBotNamePattern(playerName) || hasBotPrefix(playerName)) {
score += 25;
reasons.add("BOT_NAME_PATTERN: " + playerName);
}
// Rule 2: Join velocity (too many joins in short time from same IP)
List<Instant> joins = ipJoinTimeline.computeIfAbsent(ip, k -> new ArrayList<>());
joins.add(Instant.now());
long velocityCutoff = System.currentTimeMillis() - JOIN_VELOCITY_WINDOW_MS;
long recentJoins = joins.stream().filter(t -> t.toEpochMilli() > velocityCutoff).count();
if (recentJoins > MAX_JOINS_IN_VELOCITY_WINDOW) {
score += 35;
reasons.add("JOIN_VELOCITY: " + recentJoins + " joins in " + JOIN_VELOCITY_WINDOW_MS + "ms");
}
// Rule 3: Too many accounts from same IP
PlayerJoinRecord record = joinHistory.computeIfAbsent(ip, k -> new PlayerJoinRecord());
record.playerNames.add(playerName);
if (record.playerNames.size() > MAX_ACCOUNTS_PER_IP) {
score += 30;
reasons.add("MULTI_ACCOUNT: " + record.playerNames.size() + " accounts from IP " + ip);
}
// Rule 4: Total joins from IP in time window
Instant windowCutoff = Instant.now().minusSeconds(IP_JOIN_WINDOW_SECONDS);
joins.removeIf(t -> t.isBefore(windowCutoff));
if (joins.size() > MAX_JOINS_PER_IP_IN_WINDOW) {
score += 20;
reasons.add("IP_JOIN_FLOOD: " + joins.size() + " joins in " + IP_JOIN_WINDOW_SECONDS + "s");
}
// Rule 5: Suspicious client brand
if (clientBrand != null && isSuspiciousClient(clientBrand)) {
score += 15;
reasons.add("SUSPICIOUS_CLIENT: " + clientBrand);
}
// Rule 6: Cumulative bot score
int existingScore = botScoreByIP.getOrDefault(ip, 0);
botScoreByIP.put(ip, existingScore + score);
boolean isBot = score >= BOT_SCORE_THRESHOLD || (existingScore + score) >= BOT_SCORE_THRESHOLD;
if (isBot) {
blockedBots.put(ip, Instant.now().plusSeconds(BOT_BLOCK_SECONDS));
totalBotsDetected.incrementAndGet();
totalBotsBlocked.incrementAndGet();
return BotCheckResult.detected(score, reasons, BOT_BLOCK_SECONDS);
}
record.lastJoin = Instant.now();
return score > 0 ? BotCheckResult.suspicious(score, reasons) : BotCheckResult.clean();
}
public void unblockIP(String ip) {
blockedBots.remove(ip);
botScoreByIP.remove(ip);
joinHistory.remove(ip);
}
public Map<String, Object> getStatus() {
Map<String, Object> status = new LinkedHashMap<>();
status.put("trackedIPs", joinHistory.size());
status.put("blockedBots", blockedBots.size());
status.put("totalBotsDetected", totalBotsDetected.get());
status.put("totalBotsBlocked", totalBotsBlocked.get());
List<Map<String, Object>> blocked = new ArrayList<>();
for (Map.Entry<String, Instant> e : blockedBots.entrySet()) {
Map<String, Object> entry = new LinkedHashMap<>();
entry.put("ip", e.getKey());
entry.put("until", e.getValue().toString());
PlayerJoinRecord rec = joinHistory.get(e.getKey());
if (rec != null) entry.put("accounts", new ArrayList<>(rec.playerNames));
entry.put("botScore", botScoreByIP.getOrDefault(e.getKey(), 0));
blocked.add(entry);
}
status.put("blockedList", blocked);
return status;
}
public long getTotalBotsDetected() { return totalBotsDetected.get(); }
public long getTotalBotsBlocked() { return totalBotsBlocked.get(); }
private boolean matchesBotNamePattern(String name) {
if (name == null) return false;
for (String pattern : BOT_NAME_PATTERNS) {
if (name.matches(pattern)) return true;
}
return false;
}
private boolean hasBotPrefix(String name) {
if (name == null) return false;
for (String prefix : KNOWN_BOT_PREFIXES) {
if (name.toLowerCase().startsWith(prefix.toLowerCase())) return true;
}
return false;
}
private boolean isSuspiciousClient(String brand) {
if (brand == null) return true; // null brand = suspicious
String lower = brand.toLowerCase();
return lower.contains("bot") || lower.contains("headless")
|| lower.contains("cracked") || lower.contains("hack")
|| lower.contains("inject") || lower.contains("cheat");
}
private void cleanupExpired() {
Instant now = Instant.now();
blockedBots.entrySet().removeIf(e -> e.getValue().isBefore(now));
ipJoinTimeline.entrySet().removeIf(e -> {
e.getValue().removeIf(t -> t.isBefore(now.minusSeconds(IP_JOIN_WINDOW_SECONDS * 2)));
return e.getValue().isEmpty();
});
}
private static class PlayerJoinRecord {
final Set<String> playerNames = new HashSet<>();
Instant lastJoin = Instant.now();
}
public static class BotCheckResult {
private final boolean blocked;
private final boolean detected;
private final boolean suspicious;
private final int score;
private final List<String> reasons;
private final String message;
private final long blockSeconds;
private BotCheckResult(boolean blocked, boolean detected, boolean suspicious, int score,
List<String> reasons, String message, long blockSeconds) {
this.blocked = blocked;
this.detected = detected;
this.suspicious = suspicious;
this.score = score;
this.reasons = reasons;
this.message = message;
this.blockSeconds = blockSeconds;
}
public static BotCheckResult clean() {
return new BotCheckResult(false, false, false, 0, List.of(), null, 0);
}
public static BotCheckResult suspicious(int score, List<String> reasons) {
return new BotCheckResult(false, false, true, score, reasons, "Suspicious bot activity: " + String.join("; ", reasons), 0);
}
public static BotCheckResult detected(int score, List<String> reasons, long blockSeconds) {
return new BotCheckResult(true, true, true, score, reasons,
"Bot detected: " + String.join("; ", reasons), blockSeconds);
}
public static BotCheckResult blocked(String message) {
return new BotCheckResult(true, false, false, 0, List.of(), message, 0);
}
public boolean isBlocked() { return blocked; }
public boolean isDetected() { return detected; }
public boolean isSuspicious() { return suspicious; }
public int getScore() { return score; }
public List<String> getReasons() { return reasons; }
public String getMessage() { return message; }
public long getBlockSeconds() { return blockSeconds; }
}
}
@@ -0,0 +1,334 @@
package com.aluer.security;
import org.springframework.stereotype.Service;
import java.time.Instant;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicLong;
@Service
public class AntiGriefDetectionService {
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<>();
private final ScheduledExecutorService scheduler = Executors.newSingleThreadScheduledExecutor();
private final AtomicLong totalGriefDetections = new AtomicLong(0);
// Grief detection thresholds
private static final int MAX_BLOCKS_BROKEN_PER_MINUTE = 150;
private static final int MAX_BLOCKS_PLACED_PER_MINUTE = 200;
private static final int MAX_ENTITIES_KILLED_PER_MINUTE = 30;
private static final int MAX_CONTAINERS_OPENED_PER_MINUTE = 60;
private static final int MAX_CHAT_MESSAGES_PER_MINUTE = 20;
private static final int MAX_SIGN_CHANGES_PER_MINUTE = 10;
private static final long ACTIVITY_WINDOW_MS = 60000;
private static final Set<String> GRIEF_BLOCKS = Set.of(
"TNT", "LAVA", "WATER", "FIRE", "RESPAWN_ANCHOR", "END_CRYSTAL",
"BED", "OBSIDIAN", "WITHER_SKULL", "WITHER_SUMMON"
);
private static final Set<String> PROTECTED_CONTAINERS = Set.of(
"CHEST", "ENDER_CHEST", "SHULKER_BOX", "BARREL", "FURNACE",
"HOPPER", "DISPENSER", "DROPPER", "BREWING_STAND", "BEACON"
);
public AntiGriefDetectionService() {
scheduler.scheduleAtFixedRate(this::cleanupOldData, 120, 300, TimeUnit.SECONDS);
}
public GriefCheckResult checkBlockBreak(String playerName, String blockType, int x, int y, int z, String world) {
PlayerActivity activity = playerActivities.computeIfAbsent(playerName, k -> new PlayerActivity());
activity.recordBlockBreak(blockType, x, y, z, world);
int score = 0;
List<String> reasons = new ArrayList<>();
// Check block break rate
if (activity.blocksBrokenPerMinute > MAX_BLOCKS_BROKEN_PER_MINUTE) {
score += 30;
reasons.add("RAPID_BREAK: " + activity.blocksBrokenPerMinute + "/min");
}
// Grief blocks
if (GRIEF_BLOCKS.contains(blockType.toUpperCase())) {
score += 15;
reasons.add("GRIEF_BLOCK: " + blockType);
}
// Check for grief patterns (breaking many blocks in a line = tunnel grief)
if (activity.isTunneling(x, y, z)) {
score += 20;
reasons.add("TUNNEL_PATTERN");
}
// Check for container theft
if (PROTECTED_CONTAINERS.contains(blockType.toUpperCase())) {
score += 10;
reasons.add("CONTAINER_ACCESS: " + blockType);
}
if (score >= 40) {
return flagPlayer(playerName, "block_break", score, reasons);
}
return score > 0 ? GriefCheckResult.suspicious(score, reasons) : GriefCheckResult.clean();
}
public GriefCheckResult checkBlockPlace(String playerName, String blockType, int x, int y, int z, String world) {
PlayerActivity activity = playerActivities.computeIfAbsent(playerName, k -> new PlayerActivity());
activity.recordBlockPlace(blockType, x, y, z, world);
int score = 0;
List<String> reasons = new ArrayList<>();
if (activity.blocksPlacedPerMinute > MAX_BLOCKS_PLACED_PER_MINUTE) {
score += 25;
reasons.add("RAPID_PLACE: " + activity.blocksPlacedPerMinute + "/min");
}
if (GRIEF_BLOCKS.contains(blockType.toUpperCase())) {
score += 20;
reasons.add("GRIEF_BLOCK_PLACED: " + blockType);
}
if (score >= 30) {
return flagPlayer(playerName, "block_place", score, reasons);
}
return score > 0 ? GriefCheckResult.suspicious(score, reasons) : GriefCheckResult.clean();
}
public GriefCheckResult checkEntityKill(String playerName, String entityType, String world) {
PlayerActivity activity = playerActivities.computeIfAbsent(playerName, k -> new PlayerActivity());
activity.recordEntityKill(entityType);
int score = 0;
List<String> reasons = new ArrayList<>();
if (activity.entitiesKilledPerMinute > MAX_ENTITIES_KILLED_PER_MINUTE) {
score += 25;
reasons.add("ENTITY_SLAUGHTER: " + activity.entitiesKilledPerMinute + "/min");
}
if (score >= 25) {
return flagPlayer(playerName, "entity_kill", score, reasons);
}
return GriefCheckResult.clean();
}
public GriefCheckResult checkContainerOpen(String playerName, String containerType, int x, int y, int z) {
PlayerActivity activity = playerActivities.computeIfAbsent(playerName, k -> new PlayerActivity());
activity.recordContainerOpen(containerType);
int score = 0;
List<String> reasons = new ArrayList<>();
if (activity.containersOpenedPerMinute > MAX_CONTAINERS_OPENED_PER_MINUTE) {
score += 20;
reasons.add("CONTAINER_SPAM: " + activity.containersOpenedPerMinute + "/min");
}
if (score >= 20) {
return flagPlayer(playerName, "container_spam", score, reasons);
}
return GriefCheckResult.clean();
}
public GriefCheckResult checkChatSpam(String playerName, String message) {
PlayerActivity activity = playerActivities.computeIfAbsent(playerName, k -> new PlayerActivity());
activity.recordChatMessage();
if (activity.chatMessagesPerMinute > MAX_CHAT_MESSAGES_PER_MINUTE) {
return flagPlayer(playerName, "chat_spam",
30, List.of("CHAT_SPAM: " + activity.chatMessagesPerMinute + "/min"));
}
return GriefCheckResult.clean();
}
public void clearPlayer(String playerName) {
playerActivities.remove(playerName);
flaggedPlayers.remove(playerName);
}
public Map<String, Object> getStatus() {
Map<String, Object> status = new LinkedHashMap<>();
status.put("trackedPlayers", playerActivities.size());
status.put("flaggedPlayers", flaggedPlayers.size());
status.put("totalGriefDetections", totalGriefDetections.get());
List<Map<String, Object>> flagged = new ArrayList<>();
for (Map.Entry<String, Instant> e : flaggedPlayers.entrySet()) {
Map<String, Object> m = new LinkedHashMap<>();
m.put("player", e.getKey());
m.put("flaggedUntil", e.getValue().toString());
List<GriefEvent> events = griefEvents.get(e.getKey());
if (events != null && !events.isEmpty()) {
GriefEvent last = events.get(events.size() - 1);
m.put("lastReason", last.reasons);
m.put("lastScore", last.score);
}
PlayerActivity act = playerActivities.get(e.getKey());
if (act != null) {
m.put("blocksBrokenPerMinute", act.blocksBrokenPerMinute);
m.put("blocksPlacedPerMinute", act.blocksPlacedPerMinute);
}
flagged.add(m);
}
status.put("flaggedPlayersList", flagged);
return status;
}
public long getTotalGriefDetections() { return totalGriefDetections.get(); }
private GriefCheckResult flagPlayer(String playerName, String actionType, int score, List<String> reasons) {
GriefEvent event = new GriefEvent(Instant.now(), playerName, actionType, score, reasons);
griefEvents.computeIfAbsent(playerName, k -> new ArrayList<>()).add(event);
flaggedPlayers.put(playerName, Instant.now().plusSeconds(1800));
totalGriefDetections.incrementAndGet();
return GriefCheckResult.flagged(score, reasons);
}
private void cleanupOldData() {
Instant cutoff = Instant.now().minusSeconds(3600);
playerActivities.entrySet().removeIf(e -> {
e.getValue().cleanupOldEvents(cutoff);
return e.getValue().isEmpty();
});
flaggedPlayers.entrySet().removeIf(e -> e.getValue().isBefore(Instant.now()));
}
private static class PlayerActivity {
final List<TimedEvent> blockBreaks = new ArrayList<>();
final List<TimedEvent> blockPlaces = new ArrayList<>();
final List<TimedEvent> entityKills = new ArrayList<>();
final List<TimedEvent> containerOpens = new ArrayList<>();
final List<TimedEvent> chatMessages = new ArrayList<>();
int blocksBrokenPerMinute;
int blocksPlacedPerMinute;
int entitiesKilledPerMinute;
int containersOpenedPerMinute;
int chatMessagesPerMinute;
private int lastBreakX, lastBreakY, lastBreakZ;
void recordBlockBreak(String type, int x, int y, int z, String world) {
blockBreaks.add(new TimedEvent(Instant.now(), type, x, y, z));
lastBreakX = x; lastBreakY = y; lastBreakZ = z;
updateRates();
}
void recordBlockPlace(String type, int x, int y, int z, String world) {
blockPlaces.add(new TimedEvent(Instant.now(), type, x, y, z));
updateRates();
}
void recordEntityKill(String type) {
entityKills.add(new TimedEvent(Instant.now(), type, 0, 0, 0));
updateRates();
}
void recordContainerOpen(String type) {
containerOpens.add(new TimedEvent(Instant.now(), type, 0, 0, 0));
updateRates();
}
void recordChatMessage() {
chatMessages.add(new TimedEvent(Instant.now(), "", 0, 0, 0));
updateRates();
}
boolean isTunneling(int x, int y, int z) {
return Math.abs(x - lastBreakX) <= 1 && Math.abs(y - lastBreakY) <= 1
&& Math.abs(z - lastBreakZ) <= 1 && blockBreaks.size() > 20;
}
void updateRates() {
long cutoff = System.currentTimeMillis() - ACTIVITY_WINDOW_MS;
blocksBrokenPerMinute = (int) blockBreaks.stream().filter(e -> e.time.toEpochMilli() > cutoff).count();
blocksPlacedPerMinute = (int) blockPlaces.stream().filter(e -> e.time.toEpochMilli() > cutoff).count();
entitiesKilledPerMinute = (int) entityKills.stream().filter(e -> e.time.toEpochMilli() > cutoff).count();
containersOpenedPerMinute = (int) containerOpens.stream().filter(e -> e.time.toEpochMilli() > cutoff).count();
chatMessagesPerMinute = (int) chatMessages.stream().filter(e -> e.time.toEpochMilli() > cutoff).count();
}
void cleanupOldEvents(Instant cutoff) {
blockBreaks.removeIf(e -> e.time.isBefore(cutoff));
blockPlaces.removeIf(e -> e.time.isBefore(cutoff));
entityKills.removeIf(e -> e.time.isBefore(cutoff));
containerOpens.removeIf(e -> e.time.isBefore(cutoff));
chatMessages.removeIf(e -> e.time.isBefore(cutoff));
}
boolean isEmpty() {
return blockBreaks.isEmpty() && blockPlaces.isEmpty() && entityKills.isEmpty()
&& containerOpens.isEmpty() && chatMessages.isEmpty();
}
}
private static class TimedEvent {
final Instant time;
final String type;
final int x, y, z;
TimedEvent(Instant time, String type, int x, int y, int z) {
this.time = time;
this.type = type;
this.x = x;
this.y = y;
this.z = z;
}
}
private static class GriefEvent {
final Instant timestamp;
final String playerName;
final String actionType;
final int score;
final List<String> reasons;
GriefEvent(Instant timestamp, String playerName, String actionType, int score, List<String> reasons) {
this.timestamp = timestamp;
this.playerName = playerName;
this.actionType = actionType;
this.score = score;
this.reasons = reasons;
}
}
public static class GriefCheckResult {
private final boolean flagged;
private final boolean suspicious;
private final int score;
private final List<String> reasons;
private GriefCheckResult(boolean flagged, boolean suspicious, int score, List<String> reasons) {
this.flagged = flagged;
this.suspicious = suspicious;
this.score = score;
this.reasons = reasons;
}
public static GriefCheckResult clean() { return new GriefCheckResult(false, false, 0, List.of()); }
public static GriefCheckResult suspicious(int score, List<String> reasons) {
return new GriefCheckResult(false, true, score, reasons);
}
public static GriefCheckResult flagged(int score, List<String> reasons) {
return new GriefCheckResult(true, true, score, reasons);
}
public boolean isFlagged() { return flagged; }
public boolean isSuspicious() { return suspicious; }
public int getScore() { return score; }
public List<String> getReasons() { return reasons; }
}
}
@@ -0,0 +1,213 @@
package com.aluer.security;
import org.springframework.stereotype.Service;
import java.time.Instant;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicLong;
@Service
public class BruteForceProtectionService {
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<>();
private final ScheduledExecutorService scheduler = Executors.newSingleThreadScheduledExecutor();
private static final int MAX_ATTEMPTS_WINDOW_1 = 5;
private static final int MAX_ATTEMPTS_WINDOW_2 = 15;
private static final int MAX_ATTEMPTS_WINDOW_3 = 50;
private static final long WINDOW_1_SECONDS = 60;
private static final long WINDOW_2_SECONDS = 600;
private static final long WINDOW_3_SECONDS = 3600;
private static final long BASE_BLOCK_SECONDS = 300;
private static final long ESCALATED_BLOCK_SECONDS = 1800;
private static final long MAX_BLOCK_SECONDS = 7200;
private static final long PROGRESSIVE_DELAY_BASE_MS = 1000;
private static final long PROGRESSIVE_DELAY_PER_FAILURE_MS = 500;
private static final long MAX_PROGRESSIVE_DELAY_MS = 15000;
private static final int IP_GLOBAL_MAX_ATTEMPTS = 100;
private static final long IP_WINDOW_SECONDS = 600;
private final AtomicLong totalBlocks = new AtomicLong(0);
private final AtomicLong totalBruteForceDetections = new AtomicLong(0);
public BruteForceProtectionService() {
scheduler.scheduleAtFixedRate(this::cleanupExpired, 60, 120, TimeUnit.SECONDS);
}
public AuthResult checkLoginAttempt(String username, String ip, String source) {
if (isBlocked(username)) {
return AuthResult.blocked("Account " + username + " is temporarily locked due to excessive failed attempts");
}
if (isIPGloballyBlocked(ip)) {
return AuthResult.blocked("IP " + ip + " is temporarily blocked due to global brute force activity");
}
FailureRecord record = failureTracker.computeIfAbsent(username, k -> new FailureRecord());
long now = Instant.now().getEpochSecond();
record.attemptsInWindow1.removeIf(t -> now - t > WINDOW_1_SECONDS);
record.attemptsInWindow2.removeIf(t -> now - t > WINDOW_2_SECONDS);
record.attemptsInWindow3.removeIf(t -> now - t > WINDOW_3_SECONDS);
int w1 = record.attemptsInWindow1.size();
int w2 = record.attemptsInWindow2.size();
int w3 = record.attemptsInWindow3.size();
if (w1 >= MAX_ATTEMPTS_WINDOW_1) {
return handleBruteForce(username, ip, "Window1: " + w1 + " attempts in " + WINDOW_1_SECONDS + "s", BASE_BLOCK_SECONDS);
}
if (w2 >= MAX_ATTEMPTS_WINDOW_2) {
return handleBruteForce(username, ip, "Window2: " + w2 + " attempts in " + WINDOW_2_SECONDS + "s", ESCALATED_BLOCK_SECONDS);
}
if (w3 >= MAX_ATTEMPTS_WINDOW_3) {
return handleBruteForce(username, ip, "Window3: " + w3 + " attempts in " + WINDOW_3_SECONDS + "s", MAX_BLOCK_SECONDS);
}
long delay = Math.min(PROGRESSIVE_DELAY_BASE_MS + (record.failureCount * PROGRESSIVE_DELAY_PER_FAILURE_MS), MAX_PROGRESSIVE_DELAY_MS);
record.lastDelayMs = delay;
return AuthResult.allowed(delay);
}
public void recordFailedAttempt(String username, String ip) {
FailureRecord record = failureTracker.computeIfAbsent(username, k -> new FailureRecord());
long now = Instant.now().getEpochSecond();
record.attemptsInWindow1.add(now);
record.attemptsInWindow2.add(now);
record.attemptsInWindow3.add(now);
record.failureCount++;
record.lastFailure = Instant.now();
ipAttemptTimeline.computeIfAbsent(ip, k -> new ArrayList<>()).add(Instant.now());
}
public void recordSuccessfulAttempt(String username, String ip) {
failureTracker.remove(username);
ipAttemptTimeline.remove(ip);
}
public void unblock(String username) {
blockedAccounts.remove(username);
failureTracker.remove(username);
}
public boolean isBlocked(String username) {
Instant blockedUntil = blockedAccounts.get(username);
if (blockedUntil == null) return false;
if (Instant.now().isAfter(blockedUntil)) {
blockedAccounts.remove(username);
return false;
}
return true;
}
public Map<String, Object> getStatus() {
Map<String, Object> status = new LinkedHashMap<>();
status.put("trackedAccounts", failureTracker.size());
status.put("blockedAccounts", blockedAccounts.size());
status.put("totalBlocks", totalBlocks.get());
status.put("totalBruteForceDetections", totalBruteForceDetections.get());
List<Map<String, Object>> blocked = new ArrayList<>();
for (Map.Entry<String, Instant> e : blockedAccounts.entrySet()) {
Map<String, Object> entry = new LinkedHashMap<>();
entry.put("account", e.getKey());
entry.put("until", e.getValue().toString());
entry.put("remainingSeconds", Math.max(0, e.getValue().getEpochSecond() - Instant.now().getEpochSecond()));
blocked.add(entry);
}
status.put("blockedList", blocked);
return status;
}
public int getTrackedAccountCount() { return failureTracker.size(); }
public int getBlockedAccountCount() { return blockedAccounts.size(); }
public long getTotalBlocks() { return totalBlocks.get(); }
public long getTotalBruteForceDetections() { return totalBruteForceDetections.get(); }
private AuthResult handleBruteForce(String username, String ip, String reason, long blockSeconds) {
blockedAccounts.put(username, Instant.now().plusSeconds(blockSeconds));
totalBlocks.incrementAndGet();
totalBruteForceDetections.incrementAndGet();
return AuthResult.blockedWithDetection("Brute force detected for " + username + ": " + reason + ". Blocked for " + blockSeconds + "s");
}
private boolean isIPGloballyBlocked(String ip) {
List<Instant> attempts = ipAttemptTimeline.get(ip);
if (attempts == null) return false;
Instant cutoff = Instant.now().minusSeconds(IP_WINDOW_SECONDS);
attempts.removeIf(t -> t.isBefore(cutoff));
if (attempts.isEmpty()) {
ipAttemptTimeline.remove(ip);
return false;
}
return attempts.size() > IP_GLOBAL_MAX_ATTEMPTS;
}
private void cleanupExpired() {
Instant now = Instant.now();
failureTracker.entrySet().removeIf(e -> {
FailureRecord r = e.getValue();
r.attemptsInWindow1.removeIf(t -> now.getEpochSecond() - t > WINDOW_1_SECONDS);
r.attemptsInWindow2.removeIf(t -> now.getEpochSecond() - t > WINDOW_2_SECONDS);
r.attemptsInWindow3.removeIf(t -> now.getEpochSecond() - t > WINDOW_3_SECONDS);
return r.attemptsInWindow1.isEmpty() && r.attemptsInWindow2.isEmpty()
&& r.attemptsInWindow3.isEmpty() && r.lastFailure != null
&& r.lastFailure.plusSeconds(WINDOW_3_SECONDS * 2).isBefore(now);
});
blockedAccounts.entrySet().removeIf(e -> e.getValue().isBefore(now));
ipAttemptTimeline.entrySet().removeIf(e -> {
e.getValue().removeIf(t -> t.isBefore(now.minusSeconds(IP_WINDOW_SECONDS)));
return e.getValue().isEmpty();
});
}
private static class FailureRecord {
final List<Long> attemptsInWindow1 = new ArrayList<>();
final List<Long> attemptsInWindow2 = new ArrayList<>();
final List<Long> attemptsInWindow3 = new ArrayList<>();
int failureCount;
Instant lastFailure;
long lastDelayMs;
}
public static class AuthResult {
private final boolean allowed;
private final boolean bruteForceDetected;
private final String message;
private final long progressiveDelayMs;
private AuthResult(boolean allowed, boolean bruteForceDetected, String message, long delayMs) {
this.allowed = allowed;
this.bruteForceDetected = bruteForceDetected;
this.message = message;
this.progressiveDelayMs = delayMs;
}
public static AuthResult allowed(long delayMs) {
return new AuthResult(true, false, null, delayMs);
}
public static AuthResult blocked(String message) {
return new AuthResult(false, false, message, 0);
}
public static AuthResult blockedWithDetection(String message) {
return new AuthResult(false, true, message, 0);
}
public boolean isAllowed() { return allowed; }
public boolean isBruteForceDetected() { return bruteForceDetected; }
public String getMessage() { return message; }
public long getProgressiveDelayMs() { return progressiveDelayMs; }
}
}
@@ -0,0 +1,186 @@
package com.aluer.security;
import org.springframework.stereotype.Service;
import java.time.Instant;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicLong;
@Service
public class CSPEnforcementService {
private final Map<String, List<CSPViolation>> violations = new ConcurrentHashMap<>();
private final AtomicLong totalViolations = new AtomicLong(0);
private static final String DEFAULT_CSP_HEADER = String.join("; ",
"default-src 'self'",
"script-src 'self' 'unsafe-inline' 'unsafe-eval'",
"style-src 'self' 'unsafe-inline'",
"img-src 'self' data: https:",
"font-src 'self'",
"connect-src 'self' https://api.aluer.com",
"frame-src 'none'",
"object-src 'none'",
"base-uri 'self'",
"form-action 'self'",
"frame-ancestors 'none'",
"upgrade-insecure-requests"
);
private static final String STRICT_CSP_HEADER = String.join("; ",
"default-src 'self'",
"script-src 'self' 'nonce-{nonce}'",
"style-src 'self' 'nonce-{nonce}'",
"img-src 'self' data:",
"font-src 'self'",
"connect-src 'self'",
"frame-src 'none'",
"object-src 'none'",
"base-uri 'self'",
"form-action 'self'",
"frame-ancestors 'none'",
"require-trusted-types-for 'script'",
"report-uri /api/csp-report"
);
// XSS attack patterns in CSP reports
private static final List<String> XSS_PATTERNS = List.of(
"<script", "javascript:", "onerror=", "onload=", "onclick=",
"eval(", "document.cookie", "document.write", "innerHTML",
"<img src=x onerror", "<svg onload", "expression(",
"String.fromCharCode", "\\x", "\\u00", "fromCodePoint"
);
private static final List<String> CLICKJACK_PATTERNS = List.of(
"opacity:0", "z-index:9999", "position:absolute;top:0;left:0"
);
public CSPCheckResult checkRequest(String uri, Map<String, String> headers, String body) {
List<String> matched = new ArrayList<>();
int score = 0;
// Check for reflected XSS
if (body != null) {
String lower = body.toLowerCase();
for (String pattern : XSS_PATTERNS) {
if (lower.contains(pattern.toLowerCase())) {
matched.add("XSS: " + pattern);
score += 20;
}
}
}
// Check for clickjacking
if (body != null && body.toLowerCase().contains("iframe")) {
for (String pattern : CLICKJACK_PATTERNS) {
if (body.toLowerCase().contains(pattern.toLowerCase())) {
matched.add("CLICKJACK: " + pattern);
score += 15;
}
}
}
// Check for missing security headers
if (headers != null) {
if (!headers.containsKey("x-frame-options")) {
matched.add("MISSING_X_FRAME_OPTIONS");
score += 5;
}
if (!headers.containsKey("x-content-type-options")) {
matched.add("MISSING_X_CONTENT_TYPE_OPTIONS");
score += 5;
}
}
if (score >= 30) {
CSPViolation violation = new CSPViolation(Instant.now(), uri, matched, score);
violations.computeIfAbsent(uri, k -> new ArrayList<>()).add(violation);
totalViolations.incrementAndGet();
return CSPCheckResult.blocked(matched, score);
}
return CSPCheckResult.clean();
}
public String generateCSPHeader(boolean strict) {
if (strict) {
String nonce = generateNonce();
return STRICT_CSP_HEADER.replace("{nonce}", nonce);
}
return DEFAULT_CSP_HEADER;
}
public Map<String, String> getSecurityHeaders() {
Map<String, String> headers = new LinkedHashMap<>();
headers.put("Content-Security-Policy", DEFAULT_CSP_HEADER);
headers.put("X-Content-Type-Options", "nosniff");
headers.put("X-Frame-Options", "DENY");
headers.put("X-XSS-Protection", "1; mode=block");
headers.put("Referrer-Policy", "strict-origin-when-cross-origin");
headers.put("Permissions-Policy", "geolocation=(), microphone=(), camera=(), payment=()");
headers.put("Strict-Transport-Security", "max-age=31536000; includeSubDomains");
return headers;
}
public Map<String, Object> getStatus() {
Map<String, Object> status = new LinkedHashMap<>();
status.put("totalViolations", totalViolations.get());
status.put("violatedEndpoints", violations.size());
List<Map<String, Object>> recent = new ArrayList<>();
for (Map.Entry<String, List<CSPViolation>> e : violations.entrySet()) {
for (CSPViolation v : e.getValue()) {
Map<String, Object> m = new LinkedHashMap<>();
m.put("endpoint", e.getKey());
m.put("patterns", v.patterns);
m.put("score", v.score);
m.put("time", v.timestamp.toString());
recent.add(m);
}
}
recent.sort((a, b) -> b.get("time").toString().compareTo(a.get("time").toString()));
status.put("recentViolations", recent.subList(0, Math.min(recent.size(), 20)));
return status;
}
public long getTotalViolations() { return totalViolations.get(); }
private String generateNonce() {
byte[] bytes = new byte[24];
new Random().nextBytes(bytes);
return Base64.getEncoder().encodeToString(bytes);
}
private static class CSPViolation {
final Instant timestamp;
final String uri;
final List<String> patterns;
final int score;
CSPViolation(Instant timestamp, String uri, List<String> patterns, int score) {
this.timestamp = timestamp;
this.uri = uri;
this.patterns = patterns;
this.score = score;
}
}
public static class CSPCheckResult {
private final boolean blocked;
private final List<String> matched;
private final int score;
private CSPCheckResult(boolean blocked, List<String> matched, int score) {
this.blocked = blocked;
this.matched = matched;
this.score = score;
}
public static CSPCheckResult clean() { return new CSPCheckResult(false, List.of(), 0); }
public static CSPCheckResult blocked(List<String> matched, int score) { return new CSPCheckResult(true, matched, score); }
public boolean isBlocked() { return blocked; }
public List<String> getMatched() { return matched; }
public int getScore() { return score; }
}
}
@@ -0,0 +1,209 @@
package com.aluer.security;
import org.springframework.stereotype.Service;
import java.time.Instant;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicLong;
@Service
public class ComplianceScannerService {
private final Map<String, List<ComplianceFinding>> findings = new ConcurrentHashMap<>();
private final AtomicLong totalFindings = new AtomicLong(0);
private final ScheduledExecutorService scheduler = Executors.newSingleThreadScheduledExecutor();
public ComplianceScannerService() {
scheduler.scheduleAtFixedRate(this::runComplianceScan, 120, 86400, TimeUnit.SECONDS);
}
public ComplianceReport runComplianceScan() {
List<ComplianceFinding> allFindings = new ArrayList<>();
Instant scanTime = Instant.now();
// Check 1: File permissions
allFindings.addAll(checkFilePermissions());
// Check 2: Encryption standards
allFindings.addAll(checkEncryptionStandards());
// Check 3: Authentication requirements
allFindings.addAll(checkAuthenticationRequirements());
// Check 4: Audit logging
allFindings.addAll(checkAuditLogging());
// Check 5: Network security
allFindings.addAll(checkNetworkSecurity());
// Check 6: Minecraft server security
allFindings.addAll(checkMinecraftServerSecurity());
// Check 7: Backup compliance
allFindings.addAll(checkBackupCompliance());
for (ComplianceFinding f : allFindings) {
findings.computeIfAbsent(f.category, k -> new ArrayList<>()).add(f);
}
totalFindings.addAndGet(allFindings.size());
return new ComplianceReport(scanTime, allFindings, calculateComplianceScore(allFindings));
}
private List<ComplianceFinding> checkFilePermissions() {
List<ComplianceFinding> results = new ArrayList<>();
results.add(new ComplianceFinding("FILE_PERMISSIONS", "CONFIG_READABLE",
"Configuration files should not be world-readable", FindingSeverity.HIGH,
"chmod 600 server.properties eula.txt", false));
results.add(new ComplianceFinding("FILE_PERMISSIONS", "PLUGIN_DIR_WRITABLE",
"Plugin directory should not be writable by web server", FindingSeverity.MEDIUM,
"chmod 755 plugins/ && chown minecraft:minecraft plugins/", true));
return results;
}
private List<ComplianceFinding> checkEncryptionStandards() {
List<ComplianceFinding> results = new ArrayList<>();
results.add(new ComplianceFinding("ENCRYPTION", "SSL_TLS_VERSION",
"TLS 1.2+ should be enforced for all API endpoints", FindingSeverity.HIGH,
"Configure SSL in application.yml with modern TLS", false));
results.add(new ComplianceFinding("ENCRYPTION", "RCON_ENCRYPTION",
"RCON should use a strong password (>=16 chars)", FindingSeverity.MEDIUM,
"Set rcon.password with at least 16 characters", false));
results.add(new ComplianceFinding("ENCRYPTION", "API_KEY_STORAGE",
"API keys should use environment variables, not hardcoded", FindingSeverity.CRITICAL,
"Use ${ENV_VAR} syntax in application.yml", false));
return results;
}
private List<ComplianceFinding> checkAuthenticationRequirements() {
List<ComplianceFinding> results = new ArrayList<>();
results.add(new ComplianceFinding("AUTHENTICATION", "ONLINE_MODE",
"Minecraft server should run in online mode", FindingSeverity.CRITICAL,
"Set online-mode=true in server.properties", false));
results.add(new ComplianceFinding("AUTHENTICATION", "API_AUTH",
"API endpoints should require authentication", FindingSeverity.HIGH,
"Implement JWT or API key authentication for /api/**", false));
results.add(new ComplianceFinding("AUTHENTICATION", "WHITELIST_ENABLED",
"Whitelist should be enabled on production servers", FindingSeverity.MEDIUM,
"Set white-list=true in server.properties", false));
return results;
}
private List<ComplianceFinding> checkAuditLogging() {
List<ComplianceFinding> results = new ArrayList<>();
results.add(new ComplianceFinding("AUDIT", "COMMAND_LOGGING",
"All admin commands should be logged", FindingSeverity.HIGH,
"Set log-all-commands: true in security config", true));
results.add(new ComplianceFinding("AUDIT", "LOG_RETENTION",
"Security logs should be retained for at least 90 days", FindingSeverity.MEDIUM,
"Configure log rotation with 90-day retention", false));
results.add(new ComplianceFinding("AUDIT", "TAMPER_PROOF_LOGS",
"Audit logs should be append-only or shipped to external SIEM", FindingSeverity.HIGH,
"Configure remote syslog or SIEM integration", false));
return results;
}
private List<ComplianceFinding> checkNetworkSecurity() {
List<ComplianceFinding> results = new ArrayList<>();
results.add(new ComplianceFinding("NETWORK", "FIREWALL_ACTIVE",
"Host firewall should be active (iptables/ufw)", FindingSeverity.CRITICAL,
"ufw enable && ufw default deny incoming", false));
results.add(new ComplianceFinding("NETWORK", "UNUSED_PORTS",
"Non-essential ports should be closed", FindingSeverity.HIGH,
"Close ports except 25565 (MC), 8080 (API), 22 (SSH)", false));
results.add(new ComplianceFinding("NETWORK", "DDOS_PROTECTION",
"DDoS protection should be enabled", FindingSeverity.HIGH,
"Enable ddos-defense in security config", true));
return results;
}
private List<ComplianceFinding> checkMinecraftServerSecurity() {
List<ComplianceFinding> results = new ArrayList<>();
results.add(new ComplianceFinding("MINECRAFT", "COMMAND_BLOCKS",
"Command blocks should be disabled unless needed", FindingSeverity.MEDIUM,
"Set enable-command-block=false in server.properties", false));
results.add(new ComplianceFinding("MINECRAFT", "SPAWN_PROTECTION",
"Spawn protection should be enabled", FindingSeverity.LOW,
"Set spawn-protection=16 in server.properties", true));
results.add(new ComplianceFinding("MINECRAFT", "OP_RESTRICTION",
"Operator count should be minimal (< 5)", FindingSeverity.MEDIUM,
"Review ops.json and remove unnecessary operators", false));
results.add(new ComplianceFinding("MINECRAFT", "PLUGIN_AUDIT",
"Third-party plugins should be from trusted sources only", FindingSeverity.HIGH,
"Audit all plugins in /plugins directory", false));
return results;
}
private List<ComplianceFinding> checkBackupCompliance() {
List<ComplianceFinding> results = new ArrayList<>();
results.add(new ComplianceFinding("BACKUP", "REGULAR_BACKUPS",
"Automated backups should be enabled (daily minimum)", FindingSeverity.HIGH,
"Enable scheduled backups in config", false));
results.add(new ComplianceFinding("BACKUP", "OFFSITE_BACKUP",
"Backups should be stored offsite or in cloud storage", FindingSeverity.MEDIUM,
"Configure remote backup destination", false));
return results;
}
private double calculateComplianceScore(List<ComplianceFinding> findings) {
if (findings.isEmpty()) return 100.0;
long passed = findings.stream().filter(f -> f.compliant).count();
return (double) passed / findings.size() * 100.0;
}
public Map<String, Object> getStatus() {
Map<String, Object> status = new LinkedHashMap<>();
status.put("totalFindings", totalFindings.get());
ComplianceReport latest = runComplianceScan();
status.put("complianceScore", String.format("%.1f%%", latest.complianceScore));
status.put("totalChecks", latest.findings.size());
status.put("passedChecks", latest.findings.stream().filter(f -> f.compliant).count());
Map<String, Long> byCategory = new LinkedHashMap<>();
for (ComplianceFinding f : latest.findings) {
byCategory.merge(f.category, 1L, Long::sum);
}
status.put("checksByCategory", byCategory);
status.put("lastScanTime", latest.scanTime.toString());
return status;
}
public long getTotalFindings() { return totalFindings.get(); }
public enum FindingSeverity { LOW, MEDIUM, HIGH, CRITICAL }
public static class ComplianceFinding {
public final String category;
public final String checkId;
public final String description;
public final FindingSeverity severity;
public final String remediation;
public final boolean compliant;
ComplianceFinding(String category, String checkId, String description, FindingSeverity severity,
String remediation, boolean compliant) {
this.category = category;
this.checkId = checkId;
this.description = description;
this.severity = severity;
this.remediation = remediation;
this.compliant = compliant;
}
}
public static class ComplianceReport {
public final Instant scanTime;
public final List<ComplianceFinding> findings;
public final double complianceScore;
ComplianceReport(Instant scanTime, List<ComplianceFinding> findings, double complianceScore) {
this.scanTime = scanTime;
this.findings = findings;
this.complianceScore = complianceScore;
}
}
}
@@ -0,0 +1,230 @@
package com.aluer.security;
import org.springframework.stereotype.Service;
import java.time.Instant;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicLong;
@Service
public class DNSTunnelDetectionService {
private final Map<String, DNSQueryTracker> queryTrackers = new ConcurrentHashMap<>();
private final Map<String, Instant> blockedSources = new ConcurrentHashMap<>();
private final ScheduledExecutorService scheduler = Executors.newSingleThreadScheduledExecutor();
private final AtomicLong totalDetections = new AtomicLong(0);
// DNS tunneling indicators
private static final int MAX_QUERY_LENGTH = 52;
private static final int MAX_SUBDOMAIN_COUNT = 3;
private static final int MAX_QUERIES_PER_MINUTE = 60;
private static final int MAX_UNIQUE_SUBDOMAINS = 30;
private static final double ENTROPY_THRESHOLD = 3.8;
private static final long BLOCK_SECONDS = 1800;
private static final Set<String> SUSPICIOUS_DNS_TYPES = Set.of("TXT", "NULL", "CNAME");
private static final Set<String> SUSPICIOUS_TLDS = Set.of(".xyz", ".top", ".tk", ".ml", ".ga", ".cf", ".gq",
".pw", ".club", ".work", ".date", ".bid", ".surf");
public DNSTunnelDetectionService() {
scheduler.scheduleAtFixedRate(this::cleanupExpired, 120, 300, TimeUnit.SECONDS);
}
public TunnelCheckResult checkDNSQuery(String sourceIP, String domain, String queryType) {
if (blockedSources.containsKey(sourceIP)) {
if (Instant.now().isAfter(blockedSources.get(sourceIP))) {
blockedSources.remove(sourceIP);
} else {
return TunnelCheckResult.blocked("Source is blocked for DNS tunneling");
}
}
int score = 0;
List<String> reasons = new ArrayList<>();
// Check 1: Long subdomain (data exfiltration via DNS)
if (domain.length() > MAX_QUERY_LENGTH) {
score += 25;
reasons.add("LONG_DOMAIN: " + domain.length() + " chars");
}
// Check 2: Too many subdomains
int subdomainCount = domain.split("\\.").length - 2;
if (subdomainCount > MAX_SUBDOMAIN_COUNT) {
score += 15;
reasons.add("MANY_SUBDOMAINS: " + subdomainCount);
}
// Check 3: High entropy subdomain (encoded data)
String subdomain = domain.contains(".") ? domain.substring(0, domain.indexOf('.')) : domain;
double entropy = calculateEntropy(subdomain);
if (entropy > ENTROPY_THRESHOLD) {
score += 30;
reasons.add("HIGH_ENTROPY: " + String.format("%.2f", entropy));
}
// Check 4: Suspicious TLD
for (String tld : SUSPICIOUS_TLDS) {
if (domain.toLowerCase().endsWith(tld)) {
score += 10;
reasons.add("SUSPICIOUS_TLD: " + tld);
break;
}
}
// Check 5: Suspicious DNS record type
if (SUSPICIOUS_DNS_TYPES.contains(queryType.toUpperCase())) {
score += 15;
reasons.add("SUSPICIOUS_TYPE: " + queryType);
}
// Check 6: Query frequency
DNSQueryTracker tracker = queryTrackers.computeIfAbsent(sourceIP, k -> new DNSQueryTracker());
tracker.recordQuery(domain, queryType);
if (tracker.queriesPerMinute > MAX_QUERIES_PER_MINUTE) {
score += 20;
reasons.add("HIGH_FREQUENCY: " + tracker.queriesPerMinute + "/min");
}
if (tracker.uniqueSubdomains.size() > MAX_UNIQUE_SUBDOMAINS) {
score += 20;
reasons.add("MANY_UNIQUE: " + tracker.uniqueSubdomains.size());
}
// Check 7: Base32/Base64 encoded subdomain
if (looksEncoded(subdomain)) {
score += 20;
reasons.add("ENCODED_SUBDOMAIN");
}
if (score >= 60) {
blockedSources.put(sourceIP, Instant.now().plusSeconds(BLOCK_SECONDS));
totalDetections.incrementAndGet();
return TunnelCheckResult.detected(score, reasons, BLOCK_SECONDS);
} else if (score >= 30) {
return TunnelCheckResult.suspicious(score, reasons);
}
return TunnelCheckResult.clean();
}
public Map<String, Object> getStatus() {
Map<String, Object> status = new LinkedHashMap<>();
status.put("trackedSources", queryTrackers.size());
status.put("blockedSources", blockedSources.size());
status.put("totalDetections", totalDetections.get());
List<Map<String, Object>> tracked = new ArrayList<>();
for (Map.Entry<String, DNSQueryTracker> e : queryTrackers.entrySet()) {
Map<String, Object> m = new LinkedHashMap<>();
m.put("ip", e.getKey());
m.put("queriesPerMinute", e.getValue().queriesPerMinute);
m.put("uniqueSubdomains", e.getValue().uniqueSubdomains.size());
tracked.add(m);
}
tracked.sort((a, b) -> (int) b.get("queriesPerMinute") - (int) a.get("queriesPerMinute"));
status.put("topTrackers", tracked.subList(0, Math.min(tracked.size(), 20)));
return status;
}
public long getTotalDetections() { return totalDetections.get(); }
private double calculateEntropy(String s) {
if (s.isEmpty()) return 0;
Map<Character, Integer> freq = new HashMap<>();
for (char c : s.toCharArray()) freq.merge(c, 1, Integer::sum);
double entropy = 0;
int len = s.length();
for (int count : freq.values()) {
double p = (double) count / len;
entropy -= p * (Math.log(p) / Math.log(2));
}
return entropy;
}
private boolean looksEncoded(String s) {
int upperCount = 0;
int digitCount = 0;
for (char c : s.toCharArray()) {
if (c >= 'A' && c <= 'Z') upperCount++;
if (c >= '0' && c <= '9') digitCount++;
}
double ratio = (double) (upperCount + digitCount) / s.length();
return s.length() >= 16 && ratio > 0.9;
}
private void cleanupExpired() {
Instant now = Instant.now();
blockedSources.entrySet().removeIf(e -> e.getValue().isBefore(now));
queryTrackers.entrySet().removeIf(e -> e.getValue().lastQuery != null
&& e.getValue().lastQuery.plusSeconds(600).isBefore(now));
}
private static class DNSQueryTracker {
long queriesPerMinute;
final Set<String> uniqueSubdomains = new HashSet<>();
Instant lastQuery;
Instant minuteStart = Instant.now();
void recordQuery(String domain, String queryType) {
lastQuery = Instant.now();
if (Instant.now().isAfter(minuteStart.plusSeconds(60))) {
queriesPerMinute = 0;
minuteStart = Instant.now();
}
queriesPerMinute++;
if (domain.contains(".")) {
uniqueSubdomains.add(domain.substring(0, domain.indexOf('.')));
}
}
}
public static class TunnelCheckResult {
private final boolean blocked;
private final boolean detected;
private final boolean suspicious;
private final int score;
private final List<String> reasons;
private final String message;
private final long blockSeconds;
private TunnelCheckResult(boolean blocked, boolean detected, boolean suspicious, int score,
List<String> reasons, String message, long blockSeconds) {
this.blocked = blocked;
this.detected = detected;
this.suspicious = suspicious;
this.score = score;
this.reasons = reasons;
this.message = message;
this.blockSeconds = blockSeconds;
}
public static TunnelCheckResult clean() {
return new TunnelCheckResult(false, false, false, 0, List.of(), null, 0);
}
public static TunnelCheckResult suspicious(int score, List<String> reasons) {
return new TunnelCheckResult(false, false, true, score, reasons,
"Suspicious DNS: " + String.join("; ", reasons), 0);
}
public static TunnelCheckResult detected(int score, List<String> reasons, long blockSeconds) {
return new TunnelCheckResult(true, true, true, score, reasons,
"DNS tunnel detected: " + String.join("; ", reasons), blockSeconds);
}
public static TunnelCheckResult blocked(String message) {
return new TunnelCheckResult(true, false, false, 0, List.of(), message, 0);
}
public boolean isBlocked() { return blocked; }
public boolean isDetected() { return detected; }
public boolean isSuspicious() { return suspicious; }
public int getScore() { return score; }
public List<String> getReasons() { return reasons; }
public String getMessage() { return message; }
public long getBlockSeconds() { return blockSeconds; }
}
}
@@ -0,0 +1,184 @@
package com.aluer.security;
import org.springframework.stereotype.Service;
import java.time.Instant;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicLong;
import java.util.regex.Pattern;
@Service
public class DataLossPreventionService {
private final Map<String, List<DLPEvent>> detections = new ConcurrentHashMap<>();
private final AtomicLong totalDetections = new AtomicLong(0);
private static final List<DLPRule> RULES = new ArrayList<>();
static {
RULES.add(new DLPRule("EMAIL_LEAK", DLPLevel.HIGH,
Pattern.compile("[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}"),
"Email address detected"));
RULES.add(new DLPRule("IP_LEAK", DLPLevel.MEDIUM,
Pattern.compile("\\b(?:\\d{1,3}\\.){3}\\d{1,3}\\b"),
"IP address detected"));
RULES.add(new DLPRule("API_KEY", DLPLevel.CRITICAL,
Pattern.compile("(?:api[_-]?key|apikey|secret[_-]?key|access[_-]?token|auth[_-]?token)[=:]\\s*['\"]?[a-zA-Z0-9_\\-]{20,}['\"]?", Pattern.CASE_INSENSITIVE),
"API key or secret detected"));
RULES.add(new DLPRule("PASSWORD_LEAK", DLPLevel.CRITICAL,
Pattern.compile("(?:password|passwd|pwd|secret)[=:]\\s*['\"]?\\S{6,}['\"]?", Pattern.CASE_INSENSITIVE),
"Password or credential detected"));
RULES.add(new DLPRule("CONNECTION_STRING", DLPLevel.CRITICAL,
Pattern.compile("(?:jdbc|mongodb|redis|mysql|postgresql|sqlserver):[^\\s]{20,}", Pattern.CASE_INSENSITIVE),
"Database connection string detected"));
RULES.add(new DLPRule("SSH_KEY", DLPLevel.CRITICAL,
Pattern.compile("-----BEGIN (?:RSA |DSA |EC |OPENSSH )?PRIVATE KEY-----"),
"SSH private key detected"));
RULES.add(new DLPRule("JWT_TOKEN", DLPLevel.HIGH,
Pattern.compile("eyJ[a-zA-Z0-9_-]{20,}\\.[a-zA-Z0-9_-]{20,}\\.[a-zA-Z0-9_-]{10,}"),
"JWT token detected"));
RULES.add(new DLPRule("PHONE_NUMBER", DLPLevel.MEDIUM,
Pattern.compile("(?:\\+?86)?1[3-9]\\d{9}"),
"Phone number detected"));
RULES.add(new DLPRule("ID_CARD", DLPLevel.HIGH,
Pattern.compile("\\b\\d{17}[\\dXx]\\b"),
"ID card number detected"));
RULES.add(new DLPRule("CREDIT_CARD", DLPLevel.CRITICAL,
Pattern.compile("\\b(?:\\d[ -]?){13,19}\\b"),
"Potential credit card number"));
RULES.add(new DLPRule("RCON_PASSWORD", DLPLevel.CRITICAL,
Pattern.compile("(?:rcon\\.password|enable-rcon)\\s*=\\s*\\S+", Pattern.CASE_INSENSITIVE),
"RCON password in configuration"));
RULES.add(new DLPRule("MINECRAFT_TOKEN", DLPLevel.HIGH,
Pattern.compile("(?:accessToken|clientToken|XboxLive|minecraft:token)[=:]\\s*['\"]?[a-zA-Z0-9_\\-]{20,}['\"]?", Pattern.CASE_INSENSITIVE),
"Minecraft authentication token detected"));
}
public DLPCheckResult scan(String content, String source, String channel) {
if (content == null || content.trim().isEmpty()) return DLPCheckResult.clean();
Map<DLPLevel, List<String>> findings = new LinkedHashMap<>();
for (DLPRule rule : RULES) {
if (rule.pattern.matcher(content).find()) {
findings.computeIfAbsent(rule.level, k -> new ArrayList<>()).add(rule.name + ": " + rule.description);
}
}
if (!findings.isEmpty()) {
List<String> allFindings = new ArrayList<>();
findings.values().forEach(allFindings::addAll);
DLPEvent event = new DLPEvent(Instant.now(), source, channel, allFindings,
content.length() > 200 ? content.substring(0, 200) : content);
detections.computeIfAbsent(source, k -> new ArrayList<>()).add(event);
totalDetections.incrementAndGet();
return DLPCheckResult.detected(findings);
}
return DLPCheckResult.clean();
}
public String redactSensitiveData(String content) {
if (content == null) return null;
String result = content;
for (DLPRule rule : RULES) {
result = rule.pattern.matcher(result).replaceAll("[REDACTED-" + rule.name + "]");
}
return result;
}
public Map<String, Object> getStatus() {
Map<String, Object> status = new LinkedHashMap<>();
status.put("totalDetections", totalDetections.get());
status.put("rulesCount", RULES.size());
status.put("trackedSources", detections.size());
Map<String, Long> byType = new LinkedHashMap<>();
for (List<DLPEvent> events : detections.values()) {
for (DLPEvent e : events) {
for (String finding : e.findings) {
String type = finding.split(":")[0].trim();
byType.merge(type, 1L, Long::sum);
}
}
}
status.put("detectionsByType", byType);
List<Map<String, Object>> recent = new ArrayList<>();
for (Map.Entry<String, List<DLPEvent>> e : detections.entrySet()) {
for (DLPEvent event : e.getValue()) {
Map<String, Object> m = new LinkedHashMap<>();
m.put("source", e.getKey());
m.put("channel", event.channel);
m.put("findings", event.findings);
m.put("snippet", event.snippet);
m.put("time", event.timestamp.toString());
recent.add(m);
}
}
recent.sort((a, b) -> b.get("time").toString().compareTo(a.get("time").toString()));
status.put("recentDetections", recent.subList(0, Math.min(recent.size(), 20)));
return status;
}
public long getTotalDetections() { return totalDetections.get(); }
public enum DLPLevel { LOW, MEDIUM, HIGH, CRITICAL }
private static class DLPRule {
final String name;
final DLPLevel level;
final Pattern pattern;
final String description;
DLPRule(String name, DLPLevel level, Pattern pattern, String description) {
this.name = name;
this.level = level;
this.pattern = pattern;
this.description = description;
}
}
private static class DLPEvent {
final Instant timestamp;
final String source;
final String channel;
final List<String> findings;
final String snippet;
DLPEvent(Instant timestamp, String source, String channel, List<String> findings, String snippet) {
this.timestamp = timestamp;
this.source = source;
this.channel = channel;
this.findings = findings;
this.snippet = snippet;
}
}
public static class DLPCheckResult {
private final boolean detected;
private final Map<DLPLevel, List<String>> findings;
private DLPCheckResult(boolean detected, Map<DLPLevel, List<String>> findings) {
this.detected = detected;
this.findings = findings;
}
public static DLPCheckResult clean() { return new DLPCheckResult(false, Map.of()); }
public static DLPCheckResult detected(Map<DLPLevel, List<String>> findings) { return new DLPCheckResult(true, findings); }
public boolean isDetected() { return detected; }
public Map<DLPLevel, List<String>> getFindings() { return findings; }
}
}
@@ -0,0 +1,163 @@
package com.aluer.security;
import org.springframework.stereotype.Service;
import java.time.Instant;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicLong;
@Service
public class DatabaseFirewallService {
private final Map<String, List<QueryEvent>> queryLog = new ConcurrentHashMap<>();
private final AtomicLong totalBlocked = new AtomicLong(0);
private final AtomicLong totalQueries = new AtomicLong(0);
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",
"REVOKE", "SHUTDOWN", "xp_cmdshell", "sp_configure",
"INTO OUTFILE", "INTO DUMPFILE", "LOAD_FILE", "BENCHMARK(",
"SLEEP(", "WAITFOR DELAY", "INFORMATION_SCHEMA", "mysql.user",
"sys.tables", "sqlite_master", "pg_shadow", "pg_database"
);
private static final int MAX_QUERY_LENGTH = 4096;
private static final int MAX_UNION_COUNT = 3;
private static final int MAX_JOIN_COUNT = 10;
public QueryCheckResult checkQuery(String sql, String source, String database) {
if (sql == null || sql.trim().isEmpty()) return QueryCheckResult.clean();
totalQueries.incrementAndGet();
List<String> reasons = new ArrayList<>();
String upper = sql.toUpperCase().trim();
// Length check
if (sql.length() > MAX_QUERY_LENGTH) {
reasons.add("OVERSIZED_QUERY: " + sql.length() + " chars");
}
// Dangerous keywords
for (String keyword : DANGEROUS_KEYWORDS) {
if (upper.contains(keyword.toUpperCase())) {
reasons.add("DANGEROUS_KEYWORD: " + keyword);
}
}
// UNION-based injection
int unionCount = countKeyword(upper, "UNION");
if (unionCount > MAX_UNION_COUNT) {
reasons.add("EXCESSIVE_UNION: " + unionCount);
}
// Comment-based injection
if (upper.contains("-- ") || upper.contains("/*") || upper.contains("#")) {
if (upper.contains("' OR ") || upper.contains("' AND ") || upper.contains("1=1") || upper.contains("1=0")) {
reasons.add("COMMENT_INJECTION");
}
}
// Always-true conditions
if (upper.contains("OR 1=1") || upper.contains("OR '1'='1'")
|| upper.contains("'a'='a'") || upper.contains("\"=\"") || upper.contains("||1||")) {
reasons.add("ALWAYS_TRUE_CONDITION");
}
// Stacked queries
int semicolons = countChar(sql, ';');
if (semicolons > 1) {
reasons.add("STACKED_QUERIES: " + semicolons);
}
// Time-based injection
if (upper.contains("SLEEP(") || upper.contains("BENCHMARK(") || upper.contains("PG_SLEEP(") || upper.contains("WAITFOR")) {
reasons.add("TIME_BASED_INJECTION");
}
if (!reasons.isEmpty()) {
QueryEvent event = new QueryEvent(Instant.now(), source, database, sql.length() > 200 ? sql.substring(0, 200) : sql, reasons);
queryLog.computeIfAbsent(source, k -> new ArrayList<>()).add(event);
totalBlocked.incrementAndGet();
return QueryCheckResult.blocked(reasons);
}
return QueryCheckResult.clean();
}
public Map<String, Object> getStatus() {
Map<String, Object> status = new LinkedHashMap<>();
status.put("totalQueries", totalQueries.get());
status.put("totalBlocked", totalBlocked.get());
status.put("trackedSources", queryLog.size());
List<Map<String, Object>> recent = new ArrayList<>();
for (Map.Entry<String, List<QueryEvent>> e : queryLog.entrySet()) {
for (QueryEvent event : e.getValue()) {
Map<String, Object> m = new LinkedHashMap<>();
m.put("source", e.getKey());
m.put("database", event.database);
m.put("snippet", event.snippet);
m.put("reasons", event.reasons);
m.put("time", event.timestamp.toString());
recent.add(m);
}
}
recent.sort((a, b) -> b.get("time").toString().compareTo(a.get("time").toString()));
status.put("recentBlocked", recent.subList(0, Math.min(recent.size(), 20)));
return status;
}
public long getTotalBlocked() { return totalBlocked.get(); }
public long getTotalQueries() { return totalQueries.get(); }
private int countKeyword(String text, String keyword) {
int count = 0;
int idx = 0;
while ((idx = text.indexOf(keyword, idx)) != -1) {
count++;
idx += keyword.length();
}
return count;
}
private int countChar(String text, char c) {
int count = 0;
for (char x : text.toCharArray()) {
if (x == c) count++;
}
return count;
}
private static class QueryEvent {
final Instant timestamp;
final String source;
final String database;
final String snippet;
final List<String> reasons;
QueryEvent(Instant timestamp, String source, String database, String snippet, List<String> reasons) {
this.timestamp = timestamp;
this.source = source;
this.database = database;
this.snippet = snippet;
this.reasons = reasons;
}
}
public static class QueryCheckResult {
private final boolean blocked;
private final List<String> reasons;
private QueryCheckResult(boolean blocked, List<String> reasons) {
this.blocked = blocked;
this.reasons = reasons;
}
public static QueryCheckResult clean() { return new QueryCheckResult(false, List.of()); }
public static QueryCheckResult blocked(List<String> reasons) { return new QueryCheckResult(true, reasons); }
public boolean isBlocked() { return blocked; }
public List<String> getReasons() { return reasons; }
}
}
@@ -0,0 +1,226 @@
package com.aluer.security;
import org.springframework.stereotype.Service;
import java.time.Instant;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicLong;
import java.util.regex.Pattern;
@Service
public class ExploitSignatureService {
private final Map<String, List<ExploitEvent>> detections = new ConcurrentHashMap<>();
private final Map<String, Instant> blockedSources = new ConcurrentHashMap<>();
private final AtomicLong totalDetections = new AtomicLong(0);
private static final List<ExploitSignature> SIGNATURES = new ArrayList<>();
static {
SIGNATURES.add(new ExploitSignature("LOG4J_JNDI", Severity.CRITICAL,
Pattern.compile("\\$\\{[^}]*jndi[^}]*\\}", Pattern.CASE_INSENSITIVE),
"Log4Shell JNDI injection attempt"));
SIGNATURES.add(new ExploitSignature("LOG4J_OBFUSCATED", Severity.CRITICAL,
Pattern.compile("\\$\\{[^}]*:(lower|upper|::-)[^}]*j[^}]*n[^}]*d[^}]*i[^}]*\\}", Pattern.CASE_INSENSITIVE),
"Obfuscated Log4Shell injection"));
SIGNATURES.add(new ExploitSignature("COMMAND_INJECTION", Severity.HIGH,
Pattern.compile("(?:/execute|/op\\s|/deop\\s|/stop|/restart|/kick\\s|/ban\\s|/whitelist\\s|/save-all|/reload)", Pattern.CASE_INSENSITIVE),
"Suspicious Minecraft command injection"));
SIGNATURES.add(new ExploitSignature("BOOK_EXPLOIT", Severity.HIGH,
Pattern.compile("\"pages\"\\s*:\\s*\\[.*\"text\"\\s*:\\s*\".{10000,}\"", Pattern.CASE_INSENSITIVE),
"Book and quill exploit with oversized payload"));
SIGNATURES.add(new ExploitSignature("SIGN_EXPLOIT", Severity.HIGH,
Pattern.compile("\"Text[1-4]\"\\s*:\\s*\"\\{\\\\\"text\\\\\".{5000,}\"", Pattern.CASE_INSENSITIVE),
"Sign exploit with oversized JSON"));
SIGNATURES.add(new ExploitSignature("NBT_PATH_TRAVERSAL", Severity.HIGH,
Pattern.compile("(?:\\\\.\\\\.\\\\|\\/\\.\\.\\/|\\.\\.[\\\\/])"),
"NBT path traversal attempt"));
SIGNATURES.add(new ExploitSignature("PACKET_OVERFLOW", Severity.MEDIUM,
Pattern.compile("\\{.*\"length\"\\s*:\\s*-?[1-9]\\d{5,}.*\\}"),
"Oversized or negative packet length"));
SIGNATURES.add(new ExploitSignature("CHUNK_BAN", Severity.HIGH,
Pattern.compile("\"chunkX\"\\s*:\\s*-?\\d{6,}|palette.{5000,}"),
"Chunk loading exploit (chunk ban)"));
SIGNATURES.add(new ExploitSignature("SQL_INJECTION", Severity.CRITICAL,
Pattern.compile("(?:'\\s*(?:OR|AND|UNION|SELECT|INSERT|UPDATE|DELETE|DROP)\\s+|--\\s|;\\s*(?:DROP|CREATE|ALTER)\\s+)", Pattern.CASE_INSENSITIVE),
"SQL injection attempt"));
SIGNATURES.add(new ExploitSignature("XSS_ATTACK", Severity.HIGH,
Pattern.compile("<script[^>]*>|javascript\\s*:|on\\w+\\s*=\\s*['\"].*['\"]|<img[^>]*onerror|<svg[^>]*onload", Pattern.CASE_INSENSITIVE),
"Cross-site scripting attempt"));
SIGNATURES.add(new ExploitSignature("PATH_TRAVERSAL", Severity.MEDIUM,
Pattern.compile("(?:\\\\.\\\\.\\\\|\\/\\.\\.\\/|\\.\\.[\\\\/])(?:etc|var|home|root|winnt|windows|System32)", Pattern.CASE_INSENSITIVE),
"File path traversal attempt"));
SIGNATURES.add(new ExploitSignature("RCE_ATTEMPT", Severity.CRITICAL,
Pattern.compile("(?:Runtime\\.getRuntime|ProcessBuilder|exec\\s*\\(|System\\.exec|popen|subprocess)", Pattern.CASE_INSENSITIVE),
"Runtime command execution attempt"));
SIGNATURES.add(new ExploitSignature("DESERIALIZATION", Severity.CRITICAL,
Pattern.compile("(?:ObjectInputStream|readObject|ysoserial|CommonsCollections|InvokerTransformer)", Pattern.CASE_INSENSITIVE),
"Java deserialization exploit"));
SIGNATURES.add(new ExploitSignature("SERVER_PROPERTIES_TAMPER", Severity.HIGH,
Pattern.compile("(?:online-mode\\s*=\\s*false|enable-command-block\\s*=\\s*true|spawn-protection\\s*=\\s*0|enable-rcon\\s*=\\s*true|pvp\\s*=\\s*true)", Pattern.CASE_INSENSITIVE),
"Suspicious server.properties tampering"));
}
public ExploitCheckResult scan(String content, String sourceIP, String context) {
if (content == null || content.trim().isEmpty()) return ExploitCheckResult.clean();
if (blockedSources.containsKey(sourceIP)) {
if (Instant.now().isAfter(blockedSources.get(sourceIP))) {
blockedSources.remove(sourceIP);
} else {
return ExploitCheckResult.blocked("Source is blocked for exploit activity");
}
}
List<ExploitMatch> matches = new ArrayList<>();
for (ExploitSignature sig : SIGNATURES) {
if (sig.pattern.matcher(content).find()) {
matches.add(new ExploitMatch(sig, context));
}
}
if (!matches.isEmpty()) {
ExploitEvent event = new ExploitEvent(Instant.now(), sourceIP, context,
content.length() > 200 ? content.substring(0, 200) : content, matches);
detections.computeIfAbsent(sourceIP, k -> new ArrayList<>()).add(event);
totalDetections.incrementAndGet();
int maxSeverity = matches.stream().mapToInt(m -> m.signature.severity.ordinal()).max().orElse(0);
if (maxSeverity >= Severity.CRITICAL.ordinal()) {
blockedSources.put(sourceIP, Instant.now().plusSeconds(3600));
}
return ExploitCheckResult.detected(matches);
}
return ExploitCheckResult.clean();
}
public Map<String, Object> getStatus() {
Map<String, Object> status = new LinkedHashMap<>();
status.put("totalDetections", totalDetections.get());
status.put("signaturesCount", SIGNATURES.size());
status.put("blockedSources", blockedSources.size());
status.put("trackedSources", detections.size());
Map<String, Long> byType = new LinkedHashMap<>();
for (List<ExploitEvent> events : detections.values()) {
for (ExploitEvent e : events) {
for (ExploitMatch m : e.matches) {
byType.merge(m.signature.name, 1L, Long::sum);
}
}
}
status.put("detectionsByType", byType);
List<Map<String, Object>> recent = new ArrayList<>();
for (Map.Entry<String, List<ExploitEvent>> e : detections.entrySet()) {
for (ExploitEvent event : e.getValue()) {
Map<String, Object> m = new LinkedHashMap<>();
m.put("source", e.getKey());
m.put("context", event.context);
m.put("snippet", event.snippet);
List<String> names = new ArrayList<>();
List<String> severities = new ArrayList<>();
for (ExploitMatch match : event.matches) {
names.add(match.signature.name);
severities.add(match.signature.severity.name());
}
m.put("matches", names);
m.put("severities", severities);
m.put("time", event.timestamp.toString());
recent.add(m);
}
}
recent.sort((a, b) -> b.get("time").toString().compareTo(a.get("time").toString()));
status.put("recentDetections", recent.subList(0, Math.min(recent.size(), 20)));
return status;
}
public long getTotalDetections() { return totalDetections.get(); }
public int getSignatureCount() { return SIGNATURES.size(); }
public enum Severity { LOW, MEDIUM, HIGH, CRITICAL }
public static class ExploitSignature {
final String name;
final Severity severity;
final Pattern pattern;
final String description;
ExploitSignature(String name, Severity severity, Pattern pattern, String description) {
this.name = name;
this.severity = severity;
this.pattern = pattern;
this.description = description;
}
}
public static class ExploitMatch {
final ExploitSignature signature;
final String context;
ExploitMatch(ExploitSignature signature, String context) {
this.signature = signature;
this.context = context;
}
public String getName() { return signature.name; }
public Severity getSeverity() { return signature.severity; }
public String getDescription() { return signature.description; }
}
private static class ExploitEvent {
final Instant timestamp;
final String sourceIP;
final String context;
final String snippet;
final List<ExploitMatch> matches;
ExploitEvent(Instant timestamp, String sourceIP, String context, String snippet, List<ExploitMatch> matches) {
this.timestamp = timestamp;
this.sourceIP = sourceIP;
this.context = context;
this.snippet = snippet;
this.matches = matches;
}
}
public static class ExploitCheckResult {
private final boolean blocked;
private final boolean detected;
private final String message;
private final List<ExploitMatch> matches;
private ExploitCheckResult(boolean blocked, boolean detected, String message, List<ExploitMatch> matches) {
this.blocked = blocked;
this.detected = detected;
this.message = message;
this.matches = matches;
}
public static ExploitCheckResult clean() { return new ExploitCheckResult(false, false, null, List.of()); }
public static ExploitCheckResult detected(List<ExploitMatch> matches) {
return new ExploitCheckResult(true, true, "Exploit detected: " + matches.size() + " signature(s)", matches);
}
public static ExploitCheckResult blocked(String message) { return new ExploitCheckResult(true, false, message, List.of()); }
public boolean isBlocked() { return blocked; }
public boolean isDetected() { return detected; }
public String getMessage() { return message; }
public List<ExploitMatch> getMatches() { return matches; }
}
}
@@ -0,0 +1,227 @@
package com.aluer.security;
import org.springframework.stereotype.Service;
import java.io.*;
import java.nio.file.*;
import java.time.Instant;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicLong;
@Service
public class ForensicsCollectorService {
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() {
try {
Files.createDirectories(Path.of(evidenceDir));
} catch (IOException ignored) {}
}
public ForensicsCase openCase(String caseName, String reason, String operator) {
String caseId = "CASE-" + Instant.now().getEpochSecond() + "-" + randomHex(6);
ForensicsCase fCase = new ForensicsCase(caseId, caseName, reason, operator, Instant.now());
cases.put(caseId, fCase);
evidenceStore.put(caseId, new ArrayList<>());
return fCase;
}
public ForensicEvidence collectProcessList(String caseId) {
StringBuilder sb = new StringBuilder();
try {
Process proc = Runtime.getRuntime().exec(new String[]{"sh", "-c", "ps aux"});
try (BufferedReader reader = new BufferedReader(new InputStreamReader(proc.getInputStream()))) {
String line;
while ((line = reader.readLine()) != null) {
sb.append(line).append('\n');
}
}
} catch (Exception e) {
sb.append("ERROR: ").append(e.getMessage());
}
return addEvidence(caseId, "PROCESS_LIST", sb.toString());
}
public ForensicEvidence collectNetworkConnections(String caseId) {
StringBuilder sb = new StringBuilder();
try {
Process proc = Runtime.getRuntime().exec(new String[]{"sh", "-c", "ss -tunap 2>/dev/null || netstat -tunap 2>/dev/null"});
try (BufferedReader reader = new BufferedReader(new InputStreamReader(proc.getInputStream()))) {
String line;
while ((line = reader.readLine()) != null) {
sb.append(line).append('\n');
}
}
} catch (Exception e) {
sb.append("ERROR: ").append(e.getMessage());
}
return addEvidence(caseId, "NETWORK_CONNECTIONS", sb.toString());
}
public ForensicEvidence collectOpenFiles(String caseId) {
StringBuilder sb = new StringBuilder();
try {
Process proc = Runtime.getRuntime().exec(new String[]{"sh", "-c", "lsof -nP 2>/dev/null | head -500"});
try (BufferedReader reader = new BufferedReader(new InputStreamReader(proc.getInputStream()))) {
String line;
while ((line = reader.readLine()) != null) {
sb.append(line).append('\n');
}
}
} catch (Exception e) {
sb.append("ERROR: ").append(e.getMessage());
}
return addEvidence(caseId, "OPEN_FILES", sb.toString());
}
public ForensicEvidence collectSystemLogs(String caseId) {
StringBuilder sb = new StringBuilder();
try {
Process proc = Runtime.getRuntime().exec(new String[]{"sh", "-c", "journalctl -n 200 --no-pager 2>/dev/null || dmesg | tail -200"});
try (BufferedReader reader = new BufferedReader(new InputStreamReader(proc.getInputStream()))) {
String line;
while ((line = reader.readLine()) != null) {
sb.append(line).append('\n');
}
}
} catch (Exception e) {
sb.append("ERROR: ").append(e.getMessage());
}
return addEvidence(caseId, "SYSTEM_LOGS", sb.toString());
}
public ForensicEvidence collectMCPServerLogs(String caseId, String mcLogPath) {
if (mcLogPath == null) mcLogPath = "/opt/minecraft/logs/latest.log";
try {
String content = Files.readString(Path.of(mcLogPath));
return addEvidence(caseId, "MC_SERVER_LOG", content);
} catch (Exception e) {
return addEvidence(caseId, "MC_SERVER_LOG", "ERROR: " + e.getMessage());
}
}
public ForensicEvidence collectTimestampSnapshot(String caseId) {
Map<String, Object> snapshot = new LinkedHashMap<>();
snapshot.put("systemTime", Instant.now().toString());
snapshot.put("uptime", System.getProperty("os.name"));
snapshot.put("javaVersion", System.getProperty("java.version"));
snapshot.put("availableProcessors", Runtime.getRuntime().availableProcessors());
snapshot.put("freeMemory", Runtime.getRuntime().freeMemory());
snapshot.put("totalMemory", Runtime.getRuntime().totalMemory());
return addEvidence(caseId, "TIMESTAMP_SNAPSHOT", snapshot.toString());
}
public ForensicsCase closeCase(String caseId, String conclusion, String operator) {
ForensicsCase fCase = cases.get(caseId);
if (fCase != null) {
fCase.close(conclusion, operator);
saveCaseToDisk(fCase);
}
return fCase;
}
public Map<String, Object> getStatus() {
Map<String, Object> status = new LinkedHashMap<>();
status.put("openCases", cases.values().stream().filter(c -> !c.closed).count());
status.put("totalCases", cases.size());
status.put("totalEvidence", totalEvidenceCollected.get());
List<Map<String, Object>> caseList = new ArrayList<>();
for (ForensicsCase c : cases.values()) {
Map<String, Object> m = new LinkedHashMap<>();
m.put("id", c.caseId);
m.put("name", c.caseName);
m.put("reason", c.reason);
m.put("openTime", c.openTime.toString());
m.put("closed", c.closed);
m.put("evidenceCount", evidenceStore.getOrDefault(c.caseId, List.of()).size());
caseList.add(m);
}
caseList.sort((a, b) -> b.get("openTime").toString().compareTo(a.get("openTime").toString()));
status.put("cases", caseList);
return status;
}
public long getTotalEvidence() { return totalEvidenceCollected.get(); }
private ForensicEvidence addEvidence(String caseId, String type, String content) {
ForensicEvidence evidence = new ForensicEvidence(caseId, type, content, Instant.now());
evidenceStore.computeIfAbsent(caseId, k -> new ArrayList<>()).add(evidence);
totalEvidenceCollected.incrementAndGet();
return evidence;
}
private void saveCaseToDisk(ForensicsCase fCase) {
try {
Path caseDir = Path.of(evidenceDir, fCase.caseId);
Files.createDirectories(caseDir);
StringBuilder report = new StringBuilder();
report.append("=== FORENSICS CASE REPORT ===\n");
report.append("Case ID: ").append(fCase.caseId).append('\n');
report.append("Name: ").append(fCase.caseName).append('\n');
report.append("Reason: ").append(fCase.reason).append('\n');
report.append("Operator: ").append(fCase.operator).append('\n');
report.append("Opened: ").append(fCase.openTime).append('\n');
report.append("Closed: ").append(fCase.closeTime).append('\n');
report.append("Conclusion: ").append(fCase.conclusion).append('\n');
report.append("\n--- EVIDENCE ---\n\n");
List<ForensicEvidence> items = evidenceStore.getOrDefault(fCase.caseId, List.of());
for (ForensicEvidence e : items) {
report.append("[").append(e.type).append("] ").append(e.timestamp).append('\n');
report.append(e.content).append("\n---\n\n");
}
Files.writeString(caseDir.resolve("report.txt"), report.toString());
} catch (IOException ignored) {}
}
private String randomHex(int len) {
byte[] bytes = new byte[len];
new Random().nextBytes(bytes);
StringBuilder sb = new StringBuilder();
for (byte b : bytes) sb.append(String.format("%02x", b));
return sb.toString().substring(0, len);
}
public static class ForensicsCase {
public final String caseId;
public final String caseName;
public final String reason;
public final String operator;
public final Instant openTime;
public Instant closeTime;
public String conclusion;
public boolean closed;
ForensicsCase(String caseId, String caseName, String reason, String operator, Instant openTime) {
this.caseId = caseId;
this.caseName = caseName;
this.reason = reason;
this.operator = operator;
this.openTime = openTime;
}
void close(String conclusion, String operator) {
this.conclusion = conclusion;
this.closeTime = Instant.now();
this.closed = true;
}
}
public static class ForensicEvidence {
public final String caseId;
public final String type;
public final String content;
public final Instant timestamp;
ForensicEvidence(String caseId, String type, String content, Instant timestamp) {
this.caseId = caseId;
this.type = type;
this.content = content;
this.timestamp = timestamp;
}
}
}
@@ -0,0 +1,198 @@
package com.aluer.security;
import org.springframework.stereotype.Service;
import java.time.Instant;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicLong;
@Service
public class IncidentResponseService {
private final Map<String, Incident> activeIncidents = new ConcurrentHashMap<>();
private final Map<String, List<ResponseAction>> actionLog = new ConcurrentHashMap<>();
private final AtomicLong totalIncidents = new AtomicLong(0);
private final AtomicLong totalActions = new AtomicLong(0);
private static final Map<String, ResponsePlaybook> PLAYBOOKS = new LinkedHashMap<>();
static {
PLAYBOOKS.put("DDOS_ATTACK", new ResponsePlaybook("DDoS Attack Response", List.of(
new PlayAction("ENABLE_RATE_LIMITING", "Activate aggressive rate limiting on all endpoints", 0),
new PlayAction("BLOCK_TOP_OFFENDERS", "Block top 10 attacking IPs via firewall", 30),
new PlayAction("ENABLE_CDN_CHALLENGE", "Switch Cloudflare to 'Under Attack' mode", 60),
new PlayAction("SCALE_RESOURCES", "Increase connection pool and thread limits", 120),
new PlayAction("NOTIFY_ADMIN", "Send critical alert to administrators", 0)
), IncidentSeverity.CRITICAL));
PLAYBOOKS.put("BRUTE_FORCE", new ResponsePlaybook("Brute Force Response", List.of(
new PlayAction("LOCK_ACCOUNT", "Temporarily lock targeted account", 0),
new PlayAction("BLOCK_SOURCE_IP", "Block attacking IP for 30 minutes", 10),
new PlayAction("ENABLE_CAPTCHA", "Enable CAPTCHA for login endpoints", 60),
new PlayAction("INCREASE_DELAY", "Increase progressive delay to maximum", 0),
new PlayAction("LOG_FORENSICS", "Collect forensics data for investigation", 120)
), IncidentSeverity.HIGH));
PLAYBOOKS.put("INTRUSION_DETECTED", new ResponsePlaybook("Intrusion Response", List.of(
new PlayAction("ISOLATE_AFFECTED", "Isolate affected components immediately", 0),
new PlayAction("COLLECT_EVIDENCE", "Start forensics evidence collection", 10),
new PlayAction("BLOCK_INDICATORS", "Block all identified IOCs", 30),
new PlayAction("RESTORE_BACKUP", "Restore from last known good backup", 300),
new PlayAction("PATCH_VULNERABILITY", "Apply security patches if applicable", 600),
new PlayAction("FULL_AUDIT", "Perform complete security audit", 1800)
), IncidentSeverity.CRITICAL));
PLAYBOOKS.put("MINECRAFT_EXPLOIT", new ResponsePlaybook("Minecraft Exploit Response", List.of(
new PlayAction("KICK_EXPLOITER", "Kick player identified as exploit source", 0),
new PlayAction("BAN_IP", "Ban exploit source IP in firewall", 5),
new PlayAction("ENABLE_WHITELIST", "Temporarily enable server whitelist", 15),
new PlayAction("CHECK_PLUGINS", "Verify plugin integrity", 60),
new PlayAction("SCAN_LOGS", "Full log analysis for exploit patterns", 120)
), IncidentSeverity.HIGH));
PLAYBOOKS.put("DATA_BREACH", new ResponsePlaybook("Data Breach Response", List.of(
new PlayAction("REVOKE_TOKENS", "Revoke all active API tokens and sessions", 0),
new PlayAction("ROTATE_KEYS", "Rotate all API keys and passwords", 60),
new PlayAction("NOTIFY_USERS", "Send breach notification to affected users", 300),
new PlayAction("AUDIT_ACCESS", "Audit all recent access logs", 120),
new PlayAction("ENGAGE_LEGAL", "Engage legal/compliance team", 3600)
), IncidentSeverity.CRITICAL));
}
public Incident declareIncident(String type, String description, String source, IncidentSeverity severity) {
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());
activeIncidents.put(incidentId, incident);
totalIncidents.incrementAndGet();
if (playbook != null) {
for (PlayAction action : playbook.actions) {
executeAction(incidentId, action);
}
}
return incident;
}
public ResponseAction executeAction(String incidentId, PlayAction action) {
ResponseAction executed = new ResponseAction(action, Instant.now());
actionLog.computeIfAbsent(incidentId, k -> new ArrayList<>()).add(executed);
totalActions.incrementAndGet();
return executed;
}
public Incident resolveIncident(String incidentId, String resolution) {
Incident incident = activeIncidents.remove(incidentId);
if (incident != null) {
incident.resolve(resolution);
}
return incident;
}
public Map<String, Object> getStatus() {
Map<String, Object> status = new LinkedHashMap<>();
status.put("activeIncidents", activeIncidents.size());
status.put("totalIncidents", totalIncidents.get());
status.put("totalActions", totalActions.get());
status.put("playbooksAvailable", PLAYBOOKS.keySet());
List<Map<String, Object>> incidents = new ArrayList<>();
for (Incident inc : activeIncidents.values()) {
Map<String, Object> m = new LinkedHashMap<>();
m.put("id", inc.incidentId);
m.put("type", inc.type);
m.put("severity", inc.severity.name());
m.put("description", inc.description);
m.put("source", inc.source);
m.put("declaredTime", inc.declaredTime.toString());
m.put("actionsTaken", actionLog.getOrDefault(inc.incidentId, List.of()).size());
incidents.add(m);
}
status.put("incidents", incidents);
List<String> playbookNames = new ArrayList<>();
for (Map.Entry<String, ResponsePlaybook> e : PLAYBOOKS.entrySet()) {
playbookNames.add(e.getKey() + ": " + e.getValue().name + " (" + e.getValue().actions.size() + " actions)");
}
status.put("playbooks", playbookNames);
return status;
}
public long getTotalIncidents() { return totalIncidents.get(); }
public long getTotalActions() { return totalActions.get(); }
private String randomHex(int len) {
byte[] bytes = new byte[len];
new Random().nextBytes(bytes);
StringBuilder sb = new StringBuilder();
for (byte b : bytes) sb.append(String.format("%02x", b));
return sb.toString();
}
public enum IncidentSeverity { LOW, MEDIUM, HIGH, CRITICAL }
public static class ResponsePlaybook {
public final String name;
public final List<PlayAction> actions;
public final IncidentSeverity defaultSeverity;
ResponsePlaybook(String name, List<PlayAction> actions, IncidentSeverity defaultSeverity) {
this.name = name;
this.actions = actions;
this.defaultSeverity = defaultSeverity;
}
}
public static class PlayAction {
public final String name;
public final String description;
public final int delaySeconds;
public PlayAction(String name, String description, int delaySeconds) {
this.name = name;
this.description = description;
this.delaySeconds = delaySeconds;
}
}
public static class Incident {
public final String incidentId;
public final String type;
public final String description;
public final String source;
public final IncidentSeverity severity;
public final ResponsePlaybook playbook;
public final Instant declaredTime;
public Instant resolvedTime;
public String resolution;
public boolean resolved;
Incident(String incidentId, String type, String description, String source,
IncidentSeverity severity, ResponsePlaybook playbook, Instant declaredTime) {
this.incidentId = incidentId;
this.type = type;
this.description = description;
this.source = source;
this.severity = severity;
this.playbook = playbook;
this.declaredTime = declaredTime;
}
void resolve(String resolution) {
this.resolution = resolution;
this.resolvedTime = Instant.now();
this.resolved = true;
}
}
public static class ResponseAction {
public final PlayAction action;
public final Instant executedTime;
ResponseAction(PlayAction action, Instant executedTime) {
this.action = action;
this.executedTime = executedTime;
}
}
}
@@ -0,0 +1,223 @@
package com.aluer.security;
import org.springframework.stereotype.Service;
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.nio.charset.StandardCharsets;
import java.security.SecureRandom;
import java.time.Instant;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
@Service
public class JwtAuthService {
private static final SecureRandom SECURE_RANDOM = new SecureRandom();
private static final long DEFAULT_TTL_SECONDS = 3600;
private static final long MAX_TTL_SECONDS = 86400;
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() {
byte[] keyBytes = new byte[64];
SECURE_RANDOM.nextBytes(keyBytes);
this.secret = Base64.getUrlEncoder().withoutPadding().encodeToString(keyBytes);
}
public String createToken(String subject, Map<String, Object> claims, long ttlSeconds) {
long effectiveTtl = Math.min(ttlSeconds > 0 ? ttlSeconds : DEFAULT_TTL_SECONDS, MAX_TTL_SECONDS);
long now = Instant.now().getEpochSecond();
long exp = now + effectiveTtl;
String jti = generateJti();
StringBuilder payload = new StringBuilder();
payload.append("sub=").append(subject).append("&");
payload.append("iat=").append(now).append("&");
payload.append("exp=").append(exp).append("&");
payload.append("jti=").append(jti);
if (claims != null) {
for (Map.Entry<String, Object> e : claims.entrySet()) {
payload.append("&").append(e.getKey()).append("=").append(e.getValue());
}
}
String payloadEncoded = Base64.getUrlEncoder().withoutPadding()
.encodeToString(payload.toString().getBytes(StandardCharsets.UTF_8));
String signature = hmacSha256(payloadEncoded, secret);
String token = payloadEncoded + "." + signature;
TokenEntry entry = new TokenEntry(subject, jti, Instant.ofEpochSecond(now), Instant.ofEpochSecond(exp), claims);
tokenStore.put(jti, entry);
userTokens.computeIfAbsent(subject, k -> new ArrayList<>()).add(jti);
return token;
}
public String createToken(String subject) {
return createToken(subject, null, DEFAULT_TTL_SECONDS);
}
public TokenValidationResult validateToken(String token) {
if (token == null || !token.contains(".")) {
return TokenValidationResult.invalid("Malformed token");
}
String[] parts = token.split("\\.", 2);
String payloadEncoded = parts[0];
String providedSignature = parts[1];
String expectedSignature = hmacSha256(payloadEncoded, secret);
if (!constantTimeEquals(expectedSignature, providedSignature)) {
return TokenValidationResult.invalid("Signature mismatch");
}
String payload;
try {
payload = new String(Base64.getUrlDecoder().decode(payloadEncoded), StandardCharsets.UTF_8);
} catch (IllegalArgumentException e) {
return TokenValidationResult.invalid("Invalid base64 payload");
}
Map<String, String> parsed = parsePayload(payload);
String jti = parsed.get("jti");
if (jti == null) {
return TokenValidationResult.invalid("Missing jti");
}
if (revokedTokens.containsKey(jti)) {
return TokenValidationResult.invalid("Token revoked");
}
long exp = Long.parseLong(parsed.getOrDefault("exp", "0"));
if (Instant.now().getEpochSecond() > exp) {
return TokenValidationResult.invalid("Token expired");
}
String subject = parsed.get("sub");
Map<String, Object> claims = new HashMap<>(parsed);
claims.remove("sub");
claims.remove("iat");
claims.remove("exp");
claims.remove("jti");
return TokenValidationResult.valid(subject, jti, claims);
}
public void revokeToken(String jti) {
revokedTokens.put(jti, Instant.now());
tokenStore.remove(jti);
}
public void revokeAllUserTokens(String subject) {
List<String> tokens = userTokens.remove(subject);
if (tokens != null) {
for (String jti : tokens) {
revokedTokens.put(jti, Instant.now());
tokenStore.remove(jti);
}
}
}
public int getActiveTokenCount() {
return tokenStore.size();
}
public int getRevokedTokenCount() {
return revokedTokens.size();
}
public void cleanupExpired() {
Instant now = Instant.now();
tokenStore.entrySet().removeIf(e -> e.getValue().expiresAt.isBefore(now));
revokedTokens.entrySet().removeIf(e -> e.getValue().plusSeconds(DEFAULT_TTL_SECONDS * 2).isBefore(now));
}
private String generateJti() {
byte[] bytes = new byte[32];
SECURE_RANDOM.nextBytes(bytes);
return Base64.getUrlEncoder().withoutPadding().encodeToString(bytes);
}
private String hmacSha256(String data, String key) {
try {
Mac mac = Mac.getInstance("HmacSHA256");
SecretKeySpec keySpec = new SecretKeySpec(key.getBytes(StandardCharsets.UTF_8), "HmacSHA256");
mac.init(keySpec);
byte[] hash = mac.doFinal(data.getBytes(StandardCharsets.UTF_8));
return Base64.getUrlEncoder().withoutPadding().encodeToString(hash);
} catch (Exception e) {
throw new RuntimeException("HMAC-SHA256 failed", e);
}
}
private boolean constantTimeEquals(String a, String b) {
if (a.length() != b.length()) return false;
int result = 0;
for (int i = 0; i < a.length(); i++) {
result |= a.charAt(i) ^ b.charAt(i);
}
return result == 0;
}
private Map<String, String> parsePayload(String payload) {
Map<String, String> map = new LinkedHashMap<>();
for (String pair : payload.split("&")) {
int idx = pair.indexOf('=');
if (idx > 0) {
map.put(pair.substring(0, idx), pair.substring(idx + 1));
}
}
return map;
}
public static class TokenEntry {
public final String subject;
public final String jti;
public final Instant issuedAt;
public final Instant expiresAt;
public final Map<String, Object> claims;
TokenEntry(String subject, String jti, Instant issuedAt, Instant expiresAt, Map<String, Object> claims) {
this.subject = subject;
this.jti = jti;
this.issuedAt = issuedAt;
this.expiresAt = expiresAt;
this.claims = claims != null ? new HashMap<>(claims) : new HashMap<>();
}
}
public static class TokenValidationResult {
private final boolean valid;
private final String reason;
private final String subject;
private final String jti;
private final Map<String, Object> claims;
private TokenValidationResult(boolean valid, String reason, String subject, String jti, Map<String, Object> claims) {
this.valid = valid;
this.reason = reason;
this.subject = subject;
this.jti = jti;
this.claims = claims;
}
public static TokenValidationResult valid(String subject, String jti, Map<String, Object> claims) {
return new TokenValidationResult(true, null, subject, jti, claims);
}
public static TokenValidationResult invalid(String reason) {
return new TokenValidationResult(false, reason, null, null, null);
}
public boolean isValid() { return valid; }
public String getReason() { return reason; }
public String getSubject() { return subject; }
public String getJti() { return jti; }
public Map<String, Object> getClaims() { return claims; }
}
}
@@ -0,0 +1,171 @@
package com.aluer.security;
import org.springframework.stereotype.Service;
import java.lang.management.ManagementFactory;
import java.lang.management.MemoryMXBean;
import java.lang.management.MemoryUsage;
import java.time.Instant;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicLong;
@Service
public class MemoryProtectionService {
private final MemoryMXBean memoryMXBean = ManagementFactory.getMemoryMXBean();
private final ScheduledExecutorService scheduler = Executors.newSingleThreadScheduledExecutor();
private final Map<String, List<MemoryAlert>> alerts = new ConcurrentHashMap<>();
private final AtomicLong totalAlerts = new AtomicLong(0);
private static final double HEAP_THRESHOLD = 0.85;
private static final double NON_HEAP_THRESHOLD = 0.90;
private static final double GC_OVERHEAD_THRESHOLD = 0.15;
private static final long MEMORY_CHECK_INTERVAL_SECONDS = 30;
private long lastGcCount;
private long lastGcTime;
public MemoryProtectionService() {
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);
}
public MemoryCheckResult checkMemory() {
List<String> warnings = new ArrayList<>();
MemoryUsage heap = memoryMXBean.getHeapMemoryUsage();
double heapRatio = (double) heap.getUsed() / heap.getMax();
if (heapRatio > HEAP_THRESHOLD) {
warnings.add("HEAP_HIGH: " + String.format("%.1f%%", heapRatio * 100));
MemoryAlert alert = new MemoryAlert(Instant.now(), "HEAP_HIGH",
String.format("Heap at %.1f%% (used=%dMB, max=%dMB)", heapRatio * 100,
heap.getUsed() / 1024 / 1024, heap.getMax() / 1024 / 1024));
alerts.computeIfAbsent("heap", k -> new ArrayList<>()).add(alert);
totalAlerts.incrementAndGet();
}
MemoryUsage nonHeap = memoryMXBean.getNonHeapMemoryUsage();
double nonHeapRatio = (double) nonHeap.getUsed() / nonHeap.getMax();
if (nonHeapRatio > NON_HEAP_THRESHOLD) {
warnings.add("NON_HEAP_HIGH: " + String.format("%.1f%%", nonHeapRatio * 100));
}
long currentGcCount = ManagementFactory.getGarbageCollectorMXBeans().stream()
.mapToLong(gc -> gc.getCollectionCount()).sum();
long currentGcTime = ManagementFactory.getGarbageCollectorMXBeans().stream()
.mapToLong(gc -> gc.getCollectionTime()).sum();
long gcCountDiff = currentGcCount - lastGcCount;
long gcTimeDiff = currentGcTime - lastGcTime;
if (gcTimeDiff > MEMORY_CHECK_INTERVAL_SECONDS * 1000 * GC_OVERHEAD_THRESHOLD) {
warnings.add("GC_OVERHEAD: " + gcTimeDiff + "ms spent in GC");
MemoryAlert alert = new MemoryAlert(Instant.now(), "GC_OVERHEAD",
String.format("GC overhead: %dms (%d collections)", gcTimeDiff, gcCountDiff));
alerts.computeIfAbsent("gc", k -> new ArrayList<>()).add(alert);
totalAlerts.incrementAndGet();
}
lastGcCount = currentGcCount;
lastGcTime = currentGcTime;
// Check for memory leak patterns (steady increase without decrease)
String leakPattern = detectLeakPattern();
if (leakPattern != null) {
warnings.add("MEMORY_LEAK: " + leakPattern);
}
return new MemoryCheckResult(
heapRatio, nonHeapRatio, heap.getUsed() / 1024 / 1024,
heap.getMax() / 1024 / 1024, gcCountDiff, gcTimeDiff, warnings);
}
public Map<String, Object> getStatus() {
Map<String, Object> status = new LinkedHashMap<>();
MemoryUsage heap = memoryMXBean.getHeapMemoryUsage();
status.put("heapUsedMB", heap.getUsed() / 1024 / 1024);
status.put("heapMaxMB", heap.getMax() / 1024 / 1024);
status.put("heapRatio", String.format("%.1f%%", (double) heap.getUsed() / heap.getMax() * 100));
status.put("nonHeapUsedMB", memoryMXBean.getNonHeapMemoryUsage().getUsed() / 1024 / 1024);
status.put("totalAlerts", totalAlerts.get());
status.put("leakDetected", detectLeakPattern() != null);
return status;
}
public long getTotalAlerts() { return totalAlerts.get(); }
public void forceGC() {
System.gc();
}
private String detectLeakPattern() {
List<MemoryAlert> heapAlerts = alerts.get("heap");
if (heapAlerts == null || heapAlerts.size() < 5) return null;
boolean trending = true;
double prev = 0;
int consecutive = 0;
for (int i = Math.max(0, heapAlerts.size() - 10); i < heapAlerts.size(); i++) {
double current = extractHeapPercent(heapAlerts.get(i).message);
if (current >= prev) consecutive++;
else consecutive = 0;
if (consecutive >= 5) return "Heap usage consistently increasing over 5+ checks";
prev = current;
}
return null;
}
private double extractHeapPercent(String message) {
try {
String pct = message.replaceAll(".*?(\\d+\\.?\\d*)%.*", "$1");
return Double.parseDouble(pct);
} catch (Exception e) {
return 0;
}
}
private static class MemoryAlert {
final Instant timestamp;
final String type;
final String message;
MemoryAlert(Instant timestamp, String type, String message) {
this.timestamp = timestamp;
this.type = type;
this.message = message;
}
}
public static class MemoryCheckResult {
private final double heapRatio;
private final double nonHeapRatio;
private final long heapUsedMB;
private final long heapMaxMB;
private final long gcCount;
private final long gcTimeMs;
private final List<String> warnings;
MemoryCheckResult(double heapRatio, double nonHeapRatio, long heapUsedMB, long heapMaxMB,
long gcCount, long gcTimeMs, List<String> warnings) {
this.heapRatio = heapRatio;
this.nonHeapRatio = nonHeapRatio;
this.heapUsedMB = heapUsedMB;
this.heapMaxMB = heapMaxMB;
this.gcCount = gcCount;
this.gcTimeMs = gcTimeMs;
this.warnings = warnings;
}
public double getHeapRatio() { return heapRatio; }
public long getHeapUsedMB() { return heapUsedMB; }
public long getHeapMaxMB() { return heapMaxMB; }
public long getGcTimeMs() { return gcTimeMs; }
public List<String> getWarnings() { return warnings; }
public boolean hasWarnings() { return !warnings.isEmpty(); }
}
}
@@ -0,0 +1,223 @@
package com.aluer.security;
import org.springframework.stereotype.Service;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.time.Instant;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicLong;
@Service
public class ProcessInjectionDetectionService {
private final Map<Integer, ProcessSnapshot> processBaseline = new ConcurrentHashMap<>();
private final Map<String, List<InjectionEvent>> detections = new ConcurrentHashMap<>();
private final ScheduledExecutorService scheduler = Executors.newSingleThreadScheduledExecutor();
private final AtomicLong totalDetections = new AtomicLong(0);
private static final Set<String> SUSPICIOUS_THREADS = Set.of(
"ptrace", "process_vm_writev", "memfd_create",
"dlopen", "dlsym", "mmap", "mprotect"
);
private static final Set<String> INJECTION_INDICATORS = Set.of(
"/proc/self/mem", "/proc/self/maps", "LD_PRELOAD",
"DYLD_INSERT_LIBRARIES", "gdb", "strace", "ltrace",
"ollydbg", "x64dbg", "windbg"
);
public ProcessInjectionDetectionService() {
captureBaseline();
scheduler.scheduleAtFixedRate(this::scanProcesses, 60, 120, TimeUnit.SECONDS);
}
public InjectionCheckResult scanProcesses() {
List<String> alerts = new ArrayList<>();
Map<Integer, ProcessSnapshot> current = captureProcessSnapshots();
for (Map.Entry<Integer, ProcessSnapshot> entry : current.entrySet()) {
int pid = entry.getKey();
ProcessSnapshot snapshot = entry.getValue();
ProcessSnapshot baseline = processBaseline.get(pid);
if (baseline != null) {
// Check for new threads (possible injection)
if (snapshot.threadCount > baseline.threadCount * 1.5 && snapshot.threadCount > 50) {
alerts.add("PID " + pid + " thread spike: " + baseline.threadCount + " -> " + snapshot.threadCount);
}
// Check for new memory mappings
Set<String> newMappings = new HashSet<>(snapshot.memoryMaps);
newMappings.removeAll(baseline.memoryMaps);
for (String mapping : newMappings) {
for (String indicator : INJECTION_INDICATORS) {
if (mapping.toLowerCase().contains(indicator.toLowerCase())) {
alerts.add("PID " + pid + " suspicious mapping: " + mapping);
}
}
}
// Check for new open files
Set<String> newFds = new HashSet<>(snapshot.openFds);
newFds.removeAll(baseline.openFds);
if (newFds.size() > 100) {
alerts.add("PID " + pid + " fd spike: +" + newFds.size());
}
}
// Check for Minecraft-specific injection patterns
if (snapshot.processName.toLowerCase().contains("java") && isMinecraftProcess(snapshot)) {
for (String mapping : snapshot.memoryMaps) {
if (mapping.contains(".so") && !mapping.contains("/usr/lib") && !mapping.contains("/lib64")
&& !mapping.contains("jdk") && !mapping.contains("jre")) {
alerts.add("PID " + pid + " non-standard native lib: " + mapping);
}
}
}
}
if (!alerts.isEmpty()) {
InjectionEvent event = new InjectionEvent(Instant.now(), alerts);
detections.computeIfAbsent("process", k -> new ArrayList<>()).add(event);
totalDetections.addAndGet(alerts.size());
}
processBaseline.clear();
processBaseline.putAll(current);
return new InjectionCheckResult(alerts, current.size(), totalDetections.get());
}
public Map<String, Object> getStatus() {
Map<String, Object> status = new LinkedHashMap<>();
status.put("totalDetections", totalDetections.get());
status.put("trackedProcesses", processBaseline.size());
List<Map<String, Object>> recent = new ArrayList<>();
for (Map.Entry<String, List<InjectionEvent>> e : detections.entrySet()) {
for (InjectionEvent event : e.getValue()) {
Map<String, Object> m = new LinkedHashMap<>();
m.put("alerts", event.alerts);
m.put("time", event.timestamp.toString());
recent.add(m);
}
}
recent.sort((a, b) -> b.get("time").toString().compareTo(a.get("time").toString()));
status.put("recentDetections", recent.subList(0, Math.min(recent.size(), 10)));
return status;
}
public long getTotalDetections() { return totalDetections.get(); }
private void captureBaseline() {
processBaseline.putAll(captureProcessSnapshots());
}
private Map<Integer, ProcessSnapshot> captureProcessSnapshots() {
Map<Integer, ProcessSnapshot> snapshots = new LinkedHashMap<>();
try {
Process proc = Runtime.getRuntime().exec(new String[]{"sh", "-c", "ps -eo pid,comm,nlwp"});
try (BufferedReader reader = new BufferedReader(new InputStreamReader(proc.getInputStream()))) {
String line;
reader.readLine(); // skip header
while ((line = reader.readLine()) != null) {
line = line.trim();
if (line.isEmpty()) continue;
String[] parts = line.trim().split("\\s+", 3);
if (parts.length >= 3) {
try {
int pid = Integer.parseInt(parts[0]);
String name = parts[1];
int threads = Integer.parseInt(parts[2]);
Set<String> maps = readProcMaps(pid);
Set<String> fds = readProcFds(pid);
snapshots.put(pid, new ProcessSnapshot(pid, name, threads, maps, fds));
} catch (NumberFormatException ignored) {}
}
}
}
} catch (Exception ignored) {}
return snapshots;
}
private Set<String> readProcMaps(int pid) {
Set<String> maps = new HashSet<>();
try (BufferedReader reader = new BufferedReader(
new InputStreamReader(Runtime.getRuntime().exec(new String[]{"cat", "/proc/" + pid + "/maps"}).getInputStream()))) {
String line;
while ((line = reader.readLine()) != null) {
if (line.contains("/")) {
maps.add(line.substring(line.lastIndexOf('/')));
}
}
} catch (Exception ignored) {}
return maps;
}
private Set<String> readProcFds(int pid) {
Set<String> fds = new HashSet<>();
try {
Process proc = Runtime.getRuntime().exec(new String[]{"ls", "/proc/" + pid + "/fd/"});
try (BufferedReader reader = new BufferedReader(new InputStreamReader(proc.getInputStream()))) {
String line;
while ((line = reader.readLine()) != null) {
fds.add(line.trim());
}
}
} catch (Exception ignored) {}
return fds;
}
private boolean isMinecraftProcess(ProcessSnapshot snapshot) {
return snapshot.processName.contains("paper") || snapshot.processName.contains("spigot")
|| snapshot.processName.contains("bukkit") || snapshot.processName.contains("minecraft")
|| snapshot.processName.contains("serverguard") || snapshot.processName.contains("aluer");
}
private static class ProcessSnapshot {
final int pid;
final String processName;
final int threadCount;
final Set<String> memoryMaps;
final Set<String> openFds;
ProcessSnapshot(int pid, String processName, int threadCount, Set<String> memoryMaps, Set<String> openFds) {
this.pid = pid;
this.processName = processName;
this.threadCount = threadCount;
this.memoryMaps = memoryMaps;
this.openFds = openFds;
}
}
private static class InjectionEvent {
final Instant timestamp;
final List<String> alerts;
InjectionEvent(Instant timestamp, List<String> alerts) {
this.timestamp = timestamp;
this.alerts = alerts;
}
}
public static class InjectionCheckResult {
private final List<String> alerts;
private final int processCount;
private final long totalDetections;
InjectionCheckResult(List<String> alerts, int processCount, long totalDetections) {
this.alerts = alerts;
this.processCount = processCount;
this.totalDetections = totalDetections;
}
public List<String> getAlerts() { return alerts; }
public int getProcessCount() { return processCount; }
public long getTotalDetections() { return totalDetections; }
public boolean hasAlerts() { return !alerts.isEmpty(); }
}
}
@@ -0,0 +1,211 @@
package com.aluer.security;
import org.springframework.stereotype.Service;
import java.time.Instant;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicLong;
@Service
public class ReverseShellDetectionService {
private final Map<String, List<ShellDetectionEvent>> detections = new ConcurrentHashMap<>();
private final ScheduledExecutorService scheduler = Executors.newSingleThreadScheduledExecutor();
private final AtomicLong totalDetections = new AtomicLong(0);
// Reverse shell command patterns
private static final List<String> REVERSE_SHELL_PATTERNS = List.of(
"bash -i >& /dev/tcp/",
"bash -i &>/dev/tcp/",
"/dev/tcp/",
"nc -e /bin/",
"nc -e /bin/bash",
"nc -e /bin/sh",
"nc -c /bin/",
"ncat -e /bin/",
"rm /tmp/f;mkfifo /tmp/f",
"mknod /tmp/f p",
"python -c 'import socket",
"python -c 'import os,pty,socket",
"python -c \"import socket",
"python3 -c 'import socket",
"perl -e 'use Socket",
"ruby -rsocket -e",
"php -r '$sock=fsockopen",
"lua -e \"local s=require('socket')",
"exec 5<>/dev/tcp/",
"sh -i >& /dev/udp/",
"sh -i >& /dev/tcp/",
"zsh -c 'zmodload zsh/net/tcp",
"socat exec:'bash -li'",
"socat exec:'sh -i'",
"socat tcp-connect:",
"telnet ",
"xterm -display ",
"powershell -NoP -NonI -W Hidden -Exec Bypass",
"powershell -nop -c \"$client = New-Object",
"powershell IEX (New-Object Net.WebClient)",
"Invoke-Expression (New-Object Net.WebClient)",
"IEX(New-Object Net.WebClient)",
"msfvenom",
"meterpreter",
"java -jar ysoserial",
"wget http://.* -O /tmp/",
"curl http://.* -o /tmp/",
"wget http://.* | sh",
"curl http://.* | bash"
);
// Process-level indicators
private static final List<String> SHELL_PROCESS_INDICATORS = List.of(
"nc ", "ncat ", "netcat", "socat", "bash -i", "sh -i",
"/dev/tcp/", "/dev/udp/", "mkfifo", "mknod"
);
public ReverseShellDetectionService() {
scheduler.scheduleAtFixedRate(this::cleanupOldDetections, 300, 600, TimeUnit.SECONDS);
}
public DetectionResult scanCommand(String command, String user, String source) {
if (command == null) return DetectionResult.clean();
String lower = command.toLowerCase().trim();
List<String> matched = new ArrayList<>();
for (String pattern : REVERSE_SHELL_PATTERNS) {
if (lower.contains(pattern.toLowerCase())) {
matched.add(pattern);
}
}
if (!matched.isEmpty()) {
ShellDetectionEvent event = new ShellDetectionEvent(
Instant.now(), user, source, command, matched,
ShellSeverity.CRITICAL
);
detections.computeIfAbsent(source, k -> new ArrayList<>()).add(event);
totalDetections.incrementAndGet();
return DetectionResult.threat("Reverse shell command detected", matched, ShellSeverity.CRITICAL);
}
// Check for encoded/obfuscated commands
if (isObfuscatedShellCommand(lower)) {
ShellDetectionEvent event = new ShellDetectionEvent(
Instant.now(), user, source, command, List.of("OBFUSCATED"),
ShellSeverity.HIGH
);
detections.computeIfAbsent(source, k -> new ArrayList<>()).add(event);
totalDetections.incrementAndGet();
return DetectionResult.threat("Potentially obfuscated reverse shell command", List.of("OBFUSCATED"), ShellSeverity.HIGH);
}
return DetectionResult.clean();
}
public DetectionResult scanProcess(String processLine) {
if (processLine == null) return DetectionResult.clean();
String lower = processLine.toLowerCase();
for (String indicator : SHELL_PROCESS_INDICATORS) {
if (lower.contains(indicator.toLowerCase())) {
return DetectionResult.threat("Suspicious process: " + indicator, List.of(indicator), ShellSeverity.MEDIUM);
}
}
return DetectionResult.clean();
}
public Map<String, Object> getStatus() {
Map<String, Object> status = new LinkedHashMap<>();
status.put("totalDetections", totalDetections.get());
status.put("trackedSources", detections.size());
List<Map<String, Object>> recent = new ArrayList<>();
for (Map.Entry<String, List<ShellDetectionEvent>> entry : detections.entrySet()) {
for (ShellDetectionEvent event : entry.getValue()) {
Map<String, Object> e = new LinkedHashMap<>();
e.put("source", entry.getKey());
e.put("user", event.user);
e.put("time", event.timestamp.toString());
e.put("patterns", event.matchedPatterns);
e.put("severity", event.severity.name());
e.put("command", event.command.length() > 100 ? event.command.substring(0, 100) + "..." : event.command);
recent.add(e);
}
}
recent.sort((a, b) -> b.get("time").toString().compareTo(a.get("time").toString()));
status.put("recentDetections", recent.subList(0, Math.min(recent.size(), 20)));
return status;
}
public long getTotalDetections() { return totalDetections.get(); }
private boolean isObfuscatedShellCommand(String command) {
int indicators = 0;
if (command.contains("base64") && (command.contains("-d") || command.contains("--decode"))) indicators++;
if (command.contains("eval") || command.contains("exec")) indicators++;
if (command.contains("$(") || command.contains("`")) indicators++;
if (command.contains("socket") || command.contains("connect")) indicators++;
if (command.contains("subprocess") || command.contains("os.system")) indicators++;
return indicators >= 3;
}
private void cleanupOldDetections() {
Instant cutoff = Instant.now().minusSeconds(86400);
detections.entrySet().removeIf(e -> {
e.getValue().removeIf(d -> d.timestamp.isBefore(cutoff));
return e.getValue().isEmpty();
});
}
public enum ShellSeverity { LOW, MEDIUM, HIGH, CRITICAL }
public static class ShellDetectionEvent {
public final Instant timestamp;
public final String user;
public final String source;
public final String command;
public final List<String> matchedPatterns;
public final ShellSeverity severity;
ShellDetectionEvent(Instant timestamp, String user, String source, String command,
List<String> matchedPatterns, ShellSeverity severity) {
this.timestamp = timestamp;
this.user = user;
this.source = source;
this.command = command;
this.matchedPatterns = matchedPatterns;
this.severity = severity;
}
}
public static class DetectionResult {
private final boolean threat;
private final String message;
private final List<String> matchedPatterns;
private final ShellSeverity severity;
private DetectionResult(boolean threat, String message, List<String> matchedPatterns, ShellSeverity severity) {
this.threat = threat;
this.message = message;
this.matchedPatterns = matchedPatterns;
this.severity = severity;
}
public static DetectionResult clean() {
return new DetectionResult(false, null, List.of(), ShellSeverity.LOW);
}
public static DetectionResult threat(String message, List<String> matchedPatterns, ShellSeverity severity) {
return new DetectionResult(true, message, matchedPatterns, severity);
}
public boolean isThreat() { return threat; }
public String getMessage() { return message; }
public List<String> getMatchedPatterns() { return matchedPatterns; }
public ShellSeverity getSeverity() { return severity; }
}
}
@@ -0,0 +1,199 @@
package com.aluer.security;
import org.springframework.stereotype.Service;
import java.net.*;
import java.time.Instant;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicLong;
@Service
public class SSRFProtectionService {
private final Map<String, List<SSRFEvent>> detections = new ConcurrentHashMap<>();
private final AtomicLong totalBlocked = new AtomicLong(0);
private static final Set<String> BLOCKED_SCHEMES = Set.of(
"file", "gopher", "dict", "ftp", "ldap", "ldaps", "jar", "netdoc", "php", "tftp"
);
private static final Set<String> INTERNAL_IPS = Set.of(
"127.0.0.1", "0.0.0.0", "localhost", "::1", "[::1]",
"169.254.169.254", "100.64.0.0", "198.18.0.0", "198.19.0.0"
);
private static final List<String> INTERNAL_CIDRS = List.of(
"10.0.0.0/8", "172.16.0.0/12", "192.168.0.0/16", "0.0.0.0/8",
"127.0.0.0/8", "169.254.0.0/16", "224.0.0.0/4", "240.0.0.0/4"
);
private static final Set<String> METADATA_PATHS = Set.of(
"/latest/meta-data", "/latest/user-data", "/metadata", "/instance-identity",
"/computeMetadata/v1", "/iam/security-credentials", "/opc/v1/instance",
"/latest/dynamic", "/latest/api/token", "/a/b/c/meta-data"
);
private static final Set<String> BLOCKED_HOSTS = Set.of(
"metadata.google.internal", "metadata.tencentyun.com", "100.100.100.200",
"metadata.alibabacloud.com", "169.254.169.254"
);
public SSRFCheckResult checkURL(String url, String sourceIP) {
if (url == null || url.trim().isEmpty()) return SSRFCheckResult.clean();
List<String> reasons = new ArrayList<>();
try {
URI uri = new URI(url.trim());
// Check scheme
String scheme = uri.getScheme();
if (scheme != null && BLOCKED_SCHEMES.contains(scheme.toLowerCase())) {
reasons.add("BLOCKED_SCHEME: " + scheme);
}
// Check host
String host = uri.getHost();
if (host == null) {
reasons.add("NULL_HOST");
} else {
// Direct internal IPs
if (INTERNAL_IPS.contains(host.toLowerCase())) {
reasons.add("INTERNAL_HOST: " + host);
}
// Blocked hostnames
if (BLOCKED_HOSTS.contains(host.toLowerCase())) {
reasons.add("BLOCKED_CLOUD_METADATA: " + host);
}
// DNS rebinding check: host containing "127.0.0.1" or similar
try {
InetAddress addr = InetAddress.getByName(host);
String ip = addr.getHostAddress();
if (isInternalIP(ip)) {
reasons.add("RESOLVES_TO_INTERNAL: " + host + " -> " + ip);
}
} catch (UnknownHostException ignored) {}
// IP in hostname
if (host.matches("\\d+\\.\\d+\\.\\d+\\.\\d+") && isInternalIP(host)) {
reasons.add("INTERNAL_IP_DIRECT: " + host);
}
}
// Check path for metadata endpoints
String path = uri.getPath();
if (path != null) {
for (String metaPath : METADATA_PATHS) {
if (path.toLowerCase().contains(metaPath.toLowerCase())) {
reasons.add("CLOUD_METADATA_PATH: " + path);
break;
}
}
}
// Check for URL-encoded internal IPs
String decoded = url.toLowerCase();
if (decoded.contains("%31%32%37") || decoded.contains("127%2e") || decoded.contains("localhost")) {
reasons.add("ENCODED_INTERNAL_IP");
}
// Decimal/octal IP bypass
if (host != null && host.matches("\\d{8,12}")) {
try {
long decimal = Long.parseLong(host);
String resolved = ((decimal >> 24) & 0xFF) + "." + ((decimal >> 16) & 0xFF) + "."
+ ((decimal >> 8) & 0xFF) + "." + (decimal & 0xFF);
if (isInternalIP(resolved)) {
reasons.add("DECIMAL_IP_BYPASS: " + host + " -> " + resolved);
}
} catch (NumberFormatException ignored) {}
}
} catch (URISyntaxException e) {
reasons.add("INVALID_URI: " + e.getMessage());
}
if (!reasons.isEmpty()) {
SSRFEvent event = new SSRFEvent(Instant.now(), sourceIP, url, reasons);
detections.computeIfAbsent(sourceIP, k -> new ArrayList<>()).add(event);
totalBlocked.incrementAndGet();
return SSRFCheckResult.blocked(reasons);
}
return SSRFCheckResult.clean();
}
public Map<String, Object> getStatus() {
Map<String, Object> status = new LinkedHashMap<>();
status.put("totalBlocked", totalBlocked.get());
status.put("trackedSources", detections.size());
List<Map<String, Object>> recent = new ArrayList<>();
for (Map.Entry<String, List<SSRFEvent>> e : detections.entrySet()) {
for (SSRFEvent event : e.getValue()) {
Map<String, Object> m = new LinkedHashMap<>();
m.put("source", e.getKey());
m.put("url", event.url.length() > 80 ? event.url.substring(0, 80) : event.url);
m.put("reasons", event.reasons);
m.put("time", event.timestamp.toString());
recent.add(m);
}
}
recent.sort((a, b) -> b.get("time").toString().compareTo(a.get("time").toString()));
status.put("recentDetections", recent.subList(0, Math.min(recent.size(), 20)));
return status;
}
public long getTotalBlocked() { return totalBlocked.get(); }
private boolean isInternalIP(String ip) {
try {
String[] parts = ip.split("\\.");
if (parts.length != 4) return false;
int first = Integer.parseInt(parts[0]);
int second = Integer.parseInt(parts[1]);
if (first == 10) return true;
if (first == 127) return true;
if (first == 169 && second == 254) return true;
if (first == 172 && second >= 16 && second <= 31) return true;
if (first == 192 && second == 168) return true;
if (first == 0) return true;
if (first >= 224 && first <= 255) return true;
return false;
} catch (NumberFormatException e) {
return false;
}
}
private static class SSRFEvent {
final Instant timestamp;
final String sourceIP;
final String url;
final List<String> reasons;
SSRFEvent(Instant timestamp, String sourceIP, String url, List<String> reasons) {
this.timestamp = timestamp;
this.sourceIP = sourceIP;
this.url = url;
this.reasons = reasons;
}
}
public static class SSRFCheckResult {
private final boolean blocked;
private final List<String> reasons;
private SSRFCheckResult(boolean blocked, List<String> reasons) {
this.blocked = blocked;
this.reasons = reasons;
}
public static SSRFCheckResult clean() { return new SSRFCheckResult(false, List.of()); }
public static SSRFCheckResult blocked(List<String> reasons) { return new SSRFCheckResult(true, reasons); }
public boolean isBlocked() { return blocked; }
public List<String> getReasons() { return reasons; }
}
}
@@ -0,0 +1,205 @@
package com.aluer.security;
import org.springframework.stereotype.Service;
import java.io.File;
import java.io.RandomAccessFile;
import java.security.SecureRandom;
import java.time.Instant;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicLong;
@Service
public class SecureFileDeletionService {
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);
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 (!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);
}
int effectivePasses = Math.min(Math.max(passes, 1), MAX_PASSES);
long fileSize = file.length();
long bytesWritten = 0;
try {
for (int pass = 0; pass < effectivePasses; pass++) {
try (RandomAccessFile raf = new RandomAccessFile(file, "rws")) {
long remaining = fileSize;
byte[] buffer = new byte[BUFFER_SIZE];
while (remaining > 0) {
int writeSize = (int) Math.min(remaining, BUFFER_SIZE);
// Pass 0: zero-fill, Pass 1: random, Pass 2+: alternating patterns
if (pass == 0) {
Arrays.fill(buffer, 0, writeSize, (byte) 0);
} else if (pass == 1) {
secureRandom.nextBytes(buffer);
} else if (pass % 2 == 0) {
Arrays.fill(buffer, 0, writeSize, (byte) 0xFF);
} else {
Arrays.fill(buffer, 0, writeSize, (byte) 0xAA);
}
raf.write(buffer, 0, writeSize);
remaining -= writeSize;
bytesWritten += writeSize;
}
}
// Sync to disk
try (RandomAccessFile raf = new RandomAccessFile(file, "rws")) {
raf.getFD().sync();
}
}
// Truncate to 0 before deleting
try (RandomAccessFile raf = new RandomAccessFile(file, "rws")) {
raf.setLength(0);
}
// Delete the file
if (!file.delete()) {
file.deleteOnExit();
return DeletionResult.warning("File wiped but could not be immediately deleted: " + filePath);
}
DeletionRecord record = new DeletionRecord(filePath, fileSize, effectivePasses, Instant.now());
deletionLog.put(filePath, record);
totalFilesDeleted.incrementAndGet();
totalBytesWiped.addAndGet(bytesWritten);
return DeletionResult.success(filePath, fileSize, effectivePasses);
} catch (Exception e) {
return DeletionResult.error("Secure deletion failed: " + e.getMessage());
}
}
public DeletionResult secureDelete(String filePath) {
return secureDelete(filePath, DEFAULT_PASSES);
}
public DeletionResult secureDeleteDirectory(String dirPath, int passes) {
File dir = new File(dirPath);
if (!dir.exists() || !dir.isDirectory()) {
return DeletionResult.error("Not a directory: " + dirPath);
}
List<String> failedFiles = new ArrayList<>();
long totalSize = 0;
int count = 0;
File[] files = dir.listFiles();
if (files != null) {
for (File file : files) {
if (file.isDirectory()) {
DeletionResult subResult = secureDeleteDirectory(file.getAbsolutePath(), passes);
count += subResult.filesDeleted;
totalSize += subResult.bytesWiped;
failedFiles.addAll(subResult.failedFiles);
} else {
DeletionResult result = secureDelete(file.getAbsolutePath(), passes);
if (result.success) {
count++;
totalSize += result.bytesWiped;
} else {
failedFiles.add(file.getAbsolutePath());
}
}
}
}
dir.delete();
return new DeletionResult(true, count, totalSize, dirPath, failedFiles, null);
}
public Map<String, Object> getStatus() {
Map<String, Object> status = new LinkedHashMap<>();
status.put("totalFilesDeleted", totalFilesDeleted.get());
status.put("totalBytesWiped", totalBytesWiped.get());
List<Map<String, Object>> recent = new ArrayList<>();
for (Map.Entry<String, DeletionRecord> e : deletionLog.entrySet()) {
DeletionRecord r = e.getValue();
Map<String, Object> m = new LinkedHashMap<>();
m.put("path", r.path);
m.put("size", r.fileSize);
m.put("passes", r.passes);
m.put("time", r.timestamp.toString());
recent.add(m);
}
recent.sort((a, b) -> b.get("time").toString().compareTo(a.get("time").toString()));
status.put("recentDeletions", recent.subList(0, Math.min(recent.size(), 20)));
return status;
}
public long getTotalFilesDeleted() { return totalFilesDeleted.get(); }
public long getTotalBytesWiped() { return totalBytesWiped.get(); }
private static class DeletionRecord {
final String path;
final long fileSize;
final int passes;
final Instant timestamp;
DeletionRecord(String path, long fileSize, int passes, Instant timestamp) {
this.path = path;
this.fileSize = fileSize;
this.passes = passes;
this.timestamp = timestamp;
}
}
public static class DeletionResult {
private final boolean success;
private final int filesDeleted;
private final long bytesWiped;
private final String path;
private final List<String> failedFiles;
private final String error;
DeletionResult(boolean success, int filesDeleted, long bytesWiped, String path,
List<String> failedFiles, String error) {
this.success = success;
this.filesDeleted = filesDeleted;
this.bytesWiped = bytesWiped;
this.path = path;
this.failedFiles = failedFiles != null ? failedFiles : List.of();
this.error = error;
}
public static DeletionResult success(String path, long size, int passes) {
return new DeletionResult(true, 1, size, path, List.of(), null);
}
public static DeletionResult error(String error) {
return new DeletionResult(false, 0, 0, null, List.of(), error);
}
public static DeletionResult warning(String warning) {
return new DeletionResult(true, 1, 0, null, List.of(), warning);
}
public boolean isSuccess() { return success; }
public int getFilesDeleted() { return filesDeleted; }
public long getBytesWiped() { return bytesWiped; }
public String getPath() { return path; }
public List<String> getFailedFiles() { return failedFiles; }
public String getError() { return error; }
}
}
@@ -0,0 +1,205 @@
package com.aluer.security;
import org.springframework.stereotype.Service;
import java.time.Instant;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicLong;
@Service
public class ThreatHuntingService {
private final Map<String, List<HuntResult>> huntResults = new ConcurrentHashMap<>();
private final Map<String, HuntDefinition> huntDefinitions = new ConcurrentHashMap<>();
private final AtomicLong totalHunts = new AtomicLong(0);
private final AtomicLong totalFindings = new AtomicLong(0);
private final ScheduledExecutorService scheduler = Executors.newSingleThreadScheduledExecutor();
public ThreatHuntingService() {
registerDefaultHunts();
scheduler.scheduleAtFixedRate(this::runAllHunts, 300, 1800, TimeUnit.SECONDS);
}
private void registerDefaultHunts() {
// Minecraft-specific threat hunting
huntDefinitions.put("UNUSUAL_LOGIN_TIMES", new HuntDefinition(
"Detect logins at unusual hours (2-5 AM server time)",
"MINECRAFT", HuntSeverity.MEDIUM));
huntDefinitions.put("RAPID_WORLD_CHANGES", new HuntDefinition(
"Detect rapid dimension hopping (potential X-ray or cheat)",
"MINECRAFT", HuntSeverity.HIGH));
huntDefinitions.put("SUSPICIOUS_COMMANDS", new HuntDefinition(
"Detect suspicious command execution patterns",
"COMMAND", HuntSeverity.HIGH));
huntDefinitions.put("PERSISTENCE_MECHANISMS", new HuntDefinition(
"Detect persistence mechanisms (cron, systemd, autostart)",
"HOST", HuntSeverity.CRITICAL));
huntDefinitions.put("LATERAL_MOVEMENT", new HuntDefinition(
"Detect lateral movement indicators (SSH, RDP, SMB connections)",
"NETWORK", HuntSeverity.CRITICAL));
huntDefinitions.put("PRIVILEGE_ESCALATION", new HuntDefinition(
"Detect privilege escalation attempts (sudo, su, setuid)",
"HOST", HuntSeverity.CRITICAL));
huntDefinitions.put("DATA_EXFILTRATION", new HuntDefinition(
"Detect large outbound data transfers to external IPs",
"NETWORK", HuntSeverity.CRITICAL));
huntDefinitions.put("CRYPTOMINING", new HuntDefinition(
"Detect cryptomining indicators (high CPU, mining pools, stratum)",
"HOST", HuntSeverity.HIGH));
huntDefinitions.put("WEBSHELL", new HuntDefinition(
"Detect webshell deployment (PHP/JSP shells in web directories)",
"WEB", HuntSeverity.CRITICAL));
huntDefinitions.put("BACKDOOR_ACCOUNT", new HuntDefinition(
"Detect newly created privileged user accounts",
"HOST", HuntSeverity.CRITICAL));
}
public HuntResult runHunt(String huntId, List<String> dataSources) {
HuntDefinition def = huntDefinitions.get(huntId);
if (def == null) return null;
List<String> findings = new ArrayList<>();
for (String data : dataSources) {
List<String> result = huntForPatterns(def, data);
findings.addAll(result);
}
HuntResult result = new HuntResult(huntId, def.name, def.category, def.severity,
Instant.now(), findings, dataSources.size());
huntResults.computeIfAbsent(huntId, k -> new ArrayList<>()).add(result);
totalHunts.incrementAndGet();
if (!findings.isEmpty()) totalFindings.addAndGet(findings.size());
return result;
}
public void runAllHunts() {
// Placeholder: in a real environment, this would query SIEM, logs, and monitors
for (String huntId : huntDefinitions.keySet()) {
List<String> data = List.of("dummy-data");
runHunt(huntId, data);
}
}
private List<String> huntForPatterns(HuntDefinition def, String data) {
List<String> findings = new ArrayList<>();
String lower = data.toLowerCase();
switch (def.category) {
case "MINECRAFT":
if (lower.contains("joined the game") && lower.contains("02:") || lower.contains("03:") || lower.contains("04:")) {
findings.add("Unusual login time detected");
}
break;
case "COMMAND":
if (lower.contains("/op ") || lower.contains("/deop ") || lower.contains("/stop")) {
findings.add("Suspicious command execution");
}
break;
case "HOST":
if (lower.contains("crontab") || lower.contains("systemctl enable")
|| lower.contains("update-rc.d") || lower.contains("chkconfig")) {
findings.add("Persistence mechanism detected");
}
if (lower.contains("sudo su") || lower.contains("chmod u+s") || lower.contains("setuid")) {
findings.add("Privilege escalation attempt");
}
break;
case "NETWORK":
if (lower.contains("ssh ") && lower.contains("root@")) {
findings.add("SSH lateral movement");
}
break;
case "WEB":
if (lower.contains(".jsp") || (lower.contains("<?php") && lower.contains("exec("))) {
findings.add("Potential webshell deployment");
}
break;
}
return findings;
}
public Map<String, Object> getStatus() {
Map<String, Object> status = new LinkedHashMap<>();
status.put("huntDefinitions", huntDefinitions.size());
status.put("totalHunts", totalHunts.get());
status.put("totalFindings", totalFindings.get());
Map<String, Long> byCategory = new LinkedHashMap<>();
Map<String, Long> bySeverity = new LinkedHashMap<>();
for (HuntDefinition def : huntDefinitions.values()) {
byCategory.merge(def.category, 1L, Long::sum);
bySeverity.merge(def.severity.name(), 1L, Long::sum);
}
status.put("huntsByCategory", byCategory);
status.put("huntsBySeverity", bySeverity);
List<Map<String, Object>> recent = new ArrayList<>();
for (Map.Entry<String, List<HuntResult>> e : huntResults.entrySet()) {
for (HuntResult r : e.getValue()) {
Map<String, Object> m = new LinkedHashMap<>();
m.put("huntId", r.huntId);
m.put("name", r.huntName);
m.put("category", r.category);
m.put("severity", r.severity.name());
m.put("findings", r.findings.size());
m.put("time", r.timestamp.toString());
recent.add(m);
}
}
recent.sort((a, b) -> b.get("time").toString().compareTo(a.get("time").toString()));
status.put("recentHunts", recent.subList(0, Math.min(recent.size(), 20)));
return status;
}
public long getTotalHunts() { return totalHunts.get(); }
public long getTotalFindings() { return totalFindings.get(); }
public enum HuntSeverity { LOW, MEDIUM, HIGH, CRITICAL }
public static class HuntDefinition {
final String name;
final String category;
final HuntSeverity severity;
HuntDefinition(String name, String category, HuntSeverity severity) {
this.name = name;
this.category = category;
this.severity = severity;
}
}
public static class HuntResult {
public final String huntId;
public final String huntName;
public final String category;
public final HuntSeverity severity;
public final Instant timestamp;
public final List<String> findings;
public final int dataSourcesProcessed;
HuntResult(String huntId, String huntName, String category, HuntSeverity severity,
Instant timestamp, List<String> findings, int dataSourcesProcessed) {
this.huntId = huntId;
this.huntName = huntName;
this.category = category;
this.severity = severity;
this.timestamp = timestamp;
this.findings = findings;
this.dataSourcesProcessed = dataSourcesProcessed;
}
}
}
@@ -0,0 +1,158 @@
package com.aluer.security;
import org.springframework.stereotype.Service;
import java.time.Instant;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicLong;
@Service
public class XXEProtectionService {
private final Map<String, List<XXEEvent>> detections = new ConcurrentHashMap<>();
private final AtomicLong totalDetections = new AtomicLong(0);
private static final List<String> XXE_PATTERNS = List.of(
"<!ENTITY", "<!DOCTYPE", "SYSTEM \"file://", "SYSTEM 'file://",
"SYSTEM \"http://", "SYSTEM 'http://", "SYSTEM \"https://", "SYSTEM 'https://",
"SYSTEM \"ftp://", "SYSTEM 'ftp://", "SYSTEM \"php://", "SYSTEM 'php://",
"SYSTEM \"expect://", "SYSTEM 'expect://", "SYSTEM \"gopher://", "SYSTEM 'gopher://",
"xmlns:xsi", "xsi:schemaLocation", "ENTITY %", "parameter-entity",
"DOCTYPE xxe", "DOCTYPE root", "<?xml", "]>>", "&xxe;", "&lol;",
"%remote;", "%param1;", "</!ELEMENT", "<!ATTLIST"
);
private static final List<String> BILLION_LAUGHS_PATTERNS = List.of(
"&a1;", "&a2;", "&a3;", "&a4;", "&a5;",
"&lol1;", "&lol2;", "&lol3;", "&lol4;", "&lol5;",
"&lol6;", "&lol7;", "&lol8;", "&lol9;"
);
public XXECheckResult checkXML(String xmlContent, String sourceIP) {
if (xmlContent == null || xmlContent.trim().isEmpty()) return XXECheckResult.clean();
List<String> matched = new ArrayList<>();
boolean isBillionLaughs = false;
String lower = xmlContent.toLowerCase().trim();
if (!lower.startsWith("<?xml") && !lower.contains("<!doctype") && !lower.contains("<!entity")) {
return XXECheckResult.clean();
}
// Check for XXE patterns
for (String pattern : XXE_PATTERNS) {
if (lower.contains(pattern.toLowerCase())) {
matched.add(pattern);
}
}
// Check for Billion Laughs attack
int blMatches = 0;
for (String pattern : BILLION_LAUGHS_PATTERNS) {
if (lower.contains(pattern.toLowerCase())) blMatches++;
}
if (blMatches >= 3) {
isBillionLaughs = true;
matched.add("BILLION_LAUGHS_ATTACK");
}
// Check for external entity reference
if (lower.contains("system \"") || lower.contains("system '")) {
matched.add("EXTERNAL_ENTITY");
}
// Check for entity expansion bomb
if (countOccurrences(lower, "<!entity") >= 5) {
matched.add("ENTITY_EXPANSION_BOMB");
}
if (!matched.isEmpty()) {
XXEEvent event = new XXEEvent(Instant.now(), sourceIP, xmlContent.length() > 200 ? xmlContent.substring(0, 200) : xmlContent,
matched, isBillionLaughs);
detections.computeIfAbsent(sourceIP, k -> new ArrayList<>()).add(event);
totalDetections.incrementAndGet();
return XXECheckResult.blocked(matched, isBillionLaughs);
}
return XXECheckResult.clean();
}
public String sanitizeXML(String xmlContent) {
if (xmlContent == null) return null;
return xmlContent
.replaceAll("(?i)<!DOCTYPE[^>]*>", "<!-- DOCTYPE removed -->")
.replaceAll("(?i)<!ENTITY[^>]*>", "<!-- ENTITY removed -->")
.replaceAll("(?i)SYSTEM\\s+[\"'][^\"']*[\"']", "SYSTEM \"blocked\"");
}
public Map<String, Object> getStatus() {
Map<String, Object> status = new LinkedHashMap<>();
status.put("totalDetections", totalDetections.get());
status.put("trackedSources", detections.size());
List<Map<String, Object>> recent = new ArrayList<>();
for (Map.Entry<String, List<XXEEvent>> e : detections.entrySet()) {
for (XXEEvent event : e.getValue()) {
Map<String, Object> m = new LinkedHashMap<>();
m.put("source", e.getKey());
m.put("patterns", event.matchedPatterns);
m.put("billionLaughs", event.isBillionLaughs);
m.put("time", event.timestamp.toString());
m.put("snippet", event.snippet);
recent.add(m);
}
}
recent.sort((a, b) -> b.get("time").toString().compareTo(a.get("time").toString()));
status.put("recentDetections", recent.subList(0, Math.min(recent.size(), 20)));
return status;
}
public long getTotalDetections() { return totalDetections.get(); }
private int countOccurrences(String text, String pattern) {
int count = 0;
int idx = 0;
while ((idx = text.indexOf(pattern, idx)) != -1) {
count++;
idx += pattern.length();
}
return count;
}
private static class XXEEvent {
final Instant timestamp;
final String sourceIP;
final String snippet;
final List<String> matchedPatterns;
final boolean isBillionLaughs;
XXEEvent(Instant timestamp, String sourceIP, String snippet, List<String> matchedPatterns, boolean isBillionLaughs) {
this.timestamp = timestamp;
this.sourceIP = sourceIP;
this.snippet = snippet;
this.matchedPatterns = matchedPatterns;
this.isBillionLaughs = isBillionLaughs;
}
}
public static class XXECheckResult {
private final boolean blocked;
private final boolean isBillionLaughs;
private final List<String> matchedPatterns;
private XXECheckResult(boolean blocked, List<String> matchedPatterns, boolean isBillionLaughs) {
this.blocked = blocked;
this.matchedPatterns = matchedPatterns;
this.isBillionLaughs = isBillionLaughs;
}
public static XXECheckResult clean() { return new XXECheckResult(false, List.of(), false); }
public static XXECheckResult blocked(List<String> matchedPatterns, boolean isBillionLaughs) {
return new XXECheckResult(true, matchedPatterns, isBillionLaughs);
}
public boolean isBlocked() { return blocked; }
public boolean isBillionLaughs() { return isBillionLaughs; }
public List<String> getMatchedPatterns() { return matchedPatterns; }
}
}
@@ -8,11 +8,12 @@ import com.aluer.notification.AttackReportService;
import com.aluer.notification.WebhookService;
import com.aluer.punishment.PunishmentService;
import com.aluer.profiler.PerformanceProfiler;
import com.aluer.security.NetworkThreatFusionService;
import com.aluer.security.*;
import com.aluer.service.RconClient;
import com.aluer.world.WorldManagementService;
import org.springframework.web.bind.annotation.*;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@@ -33,7 +34,29 @@ public class DashboardController {
private final HealthService healthService;
private final AttackReportService attackReportService;
private final WebhookService webhookService;
// New super-evolution security modules
private final JwtAuthService jwtAuthService;
private final BruteForceProtectionService bruteForceProtectionService;
private final AntiBotDetectionService antiBotDetectionService;
private final ReverseShellDetectionService reverseShellDetectionService;
private final ARPSpoofDetectionService arpSpoofDetectionService;
private final DNSTunnelDetectionService dnsTunnelDetectionService;
private final ExploitSignatureService exploitSignatureService;
private final SSRFProtectionService ssrfProtectionService;
private final XXEProtectionService xxeProtectionService;
private final CSPEnforcementService cspEnforcementService;
private final DatabaseFirewallService databaseFirewallService;
private final DataLossPreventionService dataLossPreventionService;
private final MemoryProtectionService memoryProtectionService;
private final ProcessInjectionDetectionService processInjectionDetectionService;
private final SecureFileDeletionService secureFileDeletionService;
private final ForensicsCollectorService forensicsCollectorService;
private final IncidentResponseService incidentResponseService;
private final ThreatHuntingService threatHuntingService;
private final ComplianceScannerService complianceScannerService;
private final AntiGriefDetectionService antiGriefDetectionService;
@SuppressWarnings("java:S107")
public DashboardController(
RconClient rconClient,
PerformanceProfiler profiler,
@@ -46,7 +69,27 @@ public class DashboardController {
NetworkThreatFusionService networkThreatFusionService,
HealthService healthService,
AttackReportService attackReportService,
WebhookService webhookService) {
WebhookService webhookService,
JwtAuthService jwtAuthService,
BruteForceProtectionService bruteForceProtectionService,
AntiBotDetectionService antiBotDetectionService,
ReverseShellDetectionService reverseShellDetectionService,
ARPSpoofDetectionService arpSpoofDetectionService,
DNSTunnelDetectionService dnsTunnelDetectionService,
ExploitSignatureService exploitSignatureService,
SSRFProtectionService ssrfProtectionService,
XXEProtectionService xxeProtectionService,
CSPEnforcementService cspEnforcementService,
DatabaseFirewallService databaseFirewallService,
DataLossPreventionService dataLossPreventionService,
MemoryProtectionService memoryProtectionService,
ProcessInjectionDetectionService processInjectionDetectionService,
SecureFileDeletionService secureFileDeletionService,
ForensicsCollectorService forensicsCollectorService,
IncidentResponseService incidentResponseService,
ThreatHuntingService threatHuntingService,
ComplianceScannerService complianceScannerService,
AntiGriefDetectionService antiGriefDetectionService) {
this.rconClient = rconClient;
this.profiler = profiler;
this.backupService = backupService;
@@ -59,6 +102,26 @@ public class DashboardController {
this.healthService = healthService;
this.attackReportService = attackReportService;
this.webhookService = webhookService;
this.jwtAuthService = jwtAuthService;
this.bruteForceProtectionService = bruteForceProtectionService;
this.antiBotDetectionService = antiBotDetectionService;
this.reverseShellDetectionService = reverseShellDetectionService;
this.arpSpoofDetectionService = arpSpoofDetectionService;
this.dnsTunnelDetectionService = dnsTunnelDetectionService;
this.exploitSignatureService = exploitSignatureService;
this.ssrfProtectionService = ssrfProtectionService;
this.xxeProtectionService = xxeProtectionService;
this.cspEnforcementService = cspEnforcementService;
this.databaseFirewallService = databaseFirewallService;
this.dataLossPreventionService = dataLossPreventionService;
this.memoryProtectionService = memoryProtectionService;
this.processInjectionDetectionService = processInjectionDetectionService;
this.secureFileDeletionService = secureFileDeletionService;
this.forensicsCollectorService = forensicsCollectorService;
this.incidentResponseService = incidentResponseService;
this.threatHuntingService = threatHuntingService;
this.complianceScannerService = complianceScannerService;
this.antiGriefDetectionService = antiGriefDetectionService;
}
@GetMapping("/status")
@@ -222,4 +285,143 @@ public class DashboardController {
response.put("status", "sent");
return response;
}
// --- Super-Evolution New API Endpoints ---
@GetMapping("/security/auth/status")
public Map<String, Object> getAuthStatus() {
Map<String, Object> response = new HashMap<>();
response.put("activeTokens", jwtAuthService.getActiveTokenCount());
response.put("revokedTokens", jwtAuthService.getRevokedTokenCount());
return response;
}
@GetMapping("/security/brute-force/status")
public Map<String, Object> getBruteForceStatus() {
return bruteForceProtectionService.getStatus();
}
@GetMapping("/security/anti-bot/status")
public Map<String, Object> getAntiBotStatus() {
return antiBotDetectionService.getStatus();
}
@GetMapping("/security/reverse-shell/status")
public Map<String, Object> getReverseShellStatus() {
return reverseShellDetectionService.getStatus();
}
@GetMapping("/security/arp-spoof/status")
public Map<String, Object> getARPSpoofStatus() {
return arpSpoofDetectionService.getStatus();
}
@GetMapping("/security/dns-tunnel/status")
public Map<String, Object> getDNSTunnelStatus() {
return dnsTunnelDetectionService.getStatus();
}
@GetMapping("/security/exploit-signature/status")
public Map<String, Object> getExploitSignatureStatus() {
return exploitSignatureService.getStatus();
}
@PostMapping("/security/exploit-signature/scan")
public Map<String, Object> scanForExploits(@RequestParam String content, @RequestParam(defaultValue = "api") String source) {
ExploitSignatureService.ExploitCheckResult result = exploitSignatureService.scan(content, source, "api-scan");
Map<String, Object> response = new HashMap<>();
response.put("blocked", result.isBlocked());
response.put("detected", result.isDetected());
if (result.getMatches() != null) {
List<Map<String, Object>> matches = new ArrayList<>();
for (ExploitSignatureService.ExploitMatch m : result.getMatches()) {
Map<String, Object> mm = new HashMap<>();
mm.put("name", m.getName());
mm.put("severity", m.getSeverity().name());
mm.put("description", m.getDescription());
matches.add(mm);
}
response.put("matches", matches);
}
return response;
}
@GetMapping("/security/ssrf/status")
public Map<String, Object> getSSRFStatus() {
return ssrfProtectionService.getStatus();
}
@GetMapping("/security/xxe/status")
public Map<String, Object> getXXEStatus() {
return xxeProtectionService.getStatus();
}
@GetMapping("/security/csp/status")
public Map<String, Object> getCSPStatus() {
return cspEnforcementService.getStatus();
}
@GetMapping("/security/csp/headers")
public Map<String, Object> getCSPHeaders() {
Map<String, Object> response = new HashMap<>();
response.put("headers", cspEnforcementService.getSecurityHeaders());
return response;
}
@GetMapping("/security/database-firewall/status")
public Map<String, Object> getDatabaseFirewallStatus() {
return databaseFirewallService.getStatus();
}
@GetMapping("/security/dlp/status")
public Map<String, Object> getDLPStatus() {
return dataLossPreventionService.getStatus();
}
@GetMapping("/security/memory/status")
public Map<String, Object> getMemoryProtectionStatus() {
Map<String, Object> status = memoryProtectionService.getStatus();
MemoryProtectionService.MemoryCheckResult check = memoryProtectionService.checkMemory();
status.put("heapRatio", check.getHeapRatio());
status.put("heapUsedMB", check.getHeapUsedMB());
status.put("heapMaxMB", check.getHeapMaxMB());
status.put("gcTimeMs", check.getGcTimeMs());
status.put("warnings", check.getWarnings());
return status;
}
@GetMapping("/security/process-injection/status")
public Map<String, Object> getProcessInjectionStatus() {
return processInjectionDetectionService.getStatus();
}
@GetMapping("/security/secure-delete/status")
public Map<String, Object> getSecureDeleteStatus() {
return secureFileDeletionService.getStatus();
}
@GetMapping("/security/forensics/status")
public Map<String, Object> getForensicsStatus() {
return forensicsCollectorService.getStatus();
}
@GetMapping("/security/incident-response/status")
public Map<String, Object> getIncidentResponseStatus() {
return incidentResponseService.getStatus();
}
@GetMapping("/security/threat-hunting/status")
public Map<String, Object> getThreatHuntingStatus() {
return threatHuntingService.getStatus();
}
@GetMapping("/security/compliance/status")
public Map<String, Object> getComplianceStatus() {
return complianceScannerService.getStatus();
}
@GetMapping("/security/anti-grief/status")
public Map<String, Object> getAntiGriefStatus() {
return antiGriefDetectionService.getStatus();
}
}