What is the best/proper way to prepare a dynamic MySQL JSON selector for queries, in order to prevent SQL injection? As an example, say I want to execute the following query:
SELECT `key` ->> "$.key.path" AS `setting_value`
FROM `settings`
LIMIT 1
But I want to make the key path dynamic, e.g.:
$sql = <<<SQL
SELECT `key` ->> "{$path}" AS `setting_value`
FROM `settings`
LIMIT 1
SQL;
With conditional values, I can structure the SQL for a prepared statement using PDO, e.g.:
$sql = <<<SQL
SELECT *
FROM `settings`
WHERE `other_field` = :field_val
LIMIT 1
SQL;
$statement = $this->handle()->prepare($sql);
$statement->execute([':field_val' => 'some val']);
$records = $statement->fetchAll(PDO::FETCH_OBJ);
Adding something like the following to my database adapter (or even to my helper functions) seems rather inelegant and prone to errors/issues:
public function cleanJsonSelector(string $selector) : string {
return preg_replace('/[^-$a-zA-Z0-9_[].*s]/', '', $selector);
}
Thoughts/help?
2
Answers
The
->
and->>
operators only support string literals. They do not expressions, variables, or parameters.But you can use any expression — including a parameter placeholder — in the equivalent JSON function
JSON_EXTRACT()
.The
->>
operator is likeJSON_UNQUOTE(JSON_EXTRACT(json_doc, path))
This seems like an unnecessary inconsistency in the functionality of MySQL’s JSON operators, but this is how it’s implemented in the current MySQL version 8.3.
Unless I misunderstood, what you are asking for is entirely possible; I had no problem using a parameterised path.
docker-compose.yml
:test.php
:output:
With regards to SQL Injection, I think you should be fine with parameters:
I tried that with a few quotes and common sqli scenarios, the error is always the same:
Of course you will still need to handle that and similar exceptions for when the path is not valid. I also cannot guarantee that it is fully safe – something in the json handling routines might still have some sort of security problem (buffer overflows, memory corruption, denial of service etc), but probably not of the sql injection kind.
If you can afford it, I’d go for a simple/naive regex match on the path, just to be safe:
/^$[w.]*$/
, depending on how much of the json selector syntax you want to support.