loader
critical risk OSI Layer 7 / Interpreter current

Injection: SQL, Command, Template and Deserialization

Untrusted data crosses into an interpreter as code because it was combined with a query, command or object graph rather than passed as a parameter.

Defender-oriented briefing. This page describes behavior, observable signals, prevention and response. Operational attack instructions are intentionally excluded.

How it works

Data is concatenated into an interpreter context without a safe parameter boundary, so input written as syntax is executed as syntax. The same flaw appears wherever a string is assembled and then evaluated: databases, shells, template engines and object deserializers.

One flaw, many interpreters

Injection is not a family of unrelated bugs. It is a single mistake repeated across every component that turns a string into instructions: build a string from trusted syntax plus untrusted data, hand it to something that parses it, and the parser cannot tell which part you meant as code.

Recognising it as one flaw matters because the fix is also one fix, and it is not the one people reach for first. Escaping and filtering attempt to make dangerous data safe. Parameterisation removes the question by keeping data out of the syntax stream entirely — the interpreter receives the query structure and the values separately and never has to guess which is which.

This is why parameterised queries are not merely a better filter. A filter can be wrong about encoding, about a character it did not anticipate, or about a context it was not written for. A parameter cannot become syntax no matter what it contains.

SQL and the ORM assumption

SQL injection remains the most familiar case, and the modern version is more interesting than the textbook one, because most applications now use an ORM and assume that settles it.

ORMs parameterise the paths they control. They do not parameterise what you hand them as raw SQL, and every ORM has that escape hatch. Injection today is concentrated in exactly those places: a raw fragment for a query the ORM expressed awkwardly, an ORDER BY built from a sort parameter, a column name interpolated into a dynamic filter.

Identifiers deserve particular attention because they cannot be parameterised. Table and column names are not values, so a placeholder will not accept them. Where a name must be dynamic, validate it against an allow-list of known identifiers rather than escaping it — there is a finite set of legal column names, and anything outside that set is a bug or an attack.

Command injection

Command injection is less common and more severe, because it usually reaches the operating system with the application's privileges.

The mistake is building a command line as a string and handing it to a shell. The shell then interprets separators, substitutions and redirections that were never intended as syntax. Escaping shell metacharacters is a losing game — the set varies by shell, and nested quoting produces cases nobody enumerated.

The reliable fix is to avoid the shell. Invoke the binary directly with an argument array so the arguments are passed as separate values with no parsing step between. Where a shell genuinely cannot be avoided, restrict the input to a strict allow-list rather than attempting to sanitise a general string.

Template injection

Server-side template injection appears when user input becomes part of the template source rather than the data rendered by it. This happens most often in features that sound benign: customisable email templates, user-defined report layouts, tenant-configurable notifications.

The severity surprises people. Template engines are usually full expression languages with access to the object graph, so the consequence is frequently code execution rather than information disclosure.

The boundary is simple to state and easy to violate: user input is data passed to a template, never text concatenated into one. If a product requirement genuinely demands user-authored templates, that calls for a sandboxed engine with an explicit allow-list of exposed functionality, treated as a security feature rather than a formatting convenience.

Deserialization

Deserialization is injection into an object graph. Native formats that reconstruct arbitrary types will instantiate whatever the serialised data names, and in most ecosystems there exist classes whose construction has side effects. Chaining those produces code execution from data that was never expected to be executable.

The practical rule is to never deserialize untrusted data with a format capable of reconstructing arbitrary types. Use a data format that carries values rather than types — JSON parsed into a known schema — and validate it explicitly.

Where session or state tokens must round-trip through the client, sign them and verify the signature before parsing. The signature check must come first: verifying after deserializing means the dangerous step has already happened.

Containing what gets through

Assume one injection will eventually exist and limit what it reaches.

  • Database accounts scoped to the tables and operations the application actually uses, so injection cannot read unrelated data or alter schema.
  • Application processes that do not run as root and cannot write their own code directory.
  • Egress filtering, so a successful injection cannot reach out to fetch a second stage.
  • Query and process telemetry, so unusual statements or unexpected child processes are visible rather than silent.
  • Errors logged in full internally and returned generically to clients — verbose interpreter errors are the primary feedback channel an attacker uses to refine a payload.

Also known as

SQL injection NoSQL injection command injection template injection SSTI insecure deserialization

What defenders may observe

  • Interpreter errors correlate with specific input
  • Unexpected queries, commands or child processes appear
  • Input changes application control flow rather than only its data
  • Unexpected class loading or object graphs during deserialization
  • Tampered state tokens are accepted

Prevention and mitigation

  • Use parameterised interfaces everywhere — the fix is structural, not filtering
  • Never build a shell command from user input; call the binary directly with an argument array
  • Keep user input out of template source; pass it as template data
  • Avoid native object deserialization of untrusted data entirely
  • Sign state tokens and allow-list permitted types where deserialization is unavoidable
  • Run services with least-privileged identities so a successful injection reaches little

Initial response priorities

  • Contain the vulnerable route or feature.
  • Preserve requests, traces and affected records.
  • Fix the root control failure, test adjacent paths and rotate exposed secrets.

Use your approved incident-response plan and adapt decisions to your environment. This is educational guidance, not a substitute for live incident leadership.

Authoritative references