Runtime Threat Detection: Securing Java Applications with Sysdig


Article

In cloud-native environments, Java applications face evolving security threats that traditional perimeter defenses can't catch. Sysdig Runtime Security provides deep container visibility and real-time threat detection by leveraging system calls, network activity, and behavioral monitoring. For Java development teams, integrating Sysdig means moving beyond static security analysis to actively protecting running applications against zero-day attacks, suspicious process activity, and container escape attempts.

What is Sysdig Runtime Security?

Sysdig is a unified container security platform that provides:

  • Runtime Threat Detection: Real-time monitoring of system calls and container behavior
  • Forensic Analysis: Capture and inspect system activity with sysdig and csysdig
  • Falco Integration: Open-source cloud-native security tool for rule-based alerting
  • Network Security: Monitor container network traffic and connections
  • Compliance Monitoring: Ensure compliance with security benchmarks and policies

Why Runtime Security for Java Applications?

  1. Zero-Day Protection: Detect unknown vulnerabilities and attacks in real-time
  2. Container Awareness: Understand security context in Kubernetes environments
  3. Application Behavior: Monitor JVM-specific activities and anomalies
  4. Incident Response: Rapid investigation and forensics for security incidents
  5. Compliance: Meet regulatory requirements with continuous monitoring

Sysdig Architecture for Java Applications

Java App Container → System Calls → Sysdig Agent → Falco Rules → Security Events
↓
Kubernetes Metadata
↓
Cloud Provider Context

Setting Up Sysdig for Java Applications

1. Deploy Sysdig Agent in Kubernetes:

# sysdig-agent-configmap.yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: sysdig-agent-config
namespace: sysdig-agent
data:
dragent.yaml: |
# Sysdig Access Key
access_key: YOUR_ACCESS_KEY
# Collector settings
collector: ingest.us-east-1.sysdig.com
collector_port: 6443
ssl: true
ssl_verify_certificate: false
# Security settings
security:
enabled: true
featured_rules:
enabled: true
# Kubernetes settings
k8s_cluster_name: production-cluster
secure: true
# Logging
log:
level: info

2. Sysdig Agent DaemonSet:

# sysdig-agent-daemonset.yaml
apiVersion: apps/v1
kind: DaemonSet
metadata:
name: sysdig-agent
namespace: sysdig-agent
labels:
app: sysdig-agent
spec:
selector:
matchLabels:
app: sysdig-agent
template:
metadata:
labels:
app: sysdig-agent
annotations:
prometheus.io/scrape: "true"
prometheus.io/port: "7765"
spec:
hostNetwork: true
hostPID: true
hostIPC: true
serviceAccountName: sysdig-agent
containers:
- name: sysdig-agent
image: sysdig/agent:latest
imagePullPolicy: Always
securityContext:
privileged: true
env:
- name: ACCESS_KEY
valueFrom:
secretKeyRef:
name: sysdig-access-key
key: access-key
volumeMounts:
- name: sysdig-agent-config
mountPath: /opt/draios/etc
- name: docker-sock
mountPath: /var/run/docker.sock
- name: dev-vol
mountPath: /dev
- name: proc-vol
mountPath: /host/proc
readOnly: true
- name: boot-vol
mountPath: /host/boot
readOnly: true
- name: lib-modules-vol
mountPath: /host/lib/modules
readOnly: true
- name: usr-vol
mountPath: /host/usr
readOnly: true
- name: os-release-vol
mountPath: /host/etc/os-release
readOnly: true
resources:
requests:
memory: "512Mi"
cpu: "500m"
limits:
memory: "1024Mi"
cpu: "1000m"
volumes:
- name: sysdig-agent-config
configMap:
name: sysdig-agent-config
- name: docker-sock
hostPath:
path: /var/run/docker.sock
- name: dev-vol
hostPath:
path: /dev
- name: proc-vol
hostPath:
path: /proc
- name: boot-vol
hostPath:
path: /boot
- name: lib-modules-vol
hostPath:
path: /lib/modules
- name: usr-vol
hostPath:
path: /usr
- name: os-release-vol
hostPath:
path: /etc/os-release

Custom Falco Rules for Java Applications

1. Java-Specific Security Rules:

# java-security-rules.yaml
- rule: Java Process Spawned Shell
desc: Detect Java applications spawning shell processes
condition: >
spawned_process and
proc.name in (bash, sh, zsh, ksh, csh, tcsh) and
proc.aname in (java, javaw, jsvc) and
not proc.pname in (kubelet, docker, containerd)
output: >
Java process spawned shell (user=%user.name command=%proc.cmdline parent=%proc.pname 
container=%container.info image=%container.image.repository)
priority: WARNING
tags: [process, java, shell, mitre_execution]
- rule: Suspicious Java Class Loading
desc: Detect suspicious class loading activities in Java applications
condition: >
open_read and
container and
proc.name = java and
(fd.name endswith .class or fd.name contains /tmp/ or fd.name contains /var/tmp/) and
not fd.name contains /usr/lib/jvm and
not fd.name contains /opt/java
output: >
Suspicious Java class loading (user=%user.name command=%proc.cmdline 
file=%fd.filename container=%container.info)
priority: NOTICE
tags: [file, java, mitre_persistence]
- rule: Java Memory Dump Attempt
desc: Detect attempts to dump Java process memory
condition: >
proc.name in (jmap, jstack, gcore, gdb) and
proc.args contains java and
container
output: >
Java memory dump attempt (user=%user.name command=%proc.cmdline 
container=%container.info image=%container.image.repository)
priority: WARNING
tags: [process, java, memory, mitre_discovery]
- rule: Unauthorized JVM Attach
desc: Detect unauthorized JVM attachment attempts
condition: >
open_write and
container and
proc.name = java and
fd.name contains .java_pid and
not user.name in (java-app-user, spring-user, tomcat)
output: >
Unauthorized JVM attach attempt (user=%user.name command=%proc.cmdline 
file=%fd.filename container=%container.info)
priority: CRITICAL
tags: [process, java, jvm, mitre_execution]
- rule: Java Application File System Tampering
desc: Detect file system tampering by Java applications
condition: >
modify and
container and
proc.name = java and
(fd.directory contains /etc or 
fd.directory contains /bin or 
fd.directory contains /sbin or
fd.directory contains /usr/bin) and
not proc.cmdline contains ReadOnlyFileSystem
output: >
Java application file system tampering (user=%user.name command=%proc.cmdline 
file=%fd.name container=%container.info)
priority: ERROR
tags: [filesystem, java, mitre_persistence]

2. Spring Boot Specific Rules:

# spring-boot-rules.yaml
- rule: Spring Boot Actuator Exploitation
desc: Detect suspicious access to Spring Boot actuators
condition: >
open_read and
container and
proc.name = java and
(fd.name contains /actuator/env or 
fd.name contains /actuator/heapdump or
fd.name contains /actuator/dump or
fd.name contains /actuator/trace) and
not fd.name contains /actuator/health
output: >
Suspicious Spring Boot actuator access (user=%user.name command=%proc.cmdline 
endpoint=%fd.filename container=%container.info)
priority: WARNING
tags: [web, java, spring-boot, mitre_discovery]
- rule: Spring Configuration Modification
desc: Detect modification of Spring configuration files
condition: >
modify and
container and
proc.name = java and
(fd.name endswith application.yml or 
fd.name endswith application.properties or
fd.name endswith bootstrap.yml) and
not user.name in (spring-user, deploy-user)
output: >
Spring configuration file modification (user=%user.name command=%proc.cmdline 
file=%fd.name container=%container.info)
priority: ERROR
tags: [filesystem, java, spring, mitre_persistence]

3. Container Escape Detection:

# container-escape-rules.yaml
- rule: Java Container Escape Attempt
desc: Detect Java applications attempting container escape
condition: >
spawned_process and
container and
proc.pname = java and
(proc.name = mount or 
proc.cmdline contains "chroot /host" or
proc.cmdline contains "nsenter" or
proc.cmdline contains "docker.sock")
output: >
Java container escape attempt (user=%user.name command=%proc.cmdline 
parent=%proc.pname container=%container.info)
priority: CRITICAL
tags: [container, java, escape, mitre_escape]
- rule: Privileged Container Java Process
desc: Detect Java running in privileged containers
condition: >
container and
container.privileged = true and
proc.name = java
output: >
Java running in privileged container (user=%user.name command=%proc.cmdline 
container=%container.info image=%container.image.repository)
priority: ERROR
tags: [container, java, security]

Java Application Security Hardening

1. Secure Java Dockerfile:

# Secure Java Application Dockerfile
FROM eclipse-temurin:17-jre-alpine as builder
# Security scanning tools
USER root
RUN apk add --no-cache \
su-exec \
tini \
&& addgroup -S java-app -g 1000 \
&& adduser -S java-app -u 1000 -G java-app
# Application setup
WORKDIR /app
COPY target/my-application.jar app.jar
# Security hardening
RUN chown -R java-app:java-app /app \
&& chmod -R 750 /app \
&& find /app -type f -name "*.jar" -exec chmod 640 {} \; \
&& chmod 750 /app/app.jar
# Switch to non-root user
USER 1000:1000
# Use tini as init process
ENTRYPOINT ["/sbin/tini", "--"]
# Secure JVM options
CMD ["java", \
"-Djava.security.egd=file:/dev/./urandom", \
"-Djava.awt.headless=true", \
"-Dfile.encoding=UTF-8", \
"-XX:+UseContainerSupport", \
"-XX:MaxRAMPercentage=75.0", \
"-XX:+UnlockExperimentalVMOptions", \
"-XX:+UseCGroupMemoryLimitForHeap", \
"-Dspring.config.location=file:/app/config/", \
"-Djava.security.policy=/app/security.policy", \
"-jar", "/app/app.jar"]

2. Java Security Policy:

// security.policy
grant codeBase "file:/app/app.jar" {
// Required permissions
permission java.io.FilePermission "/app/logs/-", "read,write,delete";
permission java.io.FilePermission "/app/tmp/-", "read,write,delete";
permission java.util.PropertyPermission "*", "read";
permission java.lang.RuntimePermission "modifyThread";
permission java.net.SocketPermission "*", "connect,resolve";
// Restrict dangerous permissions
permission java.lang.RuntimePermission "exitVM", "";
permission java.lang.RuntimePermission "setFactory", "";
permission java.lang.RuntimePermission "createClassLoader", "";
permission java.io.FilePermission "/etc/-", "read";
permission java.io.FilePermission "/bin/-", "read";
permission java.io.FilePermission "/usr/bin/-", "read";
// Deny critical operations
permission java.lang.RuntimePermission "loadLibrary.*", "";
permission java.lang.RuntimePermission "setIO", "";
permission java.io.FilePermission "<<ALL FILES>>", "";
};

Sysdig Integration with Java Applications

1. Custom Java Security Agent:

@Component
public class SysdigSecurityAgent {
private static final Logger logger = LoggerFactory.getLogger(SysdigSecurityAgent.class);
@Value("${sysdig.runtime.monitoring.enabled:true}")
private boolean monitoringEnabled;
@EventListener
public void handleApplicationReady(ApplicationReadyEvent event) {
if (monitoringEnabled) {
initializeRuntimeMonitoring();
}
}
private void initializeRuntimeMonitoring() {
try {
// Monitor file system access
SecurityManager securityManager = new SecurityManager() {
@Override
public void checkRead(String file) {
super.checkRead(file);
logFileAccess("read", file);
}
@Override
public void checkWrite(String file) {
super.checkWrite(file);
logFileAccess("write", file);
}
@Override
public void checkExec(String cmd) {
super.checkExec(cmd);
logProcessExecution(cmd);
}
};
System.setSecurityManager(securityManager);
logger.info("Runtime security monitoring initialized");
} catch (SecurityException e) {
logger.warn("Security manager already set, skipping initialization");
}
}
private void logFileAccess(String operation, String file) {
// Log suspicious file access patterns
if (isSuspiciousFile(file)) {
logger.warn("Suspicious file {} operation: {}", operation, file);
// Send to Sysdig/SIEM
SecurityEvent event = SecurityEvent.builder()
.timestamp(Instant.now())
.operation(operation)
.filePath(file)
.user(System.getProperty("user.name"))
.severity("MEDIUM")
.build();
sendSecurityEvent(event);
}
}
private void logProcessExecution(String command) {
// Log suspicious process execution
if (isSuspiciousCommand(command)) {
logger.warn("Suspicious process execution: {}", command);
SecurityEvent event = SecurityEvent.builder()
.timestamp(Instant.now())
.operation("process_execution")
.command(command)
.severity("HIGH")
.build();
sendSecurityEvent(event);
}
}
private boolean isSuspiciousFile(String filePath) {
return filePath.contains("/etc/passwd") ||
filePath.contains("/etc/shadow") ||
filePath.contains("/proc/") ||
filePath.contains("/sys/") ||
filePath.contains("/dev/") ||
filePath.matches(".*\\.(class|jar)$") && filePath.contains("/tmp/");
}
private boolean isSuspiciousCommand(String command) {
return command.contains("chmod") && command.contains("777") ||
command.contains("chown") && command.contains("root") ||
command.contains("wget") || command.contains("curl") ||
command.contains("bash") || command.contains("sh") ||
command.contains("nc ") || command.contains("netcat");
}
private void sendSecurityEvent(SecurityEvent event) {
// Integrate with Sysdig API or logging system
logger.info("Security event: {}", event);
}
}

2. Spring Boot Actuator Security Configuration:

@Configuration
@EnableConfigurationProperties(SysdigSecurityProperties.class)
public class SecurityMonitoringConfig {
private final SysdigSecurityProperties properties;
public SecurityMonitoringConfig(SysdigSecurityProperties properties) {
this.properties = properties;
}
@Bean
public FilterRegistrationBean<SysdigSecurityFilter> sysdigSecurityFilter() {
FilterRegistrationBean<SysdigSecurityFilter> registrationBean = 
new FilterRegistrationBean<>();
registrationBean.setFilter(new SysdigSecurityFilter());
registrationBean.addUrlPatterns("/*");
registrationBean.setOrder(1);
return registrationBean;
}
@Bean
public SecurityMonitoringEndpoint securityMonitoringEndpoint() {
return new SecurityMonitoringEndpoint();
}
}
@Component
class SysdigSecurityFilter implements Filter {
@Override
public void doFilter(ServletRequest request, ServletResponse response, 
FilterChain chain) throws IOException, ServletException {
HttpServletRequest httpRequest = (HttpServletRequest) request;
String path = httpRequest.getRequestURI();
// Monitor sensitive endpoints
if (isSensitiveEndpoint(path)) {
logSensitiveAccess(httpRequest);
}
chain.doFilter(request, response);
}
private boolean isSensitiveEndpoint(String path) {
return path.contains("/actuator") && !path.contains("/health") ||
path.contains("/admin") ||
path.contains("/debug") ||
path.contains("/trace");
}
private void logSensitiveAccess(HttpServletRequest request) {
SecurityEvent event = SecurityEvent.builder()
.timestamp(Instant.now())
.operation("sensitive_endpoint_access")
.endpoint(request.getRequestURI())
.sourceIp(request.getRemoteAddr())
.userAgent(request.getHeader("User-Agent"))
.severity("LOW")
.build();
// Send to monitoring system
logger.info("Sensitive endpoint access: {}", event);
}
}

Kubernetes Security Context for Java Apps

# java-app-deployment-secure.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: java-application
namespace: production
labels:
app: java-application
security-tier: high
spec:
replicas: 3
selector:
matchLabels:
app: java-application
template:
metadata:
labels:
app: java-application
annotations:
# Sysdig monitoring annotations
sysdig.com/monitor: "true"
sysdig.com/security-policy: "java-high-security"
# Prometheus metrics
prometheus.io/scrape: "true"
prometheus.io/port: "8080"
prometheus.io/path: "/actuator/prometheus"
spec:
securityContext:
runAsNonRoot: true
runAsUser: 1000
runAsGroup: 1000
fsGroup: 1000
seccompProfile:
type: RuntimeDefault
containers:
- name: java-app
image: my-registry/java-app:latest
imagePullPolicy: IfNotPresent
securityContext:
allowPrivilegeEscalation: false
capabilities:
drop:
- ALL
readOnlyRootFilesystem: true
runAsNonRoot: true
runAsUser: 1000
ports:
- containerPort: 8080
env:
- name: JAVA_TOOL_OPTIONS
value: >
-Djava.security.egd=file:/dev/./urandom
-Djava.security.manager
-Djava.security.policy==/app/security.policy
-Dcom.sun.management.jmxremote=false
resources:
requests:
memory: "512Mi"
cpu: "250m"
limits:
memory: "1024Mi"
cpu: "500m"
livenessProbe:
httpGet:
path: /actuator/health
port: 8080
initialDelaySeconds: 30
periodSeconds: 10
readinessProbe:
httpGet:
path: /actuator/health/readiness
port: 8080
initialDelaySeconds: 15
periodSeconds: 5
volumeMounts:
- name: tmp-volume
mountPath: /tmp
- name: log-volume
mountPath: /app/logs
- name: config-volume
mountPath: /app/config
readOnly: true
volumes:
- name: tmp-volume
emptyDir:
medium: Memory
- name: log-volume
emptyDir: {}
- name: config-volume
configMap:
name: java-app-config
---
# Network Policy
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: java-app-network-policy
namespace: production
spec:
podSelector:
matchLabels:
app: java-application
policyTypes:
- Ingress
- Egress
ingress:
- from:
- namespaceSelector:
matchLabels:
name: ingress-namespace
ports:
- protocol: TCP
port: 8080
egress:
- to:
- namespaceSelector:
matchLabels:
name: database-namespace
ports:
- protocol: TCP
port: 5432
- to:
- namespaceSelector:
matchLabels:
name: monitoring-namespace
ports:
- protocol: TCP
port: 9090

Monitoring and Alerting

1. Custom Security Metrics:

@Component
public class SecurityMetrics {
private final MeterRegistry meterRegistry;
private final Counter securityEventsCounter;
private final DistributionSummary fileAccessLatency;
public SecurityMetrics(MeterRegistry meterRegistry) {
this.meterRegistry = meterRegistry;
this.securityEventsCounter = Counter.builder("security.events")
.description("Number of security events detected")
.tag("application", "java-app")
.register(meterRegistry);
this.fileAccessLatency = DistributionSummary.builder("security.file.access.latency")
.description("File access operation latency")
.register(meterRegistry);
}
public void recordSecurityEvent(String eventType, String severity) {
securityEventsCounter.increment();
meterRegistry.counter("security.events.detailed",
"type", eventType,
"severity", severity
).increment();
}
public void recordFileAccess(String operation, String file, long duration) {
fileAccessLatency.record(duration);
if (isSuspiciousFile(file)) {
recordSecurityEvent("suspicious_file_access", "MEDIUM");
}
}
}

2. Sysdig Dashboard Configuration:

{
"dashboard": {
"name": "Java Application Runtime Security",
"panels": [
{
"name": "Security Events by Severity",
"type": "timeseries",
"queries": [
{
"promql": "sum by (severity) (security_events_total{application=\"java-app\"})"
}
]
},
{
"name": "Suspicious Process Executions",
"type": "top",
"queries": [
{
"promql": "topk(10, suspicious_process_executions_total{container=~\".*java.*\"})"
}
]
},
{
"name": "Container Escape Attempts",
"type": "number",
"queries": [
{
"promql": "sum(container_escape_attempts_total{namespace=\"production\"})"
}
]
}
],
"variables": [
{
"name": "namespace",
"label": "Namespace",
"type": "prometheus",
"query": "label_values(kube_pod_info, namespace)"
}
]
}
}

Best Practices for Java Runtime Security

  1. Least Privilege: Run Java applications as non-root users
  2. Read-Only Filesystems: Mount filesystems as read-only where possible
  3. Network Policies: Restrict container network access
  4. Resource Limits: Prevent resource exhaustion attacks
  5. Regular Updates: Keep base images and dependencies updated
@Component
public class SecurityHealthIndicator implements HealthIndicator {
private final SecurityMetrics securityMetrics;
public SecurityHealthIndicator(SecurityMetrics securityMetrics) {
this.securityMetrics = securityMetrics;
}
@Override
public Health health() {
// Check security event thresholds
long criticalEvents = getCriticalSecurityEventsCount();
if (criticalEvents > 10) {
return Health.down()
.withDetail("security_status", "CRITICAL")
.withDetail("critical_events", criticalEvents)
.build();
} else if (criticalEvents > 5) {
return Health.outOfService()
.withDetail("security_status", "WARNING")
.withDetail("critical_events", criticalEvents)
.build();
}
return Health.up()
.withDetail("security_status", "SECURE")
.build();
}
private long getCriticalSecurityEventsCount() {
// Query metrics or security event store
return 0L; // Implementation depends on your metrics backend
}
}

Conclusion

Sysdig Runtime Security provides comprehensive protection for Java applications in containerized environments by monitoring system calls, network activity, and container behavior. By combining custom Falco rules tailored for Java applications with secure deployment practices and runtime monitoring, development teams can detect and respond to security threats in real-time.

The integration of Sysdig with Java security features like SecurityManager, combined with Kubernetes security contexts and network policies, creates a defense-in-depth strategy that protects applications from both known and unknown threats. As Java applications continue to move to cloud-native architectures, runtime security monitoring becomes an essential component of the overall security posture.

Leave a Reply

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


Macro Nepal Helper