Injection classes

In Java, the type system draws a hard line between code and data — a String never becomes a method call just because it contains the wrong characters. The interpreters your code hands strings to don't share that guarantee: a SQL engine, a shell and Java's own ObjectInputStream protocol all read a byte stream that decides what runs, not just what values move around. SQL injection, deserialization and OS command injection are the same design mistake wearing three different costumes — and the fix is always the same shape: never let untrusted bytes reach the interpreter as anything but inert data.

SQL injectiondeserialization OS command injectionsource → sink SSRF & prompt injection: same shape →

One shape, three sinks

Every scenario below is source → sink: one line where untrusted input enters (request.getParameter(...), a socket's InputStream), and one line where an interpreter consumes it. The vulnerable line and the safe line reach the same sink API — the difference is never "use a different function", it's whether attacker bytes can still change what the interpreter does once they arrive. This is exactly the pattern-sources / pattern-sinks shape a Semgrep taint rule checks, which is what sec-semgrep has you write next.

Why "use the safe API" is not the whole lesson

Scenario 2 is the trap that catches people who already know the rule: PreparedStatement is "the safe API" for SQL, but conn.prepareStatement(sql) is only safe when sql itself has no concatenated data in it. Build the query string by splicing in a request parameter first, then hand the finished string to prepareStatement, and you get every bit of SQL injection's danger behind an API that looks parameterized. The interpreter doesn't know or care which Java method handed it the bytes — it only sees the string.

Takeaways: a sink is unsafe because of what reaches it, not which function it is — prepareStatement, exec and readObject are each safe on trusted input and dangerous on attacker-controlled input, full stop. Fix the boundary (bind parameters, pass argv arrays instead of shell strings, only deserialize bytes from a source you control), not the call site's name. SSRF (an untrusted URL reaching an HTTP client) and prompt injection (untrusted text reaching an LLM's instruction-following) are the same source-reaches-interpreter shape with a different interpreter — sec-ssrf and sec-prompt-injection pick those up.