flawopen.com/SQL Injection/Kotlin

SQL Injection in Kotlin

Critical CWE-89 Draft — pending review
ELI5

Imagine a form that only ever expects a ticket number, like 482. SQL injection is what happens when someone types something sneaky into that box instead of a number — a trick phrase that makes the system say "show me every ticket" instead of just ticket 482, because the system never checked that what it received was actually just a number.

Key terms on this page
user-controlled input
Any value that ultimately came from whoever is using — or attacking — the app: a form field, a URL parameter, an uploaded filename, an HTTP header. The app can't assume it's well-formed or safe.
SQL query
The command sent to a database — e.g. "get this row," "delete this table." Its meaning comes entirely from its exact text, which is what makes injecting extra text into it dangerous.

What's happening

SQL injection happens when user-controlled input gets inserted directly into a database query's text. On Android, this shows up in SQLiteDatabase.rawQuery() calls built with Kotlin's string templates; on the server side (Ktor, Spring), the same JDBC-level rules apply as in Java, since Kotlin compiles to the same bytecode and calls the same APIs.

Real-world impact

In 2015, UK telecom TalkTalk suffered a breach affecting over 150,000 customers after attackers exploited a SQL injection flaw in a legacy web page inherited through a company acquisition. The UK's data protection regulator fined TalkTalk £400,000, describing the failure as preventable and basic.

Source: UK Information Commissioner's Office enforcement notice, 2016 — see References below.

Vulnerable vs. fixed

VULNERABLE
// userId comes straight from user input
val query =
  "SELECT * FROM users WHERE id = $userId"
val cursor = db.rawQuery(query, null)
FIXED
// value bound, never templated in
val cursor = db.rawQuery(
  "SELECT * FROM users WHERE id = ?",
  arrayOf(userId)
)

Why the fix works

The ? placeholder is resolved by SQLite's own bind mechanism, using the values in the second argument array — the query text is fixed before any value is attached to it. Kotlin's $userId string template can't provide this: it resolves before the string is ever handed to rawQuery(), so the value is already indistinguishable from the rest of the query.

Kotlin-specific gotchas

String templates make inline interpolation the path of least resistance

"...$userId" is Kotlin's most natural, idiomatic way to build any string — exactly the same trap shape as Python's f-strings or JS template literals, just with Kotlin's own syntax.

Prefer SQLiteDatabase.query() over rawQuery() where possible

The structured query() builder method takes selection arguments as a genuinely separate parameter, removing the temptation to build a WHERE clause by hand at all — it's a stronger default than remembering to parameterize rawQuery() correctly every time.

On the JVM server side, Exposed's DSL parameterizes, its exec() doesn't

Kotlin's Exposed ORM parameterizes automatically through its typed DSL. Its exec() escape hatch for raw SQL carries the same risk as JDBC's raw Statement if the string passed to it was built with interpolation.

Common misconceptions

"Kotlin's null-safety features protect against this"

Null-safety and query-construction safety are unrelated properties — a non-null, well-typed String can still carry an injected SQL fragment.

"This is Android, so it's not really 'a database' in the risky sense"

A local SQLite database still parses and executes SQL exactly like a server-side one — an attacker who can influence the query (e.g. through a malicious Intent, a shared file, or synced data) has the same leverage as against a remote database.

How to check if you're affected

grep -rn "rawQuery(" --include="*.kt" . | grep '\$' grep -rn "\.exec(" --include="*.kt" . | grep '\$\|+'
Android Lint's SQLite-injection check and detekt's custom rule support can both be configured to flag string-templated rawQuery calls in CI.

Prevention checklist

FAQ

Does this apply the same way to Kotlin on the JVM server side?

Yes — Kotlin compiles to JVM bytecode and calls the same JDBC APIs as Java, so the same PreparedStatement-vs-Statement distinction from Java applies directly.

Is Room (Android's Jetpack persistence library) safe by default?

Room's @Query annotations with :param placeholders parameterize automatically — the same discipline applies if you ever drop to raw SupportSQLiteDatabase calls underneath it.

References

View in: Python JavaScript Go Java PHP C# Ruby C/C++ Rust Kotlin Swift Solidity (N/A)
Also see: Command InjectionPath Traversal XSSInsecure Deserialization