I'm trying to take the result of an Eloquent query, and output its results as a JSON response. My application uses Slim and Twig to generate HTML responses, however I am uncertain if I should be using Twig to generate JSON as well.
I know that I can use PHP's native echo json_encode(...)
function, but this creates a potential XSS vulnerability if my database contains HTML entities. Twig is supposed to be in charge of escaping my output appropriately.
I am aware of this question, however it doesn't seem to provide a relevant answer. I am also aware of the json_encode
filter, but when I do this:
/api/users-json.twig
{
"rows" : {{rows | json_encode}}
}
/api/users controller:
// Simulate database query results
$result = [
"rows" => [
[
"user_name" => "alex",
"message" => "grawr!"
],
[
"user_name" => "h4xx0r",
"message" => "<script>alert('hello, I can execute JS on your website!');</script>"
]
]
];
$app->response->headers->set('Content-Type', 'application/json; charset=utf-8');
$app->render("api/users-json.twig", $result);
The response looks like:
{
"rows" : [{"user_name":"alex","message":"grawr!"},{"user_name":"h4xx0r","message":"<script>alert('hello, I can execute JS on your website!');<\/script>"}]
}
Which is not interpretable client-side without further processing. According to my browser, the content-type is correctly set to application/json
.
I can, of course, do: /api/users-json.twig
{
"rows" : {{rows | json_encode | raw}}
}
Which gives me the response:
{
"rows" : [{"user_name":"alex","message":"grawr!"},{"user_name":"h4xx0r","message":"<script>alert('hello, I can execute JS on your website!');<\/script>"}]
}
But if I were to render h4xx0r's message in client-side code, I am open to an XSS attack.
The output that I believe would be "correct" would be:
{
"rows" : [{"user_name":"alex","message":"grawr!"},{"user_name":"h4xx0r","message":"<script>alert('hello, I can execute JS on your website!');<\/script>"}]
}
Note that h4xx0r's "message" is now escaped, but the structure of the response as a whole is preserved as valid JSON.
I could, of course, loop through every row and manually htmlspecialchars
each value, and then either echo json_encode
or pass it off to Twig. But this seems like it should be Twig's responsibility!
Edit: It would seem that PHP's filter_var_array
, combined with json_encode
, is a reasonable alternative to using Twig:
$app->response->headers->set('Content-Type', 'application/json; charset=utf-8');
echo json_encode(filter_var_array($result, FILTER_SANITIZE_SPECIAL_CHARS));
Produces:
{"rows":[{"user_name":"alex","message":"grawr!"},{"user_name":"h4xx0r","message":"<script>alert('hello, I can execute JS on your website!');<\/script>"}]}
But I am still not sure if this is something that "should" be done with Twig instead.
Is there a way to do this with Slim and Twig at all? Or, am I on completely the wrong track, and should it be the responsibility of my client-side (JS) code to properly escape content before rendering?