24

I have a stdClass object in PHP, something like

$o = new stdClass;
$o->foo = $bar

The variable $bar contains an untrusted string.

Is the following PHP template code sufficient XSS protection

<script type="text/javascript">
    var o = <?php echo json_encode($o); ?>;
</script>

My initial gut reaction is that is is safe, as encoding an object as JSON will ensure any potential javascript exploits will be rendered inert by being included as JSON string property objects. Something like this

$o = new stdClass;
$o->foo = "<script type=\"text/javascript\">alert(document.cookie)</script>";
?>
<script type="text/javascript">
    var o = <?php echo json_encode($o) ?>;    
</script>    

Resulting in output like this

<script type="text/javascript">
    var o = {"foo":"<script type=\"text\/javascript\">alert(document.cookie) <\/script>"};    
</script>    

If this is known unsafe, is there's a standard, mature way of serializing a simple stdClass object to a JSON string for use in a the <script/> portion of an HTML document.

In anticipation of the first quick answer, I realize that stripping out any HTML tags, or otherwise XSS filtering each element of the JSON object would work, but I'm looking for a concise way of doing this. Similar to how this

//$eBar = addslashes($bar);
$sql = sprintf("SELECT * FROM table WHERE foo = '%s'",mysql_real_escape_string($bar));

and this

$sql = $db->select('SELECT * from table where foo = ?', $bar);

are (in most contexts) functionally equivalent, but the later is considered better, more secure code since the end programmer user doesn't need to worry about escaping schemes.

tonix
  • 6,671
  • 13
  • 75
  • 136
Alana Storm
  • 164,128
  • 91
  • 395
  • 599
  • 2
    [Similar question](http://stackoverflow.com/questions/6816787/json-php-to-javascript-safe-or-not). I found the unaccepted answer from "bobince" an interesting read – Brad Aug 21 '12 at 20:11
  • use `HttpOnly` cookies and `alert(document.cookie)` will be useless... – Dejan Marjanović Aug 21 '12 at 20:13
  • Sometimes it's best just to ask the real question .. –  Aug 21 '12 at 20:18

5 Answers5

11

Seems as through the best answer to this question lies in another question.

To sum up, PHP's JSON encoder escapes all non ASCII characters, so newlines/carriage returns can't be inserted to bollacks up the Javascript string portion of the JSON property. This may not be true of other JSON encoders.

However, passing in a raw string to JSON encode can lead to the usual litany of XSS attacks, the following combination of constants is suggested.

var v= <?php echo json_encode($value, JSON_HEX_QUOT|JSON_HEX_TAG|JSON_HEX_AMP|JSON_HEX_APOS); ?>;

or ensure the variable passed to json_encode is really an object.

Community
  • 1
  • 1
Alana Storm
  • 164,128
  • 91
  • 395
  • 599
  • 6
    Why the "random" selection of options? `/` is protected with `\/` (`"<\/script>`" is OK - why HEX_TAG?), JSON string values are surrounding by double quotes (`"'"` is OK - why HEX_APOS?), double quotes in string values are escaped (`"\""` is OK - why HEX_QUOT?) and SCRIPT elements in HTML are CDATA (so unguarded `&entity;` is OK - why HEX_AMP?). If putting the JSON in an HTML attribute (e.g. not in a SCRIPT/CDATA context) then the correct solution would be to escape it for the context *after* applying json_encode. – user2864740 Aug 16 '14 at 22:54
  • It's explained pretty well in the link. http://stackoverflow.com/questions/6816787/json-php-to-javascript-safe-or-not/6817147#6817147 – Alana Storm Aug 17 '14 at 01:26
  • Actually PHP has always encoded forward slashes with a backslash so there is no reason for the random flags. Proof: https://3v4l.org/1HGt2 – Boy Baukema Jun 19 '17 at 20:43
  • 3
    @user2864740 I agree, no need for all these flags. *Except* for `JSON_HEX_TAG` (see: https://stackoverflow.com/questions/20942452/why-does-script-cause-a-dom-tree-break-on-the-browser ), since ``) – flen Dec 16 '17 at 23:21
2

XSS is very broad and it's actually impossible to know at any time whether untrusted data you are emitting is safe.

The answer is really that it depends on the situation. json_encode does no escaping on its own whatsoever -- you're only using it for serialization purposes. The escape function you want to use would be htmlspecialchars.

However, whether or not you even want to use htmlspecialchars depends. For example, will you insert the value of o.foo using innerHTML or textContent? The latter would lead to a double-escape, but the former would insert a script. What about if you were going to use eval (in JS)?

By the way addslashes is not functionally equivalent to mysql escaping.

I wouldn't mix JavaScript and PHP in this way to begin with, but that's another story.

Explosion Pills
  • 188,624
  • 52
  • 326
  • 405
  • +1. The **not** in the functionally equivalent remark should be stressed, IMOHO. –  Aug 21 '12 at 20:11
  • 1
    Also, *newer* PHP versions (5.3+) do add the options for additional encoding rules (e.g `JSON_HEX_TAG`), but I am not sure about the defaults, etc. –  Aug 21 '12 at 20:12
  • Apologies for not being clear, while it's 100% accurate, this isn't the sort of answer I was looking for. htmlspecialchars is the correct function to use for escaping strings for use in a html body context. I'm looking for well test techniques for getting serialized JSON objects to the page's html script tag context in a way that removes the burden of escaping things from the client programmer. – Alana Storm Aug 21 '12 at 20:13
  • @AlanStorm you could try the extra arguments of `json_encode` such as `JSON_HEX_TAG` as suggested by pst, if they're available to you. – Explosion Pills Aug 21 '12 at 20:57
  • 1
    It really depends on *how* the value will be used in JavaScript and as mentioned, the method of output.`json_encode` will only return valid JSON which will not get executed. Unless you're going to `eval()` (something that should be avoided anyways) the string `o.foo` you should be fine. – beeplogic Aug 21 '12 at 21:26
2

this will work ;)

...?payload=<img%20src=x%20onerror=alert(document.cookie);>

with json_encode ...

<?php echo json_encode($_GET['payload']); ?>

;)

Ensai Tankado
  • 189
  • 1
  • 4
  • +1 for useful information. This does protect in this one specific instance -- although there may be cases where a very specially constructed payload might prevent that protection from working. – Alana Storm Aug 28 '20 at 20:27
1

What I do is to evaluate the json object before assuming its safe. I think the method is evalJSON(true) in prototype and jquery has a similar implementation. I don't know much about xss standards with JSON but this helps me

kingletas
  • 44
  • 2
0

As other answers have said; json_encode is not built for anti-xss protections. Unless you specifically encode the unsafe string (or sanitize properly) you're going to have a potential issue.

Furthermore, once that string is extracted from the JSON object, it is still potentially hazardous if injected into the page at any point. For example:

<?php $a->foo = "<script>alert(1)</script>"; ?>
var v = <?php echo json_encode($a); ?>

isn't likely to execute (although you can't be certain). But if you were to do:

$('#some-element').html(v.foo);

you would absolutely encounter a vulnerability.

TheMonarch
  • 577
  • 1
  • 5
  • 19