7

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"  : [{&quot;user_name&quot;:&quot;alex&quot;,&quot;message&quot;:&quot;grawr!&quot;},{&quot;user_name&quot;:&quot;h4xx0r&quot;,&quot;message&quot;:&quot;&lt;script&gt;alert(&#039;hello, I can execute JS on your website!&#039;);&lt;\/script&gt;&quot;}]
}

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":"&lt;script&gt;alert(&#039;hello, I can execute JS on your website!&#039;);&lt;\/script&gt;"}]
}

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":"&#60;script&#62;alert(&#39;hello, I can execute JS on your website!&#39;);&#60;\/script&#62;"}]}

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?

Community
  • 1
  • 1
alexw
  • 8,468
  • 6
  • 54
  • 86
  • I don't think that preventing XSS atracks is Twig's responsibility. To prevent it you have to filter your input, not the output. – Gustavo Straube Sep 27 '15 at 21:17
  • @gustavo This is not correct. Sure, you should always **filter** your input when possible, but **escaping** is something that should be done in output. Escaping input is an [antipattern](http://security.stackexchange.com/a/42521/74909). – alexw Sep 27 '15 at 22:04
  • Uhh, are you asking rhetorically? – alexw Sep 28 '15 at 18:32
  • If your system received a JSON message, would it be important to you that it was generated via a 'twig template'? If the exact same, valid `JSON` response is created by 'twig' and 'some magic system'. Does it matter? – Ryan Vincent Sep 28 '15 at 18:42
  • Yes. Structure and best practices are important to me. – alexw Sep 28 '15 at 18:43
  • Thanks for the clarification - it helps. – Ryan Vincent Sep 28 '15 at 18:51
  • maybe you could use strip_tags to only remove the script tag? – r3wt Dec 10 '15 at 17:06
  • 1
    I wouldn't use filtering server side to attempt to remove XSS in a JSON response anyway. Use the proper escaping and filtering in Javascript before you do the output. The point of a JSON response is to get the information from your server to your Javascript. Separation of concerns tells us that your server side code shouldn't be involved in what your Javascript is doing with the data. – Reid Johnson Apr 29 '16 at 17:08
  • @ReidJohnson you should turn your comment into a full answer. – alexw Jun 15 '17 at 21:04

1 Answers1

0

Twig will render any given variable as html encoded. However, as you want to json encode the result, you need need to iterate through the data yourself as Twig doesn't delve into the array for you.

Rob Allen
  • 12,643
  • 1
  • 40
  • 49