🛠️ Testing, Tools & Ecosystem · Advanced

Writing secure Java

Input validation, injection, unsafe deserialization, secrets handling.

🧩 The mysteryA download endpoint takes a file name. An attacker asks for "../../etc/passwd", and your server politely hands it over. One line of Java would have stopped it.

All input is hostile

Treat every external input as hostile, even from "your own" frontend: attackers don't have to use it. Validate type, length and format at the boundary with allow-lists (only known-good values pass). Deny-lists always miss something.

🔮 Predict it

Climbing out of the folder

What does this print?

Path base = Path.of("/srv/files");
Path p = base.resolve("docs/../../secret.txt")
        .normalize();
System.out.println(p);
System.out.println(p.startsWith(base));
  1. /srv/secret.txt false
  2. /srv/files/secret.txt true
  3. /srv/files/docs/../../secret.txt true
Show the answer

normalize() resolves the .. segments, which climb out of /srv/files. The startsWith(base) check after normalizing reveals the escape: reject such paths.

Injection, everywhere

Never build SQL, OS commands or file paths by concatenating input. Use parameterized queries, pass command arguments as a list, and for paths normalize, then check they stay inside the allowed folder.

⚠️ The trap

Deserializing strangers' bytes

Calling readObject() on untrusted data is one of Java's most dangerous lines. Crafted bytes can trigger gadget chains in classes on your classpath during deserialization, up to remote code execution, before you ever check the result. Use JSON with explicit types, or at least an ObjectInputFilter allow-list.

var in = new ObjectInputStream(request);
Object o = in.readObject(); // danger!

Handling a secret

✗ Hard-coded
static final String KEY = "prod-9f2c";

Ends up in source control, builds and logs. Rotate it!

✓ Injected
String key = System.getenv("PAY_KEY");

From the environment or a secrets manager; never in code.

🤔 Think first

Random enough?

You generate password-reset tokens. Why not new Random() or Math.random()?

Think about it, then reveal the answer

They're predictable once an attacker has seen a few outputs. Use **SecureRandom**, which is cryptographically strong.

No more Security Manager

Old Java tried to sandbox untrusted code with the Security Manager. It has been permanently disabled since Java 24 (JEP 486). To isolate untrusted code today, use OS-level mechanisms: containers or separate processes.

💼 In the real world

Security reviews

Reviewers look for exactly these lines: concatenated SQL, readObject() on request data, keys in source, user-controlled paths, Random for tokens. Automated scanners help, but knowing the patterns lets you avoid them while writing the code.

Key takeaways

  1. Allow-list validation beats deny-lists
  2. Parameterized queries; normalize and check file paths
  3. Untrusted ObjectInputStream data can mean remote code execution
  4. Secrets from env vars or a secrets manager; SecureRandom for tokens
🤯 Did you know?

The 2017 Equifax breach began with an unpatched vulnerability in Apache Struts, a Java web framework.

Practice questions

A download endpoint resolves a user-supplied file name. What does this print?

Path base = Path.of("/srv/files");
Path p = base.resolve("../../etc/passwd")
        .normalize();
System.out.println(p);
System.out.println(p.startsWith(base));
  1. /srv/etc/passwd false
  2. /srv/files/etc/passwd true
  3. /etc/passwd false
  4. /srv/files/../../etc/passwd true
Check your answer

/etc/passwd false. normalize() resolves the .. segments, which climb out of /srv/files to /etc/passwd. The startsWith check reveals the escape.

An endpoint accepts a Base64 blob and calls `new ObjectInputStream(in).readObject()` on it. What's the biggest risk?

  1. Base64 is slow to decode
  2. readObject() always returns null for remote data
  3. The blob might be too large to log
  4. Crafted data can trigger gadget chains during deserialization, up to remote code execution
Check your answer

Crafted data can trigger gadget chains during deserialization, up to remote code execution. Deserialization can run code in classes on your classpath before you ever check the result. Use JSON with explicit types, or at least an ObjectInputFilter allow-list.

Secure code that's too slow still lets users down. Next: measure, don't guess. Profiling and JMH.