2
votes

I am building an Ecto query like this:

from item in query,
where:  like(item.description, ^"%#{text}%")

I'm concerned that this allows SQL injection in text. Before trying to fix that, I want to see how the query is actually sent to the database.

If I inspect the query or look at what is logged, I see some SQL, but it's not valid.

For instance, inspecting the query shows me this:

{"SELECT i0.\"id\", i0.\"store_id\", i0.\"title\", i0.\"description\" 
  FROM \"items\" AS i0 WHERE (i0.\"description\" LIKE $1)",
 ["%foo%"]}

When I pass this query to Repo.all, it logs this:

SELECT i0."id", i0."store_id", i0."title", i0."description"
  FROM "items" AS i0 WHERE (i0."description" LIKE $1) ["%foo%"]

But if I copy and paste that into psql, PostgreSQL gives me an error:

ERROR: 42P02: there is no parameter $1

It seems as though Ecto may actually be doing a parameterized query, like this:

PREPARE bydesc(text) AS SELECT i0."id", 
  i0."store_id", i0."title", i0."description" 
  FROM "items" AS i0 WHERE (i0."description" LIKE $1);
EXECUTE bydesc('foo');

If so, I think that would prevent SQL injection. But I'm just guessing that this is what Ecto does.

How can I see the actual SQL that Ecto is executing?

2

2 Answers

10
votes

Ecto uses only prepared statements. When using ecto query syntax, introducing SQL injection is not possible. The query syntax verifies at compile-time that no SQL injection is possible.

Showing exactly the queries executed might be difficult because of couple reasons:

  • Postgrex (and hence Ecto) uses the postgresql binary protocol (instead of the most common, but less efficient, text protocol), so the PREPARE query never actually exists as a string.
  • For most cases all you would see would be one initial PREPARE 64237612638712636123(...) AS ... and later a lot of EXECUTE 64237612638712636123(...) which isn't that helpful. Trying to relate one to another would be horrible.

From my experience most software of that kind, use prepare statements and log them instead of raw queries, since it's much more helpful in understanding the behaviour of the system.

6
votes

Yes, that is the exact SQL that is being executed by Ecto (it uses prepared queries through the db_connection package internally) and no SQL injection is possible in that code. This can be verified by turning on logging of all executed SQL queries by changing log_statement to all in postgresql.conf:

...
log_statement = 'all'
...

and then restarting PostgreSQL and running a query. For the following queries:

Repo.get(Post, 1)
Repo.get(Post, 2)

this is logged:

LOG:  execute ecto_818: SELECT p0."id", p0."title", p0."user_id", p0."inserted_at", p0."updated_at" FROM "posts" AS p0 WHERE (p0."id" = $1)
DETAIL:  parameters: $1 = '1'
LOG:  execute ecto_818: SELECT p0."id", p0."title", p0."user_id", p0."inserted_at", p0."updated_at" FROM "posts" AS p0 WHERE (p0."id" = $1)
DETAIL:  parameters: $1 = '2'