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.
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.
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.
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.