A rich set of vulnerable × fixed patterns for Java / Spring. Mapped to the OWASP Top 10:2025.
Jump to: Injection · Deserialization & XXE · Crypto & secrets · Access control · SSRF · Auth · Errors · Mini-case
❌ Vulnerable:
Statement st = conn.createStatement();
ResultSet rs = st.executeQuery("SELECT * FROM users WHERE email = '" + email + "'");
// JPA:
em.createQuery("FROM User WHERE email = '" + email + "'");✅ Fixed — PreparedStatement / bound JPQL:
PreparedStatement ps = conn.prepareStatement("SELECT * FROM users WHERE email = ?");
ps.setString(1, email);
em.createQuery("FROM User WHERE email = :email").setParameter("email", email);❌ Vulnerable:
Runtime.getRuntime().exec("ping " + host);✅ Fixed — arg array, no shell:
new ProcessBuilder("ping", "-c", "1", host).start();❌ Vulnerable — evaluating user input as SpEL:
Expression exp = new SpelExpressionParser().parseExpression(userInput); // RCE
exp.getValue();✅ Fixed — never parse user input as an expression; if templating is needed, use a restricted context and a fixed template.
❌ Vulnerable — zip entry name used directly:
File out = new File(destDir, entry.getName()); // "../../etc/cron.d/x"✅ Fixed — validate the canonical path stays under destDir:
File out = new File(destDir, entry.getName());
if (!out.getCanonicalPath().startsWith(destDir.getCanonicalPath() + File.separator))
throw new SecurityException("zip slip blocked");❌ Vulnerable:
ObjectInputStream ois = new ObjectInputStream(request.getInputStream());
Object obj = ois.readObject(); // gadget-chain RCE✅ Fixed — avoid native serialization; fixed-type data format:
MyDto dto = new ObjectMapper().readValue(json, MyDto.class); // no polymorphic typingNever enable Jackson
activateDefaultTyping()/@JsonTypeInfoon untrusted input.
❌ Vulnerable:
DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
Document doc = dbf.newDocumentBuilder().parse(input);✅ Fixed — disable DTDs / external entities:
DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
dbf.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true);
dbf.setFeature("http://xml.org/sax/features/external-general-entities", false);
dbf.setFeature("http://xml.org/sax/features/external-parameter-entities", false);
Document doc = dbf.newDocumentBuilder().parse(input);❌ Vulnerable:
String hash = DigestUtils.md5Hex(password);✅ Fixed — BCrypt (Spring Security):
PasswordEncoder enc = new BCryptPasswordEncoder();
String hash = enc.encode(password);
boolean ok = enc.matches(password, hash);❌ Vulnerable:
private static final String API_KEY = "sk_live_...redacted";✅ Fixed:
@Value("${api.key}") // env / Vault / config server
private String apiKey;❌ Vulnerable:
String token = Long.toString(new Random().nextLong());✅ Fixed — SecureRandom:
byte[] b = new byte[32];
new SecureRandom().nextBytes(b);
String token = HexFormat.of().formatHex(b);❌ Vulnerable:
@GetMapping("/admin/users")
public List<User> all() { return repo.findAll(); } // anyone✅ Fixed — method security:
@PreAuthorize("hasRole('ADMIN')")
@GetMapping("/admin/users")
public List<User> all() { return repo.findAll(); }❌ Vulnerable:
@GetMapping("/invoices/{id}")
public Invoice get(@PathVariable Long id) { return repo.findById(id).orElseThrow(); }✅ Fixed — check ownership against the principal:
@GetMapping("/invoices/{id}")
public Invoice get(@PathVariable Long id, @AuthenticationPrincipal UserDetails me) {
Invoice inv = repo.findById(id).orElseThrow();
if (!inv.getOwner().equals(me.getUsername())) throw new AccessDeniedException("nope");
return inv;
}❌ Vulnerable:
InputStream in = new URL(userSuppliedUrl).openStream();✅ Fixed — allowlist host + reject private ranges:
URI uri = URI.create(userSuppliedUrl);
if (!ALLOWED.contains(uri.getHost())) throw new SecurityException("host not allowed");
InetAddress addr = InetAddress.getByName(uri.getHost());
if (addr.isSiteLocalAddress() || addr.isLoopbackAddress() || addr.isLinkLocalAddress())
throw new SecurityException("internal blocked");❌ Vulnerable:
return "redirect:" + request.getParameter("next");✅ Fixed — allow only relative paths:
String next = request.getParameter("next");
if (next == null || !next.startsWith("/") || next.startsWith("//")) next = "/";
return "redirect:" + next;❌ Vulnerable:
http.csrf(csrf -> csrf.disable()); // blanket disable on a session-cookie app✅ Fixed — keep CSRF for browser flows (disable only for stateless token APIs, deliberately):
http.csrf(Customizer.withDefaults());❌ Vulnerable:
if (!userExists(u)) throw new RuntimeException("no such user"); // user enumeration✅ Fixed — generic message for both cases:
if (!authenticate(u, p)) throw new BadCredentialsException("invalid credentials");❌ Vulnerable:
} catch (Exception e) { return ResponseEntity.status(500).body(e.toString()); }✅ Fixed — log internally, generic response:
} catch (Exception e) {
log.error("operation failed", e);
return ResponseEntity.status(500).body("Unexpected error");
}❌ Vulnerable:
@GetMapping("/report/{id}")
public String report(@PathVariable Long id) {
Report r = em.createQuery("FROM Report WHERE id = " + id, Report.class) // A05
.getSingleResult();
log.info("served {} key {}", id, apiKey); // A09
return "<h1>" + r.getTitle() + "</h1>"; // A05 XSS
}✅ Fixed:
@GetMapping("/report/{id}")
public String report(@PathVariable Long id, @AuthenticationPrincipal UserDetails me, Model model) {
Report r = repo.findById(id).orElseThrow(); // A05: no raw JPQL
if (!r.getOwner().equals(me.getUsername())) throw new AccessDeniedException("nope"); // A01
log.info("served {}", id); // A09
model.addAttribute("report", r);
return "report"; // A05: template escapes
}readObject(),Runtime.execw/ strings, default XML parsers,activateDefaultTyping, SpEL on input → justify or reject.- Spring:
@PreAuthorize/method security present, CSRF not blindly disabled, actuator endpoints secured. - Secrets via
@Value/Vault, never in committedapplication.properties. new Random()/Math.random()for tokens →SecureRandom; Zip Slip guarded on extraction.
- OWASP: Cheat Sheet Series · XXE Prevention · Deserialization · SQL Injection Prevention · Mass Assignment
- Tools: OWASP Dependency-Check · find-sec-bugs · Semgrep