PS Product SecurityKnowledge Base

☕ Java Vulnerability Examples and Fixes

Use this page when: reviewing Java or Spring services, servlet-era code, XML-heavy integrations, or enterprise APIs that mix legacy libraries with modern platform expectations.

How to read these examples

  • Vulnerable snippet shows the unsafe habit.
  • Safer pattern shows the direction you want in production code.
  • Why it matters ties the defect to attacker value and business impact.
  • Review cue is phrased so it can become a pull-request comment or checklist item.

Example 1 — SQL injection with Statement

Vulnerable snippet

String email = request.getParameter("email");
Statement st = conn.createStatement();
ResultSet rs = st.executeQuery(
    "SELECT id, role FROM users WHERE email = '" + email + "'"
);

Safer pattern

String email = request.getParameter("email");
PreparedStatement ps = conn.prepareStatement(
    "SELECT id, role FROM users WHERE email = ?"
);
ps.setString(1, email);
ResultSet rs = ps.executeQuery();

Why it matters

  • Plain string concatenation puts attacker-controlled content into the SQL grammar itself.

Business impact

  • Sensitive data exposure, account takeover via auth queries, and broader blast radius if DB roles are too wide.

Review cue

  • Default to PreparedStatement or framework-level parameter binding; ban dynamic SQL for plain value insertion.

Example 2 — XXE in XML parser configuration

Vulnerable snippet

DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
DocumentBuilder db = dbf.newDocumentBuilder();
Document doc = db.parse(inputStream);

Safer pattern

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);
dbf.setXIncludeAware(false);
dbf.setExpandEntityReferences(false);
DocumentBuilder db = dbf.newDocumentBuilder();
Document doc = db.parse(inputStream);

Why it matters

  • Default XML parsing can resolve external entities and expose files, services, or network paths you never intended to trust.

Business impact

  • File disclosure, SSRF, parser-based denial of service, or internal network reachability.

Review cue

  • Every XML parser must be hardened deliberately; never assume safe defaults in older libraries or frameworks.

Example 3 — Unsafe deserialization of untrusted data

Vulnerable snippet

byte[] bytes = Base64.getDecoder().decode(request.getParameter("state"));
ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(bytes));
Object state = ois.readObject();

Safer pattern

StateDto state = objectMapper.readValue(request.getParameter("state"), StateDto.class);
validateStateDto(state);

Why it matters

  • Native Java deserialization has a long history of gadget-based exploitation and logic abuse when fed untrusted bytes.

Business impact

  • Remote code execution, denial of service, and deep compromise of app servers or worker tiers.

Review cue

  • Avoid native object deserialization for untrusted data; prefer constrained formats and explicit DTO validation.

Example 4 — Path traversal in report download

Vulnerable snippet

String name = request.getParameter("name");
Path path = Paths.get("/srv/reports", name);
return Files.readString(path);

Safer pattern

String name = request.getParameter("name");
Path base = Paths.get("/srv/reports").toRealPath();
Path path = base.resolve(name).normalize();
if (!path.startsWith(base)) {
    throw new AccessDeniedException("invalid path");
}
return Files.readString(path);

Why it matters

  • User-supplied file names can escape the intended directory unless the final normalized path is checked.

Business impact

  • Disclosure of secrets, app config, source code, and operational documents.

Review cue

  • Resolve, normalize, and verify startsWith(base) before touching the file system.

Example 5 — Broken access control in controller or service layer

Vulnerable snippet

@GetMapping("/orders/{id}")
public Order getOrder(@PathVariable Long id) {
    return orderRepository.findById(id).orElseThrow();
}

Safer pattern

@GetMapping("/orders/{id}")
public Order getOrder(@AuthenticationPrincipal AppUser user,
                      @PathVariable Long id) {
    return orderRepository.findByIdAndOwnerUserId(id, user.getId())
        .orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND));
}

Why it matters

  • Looking up records by ID alone turns every predictable identifier into a possible cross-user access path.

Business impact

  • Cross-account data exposure, unauthorized order actions, and high-severity incident response burden.

Review cue

  • The repository or service call should encode ownership or tenant scope, not just the controller path.

Author attribution: Ivan Piskunov, 2026 - Educational and defensive-engineering use.