PartiQL is DynamoDB's SQL-compatible query language, executed through the ExecuteStatement, BatchExecuteStatement, and ExecuteTransaction APIs. Because the syntax is SQL-like (SELECT, INSERT, UPDATE, DELETE, WHERE, AND/OR), developers coming from relational databases build statements the same way they always have (with string formatting) and reintroduce the same injection class. The parameterized form uses ? placeholders supplied in a separate Parameters list; injection happens when input is concatenated into the statement text instead.
Scope. For authorized penetration tests, CTF labs, and code review of systems you own or are contracted to assess.
Vulnerable pattern
# user_id and category come from the request
stmt = (
"SELECT * FROM Orders "
f"WHERE user_id = '{user_id}' AND category = '{category}'"
)
client.execute_statement(Statement=stmt)
The safe form keeps the statement static and passes values out of band:
client.execute_statement(
Statement="SELECT * FROM Orders WHERE user_id = ? AND category = ?",
Parameters=[{"S": user_id}, {"S": category}],
)
Exploitation
PartiQL quotes string literals with single quotes and identifiers with double quotes, so the break-out primitive is the single quote, exactly as in SQL.
Widen the scope with OR. Close the attacker-controlled value, inject a tautology, and comment out or absorb the trailing predicate so every row matches:
' OR 'a'='a
Injected into the user_id position above, the statement becomes:
SELECT * FROM Orders WHERE user_id = '' OR 'a'='a' AND category = 'books'
Because OR has lower precedence than AND, feeding the payload through the last value is cleaner, since it leaves no dangling operator:
books' OR user_id <> 'x
yields ... AND category = 'books' OR user_id <> 'x', returning the whole table.
Read other users' items. When the guard is a single ownership check, invert it to reach everyone else's records:
' OR user_id > '
Target specific items by key. If you know or can guess another partition key, pivot directly:
' OR user_id = 'victim@example.com
Operator and function abuse. PartiQL supports IN, BETWEEN, CONTAINS, begins_with, and attribute_exists/attribute_not_exists. Use them to broaden matching or to probe attribute presence:
' OR attribute_exists(ssn) OR '1'='1
Scope of impact. DynamoDB does not chain multiple statements from one ExecuteStatement call the way stacked SQL queries do, so injection is confined to the single statement. Within a SELECT that is still enough to exfiltrate the full table. On the write side the impact is bounded: PartiQL UPDATE and DELETE must identify a single item by its complete primary key, so a widened WHERE is rejected rather than becoming a mass modification, and the attacker can only reach items whose full key they can address.
Because PartiQL is SQL-compatible, standard boolean and relational payloads transfer directly; adjust quoting and attribute names to the target table's schema.