Overview
Most web vulnerabilities in Java apps are not exotic: they are the OWASP top-10 classics
surfacing through JDBC, template engines, Jackson and servlet plumbing. The frameworks
absorb a lot — Spring Security in particular ships secure-by-default CSRF, headers and
session handling — but each protection has a boundary where responsibility passes back to
you: native queries, unsafe template modes, permissive CORS, polymorphic
deserialization. This note walks the top-10 concerns and marks, per stack, what is handled
and what is your job. The attack theory itself lives in
AppSec fundamentals.
Key points
- Injection: parameterized statements everywhere —
PreparedStatement, JPA/Hibernate bind parameters, jOOQ's DSL. The ORM protects you only until you concatenate into JPQL/native queries or dynamicORDER BYcolumns. - XSS: rely on the template engine's contextual auto-escaping — Thymeleaf (
th:textvs the dangerousth:utext), Quarkus Qute (escapes by default in HTML,{value.raw}opts out). JSON APIs shift XSS to the consuming frontend, but setX-Content-Type-Options: nosniffand correctContent-Typeanyway. - CSRF: Spring Security enables token protection by default; disable it only for
stateless bearer-token APIs (no cookie = no CSRF vector). Quarkus has
quarkus-rest-csrf; Micronaut shipsmicronaut-security-csrf— both opt-in. - CORS: never
*with credentials. Spring:CorsConfigurationSourcewired into the filter chain; Quarkus:quarkus.http.cors.*properties; Micronaut:micronaut.server.cors.*. An allowlist of exact origins, not a reflexallow-all. - Security headers: Spring Security writes sensible defaults (HSTS on HTTPS,
X-Content-Type-Options, cache control, frame options) but no CSP — add that yourself. Quarkus and Micronaut set little by default: configure headers explicitly (quarkus.http.header.*, Micronaut server filters). - Sessions: servlet containers regenerate the session ID on login when the framework
drives authentication (Spring Security's session-fixation protection defaults to
changeSessionId); cookies needHttpOnly,Secure, and a consideredSameSite. - Deserialization: never deserialize untrusted data with Java native serialization;
if legacy forces it, use JEP 290 serialization filters
(
ObjectInputFilter/jdk.serialFilter). In Jackson, avoid enabling polymorphic default typing — gadget-chain RCE lives there. - Path traversal & uploads: canonicalize and containment-check every user-supplied path; treat uploaded filenames as hostile display data, store under generated names.
- SSRF: any URL fetched on behalf of a user needs an allowlist and a metadata-endpoint block; no framework does this for you.
Details
Injection — where the ORM's protection ends
PreparedStatement and JPA parameter binding (:name, ?1) make classic SQLi hard to
write. The residual risk is structural SQL that cannot be bound: table/column names,
ORDER BY clauses, IN-list construction via string building, and
entityManager.createNativeQuery(concatenated). Map user input to a fixed set of known
column names rather than sanitising. Same rule for the other injections: LDAP filters,
OS commands (ProcessBuilder with argument lists, never sh -c + concat), and NoSQL
query documents built from raw JSON.
XSS and output encoding
Server-rendered HTML: Thymeleaf escapes th:text/${...} output contextually;
th:utext and inline JavaScript blocks are the escape hatches to audit. Qute escapes
expressions in HTML/XML templates by default; raw opts out. JSP without <c:out> (raw
EL ${} in template text) is the legacy trap. For rich-text features, sanitise on input
with the OWASP Java HTML Sanitizer rather than trying to encode on output. A CSP is the
defence-in-depth backstop for whatever slips through.
CSRF and statelessness
CSRF needs an ambient credential — a cookie. Session-cookie apps must keep CSRF tokens on
(Spring's CsrfFilter with the cookie/header handshake for SPAs:
CookieCsrfTokenRepository). A pure bearer-token API (Authorization header, no cookies)
can and should disable it: http.csrf(csrf -> csrf.disable()) — the single legitimate
use of that much-cargo-culted line. Beware hybrid apps: an OIDC-login session cookie plus
"stateless" API endpoints on the same host is not CSRF-immune.
Framework responsibility table
| Concern | Spring | Quarkus | Micronaut |
|---|---|---|---|
| CSRF | on by default (Spring Security) | quarkus-rest-csrf opt-in |
micronaut-security-csrf opt-in |
| CORS | filter-chain config | quarkus.http.cors.* |
micronaut.server.cors.* |
| Headers (HSTS etc.) | defaults on, CSP manual | manual (quarkus.http.header.*) |
manual (filters/config) |
| Session fixation | auto (changeSessionId) |
container/OIDC handled | session module handles regeneration |
| Auth rate limiting / lockout | manual/bucket4j | manual | manual |
Deserialization in depth
Java native serialization over any untrusted boundary (RMI, cached blobs, cookies) is an
RCE machine — ysoserial gadget chains need only a vulnerable classpath. Mitigate with JEP
290 filters (allowlist patterns via ObjectInputFilter.Config) or, better, replace the
format. Jackson is safe by default; danger arrives with
activateDefaultTyping/@JsonTypeInfo over broad base types (Object, Serializable),
which reintroduces attacker-chosen classes. Keep polymorphism closed: @JsonSubTypes
with an explicit, finite list.
Examples
// Spring Security 6 — stateless API: CSRF off, explicit headers incl. CSP
@Bean
SecurityFilterChain api(HttpSecurity http) throws Exception {
return http
.csrf(csrf -> csrf.disable()) // bearer-token only, no cookies
.sessionManagement(s -> s.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
.headers(h -> h.contentSecurityPolicy(csp ->
csp.policyDirectives("default-src 'none'; frame-ancestors 'none'")))
.authorizeHttpRequests(a -> a.anyRequest().authenticated())
.oauth2ResourceServer(rs -> rs.jwt(Customizer.withDefaults()))
.build();
}
// Path containment check — canonicalize before use
Path base = Path.of("/var/uploads").toRealPath();
Path target = base.resolve(userSupplied).normalize();
if (!target.startsWith(base)) throw new SecurityException("traversal attempt");
Related
- Security in Java applications — the map — parent overview and the concern-to-module table.
- AppSec fundamentals — the OWASP theory this note maps onto Java stacks.
- OAuth2 & OIDC in Java — the token-based auth model that makes CSRF-off legitimate.
- REST in Java — the endpoints these protections wrap.