Lateral Movement Detection in Java: Complete Security Monitoring Guide


Article

Lateral movement refers to techniques attackers use to progressively move through a network in search of key assets and data after gaining initial access. For Java applications, detecting lateral movement is crucial for identifying compromised systems and preventing broader network breaches.

This guide covers everything from basic detection patterns to advanced correlation and response strategies for lateral movement in Java environments.

Lateral Movement Patterns Overview

  • Credential Access: Stealing tokens, keys, and passwords
  • Remote Execution: PSExec, WMI, RDP, SSH
  • Application Abuse: Using legitimate tools for malicious purposes
  • Persistence: Establishing footholds for continued access
  • Privilege Escalation: Gaining higher-level permissions

1. Project Setup and Dependencies

Maven Dependencies:

<properties>
<spring.boot.version>3.1.0</spring.boot.version>
<kafka.version>3.4.0</kafka.version>
<elasticsearch.version>8.8.0</elasticsearch.version>
<jackson.version>2.15.2</jackson.version>
</properties>
<dependencies>
<!-- Spring Boot -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<version>${spring.boot.version}</version>
</dependency>
<!-- Security -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
<version>${spring.boot.version}</version>
</dependency>
<!-- Kafka for event streaming -->
<dependency>
<groupId>org.springframework.kafka</groupId>
<artifactId>spring-kafka</artifactId>
<version>3.0.8</version>
</dependency>
<!-- Elasticsearch for log storage -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-elasticsearch</artifactId>
<version>${spring.boot.version}</version>
</dependency>
<!-- JSON Processing -->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>${jackson.version}</version>
</dependency>
<!-- JWT for token analysis -->
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-api</artifactId>
<version>0.11.5</version>
</dependency>
</dependencies>

2. Core Lateral Movement Detection Models

Security Event Models:

package com.example.security.lateral.model;
import java.time.Instant;
import java.util.*;
public class SecurityEvent {
private String id;
private EventType eventType;
private String sourceIp;
private String destinationIp;
private String username;
private String service;
private Instant timestamp;
private Map<String, Object> details = new HashMap<>();
private int severity;
private boolean suspicious;
private String correlationId;
public SecurityEvent() {
this.id = UUID.randomUUID().toString();
this.timestamp = Instant.now();
}
// Getters and setters
public String getId() { return id; }
public EventType getEventType() { return eventType; }
public void setEventType(EventType eventType) { this.eventType = eventType; }
public String getSourceIp() { return sourceIp; }
public void setSourceIp(String sourceIp) { this.sourceIp = sourceIp; }
public String getDestinationIp() { return destinationIp; }
public void setDestinationIp(String destinationIp) { this.destinationIp = destinationIp; }
public String getUsername() { return username; }
public void setUsername(String username) { this.username = username; }
public String getService() { return service; }
public void setService(String service) { this.service = service; }
public Instant getTimestamp() { return timestamp; }
public void setTimestamp(Instant timestamp) { this.timestamp = timestamp; }
public Map<String, Object> getDetails() { return details; }
public void addDetail(String key, Object value) { this.details.put(key, value); }
public int getSeverity() { return severity; }
public void setSeverity(int severity) { this.severity = severity; }
public boolean isSuspicious() { return suspicious; }
public void setSuspicious(boolean suspicious) { this.suspicious = suspicious; }
public String getCorrelationId() { return correlationId; }
public void setCorrelationId(String correlationId) { this.correlationId = correlationId; }
}
enum EventType {
// Authentication events
LOGIN_SUCCESS, LOGIN_FAILURE, LOGOUT, TOKEN_REFRESH,
// Network events
NETWORK_CONNECTION, PORT_SCAN, SERVICE_ACCESS,
// File system events
FILE_ACCESS, FILE_MODIFICATION, FILE_DELETION,
// Process events
PROCESS_CREATION, PROCESS_TERMINATION, PROCESS_INJECTION,
// Application events
API_CALL, DATABASE_QUERY, CONFIGURATION_CHANGE,
// Lateral movement indicators
REMOTE_EXECUTION, PASS_THE_HASH, TOKEN_IMPERSONATION,
SCHEDULED_TASK_CREATION, SERVICE_INSTALLATION
}
class LateralMovementAlert {
private String alertId;
private String title;
private String description;
private AlertSeverity severity;
private List<SecurityEvent> correlatedEvents = new ArrayList<>();
private Instant detectedAt;
private String attackerIp;
private String compromisedUser;
private List<String> techniques;
private double confidenceScore;
private AlertStatus status;
private Map<String, Object> mitigationSteps = new HashMap<>();
public LateralMovementAlert() {
this.alertId = "LMA-" + UUID.randomUUID().toString().substring(0, 8);
this.detectedAt = Instant.now();
this.status = AlertStatus.NEW;
}
// Getters and setters
public String getAlertId() { return alertId; }
public String getTitle() { return title; }
public void setTitle(String title) { this.title = title; }
public String getDescription() { return description; }
public void setDescription(String description) { this.description = description; }
public AlertSeverity getSeverity() { return severity; }
public void setSeverity(AlertSeverity severity) { this.severity = severity; }
public List<SecurityEvent> getCorrelatedEvents() { return correlatedEvents; }
public void addCorrelatedEvent(SecurityEvent event) { this.correlatedEvents.add(event); }
public Instant getDetectedAt() { return detectedAt; }
public String getAttackerIp() { return attackerIp; }
public void setAttackerIp(String attackerIp) { this.attackerIp = attackerIp; }
public String getCompromisedUser() { return compromisedUser; }
public void setCompromisedUser(String compromisedUser) { this.compromisedUser = compromisedUser; }
public List<String> getTechniques() { return techniques; }
public void setTechniques(List<String> techniques) { this.techniques = techniques; }
public double getConfidenceScore() { return confidenceScore; }
public void setConfidenceScore(double confidenceScore) { this.confidenceScore = confidenceScore; }
public AlertStatus getStatus() { return status; }
public void setStatus(AlertStatus status) { this.status = status; }
public Map<String, Object> getMitigationSteps() { return mitigationSteps; }
public void addMitigationStep(String step, Object details) { this.mitigationSteps.put(step, details); }
}
enum AlertSeverity {
LOW, MEDIUM, HIGH, CRITICAL
}
enum AlertStatus {
NEW, INVESTIGATING, MITIGATED, RESOLVED, FALSE_POSITIVE
}

3. Lateral Movement Detection Engine

Core Detection Service:

package com.example.security.lateral.detection;
import com.example.security.lateral.model.*;
import org.springframework.stereotype.Service;
import java.time.Duration;
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.stream.Collectors;
@Service
public class LateralMovementDetector {
private final Map<String, List<SecurityEvent>> eventStore = new ConcurrentHashMap<>();
private final Map<String, UserBehaviorProfile> userProfiles = new ConcurrentHashMap<>();
private final Map<String, HostBehaviorProfile> hostProfiles = new ConcurrentHashMap<>();
private final ScheduledExecutorService correlationScheduler;
private final List<DetectionRule> detectionRules;
// Alert thresholds
private static final int FAILED_LOGIN_THRESHOLD = 5;
private static final int UNUSUAL_SERVICE_THRESHOLD = 3;
private static final Duration CORRELATION_WINDOW = Duration.ofMinutes(30);
public LateralMovementDetector() {
this.correlationScheduler = Executors.newScheduledThreadPool(2);
this.detectionRules = initializeDetectionRules();
startCorrelationEngine();
}
public void processSecurityEvent(SecurityEvent event) {
// Store event for correlation
storeEvent(event);
// Update behavior profiles
updateBehaviorProfiles(event);
// Check immediate detection rules
checkImmediateDetectionRules(event);
}
private void storeEvent(SecurityEvent event) {
String key = getEventStoreKey(event);
eventStore.computeIfAbsent(key, k -> new ArrayList<>()).add(event);
// Clean old events
cleanOldEvents();
}
private String getEventStoreKey(SecurityEvent event) {
return event.getUsername() + "|" + event.getSourceIp();
}
private void cleanOldEvents() {
Instant cutoff = Instant.now().minus(CORRELATION_WINDOW);
eventStore.forEach((key, events) -> {
events.removeIf(event -> event.getTimestamp().isBefore(cutoff));
});
// Remove empty lists
eventStore.entrySet().removeIf(entry -> entry.getValue().isEmpty());
}
private void updateBehaviorProfiles(SecurityEvent event) {
// Update user behavior profile
UserBehaviorProfile userProfile = userProfiles.computeIfAbsent(
event.getUsername(), k -> new UserBehaviorProfile(event.getUsername()));
userProfile.updateProfile(event);
// Update host behavior profile
if (event.getSourceIp() != null) {
HostBehaviorProfile hostProfile = hostProfiles.computeIfAbsent(
event.getSourceIp(), k -> new HostBehaviorProfile(event.getSourceIp()));
hostProfile.updateProfile(event);
}
}
private void checkImmediateDetectionRules(SecurityEvent event) {
for (DetectionRule rule : detectionRules) {
if (rule.isImmediate() && rule.matches(event)) {
LateralMovementAlert alert = rule.generateAlert(event);
triggerAlert(alert);
}
}
}
private void startCorrelationEngine() {
correlationScheduler.scheduleAtFixedRate(this::runCorrelationRules, 
1, 1, TimeUnit.MINUTES);
}
private void runCorrelationRules() {
// Run correlation-based detection rules
for (DetectionRule rule : detectionRules) {
if (!rule.isImmediate()) {
List<LateralMovementAlert> alerts = rule.correlateEvents(eventStore);
alerts.forEach(this::triggerAlert);
}
}
// Check for behavioral anomalies
checkBehavioralAnomalies();
}
private void checkBehavioralAnomalies() {
// Check for unusual user behavior
userProfiles.values().forEach(this::checkUserAnomalies);
// Check for unusual host behavior
hostProfiles.values().forEach(this::checkHostAnomalies);
}
private void checkUserAnomalies(UserBehaviorProfile userProfile) {
// Failed login attempts
if (userProfile.getRecentFailedLogins() > FAILED_LOGIN_THRESHOLD) {
createAlertForFailedLogins(userProfile);
}
// Unusual service access
if (userProfile.getUnusualServiceAccessCount() > UNUSUAL_SERVICE_THRESHOLD) {
createAlertForUnusualServiceAccess(userProfile);
}
// Geographic anomalies
if (userProfile.hasGeographicAnomaly()) {
createAlertForGeographicAnomaly(userProfile);
}
// Time-based anomalies
if (userProfile.hasTimeAnomaly()) {
createAlertForTimeAnomaly(userProfile);
}
}
private void checkHostAnomalies(HostBehaviorProfile hostProfile) {
// Port scanning detection
if (hostProfile.isPortScanningDetected()) {
createAlertForPortScanning(hostProfile);
}
// Multiple service connections
if (hostProfile.hasMultipleServiceConnections()) {
createAlertForMultipleServices(hostProfile);
}
}
private void createAlertForFailedLogins(UserBehaviorProfile userProfile) {
LateralMovementAlert alert = new LateralMovementAlert();
alert.setTitle("Multiple Failed Login Attempts");
alert.setDescription(String.format(
"User %s has %d failed login attempts in the last %s",
userProfile.getUsername(),
userProfile.getRecentFailedLogins(),
CORRELATION_WINDOW
));
alert.setSeverity(AlertSeverity.HIGH);
alert.setCompromisedUser(userProfile.getUsername());
alert.setTechniques(List.of("Brute Force", "Password Spraying"));
alert.setConfidenceScore(0.85);
triggerAlert(alert);
}
private void createAlertForUnusualServiceAccess(UserBehaviorProfile userProfile) {
LateralMovementAlert alert = new LateralMovementAlert();
alert.setTitle("Unusual Service Access Pattern");
alert.setDescription(String.format(
"User %s accessed %d unusual services",
userProfile.getUsername(),
userProfile.getUnusualServiceAccessCount()
));
alert.setSeverity(AlertSeverity.MEDIUM);
alert.setCompromisedUser(userProfile.getUsername());
alert.setTechniques(List.of("Service Discovery", "Lateral Movement"));
alert.setConfidenceScore(0.70);
triggerAlert(alert);
}
private void createAlertForGeographicAnomaly(UserBehaviorProfile userProfile) {
LateralMovementAlert alert = new LateralMovementAlert();
alert.setTitle("Geographic Login Anomaly");
alert.setDescription(String.format(
"User %s logged in from unusual location: %s",
userProfile.getUsername(),
userProfile.getLastLocation()
));
alert.setSeverity(AlertSeverity.HIGH);
alert.setCompromisedUser(userProfile.getUsername());
alert.setTechniques(List.of("Account Compromise", "Credential Theft"));
alert.setConfidenceScore(0.90);
triggerAlert(alert);
}
private void createAlertForTimeAnomaly(UserBehaviorProfile userProfile) {
LateralMovementAlert alert = new LateralMovementAlert();
alert.setTitle("Unusual Login Time");
alert.setDescription(String.format(
"User %s logged in during unusual hours",
userProfile.getUsername()
));
alert.setSeverity(AlertSeverity.MEDIUM);
alert.setCompromisedUser(userProfile.getUsername());
alert.setTechniques(List.of("Account Compromise"));
alert.setConfidenceScore(0.65);
triggerAlert(alert);
}
private void createAlertForPortScanning(HostBehaviorProfile hostProfile) {
LateralMovementAlert alert = new LateralMovementAlert();
alert.setTitle("Port Scanning Detected");
alert.setDescription(String.format(
"Host %s is scanning multiple ports",
hostProfile.getHostIp()
));
alert.setSeverity(AlertSeverity.HIGH);
alert.setAttackerIp(hostProfile.getHostIp());
alert.setTechniques(List.of("Network Scanning", "Reconnaissance"));
alert.setConfidenceScore(0.80);
triggerAlert(alert);
}
private void createAlertForMultipleServices(HostBehaviorProfile hostProfile) {
LateralMovementAlert alert = new LateralMovementAlert();
alert.setTitle("Multiple Service Connections");
alert.setDescription(String.format(
"Host %s connected to %d different services",
hostProfile.getHostIp(),
hostProfile.getServiceConnectionCount()
));
alert.setSeverity(AlertSeverity.MEDIUM);
alert.setAttackerIp(hostProfile.getHostIp());
alert.setTechniques(List.of("Lateral Movement", "Service Enumeration"));
alert.setConfidenceScore(0.75);
triggerAlert(alert);
}
private void triggerAlert(LateralMovementAlert alert) {
// Send to alert processor
AlertProcessor.processAlert(alert);
// Log the alert
System.err.println("LATERAL MOVEMENT ALERT: " + alert.getTitle());
System.err.println("Description: " + alert.getDescription());
System.err.println("Severity: " + alert.getSeverity());
System.err.println("Confidence: " + alert.getConfidenceScore());
}
private List<DetectionRule> initializeDetectionRules() {
List<DetectionRule> rules = new ArrayList<>();
// Immediate detection rules
rules.add(new FailedLoginRule());
rules.add(new UnusualServiceAccessRule());
rules.add(new TokenTheftRule());
rules.add(new RemoteExecutionRule());
// Correlation rules
rules.add(new PassTheHashRule());
rules.add(new GoldenTicketRule());
rules.add(new LateralMovementChainRule());
return rules;
}
@PreDestroy
public void cleanup() {
correlationScheduler.shutdown();
}
}
class UserBehaviorProfile {
private String username;
private List<String> usualServices = new ArrayList<>();
private Map<String, Integer> serviceAccessCount = new HashMap<>();
private List<String> usualLocations = new ArrayList<>();
private List<Integer> usualLoginHours = new ArrayList<>();
private int failedLoginCount;
private Instant lastLogin;
private String lastLocation;
public UserBehaviorProfile(String username) {
this.username = username;
}
public void updateProfile(SecurityEvent event) {
switch (event.getEventType()) {
case LOGIN_SUCCESS:
updateLoginBehavior(event);
break;
case LOGIN_FAILURE:
failedLoginCount++;
break;
case SERVICE_ACCESS:
updateServiceAccess(event);
break;
}
}
private void updateLoginBehavior(SecurityEvent event) {
lastLogin = event.getTimestamp();
lastLocation = event.getSourceIp();
// Update usual login hours
int loginHour = event.getTimestamp().atZone(java.time.ZoneId.systemDefault()).getHour();
if (!usualLoginHours.contains(loginHour)) {
usualLoginHours.add(loginHour);
}
// Update usual locations
if (!usualLocations.contains(event.getSourceIp())) {
usualLocations.add(event.getSourceIp());
}
}
private void updateServiceAccess(SecurityEvent event) {
String service = event.getService();
serviceAccessCount.put(service, serviceAccessCount.getOrDefault(service, 0) + 1);
if (!usualServices.contains(service)) {
usualServices.add(service);
}
}
public int getRecentFailedLogins() {
// Reset counter periodically (implementation depends on time window)
return failedLoginCount;
}
public int getUnusualServiceAccessCount() {
return (int) serviceAccessCount.entrySet().stream()
.filter(entry -> !usualServices.contains(entry.getKey()))
.count();
}
public boolean hasGeographicAnomaly() {
return lastLocation != null && !usualLocations.contains(lastLocation);
}
public boolean hasTimeAnomaly() {
if (lastLogin == null) return false;
int loginHour = lastLogin.atZone(java.time.ZoneId.systemDefault()).getHour();
return !usualLoginHours.contains(loginHour);
}
// Getters
public String getUsername() { return username; }
public String getLastLocation() { return lastLocation; }
}
class HostBehaviorProfile {
private String hostIp;
private Set<Integer> scannedPorts = new HashSet<>();
private Set<String> connectedServices = new HashSet<>();
private Instant lastActivity;
public HostBehaviorProfile(String hostIp) {
this.hostIp = hostIp;
}
public void updateProfile(SecurityEvent event) {
lastActivity = event.getTimestamp();
if (event.getEventType() == EventType.NETWORK_CONNECTION) {
Integer port = (Integer) event.getDetails().get("destinationPort");
if (port != null) {
scannedPorts.add(port);
}
}
if (event.getEventType() == EventType.SERVICE_ACCESS) {
connectedServices.add(event.getService());
}
}
public boolean isPortScanningDetected() {
return scannedPorts.size() > 10; // Threshold for port scanning
}
public boolean hasMultipleServiceConnections() {
return connectedServices.size() > 5; // Threshold for multiple services
}
public int getServiceConnectionCount() {
return connectedServices.size();
}
// Getters
public String getHostIp() { return hostIp; }
}

4. Detection Rules Implementation

Detection Rules Framework:

package com.example.security.lateral.detection;
import com.example.security.lateral.model.*;
import java.util.List;
import java.util.Map;
public abstract class DetectionRule {
protected String ruleId;
protected String ruleName;
protected boolean immediate;
protected double baseConfidence;
public abstract boolean matches(SecurityEvent event);
public abstract List<LateralMovementAlert> correlateEvents(Map<String, List<SecurityEvent>> eventStore);
public abstract LateralMovementAlert generateAlert(SecurityEvent event);
// Getters
public String getRuleId() { return ruleId; }
public String getRuleName() { return ruleName; }
public boolean isImmediate() { return immediate; }
public double getBaseConfidence() { return baseConfidence; }
}
// Concrete rule implementations
class FailedLoginRule extends DetectionRule {
public FailedLoginRule() {
this.ruleId = "RULE-001";
this.ruleName = "Failed Login Attempts";
this.immediate = true;
this.baseConfidence = 0.7;
}
@Override
public boolean matches(SecurityEvent event) {
return event.getEventType() == EventType.LOGIN_FAILURE;
}
@Override
public List<LateralMovementAlert> correlateEvents(Map<String, List<SecurityEvent>> eventStore) {
// This rule doesn't use correlation
return List.of();
}
@Override
public LateralMovementAlert generateAlert(SecurityEvent event) {
LateralMovementAlert alert = new LateralMovementAlert();
alert.setTitle("Failed Login Attempt");
alert.setDescription(String.format(
"Failed login for user %s from IP %s",
event.getUsername(), event.getSourceIp()
));
alert.setSeverity(AlertSeverity.MEDIUM);
alert.setConfidenceScore(baseConfidence);
alert.setTechniques(List.of("Brute Force", "Credential Stuffing"));
return alert;
}
}
class UnusualServiceAccessRule extends DetectionRule {
public UnusualServiceAccessRule() {
this.ruleId = "RULE-002";
this.ruleName = "Unusual Service Access";
this.immediate = true;
this.baseConfidence = 0.6;
}
@Override
public boolean matches(SecurityEvent event) {
if (event.getEventType() != EventType.SERVICE_ACCESS) return false;
// Check if service is unusual for this user
String service = event.getService();
return isUnusualService(service, event.getUsername());
}
private boolean isUnusualService(String service, String username) {
// Implement service whitelist/blacklist logic
List<String> unusualServices = List.of("LDAP", "RDP", "SSH", "SMB", "WMI");
return unusualServices.contains(service);
}
@Override
public List<LateralMovementAlert> correlateEvents(Map<String, List<SecurityEvent>> eventStore) {
return List.of();
}
@Override
public LateralMovementAlert generateAlert(SecurityEvent event) {
LateralMovementAlert alert = new LateralMovementAlert();
alert.setTitle("Unusual Service Access");
alert.setDescription(String.format(
"User %s accessed unusual service: %s",
event.getUsername(), event.getService()
));
alert.setSeverity(AlertSeverity.MEDIUM);
alert.setConfidenceScore(baseConfidence);
alert.setTechniques(List.of("Lateral Movement", "Service Discovery"));
return alert;
}
}
class PassTheHashRule extends DetectionRule {
public PassTheHashRule() {
this.ruleId = "RULE-101";
this.ruleName = "Pass-the-Hash Detection";
this.immediate = false;
this.baseConfidence = 0.9;
}
@Override
public boolean matches(SecurityEvent event) {
return false; // This is a correlation-only rule
}
@Override
public List<LateralMovementAlert> correlateEvents(Map<String, List<SecurityEvent>> eventStore) {
// Look for patterns indicative of Pass-the-Hash attacks
// Multiple systems accessed with the same credentials in short time
// Unusual authentication patterns
List<LateralMovementAlert> alerts = new ArrayList<>();
eventStore.forEach((key, events) -> {
if (isPassTheHashPattern(events)) {
LateralMovementAlert alert = createPassTheHashAlert(events);
alerts.add(alert);
}
});
return alerts;
}
private boolean isPassTheHashPattern(List<SecurityEvent> events) {
// Implement PTH detection logic
long uniqueHosts = events.stream()
.filter(e -> e.getEventType() == EventType.LOGIN_SUCCESS)
.map(SecurityEvent::getDestinationIp)
.distinct()
.count();
return uniqueHosts >= 3; // Accessed 3+ different hosts in short time
}
private LateralMovementAlert createPassTheHashAlert(List<SecurityEvent> events) {
LateralMovementAlert alert = new LateralMovementAlert();
alert.setTitle("Pass-the-Hash Attack Detected");
alert.setDescription("Multiple systems accessed with same credentials in short timeframe");
alert.setSeverity(AlertSeverity.CRITICAL);
alert.setConfidenceScore(baseConfidence);
alert.setTechniques(List.of("Pass-the-Hash", "Lateral Movement"));
events.forEach(alert::addCorrelatedEvent);
return alert;
}
@Override
public LateralMovementAlert generateAlert(SecurityEvent event) {
return null; // Not used for immediate alerts
}
}
class LateralMovementChainRule extends DetectionRule {
public LateralMovementChainRule() {
this.ruleId = "RULE-102";
this.ruleName = "Lateral Movement Chain";
this.immediate = false;
this.baseConfidence = 0.85;
}
@Override
public boolean matches(SecurityEvent event) {
return false;
}
@Override
public List<LateralMovementAlert> correlateEvents(Map<String, List<SecurityEvent>> eventStore) {
List<LateralMovementAlert> alerts = new ArrayList<>();
// Look for chains of suspicious activities
eventStore.forEach((key, events) -> {
if (isLateralMovementChain(events)) {
LateralMovementAlert alert = createLateralMovementAlert(events);
alerts.add(alert);
}
});
return alerts;
}
private boolean isLateralMovementChain(List<SecurityEvent> events) {
// Check for reconnaissance -> credential access -> lateral movement pattern
boolean hasRecon = events.stream().anyMatch(e -> 
e.getEventType() == EventType.NETWORK_CONNECTION && 
e.getDetails().containsKey("portScan"));
boolean hasCredentialAccess = events.stream().anyMatch(e -> 
e.getEventType() == EventType.LOGIN_FAILURE ||
e.getEventType() == EventType.TOKEN_REFRESH);
boolean hasLateralMovement = events.stream().anyMatch(e -> 
e.getEventType() == EventType.REMOTE_EXECUTION ||
e.getEventType() == EventType.SERVICE_ACCESS);
return hasRecon && hasCredentialAccess && hasLateralMovement;
}
private LateralMovementAlert createLateralMovementAlert(List<SecurityEvent> events) {
LateralMovementAlert alert = new LateralMovementAlert();
alert.setTitle("Lateral Movement Chain Detected");
alert.setDescription("Reconnaissance -> Credential Access -> Lateral Movement pattern detected");
alert.setSeverity(AlertSeverity.HIGH);
alert.setConfidenceScore(baseConfidence);
alert.setTechniques(List.of("Network Scanning", "Credential Access", "Lateral Movement"));
events.forEach(alert::addCorrelatedEvent);
return alert;
}
@Override
public LateralMovementAlert generateAlert(SecurityEvent event) {
return null;
}
}

5. Alert Processing and Response

Alert Processor:

package com.example.security.lateral.alert;
import com.example.security.lateral.model.LateralMovementAlert;
import org.springframework.kafka.core.KafkaTemplate;
import org.springframework.stereotype.Service;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
@Service
public class AlertProcessor {
private final BlockingQueue<LateralMovementAlert> alertQueue = new LinkedBlockingQueue<>();
private final ScheduledExecutorService alertScheduler;
private final KafkaTemplate<String, Object> kafkaTemplate;
private final AlertNotifier alertNotifier;
private final ThreatIntelligenceService threatIntelligence;
public AlertProcessor(KafkaTemplate<String, Object> kafkaTemplate,
AlertNotifier alertNotifier,
ThreatIntelligenceService threatIntelligence) {
this.kafkaTemplate = kafkaTemplate;
this.alertNotifier = alertNotifier;
this.threatIntelligence = threatIntelligence;
this.alertScheduler = Executors.newScheduledThreadPool(2);
startAlertProcessing();
}
public static void processAlert(LateralMovementAlert alert) {
// Static method for easy access from detectors
AlertProcessor instance = getInstance(); // Would need proper dependency injection
instance.queueAlert(alert);
}
private void queueAlert(LateralMovementAlert alert) {
try {
alertQueue.put(alert);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
System.err.println("Failed to queue alert: " + e.getMessage());
}
}
private void startAlertProcessing() {
// Process alerts from queue
alertScheduler.scheduleAtFixedRate(this::processAlertQueue, 0, 1, TimeUnit.SECONDS);
// Aggregate and analyze alerts
alertScheduler.scheduleAtFixedRate(this::analyzeAlertPatterns, 0, 5, TimeUnit.MINUTES);
}
private void processAlertQueue() {
while (!alertQueue.isEmpty()) {
try {
LateralMovementAlert alert = alertQueue.take();
processSingleAlert(alert);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
break;
}
}
}
private void processSingleAlert(LateralMovementAlert alert) {
try {
// Enrich with threat intelligence
enrichWithThreatIntelligence(alert);
// Calculate risk score
calculateRiskScore(alert);
// Send to Kafka for further processing
sendToKafka(alert);
// Notify security team
notifySecurityTeam(alert);
// Take automated response actions for critical alerts
if (alert.getSeverity() == AlertSeverity.CRITICAL) {
takeAutomatedResponse(alert);
}
// Log the alert
logAlert(alert);
} catch (Exception e) {
System.err.println("Failed to process alert: " + e.getMessage());
}
}
private void enrichWithThreatIntelligence(LateralMovementAlert alert) {
// Check if source IP is known malicious
if (threatIntelligence.isMaliciousIp(alert.getAttackerIp())) {
alert.setConfidenceScore(Math.min(1.0, alert.getConfidenceScore() + 0.2));
alert.addMitigationStep("block_ip", "Source IP is known malicious");
}
// Check if techniques match known attack patterns
if (threatIntelligence.isKnownAttackPattern(alert.getTechniques())) {
alert.setConfidenceScore(Math.min(1.0, alert.getConfidenceScore() + 0.15));
}
}
private void calculateRiskScore(LateralMovementAlert alert) {
double riskScore = alert.getConfidenceScore();
// Adjust based on severity
switch (alert.getSeverity()) {
case CRITICAL:
riskScore *= 1.5;
break;
case HIGH:
riskScore *= 1.2;
break;
case MEDIUM:
riskScore *= 1.0;
break;
case LOW:
riskScore *= 0.8;
break;
}
// Adjust based on number of correlated events
riskScore *= Math.min(1.0, 1.0 + (alert.getCorrelatedEvents().size() * 0.1));
alert.addDetail("calculatedRiskScore", Math.min(1.0, riskScore));
}
private void sendToKafka(LateralMovementAlert alert) {
kafkaTemplate.send("lateral-movement-alerts", alert.getAlertId(), alert);
}
private void notifySecurityTeam(LateralMovementAlert alert) {
alertNotifier.notifySecurityTeam(alert);
}
private void takeAutomatedResponse(LateralMovementAlert alert) {
// Implement automated response actions
System.err.println("AUTOMATED RESPONSE for critical alert: " + alert.getAlertId());
// Example actions:
// - Block source IP in firewall
// - Disable compromised user account
// - Isolate affected systems
// - Increase logging levels
}
private void logAlert(LateralMovementAlert alert) {
// Log to security information and event management (SIEM)
System.err.println("SECURITY ALERT LOGGED: " + alert.getAlertId());
}
private void analyzeAlertPatterns() {
// Analyze alert patterns for advanced threat detection
// This could identify coordinated attacks or advanced persistent threats
}
@PreDestroy
public void cleanup() {
alertScheduler.shutdown();
}
// Singleton pattern for static access (simplified)
private static AlertProcessor instance;
private static AlertProcessor getInstance() {
// In real implementation, use proper dependency injection
return instance;
}
}

6. Integration with Security Tools

Kafka Configuration for Event Streaming:

package com.example.security.config;
import org.apache.kafka.clients.producer.ProducerConfig;
import org.apache.kafka.common.serialization.StringSerializer;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.kafka.core.DefaultKafkaProducerFactory;
import org.springframework.kafka.core.KafkaTemplate;
import org.springframework.kafka.core.ProducerFactory;
import org.springframework.kafka.support.serializer.JsonSerializer;
import java.util.HashMap;
import java.util.Map;
@Configuration
public class KafkaConfig {
@Bean
public ProducerFactory<String, Object> producerFactory() {
Map<String, Object> configProps = new HashMap<>();
configProps.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG, "localhost:9092");
configProps.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, StringSerializer.class);
configProps.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG, JsonSerializer.class);
configProps.put(ProducerConfig.ACKS_CONFIG, "all");
configProps.put(ProducerConfig.RETRIES_CONFIG, 3);
return new DefaultKafkaProducerFactory<>(configProps);
}
@Bean
public KafkaTemplate<String, Object> kafkaTemplate() {
return new KafkaTemplate<>(producerFactory());
}
}

Elasticsearch Integration for Log Storage:

package com.example.security.integration.elasticsearch;
import com.example.security.lateral.model.LateralMovementAlert;
import org.springframework.data.elasticsearch.repository.ElasticsearchRepository;
import org.springframework.stereotype.Repository;
import java.util.List;
@Repository
public interface AlertRepository extends ElasticsearchRepository<LateralMovementAlert, String> {
List<LateralMovementAlert> findBySeverity(String severity);
List<LateralMovementAlert> findByStatus(String status);
List<LateralMovementAlert> findByAttackerIp(String attackerIp);
List<LateralMovementAlert> findByCompromisedUser(String compromisedUser);
@Query("{\"bool\": {\"must\": [{\"range\": {\"detectedAt\": {\"gte\": \"?0\"}}}]}}")
List<LateralMovementAlert> findRecentAlerts(String fromDate);
}

7. Real-time Dashboard and Monitoring

Security Dashboard Controller:

package com.example.security.controller;
import com.example.security.lateral.model.LateralMovementAlert;
import com.example.security.integration.elasticsearch.AlertRepository;
import org.springframework.web.bind.annotation.*;
import java.time.Instant;
import java.time.temporal.ChronoUnit;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@RestController
@RequestMapping("/api/security")
public class SecurityDashboardController {
private final AlertRepository alertRepository;
public SecurityDashboardController(AlertRepository alertRepository) {
this.alertRepository = alertRepository;
}
@GetMapping("/alerts/recent")
public List<LateralMovementAlert> getRecentAlerts() {
String fromDate = Instant.now().minus(24, ChronoUnit.HOURS).toString();
return alertRepository.findRecentAlerts(fromDate);
}
@GetMapping("/alerts/statistics")
public Map<String, Object> getAlertStatistics() {
Map<String, Object> stats = new HashMap<>();
List<LateralMovementAlert> recentAlerts = getRecentAlerts();
stats.put("totalAlerts", recentAlerts.size());
stats.put("criticalAlerts", recentAlerts.stream()
.filter(a -> a.getSeverity() == AlertSeverity.CRITICAL).count());
stats.put("highAlerts", recentAlerts.stream()
.filter(a -> a.getSeverity() == AlertSeverity.HIGH).count());
stats.put("activeInvestigations", recentAlerts.stream()
.filter(a -> a.getStatus() == AlertStatus.INVESTIGATING).count());
return stats;
}
@PostMapping("/alerts/{alertId}/status")
public void updateAlertStatus(@PathVariable String alertId, 
@RequestBody StatusUpdateRequest request) {
LateralMovementAlert alert = alertRepository.findById(alertId).orElse(null);
if (alert != null) {
alert.setStatus(request.getStatus());
alertRepository.save(alert);
}
}
@GetMapping("/alerts/techniques")
public Map<String, Long> getTechniqueStatistics() {
List<LateralMovementAlert> recentAlerts = getRecentAlerts();
return recentAlerts.stream()
.flatMap(alert -> alert.getTechniques().stream())
.collect(Collectors.groupingBy(technique -> technique, Collectors.counting()));
}
}
class StatusUpdateRequest {
private AlertStatus status;
public AlertStatus getStatus() { return status; }
public void setStatus(AlertStatus status) { this.status = status; }
}

8. MITRE ATT&CK Integration

MITRE ATT&CK Framework Mapping:

package com.example.security.mitre;
import java.util.*;
public class MitreAttackMapper {
private static final Map<String, MitreTechnique> TECHNIQUE_MAP = new HashMap<>();
static {
// Lateral Movement Techniques
TECHNIQUE_MAP.put("Remote Desktop Protocol", new MitreTechnique("T1021.001", "Remote Desktop Protocol"));
TECHNIQUE_MAP.put("SMB/Windows Admin Shares", new MitreTechnique("T1021.002", "SMB/Windows Admin Shares"));
TECHNIQUE_MAP.put("Distributed Component Object Model", new MitreTechnique("T1021.003", "Distributed Component Object Model"));
TECHNIQUE_MAP.put("SSH", new MitreTechnique("T1021.004", "SSH"));
TECHNIQUE_MAP.put("VNC", new MitreTechnique("T1021.005", "VNC"));
TECHNIQUE_MAP.put("Windows Remote Management", new MitreTechnique("T1021.006", "Windows Remote Management"));
// Credential Access Techniques
TECHNIQUE_MAP.put("OS Credential Dumping", new MitreTechnique("T1003", "OS Credential Dumping"));
TECHNIQUE_MAP.put("Pass the Hash", new MitreTechnique("T1550.002", "Pass the Hash"));
TECHNIQUE_MAP.put("Pass the Ticket", new MitreTechnique("T1550.003", "Pass the Ticket"));
TECHNIQUE_MAP.put("Kerberoasting", new MitreTechnique("T1558.003", "Kerberoasting"));
// Discovery Techniques
TECHNIQUE_MAP.put("Network Service Scanning", new MitreTechnique("T1046", "Network Service Scanning"));
TECHNIQUE_MAP.put("Network Share Discovery", new MitreTechnique("T1135", "Network Share Discovery"));
TECHNIQUE_MAP.put("Remote System Discovery", new MitreTechnique("T1018", "Remote System Discovery"));
}
public static List<MitreTechnique> mapToMitreTechniques(List<String> techniques) {
return techniques.stream()
.map(TECHNIQUE_MAP::get)
.filter(Objects::nonNull)
.collect(Collectors.toList());
}
public static String getMitreTactic(String techniqueId) {
// Map technique IDs to MITRE tactics
Map<String, String> tacticMap = Map.of(
"T1021", "Lateral Movement",
"T1550", "Lateral Movement", 
"T1003", "Credential Access",
"T1046", "Discovery",
"T1018", "Discovery"
);
String baseId = techniqueId.split("\\.")[0];
return tacticMap.getOrDefault(baseId, "Unknown");
}
}
class MitreTechnique {
private String id;
private String name;
private String description;
private String url;
public MitreTechnique(String id, String name) {
this.id = id;
this.name = name;
this.url = "https://attack.mitre.org/techniques/" + id.replace(".", "/");
}
// Getters
public String getId() { return id; }
public String getName() { return name; }
public String getDescription() { return description; }
public void setDescription(String description) { this.description = description; }
public String getUrl() { return url; }
}

9. Response Playbooks

Automated Response Service:

package com.example.security.response;
import com.example.security.lateral.model.LateralMovementAlert;
import org.springframework.stereotype.Service;
import java.util.*;
@Service
public class ResponsePlaybookService {
private final Map<String, ResponsePlaybook> playbooks = new HashMap<>();
public ResponsePlaybookService() {
initializePlaybooks();
}
private void initializePlaybooks() {
// Pass-the-Hash response playbook
ResponsePlaybook pthPlaybook = new ResponsePlaybook();
pthPlaybook.setId("PLAYBOOK-PTH");
pthPlaybook.setName("Pass-the-Hash Attack Response");
pthPlaybook.setDescription("Standard response procedure for PTH attacks");
pthPlaybook.setSteps(Arrays.asList(
"1. Immediately reset passwords for compromised accounts",
"2. Block source IP addresses in firewall",
"3. Check for persistence mechanisms on compromised systems",
"4. Review authentication logs for additional compromised accounts",
"5. Isolate affected systems from network",
"6. Conduct forensic analysis on compromised hosts"
));
pthPlaybook.setAutomatedActions(Arrays.asList("block_ip", "disable_user"));
playbooks.put("Pass-the-Hash", pthPlaybook);
// Lateral Movement response playbook
ResponsePlaybook lmPlaybook = new ResponsePlaybook();
lmPlaybook.setId("PLAYBOOK-LM");
lmPlaybook.setName("Lateral Movement Response");
lmPlaybook.setDescription("Response to detected lateral movement activities");
lmPlaybook.setSteps(Arrays.asList(
"1. Identify all compromised systems in the movement chain",
"2. Isolate affected systems from production network",
"3. Review and update access controls",
"4. Check for data exfiltration attempts",
"5. Conduct threat hunting for additional compromises",
"6. Implement additional monitoring on critical systems"
));
lmPlaybook.setAutomatedActions(Arrays.asList("isolate_hosts", "increase_monitoring"));
playbooks.put("Lateral Movement", lmPlaybook);
}
public ResponsePlaybook getPlaybookForAlert(LateralMovementAlert alert) {
// Find the most appropriate playbook based on alert techniques
for (String technique : alert.getTechniques()) {
if (playbooks.containsKey(technique)) {
return playbooks.get(technique);
}
}
// Default playbook for lateral movement
return playbooks.get("Lateral Movement");
}
public void executeAutomatedActions(ResponsePlaybook playbook, LateralMovementAlert alert) {
for (String action : playbook.getAutomatedActions()) {
executeAutomatedAction(action, alert);
}
}
private void executeAutomatedAction(String action, LateralMovementAlert alert) {
switch (action) {
case "block_ip":
blockIpAddress(alert.getAttackerIp());
break;
case "disable_user":
disableUserAccount(alert.getCompromisedUser());
break;
case "isolate_hosts":
isolateCompromisedHosts(alert);
break;
case "increase_monitoring":
increaseMonitoring(alert);
break;
}
}
private void blockIpAddress(String ipAddress) {
// Implement IP blocking logic
System.err.println("Blocking IP address: " + ipAddress);
}
private void disableUserAccount(String username) {
// Implement user account disabling
System.err.println("Disabling user account: " + username);
}
private void isolateCompromisedHosts(LateralMovementAlert alert) {
// Implement host isolation logic
System.err.println("Isolating compromised hosts for alert: " + alert.getAlertId());
}
private void increaseMonitoring(LateralMovementAlert alert) {
// Implement increased monitoring
System.err.println("Increasing monitoring for alert: " + alert.getAlertId());
}
}
class ResponsePlaybook {
private String id;
private String name;
private String description;
private List<String> steps = new ArrayList<>();
private List<String> automatedActions = new ArrayList<>();
// Getters and setters
public String getId() { return id; }
public void setId(String id) { this.id = id; }
public String getName() { return name; }
public void setName(String name) { this.name = name; }
public String getDescription() { return description; }
public void setDescription(String description) { this.description = description; }
public List<String> getSteps() { return steps; }
public void setSteps(List<String> steps) { this.steps = steps; }
public List<String> getAutomatedActions() { return automatedActions; }
public void setAutomatedActions(List<String> automatedActions) { this.automatedActions = automatedActions; }
}

10. Testing and Validation

Detection Rule Testing:

package com.example.security.test;
import com.example.security.lateral.detection.*;
import com.example.security.lateral.model.*;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
import java.time.Instant;
import java.util.*;
@SpringBootTest
public class LateralMovementDetectionTest {
@Test
public void testPassTheHashDetection() {
PassTheHashRule rule = new PassTheHashRule();
Map<String, List<SecurityEvent>> eventStore = new HashMap<>();
// Create simulated PTH pattern
List<SecurityEvent> events = new ArrayList<>();
String username = "testuser";
String sourceIp = "192.168.1.100";
// Multiple successful logins to different hosts in short time
for (int i = 0; i < 5; i++) {
SecurityEvent event = new SecurityEvent();
event.setEventType(EventType.LOGIN_SUCCESS);
event.setUsername(username);
event.setSourceIp(sourceIp);
event.setDestinationIp("192.168.1." + (10 + i));
event.setTimestamp(Instant.now().minusSeconds(i * 60)); // 1 minute apart
events.add(event);
}
eventStore.put(username + "|" + sourceIp, events);
List<LateralMovementAlert> alerts = rule.correlateEvents(eventStore);
assert !alerts.isEmpty() : "Should detect PTH pattern";
assert alerts.get(0).getTechniques().contains("Pass-the-Hash") : "Should identify PTH technique";
}
@Test
public void testFailedLoginRule() {
FailedLoginRule rule = new FailedLoginRule();
SecurityEvent event = new SecurityEvent();
event.setEventType(EventType.LOGIN_FAILURE);
event.setUsername("testuser");
event.setSourceIp("192.168.1.100");
boolean matches = rule.matches(event);
assert matches : "Should match failed login events";
LateralMovementAlert alert = rule.generateAlert(event);
assert alert != null : "Should generate alert for failed login";
assert alert.getSeverity() == AlertSeverity.MEDIUM : "Should have medium severity";
}
}

Conclusion

Lateral movement detection in Java provides:

  1. Real-time Monitoring: Continuous security event analysis
  2. Behavioral Analysis: User and host behavior profiling
  3. Pattern Recognition: Correlation of suspicious activities
  4. Automated Response: Immediate action for critical threats
  5. MITRE ATT&CK Integration: Industry-standard framework mapping

Key Detection Capabilities:

  • Credential-based attacks (Pass-the-Hash, Golden Ticket)
  • Remote execution and service abuse
  • Network reconnaissance and scanning
  • Behavioral anomalies and unusual access patterns
  • Attack chain correlation and analysis

By implementing comprehensive lateral movement detection, organizations can significantly reduce their attack surface and detect threats before they cause significant damage to the network infrastructure.



Article

Lateral movement refers to techniques attackers use to progressively move through a network in search of key assets and data after gaining initial access. For Java applications, detecting lateral movement is crucial for identifying compromised systems and preventing broader network breaches.

This guide covers everything from basic detection patterns to advanced correlation and response strategies for lateral movement in Java environments.

Lateral Movement Patterns Overview

  • Credential Access: Stealing tokens, keys, and passwords
  • Remote Execution: PSExec, WMI, RDP, SSH
  • Application Abuse: Using legitimate tools for malicious purposes
  • Persistence: Establishing footholds for continued access
  • Privilege Escalation: Gaining higher-level permissions

1. Project Setup and Dependencies

Maven Dependencies:

<properties>
<spring.boot.version>3.1.0</spring.boot.version>
<kafka.version>3.4.0</kafka.version>
<elasticsearch.version>8.8.0</elasticsearch.version>
<jackson.version>2.15.2</jackson.version>
</properties>
<dependencies>
<!-- Spring Boot -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<version>${spring.boot.version}</version>
</dependency>
<!-- Security -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
<version>${spring.boot.version}</version>
</dependency>
<!-- Kafka for event streaming -->
<dependency>
<groupId>org.springframework.kafka</groupId>
<artifactId>spring-kafka</artifactId>
<version>3.0.8</version>
</dependency>
<!-- Elasticsearch for log storage -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-elasticsearch</artifactId>
<version>${spring.boot.version}</version>
</dependency>
<!-- JSON Processing -->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>${jackson.version}</version>
</dependency>
<!-- JWT for token analysis -->
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-api</artifactId>
<version>0.11.5</version>
</dependency>
</dependencies>

2. Core Lateral Movement Detection Models

Security Event Models:

package com.example.security.lateral.model;
import java.time.Instant;
import java.util.*;
public class SecurityEvent {
private String id;
private EventType eventType;
private String sourceIp;
private String destinationIp;
private String username;
private String service;
private Instant timestamp;
private Map<String, Object> details = new HashMap<>();
private int severity;
private boolean suspicious;
private String correlationId;
public SecurityEvent() {
this.id = UUID.randomUUID().toString();
this.timestamp = Instant.now();
}
// Getters and setters
public String getId() { return id; }
public EventType getEventType() { return eventType; }
public void setEventType(EventType eventType) { this.eventType = eventType; }
public String getSourceIp() { return sourceIp; }
public void setSourceIp(String sourceIp) { this.sourceIp = sourceIp; }
public String getDestinationIp() { return destinationIp; }
public void setDestinationIp(String destinationIp) { this.destinationIp = destinationIp; }
public String getUsername() { return username; }
public void setUsername(String username) { this.username = username; }
public String getService() { return service; }
public void setService(String service) { this.service = service; }
public Instant getTimestamp() { return timestamp; }
public void setTimestamp(Instant timestamp) { this.timestamp = timestamp; }
public Map<String, Object> getDetails() { return details; }
public void addDetail(String key, Object value) { this.details.put(key, value); }
public int getSeverity() { return severity; }
public void setSeverity(int severity) { this.severity = severity; }
public boolean isSuspicious() { return suspicious; }
public void setSuspicious(boolean suspicious) { this.suspicious = suspicious; }
public String getCorrelationId() { return correlationId; }
public void setCorrelationId(String correlationId) { this.correlationId = correlationId; }
}
enum EventType {
// Authentication events
LOGIN_SUCCESS, LOGIN_FAILURE, LOGOUT, TOKEN_REFRESH,
// Network events
NETWORK_CONNECTION, PORT_SCAN, SERVICE_ACCESS,
// File system events
FILE_ACCESS, FILE_MODIFICATION, FILE_DELETION,
// Process events
PROCESS_CREATION, PROCESS_TERMINATION, PROCESS_INJECTION,
// Application events
API_CALL, DATABASE_QUERY, CONFIGURATION_CHANGE,
// Lateral movement indicators
REMOTE_EXECUTION, PASS_THE_HASH, TOKEN_IMPERSONATION,
SCHEDULED_TASK_CREATION, SERVICE_INSTALLATION
}
class LateralMovementAlert {
private String alertId;
private String title;
private String description;
private AlertSeverity severity;
private List<SecurityEvent> correlatedEvents = new ArrayList<>();
private Instant detectedAt;
private String attackerIp;
private String compromisedUser;
private List<String> techniques;
private double confidenceScore;
private AlertStatus status;
private Map<String, Object> mitigationSteps = new HashMap<>();
public LateralMovementAlert() {
this.alertId = "LMA-" + UUID.randomUUID().toString().substring(0, 8);
this.detectedAt = Instant.now();
this.status = AlertStatus.NEW;
}
// Getters and setters
public String getAlertId() { return alertId; }
public String getTitle() { return title; }
public void setTitle(String title) { this.title = title; }
public String getDescription() { return description; }
public void setDescription(String description) { this.description = description; }
public AlertSeverity getSeverity() { return severity; }
public void setSeverity(AlertSeverity severity) { this.severity = severity; }
public List<SecurityEvent> getCorrelatedEvents() { return correlatedEvents; }
public void addCorrelatedEvent(SecurityEvent event) { this.correlatedEvents.add(event); }
public Instant getDetectedAt() { return detectedAt; }
public String getAttackerIp() { return attackerIp; }
public void setAttackerIp(String attackerIp) { this.attackerIp = attackerIp; }
public String getCompromisedUser() { return compromisedUser; }
public void setCompromisedUser(String compromisedUser) { this.compromisedUser = compromisedUser; }
public List<String> getTechniques() { return techniques; }
public void setTechniques(List<String> techniques) { this.techniques = techniques; }
public double getConfidenceScore() { return confidenceScore; }
public void setConfidenceScore(double confidenceScore) { this.confidenceScore = confidenceScore; }
public AlertStatus getStatus() { return status; }
public void setStatus(AlertStatus status) { this.status = status; }
public Map<String, Object> getMitigationSteps() { return mitigationSteps; }
public void addMitigationStep(String step, Object details) { this.mitigationSteps.put(step, details); }
}
enum AlertSeverity {
LOW, MEDIUM, HIGH, CRITICAL
}
enum AlertStatus {
NEW, INVESTIGATING, MITIGATED, RESOLVED, FALSE_POSITIVE
}

3. Lateral Movement Detection Engine

Core Detection Service:

package com.example.security.lateral.detection;
import com.example.security.lateral.model.*;
import org.springframework.stereotype.Service;
import java.time.Duration;
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.stream.Collectors;
@Service
public class LateralMovementDetector {
private final Map<String, List<SecurityEvent>> eventStore = new ConcurrentHashMap<>();
private final Map<String, UserBehaviorProfile> userProfiles = new ConcurrentHashMap<>();
private final Map<String, HostBehaviorProfile> hostProfiles = new ConcurrentHashMap<>();
private final ScheduledExecutorService correlationScheduler;
private final List<DetectionRule> detectionRules;
// Alert thresholds
private static final int FAILED_LOGIN_THRESHOLD = 5;
private static final int UNUSUAL_SERVICE_THRESHOLD = 3;
private static final Duration CORRELATION_WINDOW = Duration.ofMinutes(30);
public LateralMovementDetector() {
this.correlationScheduler = Executors.newScheduledThreadPool(2);
this.detectionRules = initializeDetectionRules();
startCorrelationEngine();
}
public void processSecurityEvent(SecurityEvent event) {
// Store event for correlation
storeEvent(event);
// Update behavior profiles
updateBehaviorProfiles(event);
// Check immediate detection rules
checkImmediateDetectionRules(event);
}
private void storeEvent(SecurityEvent event) {
String key = getEventStoreKey(event);
eventStore.computeIfAbsent(key, k -> new ArrayList<>()).add(event);
// Clean old events
cleanOldEvents();
}
private String getEventStoreKey(SecurityEvent event) {
return event.getUsername() + "|" + event.getSourceIp();
}
private void cleanOldEvents() {
Instant cutoff = Instant.now().minus(CORRELATION_WINDOW);
eventStore.forEach((key, events) -> {
events.removeIf(event -> event.getTimestamp().isBefore(cutoff));
});
// Remove empty lists
eventStore.entrySet().removeIf(entry -> entry.getValue().isEmpty());
}
private void updateBehaviorProfiles(SecurityEvent event) {
// Update user behavior profile
UserBehaviorProfile userProfile = userProfiles.computeIfAbsent(
event.getUsername(), k -> new UserBehaviorProfile(event.getUsername()));
userProfile.updateProfile(event);
// Update host behavior profile
if (event.getSourceIp() != null) {
HostBehaviorProfile hostProfile = hostProfiles.computeIfAbsent(
event.getSourceIp(), k -> new HostBehaviorProfile(event.getSourceIp()));
hostProfile.updateProfile(event);
}
}
private void checkImmediateDetectionRules(SecurityEvent event) {
for (DetectionRule rule : detectionRules) {
if (rule.isImmediate() && rule.matches(event)) {
LateralMovementAlert alert = rule.generateAlert(event);
triggerAlert(alert);
}
}
}
private void startCorrelationEngine() {
correlationScheduler.scheduleAtFixedRate(this::runCorrelationRules, 
1, 1, TimeUnit.MINUTES);
}
private void runCorrelationRules() {
// Run correlation-based detection rules
for (DetectionRule rule : detectionRules) {
if (!rule.isImmediate()) {
List<LateralMovementAlert> alerts = rule.correlateEvents(eventStore);
alerts.forEach(this::triggerAlert);
}
}
// Check for behavioral anomalies
checkBehavioralAnomalies();
}
private void checkBehavioralAnomalies() {
// Check for unusual user behavior
userProfiles.values().forEach(this::checkUserAnomalies);
// Check for unusual host behavior
hostProfiles.values().forEach(this::checkHostAnomalies);
}
private void checkUserAnomalies(UserBehaviorProfile userProfile) {
// Failed login attempts
if (userProfile.getRecentFailedLogins() > FAILED_LOGIN_THRESHOLD) {
createAlertForFailedLogins(userProfile);
}
// Unusual service access
if (userProfile.getUnusualServiceAccessCount() > UNUSUAL_SERVICE_THRESHOLD) {
createAlertForUnusualServiceAccess(userProfile);
}
// Geographic anomalies
if (userProfile.hasGeographicAnomaly()) {
createAlertForGeographicAnomaly(userProfile);
}
// Time-based anomalies
if (userProfile.hasTimeAnomaly()) {
createAlertForTimeAnomaly(userProfile);
}
}
private void checkHostAnomalies(HostBehaviorProfile hostProfile) {
// Port scanning detection
if (hostProfile.isPortScanningDetected()) {
createAlertForPortScanning(hostProfile);
}
// Multiple service connections
if (hostProfile.hasMultipleServiceConnections()) {
createAlertForMultipleServices(hostProfile);
}
}
private void createAlertForFailedLogins(UserBehaviorProfile userProfile) {
LateralMovementAlert alert = new LateralMovementAlert();
alert.setTitle("Multiple Failed Login Attempts");
alert.setDescription(String.format(
"User %s has %d failed login attempts in the last %s",
userProfile.getUsername(),
userProfile.getRecentFailedLogins(),
CORRELATION_WINDOW
));
alert.setSeverity(AlertSeverity.HIGH);
alert.setCompromisedUser(userProfile.getUsername());
alert.setTechniques(List.of("Brute Force", "Password Spraying"));
alert.setConfidenceScore(0.85);
triggerAlert(alert);
}
private void createAlertForUnusualServiceAccess(UserBehaviorProfile userProfile) {
LateralMovementAlert alert = new LateralMovementAlert();
alert.setTitle("Unusual Service Access Pattern");
alert.setDescription(String.format(
"User %s accessed %d unusual services",
userProfile.getUsername(),
userProfile.getUnusualServiceAccessCount()
));
alert.setSeverity(AlertSeverity.MEDIUM);
alert.setCompromisedUser(userProfile.getUsername());
alert.setTechniques(List.of("Service Discovery", "Lateral Movement"));
alert.setConfidenceScore(0.70);
triggerAlert(alert);
}
private void createAlertForGeographicAnomaly(UserBehaviorProfile userProfile) {
LateralMovementAlert alert = new LateralMovementAlert();
alert.setTitle("Geographic Login Anomaly");
alert.setDescription(String.format(
"User %s logged in from unusual location: %s",
userProfile.getUsername(),
userProfile.getLastLocation()
));
alert.setSeverity(AlertSeverity.HIGH);
alert.setCompromisedUser(userProfile.getUsername());
alert.setTechniques(List.of("Account Compromise", "Credential Theft"));
alert.setConfidenceScore(0.90);
triggerAlert(alert);
}
private void createAlertForTimeAnomaly(UserBehaviorProfile userProfile) {
LateralMovementAlert alert = new LateralMovementAlert();
alert.setTitle("Unusual Login Time");
alert.setDescription(String.format(
"User %s logged in during unusual hours",
userProfile.getUsername()
));
alert.setSeverity(AlertSeverity.MEDIUM);
alert.setCompromisedUser(userProfile.getUsername());
alert.setTechniques(List.of("Account Compromise"));
alert.setConfidenceScore(0.65);
triggerAlert(alert);
}
private void createAlertForPortScanning(HostBehaviorProfile hostProfile) {
LateralMovementAlert alert = new LateralMovementAlert();
alert.setTitle("Port Scanning Detected");
alert.setDescription(String.format(
"Host %s is scanning multiple ports",
hostProfile.getHostIp()
));
alert.setSeverity(AlertSeverity.HIGH);
alert.setAttackerIp(hostProfile.getHostIp());
alert.setTechniques(List.of("Network Scanning", "Reconnaissance"));
alert.setConfidenceScore(0.80);
triggerAlert(alert);
}
private void createAlertForMultipleServices(HostBehaviorProfile hostProfile) {
LateralMovementAlert alert = new LateralMovementAlert();
alert.setTitle("Multiple Service Connections");
alert.setDescription(String.format(
"Host %s connected to %d different services",
hostProfile.getHostIp(),
hostProfile.getServiceConnectionCount()
));
alert.setSeverity(AlertSeverity.MEDIUM);
alert.setAttackerIp(hostProfile.getHostIp());
alert.setTechniques(List.of("Lateral Movement", "Service Enumeration"));
alert.setConfidenceScore(0.75);
triggerAlert(alert);
}
private void triggerAlert(LateralMovementAlert alert) {
// Send to alert processor
AlertProcessor.processAlert(alert);
// Log the alert
System.err.println("LATERAL MOVEMENT ALERT: " + alert.getTitle());
System.err.println("Description: " + alert.getDescription());
System.err.println("Severity: " + alert.getSeverity());
System.err.println("Confidence: " + alert.getConfidenceScore());
}
private List<DetectionRule> initializeDetectionRules() {
List<DetectionRule> rules = new ArrayList<>();
// Immediate detection rules
rules.add(new FailedLoginRule());
rules.add(new UnusualServiceAccessRule());
rules.add(new TokenTheftRule());
rules.add(new RemoteExecutionRule());
// Correlation rules
rules.add(new PassTheHashRule());
rules.add(new GoldenTicketRule());
rules.add(new LateralMovementChainRule());
return rules;
}
@PreDestroy
public void cleanup() {
correlationScheduler.shutdown();
}
}
class UserBehaviorProfile {
private String username;
private List<String> usualServices = new ArrayList<>();
private Map<String, Integer> serviceAccessCount = new HashMap<>();
private List<String> usualLocations = new ArrayList<>();
private List<Integer> usualLoginHours = new ArrayList<>();
private int failedLoginCount;
private Instant lastLogin;
private String lastLocation;
public UserBehaviorProfile(String username) {
this.username = username;
}
public void updateProfile(SecurityEvent event) {
switch (event.getEventType()) {
case LOGIN_SUCCESS:
updateLoginBehavior(event);
break;
case LOGIN_FAILURE:
failedLoginCount++;
break;
case SERVICE_ACCESS:
updateServiceAccess(event);
break;
}
}
private void updateLoginBehavior(SecurityEvent event) {
lastLogin = event.getTimestamp();
lastLocation = event.getSourceIp();
// Update usual login hours
int loginHour = event.getTimestamp().atZone(java.time.ZoneId.systemDefault()).getHour();
if (!usualLoginHours.contains(loginHour)) {
usualLoginHours.add(loginHour);
}
// Update usual locations
if (!usualLocations.contains(event.getSourceIp())) {
usualLocations.add(event.getSourceIp());
}
}
private void updateServiceAccess(SecurityEvent event) {
String service = event.getService();
serviceAccessCount.put(service, serviceAccessCount.getOrDefault(service, 0) + 1);
if (!usualServices.contains(service)) {
usualServices.add(service);
}
}
public int getRecentFailedLogins() {
// Reset counter periodically (implementation depends on time window)
return failedLoginCount;
}
public int getUnusualServiceAccessCount() {
return (int) serviceAccessCount.entrySet().stream()
.filter(entry -> !usualServices.contains(entry.getKey()))
.count();
}
public boolean hasGeographicAnomaly() {
return lastLocation != null && !usualLocations.contains(lastLocation);
}
public boolean hasTimeAnomaly() {
if (lastLogin == null) return false;
int loginHour = lastLogin.atZone(java.time.ZoneId.systemDefault()).getHour();
return !usualLoginHours.contains(loginHour);
}
// Getters
public String getUsername() { return username; }
public String getLastLocation() { return lastLocation; }
}
class HostBehaviorProfile {
private String hostIp;
private Set<Integer> scannedPorts = new HashSet<>();
private Set<String> connectedServices = new HashSet<>();
private Instant lastActivity;
public HostBehaviorProfile(String hostIp) {
this.hostIp = hostIp;
}
public void updateProfile(SecurityEvent event) {
lastActivity = event.getTimestamp();
if (event.getEventType() == EventType.NETWORK_CONNECTION) {
Integer port = (Integer) event.getDetails().get("destinationPort");
if (port != null) {
scannedPorts.add(port);
}
}
if (event.getEventType() == EventType.SERVICE_ACCESS) {
connectedServices.add(event.getService());
}
}
public boolean isPortScanningDetected() {
return scannedPorts.size() > 10; // Threshold for port scanning
}
public boolean hasMultipleServiceConnections() {
return connectedServices.size() > 5; // Threshold for multiple services
}
public int getServiceConnectionCount() {
return connectedServices.size();
}
// Getters
public String getHostIp() { return hostIp; }
}

4. Detection Rules Implementation

Detection Rules Framework:

package com.example.security.lateral.detection;
import com.example.security.lateral.model.*;
import java.util.List;
import java.util.Map;
public abstract class DetectionRule {
protected String ruleId;
protected String ruleName;
protected boolean immediate;
protected double baseConfidence;
public abstract boolean matches(SecurityEvent event);
public abstract List<LateralMovementAlert> correlateEvents(Map<String, List<SecurityEvent>> eventStore);
public abstract LateralMovementAlert generateAlert(SecurityEvent event);
// Getters
public String getRuleId() { return ruleId; }
public String getRuleName() { return ruleName; }
public boolean isImmediate() { return immediate; }
public double getBaseConfidence() { return baseConfidence; }
}
// Concrete rule implementations
class FailedLoginRule extends DetectionRule {
public FailedLoginRule() {
this.ruleId = "RULE-001";
this.ruleName = "Failed Login Attempts";
this.immediate = true;
this.baseConfidence = 0.7;
}
@Override
public boolean matches(SecurityEvent event) {
return event.getEventType() == EventType.LOGIN_FAILURE;
}
@Override
public List<LateralMovementAlert> correlateEvents(Map<String, List<SecurityEvent>> eventStore) {
// This rule doesn't use correlation
return List.of();
}
@Override
public LateralMovementAlert generateAlert(SecurityEvent event) {
LateralMovementAlert alert = new LateralMovementAlert();
alert.setTitle("Failed Login Attempt");
alert.setDescription(String.format(
"Failed login for user %s from IP %s",
event.getUsername(), event.getSourceIp()
));
alert.setSeverity(AlertSeverity.MEDIUM);
alert.setConfidenceScore(baseConfidence);
alert.setTechniques(List.of("Brute Force", "Credential Stuffing"));
return alert;
}
}
class UnusualServiceAccessRule extends DetectionRule {
public UnusualServiceAccessRule() {
this.ruleId = "RULE-002";
this.ruleName = "Unusual Service Access";
this.immediate = true;
this.baseConfidence = 0.6;
}
@Override
public boolean matches(SecurityEvent event) {
if (event.getEventType() != EventType.SERVICE_ACCESS) return false;
// Check if service is unusual for this user
String service = event.getService();
return isUnusualService(service, event.getUsername());
}
private boolean isUnusualService(String service, String username) {
// Implement service whitelist/blacklist logic
List<String> unusualServices = List.of("LDAP", "RDP", "SSH", "SMB", "WMI");
return unusualServices.contains(service);
}
@Override
public List<LateralMovementAlert> correlateEvents(Map<String, List<SecurityEvent>> eventStore) {
return List.of();
}
@Override
public LateralMovementAlert generateAlert(SecurityEvent event) {
LateralMovementAlert alert = new LateralMovementAlert();
alert.setTitle("Unusual Service Access");
alert.setDescription(String.format(
"User %s accessed unusual service: %s",
event.getUsername(), event.getService()
));
alert.setSeverity(AlertSeverity.MEDIUM);
alert.setConfidenceScore(baseConfidence);
alert.setTechniques(List.of("Lateral Movement", "Service Discovery"));
return alert;
}
}
class PassTheHashRule extends DetectionRule {
public PassTheHashRule() {
this.ruleId = "RULE-101";
this.ruleName = "Pass-the-Hash Detection";
this.immediate = false;
this.baseConfidence = 0.9;
}
@Override
public boolean matches(SecurityEvent event) {
return false; // This is a correlation-only rule
}
@Override
public List<LateralMovementAlert> correlateEvents(Map<String, List<SecurityEvent>> eventStore) {
// Look for patterns indicative of Pass-the-Hash attacks
// Multiple systems accessed with the same credentials in short time
// Unusual authentication patterns
List<LateralMovementAlert> alerts = new ArrayList<>();
eventStore.forEach((key, events) -> {
if (isPassTheHashPattern(events)) {
LateralMovementAlert alert = createPassTheHashAlert(events);
alerts.add(alert);
}
});
return alerts;
}
private boolean isPassTheHashPattern(List<SecurityEvent> events) {
// Implement PTH detection logic
long uniqueHosts = events.stream()
.filter(e -> e.getEventType() == EventType.LOGIN_SUCCESS)
.map(SecurityEvent::getDestinationIp)
.distinct()
.count();
return uniqueHosts >= 3; // Accessed 3+ different hosts in short time
}
private LateralMovementAlert createPassTheHashAlert(List<SecurityEvent> events) {
LateralMovementAlert alert = new LateralMovementAlert();
alert.setTitle("Pass-the-Hash Attack Detected");
alert.setDescription("Multiple systems accessed with same credentials in short timeframe");
alert.setSeverity(AlertSeverity.CRITICAL);
alert.setConfidenceScore(baseConfidence);
alert.setTechniques(List.of("Pass-the-Hash", "Lateral Movement"));
events.forEach(alert::addCorrelatedEvent);
return alert;
}
@Override
public LateralMovementAlert generateAlert(SecurityEvent event) {
return null; // Not used for immediate alerts
}
}
class LateralMovementChainRule extends DetectionRule {
public LateralMovementChainRule() {
this.ruleId = "RULE-102";
this.ruleName = "Lateral Movement Chain";
this.immediate = false;
this.baseConfidence = 0.85;
}
@Override
public boolean matches(SecurityEvent event) {
return false;
}
@Override
public List<LateralMovementAlert> correlateEvents(Map<String, List<SecurityEvent>> eventStore) {
List<LateralMovementAlert> alerts = new ArrayList<>();
// Look for chains of suspicious activities
eventStore.forEach((key, events) -> {
if (isLateralMovementChain(events)) {
LateralMovementAlert alert = createLateralMovementAlert(events);
alerts.add(alert);
}
});
return alerts;
}
private boolean isLateralMovementChain(List<SecurityEvent> events) {
// Check for reconnaissance -> credential access -> lateral movement pattern
boolean hasRecon = events.stream().anyMatch(e -> 
e.getEventType() == EventType.NETWORK_CONNECTION && 
e.getDetails().containsKey("portScan"));
boolean hasCredentialAccess = events.stream().anyMatch(e -> 
e.getEventType() == EventType.LOGIN_FAILURE ||
e.getEventType() == EventType.TOKEN_REFRESH);
boolean hasLateralMovement = events.stream().anyMatch(e -> 
e.getEventType() == EventType.REMOTE_EXECUTION ||
e.getEventType() == EventType.SERVICE_ACCESS);
return hasRecon && hasCredentialAccess && hasLateralMovement;
}
private LateralMovementAlert createLateralMovementAlert(List<SecurityEvent> events) {
LateralMovementAlert alert = new LateralMovementAlert();
alert.setTitle("Lateral Movement Chain Detected");
alert.setDescription("Reconnaissance -> Credential Access -> Lateral Movement pattern detected");
alert.setSeverity(AlertSeverity.HIGH);
alert.setConfidenceScore(baseConfidence);
alert.setTechniques(List.of("Network Scanning", "Credential Access", "Lateral Movement"));
events.forEach(alert::addCorrelatedEvent);
return alert;
}
@Override
public LateralMovementAlert generateAlert(SecurityEvent event) {
return null;
}
}

5. Alert Processing and Response

Alert Processor:

package com.example.security.lateral.alert;
import com.example.security.lateral.model.LateralMovementAlert;
import org.springframework.kafka.core.KafkaTemplate;
import org.springframework.stereotype.Service;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
@Service
public class AlertProcessor {
private final BlockingQueue<LateralMovementAlert> alertQueue = new LinkedBlockingQueue<>();
private final ScheduledExecutorService alertScheduler;
private final KafkaTemplate<String, Object> kafkaTemplate;
private final AlertNotifier alertNotifier;
private final ThreatIntelligenceService threatIntelligence;
public AlertProcessor(KafkaTemplate<String, Object> kafkaTemplate,
AlertNotifier alertNotifier,
ThreatIntelligenceService threatIntelligence) {
this.kafkaTemplate = kafkaTemplate;
this.alertNotifier = alertNotifier;
this.threatIntelligence = threatIntelligence;
this.alertScheduler = Executors.newScheduledThreadPool(2);
startAlertProcessing();
}
public static void processAlert(LateralMovementAlert alert) {
// Static method for easy access from detectors
AlertProcessor instance = getInstance(); // Would need proper dependency injection
instance.queueAlert(alert);
}
private void queueAlert(LateralMovementAlert alert) {
try {
alertQueue.put(alert);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
System.err.println("Failed to queue alert: " + e.getMessage());
}
}
private void startAlertProcessing() {
// Process alerts from queue
alertScheduler.scheduleAtFixedRate(this::processAlertQueue, 0, 1, TimeUnit.SECONDS);
// Aggregate and analyze alerts
alertScheduler.scheduleAtFixedRate(this::analyzeAlertPatterns, 0, 5, TimeUnit.MINUTES);
}
private void processAlertQueue() {
while (!alertQueue.isEmpty()) {
try {
LateralMovementAlert alert = alertQueue.take();
processSingleAlert(alert);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
break;
}
}
}
private void processSingleAlert(LateralMovementAlert alert) {
try {
// Enrich with threat intelligence
enrichWithThreatIntelligence(alert);
// Calculate risk score
calculateRiskScore(alert);
// Send to Kafka for further processing
sendToKafka(alert);
// Notify security team
notifySecurityTeam(alert);
// Take automated response actions for critical alerts
if (alert.getSeverity() == AlertSeverity.CRITICAL) {
takeAutomatedResponse(alert);
}
// Log the alert
logAlert(alert);
} catch (Exception e) {
System.err.println("Failed to process alert: " + e.getMessage());
}
}
private void enrichWithThreatIntelligence(LateralMovementAlert alert) {
// Check if source IP is known malicious
if (threatIntelligence.isMaliciousIp(alert.getAttackerIp())) {
alert.setConfidenceScore(Math.min(1.0, alert.getConfidenceScore() + 0.2));
alert.addMitigationStep("block_ip", "Source IP is known malicious");
}
// Check if techniques match known attack patterns
if (threatIntelligence.isKnownAttackPattern(alert.getTechniques())) {
alert.setConfidenceScore(Math.min(1.0, alert.getConfidenceScore() + 0.15));
}
}
private void calculateRiskScore(LateralMovementAlert alert) {
double riskScore = alert.getConfidenceScore();
// Adjust based on severity
switch (alert.getSeverity()) {
case CRITICAL:
riskScore *= 1.5;
break;
case HIGH:
riskScore *= 1.2;
break;
case MEDIUM:
riskScore *= 1.0;
break;
case LOW:
riskScore *= 0.8;
break;
}
// Adjust based on number of correlated events
riskScore *= Math.min(1.0, 1.0 + (alert.getCorrelatedEvents().size() * 0.1));
alert.addDetail("calculatedRiskScore", Math.min(1.0, riskScore));
}
private void sendToKafka(LateralMovementAlert alert) {
kafkaTemplate.send("lateral-movement-alerts", alert.getAlertId(), alert);
}
private void notifySecurityTeam(LateralMovementAlert alert) {
alertNotifier.notifySecurityTeam(alert);
}
private void takeAutomatedResponse(LateralMovementAlert alert) {
// Implement automated response actions
System.err.println("AUTOMATED RESPONSE for critical alert: " + alert.getAlertId());
// Example actions:
// - Block source IP in firewall
// - Disable compromised user account
// - Isolate affected systems
// - Increase logging levels
}
private void logAlert(LateralMovementAlert alert) {
// Log to security information and event management (SIEM)
System.err.println("SECURITY ALERT LOGGED: " + alert.getAlertId());
}
private void analyzeAlertPatterns() {
// Analyze alert patterns for advanced threat detection
// This could identify coordinated attacks or advanced persistent threats
}
@PreDestroy
public void cleanup() {
alertScheduler.shutdown();
}
// Singleton pattern for static access (simplified)
private static AlertProcessor instance;
private static AlertProcessor getInstance() {
// In real implementation, use proper dependency injection
return instance;
}
}

6. Integration with Security Tools

Kafka Configuration for Event Streaming:

package com.example.security.config;
import org.apache.kafka.clients.producer.ProducerConfig;
import org.apache.kafka.common.serialization.StringSerializer;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.kafka.core.DefaultKafkaProducerFactory;
import org.springframework.kafka.core.KafkaTemplate;
import org.springframework.kafka.core.ProducerFactory;
import org.springframework.kafka.support.serializer.JsonSerializer;
import java.util.HashMap;
import java.util.Map;
@Configuration
public class KafkaConfig {
@Bean
public ProducerFactory<String, Object> producerFactory() {
Map<String, Object> configProps = new HashMap<>();
configProps.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG, "localhost:9092");
configProps.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, StringSerializer.class);
configProps.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG, JsonSerializer.class);
configProps.put(ProducerConfig.ACKS_CONFIG, "all");
configProps.put(ProducerConfig.RETRIES_CONFIG, 3);
return new DefaultKafkaProducerFactory<>(configProps);
}
@Bean
public KafkaTemplate<String, Object> kafkaTemplate() {
return new KafkaTemplate<>(producerFactory());
}
}

Elasticsearch Integration for Log Storage:

package com.example.security.integration.elasticsearch;
import com.example.security.lateral.model.LateralMovementAlert;
import org.springframework.data.elasticsearch.repository.ElasticsearchRepository;
import org.springframework.stereotype.Repository;
import java.util.List;
@Repository
public interface AlertRepository extends ElasticsearchRepository<LateralMovementAlert, String> {
List<LateralMovementAlert> findBySeverity(String severity);
List<LateralMovementAlert> findByStatus(String status);
List<LateralMovementAlert> findByAttackerIp(String attackerIp);
List<LateralMovementAlert> findByCompromisedUser(String compromisedUser);
@Query("{\"bool\": {\"must\": [{\"range\": {\"detectedAt\": {\"gte\": \"?0\"}}}]}}")
List<LateralMovementAlert> findRecentAlerts(String fromDate);
}

7. Real-time Dashboard and Monitoring

Security Dashboard Controller:

package com.example.security.controller;
import com.example.security.lateral.model.LateralMovementAlert;
import com.example.security.integration.elasticsearch.AlertRepository;
import org.springframework.web.bind.annotation.*;
import java.time.Instant;
import java.time.temporal.ChronoUnit;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@RestController
@RequestMapping("/api/security")
public class SecurityDashboardController {
private final AlertRepository alertRepository;
public SecurityDashboardController(AlertRepository alertRepository) {
this.alertRepository = alertRepository;
}
@GetMapping("/alerts/recent")
public List<LateralMovementAlert> getRecentAlerts() {
String fromDate = Instant.now().minus(24, ChronoUnit.HOURS).toString();
return alertRepository.findRecentAlerts(fromDate);
}
@GetMapping("/alerts/statistics")
public Map<String, Object> getAlertStatistics() {
Map<String, Object> stats = new HashMap<>();
List<LateralMovementAlert> recentAlerts = getRecentAlerts();
stats.put("totalAlerts", recentAlerts.size());
stats.put("criticalAlerts", recentAlerts.stream()
.filter(a -> a.getSeverity() == AlertSeverity.CRITICAL).count());
stats.put("highAlerts", recentAlerts.stream()
.filter(a -> a.getSeverity() == AlertSeverity.HIGH).count());
stats.put("activeInvestigations", recentAlerts.stream()
.filter(a -> a.getStatus() == AlertStatus.INVESTIGATING).count());
return stats;
}
@PostMapping("/alerts/{alertId}/status")
public void updateAlertStatus(@PathVariable String alertId, 
@RequestBody StatusUpdateRequest request) {
LateralMovementAlert alert = alertRepository.findById(alertId).orElse(null);
if (alert != null) {
alert.setStatus(request.getStatus());
alertRepository.save(alert);
}
}
@GetMapping("/alerts/techniques")
public Map<String, Long> getTechniqueStatistics() {
List<LateralMovementAlert> recentAlerts = getRecentAlerts();
return recentAlerts.stream()
.flatMap(alert -> alert.getTechniques().stream())
.collect(Collectors.groupingBy(technique -> technique, Collectors.counting()));
}
}
class StatusUpdateRequest {
private AlertStatus status;
public AlertStatus getStatus() { return status; }
public void setStatus(AlertStatus status) { this.status = status; }
}

8. MITRE ATT&CK Integration

MITRE ATT&CK Framework Mapping:

package com.example.security.mitre;
import java.util.*;
public class MitreAttackMapper {
private static final Map<String, MitreTechnique> TECHNIQUE_MAP = new HashMap<>();
static {
// Lateral Movement Techniques
TECHNIQUE_MAP.put("Remote Desktop Protocol", new MitreTechnique("T1021.001", "Remote Desktop Protocol"));
TECHNIQUE_MAP.put("SMB/Windows Admin Shares", new MitreTechnique("T1021.002", "SMB/Windows Admin Shares"));
TECHNIQUE_MAP.put("Distributed Component Object Model", new MitreTechnique("T1021.003", "Distributed Component Object Model"));
TECHNIQUE_MAP.put("SSH", new MitreTechnique("T1021.004", "SSH"));
TECHNIQUE_MAP.put("VNC", new MitreTechnique("T1021.005", "VNC"));
TECHNIQUE_MAP.put("Windows Remote Management", new MitreTechnique("T1021.006", "Windows Remote Management"));
// Credential Access Techniques
TECHNIQUE_MAP.put("OS Credential Dumping", new MitreTechnique("T1003", "OS Credential Dumping"));
TECHNIQUE_MAP.put("Pass the Hash", new MitreTechnique("T1550.002", "Pass the Hash"));
TECHNIQUE_MAP.put("Pass the Ticket", new MitreTechnique("T1550.003", "Pass the Ticket"));
TECHNIQUE_MAP.put("Kerberoasting", new MitreTechnique("T1558.003", "Kerberoasting"));
// Discovery Techniques
TECHNIQUE_MAP.put("Network Service Scanning", new MitreTechnique("T1046", "Network Service Scanning"));
TECHNIQUE_MAP.put("Network Share Discovery", new MitreTechnique("T1135", "Network Share Discovery"));
TECHNIQUE_MAP.put("Remote System Discovery", new MitreTechnique("T1018", "Remote System Discovery"));
}
public static List<MitreTechnique> mapToMitreTechniques(List<String> techniques) {
return techniques.stream()
.map(TECHNIQUE_MAP::get)
.filter(Objects::nonNull)
.collect(Collectors.toList());
}
public static String getMitreTactic(String techniqueId) {
// Map technique IDs to MITRE tactics
Map<String, String> tacticMap = Map.of(
"T1021", "Lateral Movement",
"T1550", "Lateral Movement", 
"T1003", "Credential Access",
"T1046", "Discovery",
"T1018", "Discovery"
);
String baseId = techniqueId.split("\\.")[0];
return tacticMap.getOrDefault(baseId, "Unknown");
}
}
class MitreTechnique {
private String id;
private String name;
private String description;
private String url;
public MitreTechnique(String id, String name) {
this.id = id;
this.name = name;
this.url = "https://attack.mitre.org/techniques/" + id.replace(".", "/");
}
// Getters
public String getId() { return id; }
public String getName() { return name; }
public String getDescription() { return description; }
public void setDescription(String description) { this.description = description; }
public String getUrl() { return url; }
}

9. Response Playbooks

Automated Response Service:

package com.example.security.response;
import com.example.security.lateral.model.LateralMovementAlert;
import org.springframework.stereotype.Service;
import java.util.*;
@Service
public class ResponsePlaybookService {
private final Map<String, ResponsePlaybook> playbooks = new HashMap<>();
public ResponsePlaybookService() {
initializePlaybooks();
}
private void initializePlaybooks() {
// Pass-the-Hash response playbook
ResponsePlaybook pthPlaybook = new ResponsePlaybook();
pthPlaybook.setId("PLAYBOOK-PTH");
pthPlaybook.setName("Pass-the-Hash Attack Response");
pthPlaybook.setDescription("Standard response procedure for PTH attacks");
pthPlaybook.setSteps(Arrays.asList(
"1. Immediately reset passwords for compromised accounts",
"2. Block source IP addresses in firewall",
"3. Check for persistence mechanisms on compromised systems",
"4. Review authentication logs for additional compromised accounts",
"5. Isolate affected systems from network",
"6. Conduct forensic analysis on compromised hosts"
));
pthPlaybook.setAutomatedActions(Arrays.asList("block_ip", "disable_user"));
playbooks.put("Pass-the-Hash", pthPlaybook);
// Lateral Movement response playbook
ResponsePlaybook lmPlaybook = new ResponsePlaybook();
lmPlaybook.setId("PLAYBOOK-LM");
lmPlaybook.setName("Lateral Movement Response");
lmPlaybook.setDescription("Response to detected lateral movement activities");
lmPlaybook.setSteps(Arrays.asList(
"1. Identify all compromised systems in the movement chain",
"2. Isolate affected systems from production network",
"3. Review and update access controls",
"4. Check for data exfiltration attempts",
"5. Conduct threat hunting for additional compromises",
"6. Implement additional monitoring on critical systems"
));
lmPlaybook.setAutomatedActions(Arrays.asList("isolate_hosts", "increase_monitoring"));
playbooks.put("Lateral Movement", lmPlaybook);
}
public ResponsePlaybook getPlaybookForAlert(LateralMovementAlert alert) {
// Find the most appropriate playbook based on alert techniques
for (String technique : alert.getTechniques()) {
if (playbooks.containsKey(technique)) {
return playbooks.get(technique);
}
}
// Default playbook for lateral movement
return playbooks.get("Lateral Movement");
}
public void executeAutomatedActions(ResponsePlaybook playbook, LateralMovementAlert alert) {
for (String action : playbook.getAutomatedActions()) {
executeAutomatedAction(action, alert);
}
}
private void executeAutomatedAction(String action, LateralMovementAlert alert) {
switch (action) {
case "block_ip":
blockIpAddress(alert.getAttackerIp());
break;
case "disable_user":
disableUserAccount(alert.getCompromisedUser());
break;
case "isolate_hosts":
isolateCompromisedHosts(alert);
break;
case "increase_monitoring":
increaseMonitoring(alert);
break;
}
}
private void blockIpAddress(String ipAddress) {
// Implement IP blocking logic
System.err.println("Blocking IP address: " + ipAddress);
}
private void disableUserAccount(String username) {
// Implement user account disabling
System.err.println("Disabling user account: " + username);
}
private void isolateCompromisedHosts(LateralMovementAlert alert) {
// Implement host isolation logic
System.err.println("Isolating compromised hosts for alert: " + alert.getAlertId());
}
private void increaseMonitoring(LateralMovementAlert alert) {
// Implement increased monitoring
System.err.println("Increasing monitoring for alert: " + alert.getAlertId());
}
}
class ResponsePlaybook {
private String id;
private String name;
private String description;
private List<String> steps = new ArrayList<>();
private List<String> automatedActions = new ArrayList<>();
// Getters and setters
public String getId() { return id; }
public void setId(String id) { this.id = id; }
public String getName() { return name; }
public void setName(String name) { this.name = name; }
public String getDescription() { return description; }
public void setDescription(String description) { this.description = description; }
public List<String> getSteps() { return steps; }
public void setSteps(List<String> steps) { this.steps = steps; }
public List<String> getAutomatedActions() { return automatedActions; }
public void setAutomatedActions(List<String> automatedActions) { this.automatedActions = automatedActions; }
}

10. Testing and Validation

Detection Rule Testing:

package com.example.security.test;
import com.example.security.lateral.detection.*;
import com.example.security.lateral.model.*;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
import java.time.Instant;
import java.util.*;
@SpringBootTest
public class LateralMovementDetectionTest {
@Test
public void testPassTheHashDetection() {
PassTheHashRule rule = new PassTheHashRule();
Map<String, List<SecurityEvent>> eventStore = new HashMap<>();
// Create simulated PTH pattern
List<SecurityEvent> events = new ArrayList<>();
String username = "testuser";
String sourceIp = "192.168.1.100";
// Multiple successful logins to different hosts in short time
for (int i = 0; i < 5; i++) {
SecurityEvent event = new SecurityEvent();
event.setEventType(EventType.LOGIN_SUCCESS);
event.setUsername(username);
event.setSourceIp(sourceIp);
event.setDestinationIp("192.168.1." + (10 + i));
event.setTimestamp(Instant.now().minusSeconds(i * 60)); // 1 minute apart
events.add(event);
}
eventStore.put(username + "|" + sourceIp, events);
List<LateralMovementAlert> alerts = rule.correlateEvents(eventStore);
assert !alerts.isEmpty() : "Should detect PTH pattern";
assert alerts.get(0).getTechniques().contains("Pass-the-Hash") : "Should identify PTH technique";
}
@Test
public void testFailedLoginRule() {
FailedLoginRule rule = new FailedLoginRule();
SecurityEvent event = new SecurityEvent();
event.setEventType(EventType.LOGIN_FAILURE);
event.setUsername("testuser");
event.setSourceIp("192.168.1.100");
boolean matches = rule.matches(event);
assert matches : "Should match failed login events";
LateralMovementAlert alert = rule.generateAlert(event);
assert alert != null : "Should generate alert for failed login";
assert alert.getSeverity() == AlertSeverity.MEDIUM : "Should have medium severity";
}
}

Conclusion

Lateral movement detection in Java provides:

  1. Real-time Monitoring: Continuous security event analysis
  2. Behavioral Analysis: User and host behavior profiling
  3. Pattern Recognition: Correlation of suspicious activities
  4. Automated Response: Immediate action for critical threats
  5. MITRE ATT&CK Integration: Industry-standard framework mapping

Key Detection Capabilities:

  • Credential-based attacks (Pass-the-Hash, Golden Ticket)
  • Remote execution and service abuse
  • Network reconnaissance and scanning
  • Behavioral anomalies and unusual access patterns
  • Attack chain correlation and analysis

By implementing comprehensive lateral movement detection, organizations can significantly reduce their attack surface and detect threats before they cause significant damage to the network infrastructure.

Advanced Java Supply Chain Security, Kubernetes Hardening & Runtime Threat Detection

Sigstore Rekor in Java – https://macronepal.com/blog/sigstore-rekor-in-java/
Explains integrating Sigstore Rekor into Java systems to create a transparent, tamper-proof log of software signatures and metadata for verifying supply chain integrity.

Securing Java Applications with Chainguard Wolfi – https://macronepal.com/blog/securing-java-applications-with-chainguard-wolfi-a-comprehensive-guide/
Explains using Chainguard Wolfi minimal container images to reduce vulnerabilities and secure Java applications with hardened, lightweight runtime environments.

Cosign Image Signing in Java Complete Guide – https://macronepal.com/blog/cosign-image-signing-in-java-complete-guide/
Explains how to digitally sign container images using Cosign in Java-based workflows to ensure authenticity and prevent unauthorized modifications.

Secure Supply Chain Enforcement Kyverno Image Verification for Java Containers – https://macronepal.com/blog/secure-supply-chain-enforcement-kyverno-image-verification-for-java-containers/
Explains enforcing Kubernetes policies with Kyverno to verify container image signatures and ensure only trusted Java container images are deployed.

Pod Security Admission in Java Securing Kubernetes Deployments for JVM Applications – https://macronepal.com/blog/pod-security-admission-in-java-securing-kubernetes-deployments-for-jvm-applications/
Explains Kubernetes Pod Security Admission policies that enforce security rules like restricted privileges and safe configurations for Java workloads.

Securing Java Applications at Runtime Kubernetes Security Context – https://macronepal.com/blog/securing-java-applications-at-runtime-a-guide-to-kubernetes-security-context/
Explains how Kubernetes security contexts control runtime permissions, user IDs, and access rights for Java containers to improve isolation.

Process Anomaly Detection in Java Behavioral Monitoring – https://macronepal.com/blog/process-anomaly-detection-in-java-comprehensive-behavioral-monitoring-2/
Explains detecting abnormal runtime behavior in Java applications to identify potential security threats using process monitoring techniques.

Achieving Security Excellence CIS Benchmark Compliance for Java Applications – https://macronepal.com/blog/achieving-security-excellence-implementing-cis-benchmark-compliance-for-java-applications/
Explains applying CIS security benchmarks to Java environments to standardize hardening and improve overall system security posture.

Process Anomaly Detection in Java Behavioral Monitoring – https://macronepal.com/blog/process-anomaly-detection-in-java-comprehensive-behavioral-monitoring/
Explains behavioral monitoring of Java processes to detect anomalies and improve runtime security through continuous observation and analysis.

Leave a Reply

Your email address will not be published. Required fields are marked *


Macro Nepal Helper