SpiderLabs Blog

Accidental Stored XSS Flaw in Zemanta 'Related Posts' Plugin for TypePad

Written by Ryan Barnett | Apr 22, 2013 12:44:00 PM

Note that the vulnerability described here was fixed by Zemanta.

It all started innocently enough

I was here in the TypePad editor working on a new web honeypot blog post about some XSS attacks we were seeing when, BOOM, my browser was suddently redirected to the hxxp://www.txt2pic.com website. What the heck just happened?!? I clicked back in my browser to take me back to the TypePad editor session. Then, a few moments later... I was redirected yet again. Something was seriously wrong here. I sat there for a moment with a blank stare on my face as my mind quickly ran through different scenarios of what could be happening when it suddenly hit me. Wait a minute, that URL (hxxp://www.txt2pic.com) was familiar.

A few days earlier...

It was only a few days eariler that I had posted another blog post on some XSS attacks that were identified in the User-Agent string of requests. Here are the example entries:


You will notice that this is actually a graphics image. In the original blog post, however, I had actually pasted in these entries directly in as text. If you clicked on the HTML editor link, however, you would see that the TypePad editor properly escaped these HTML characters using HTML Entity Encoding:

This means that the Javascript data would be rendered as text when it reaches the blog reader's web browser rather than it being mistakenly executed as code. I quickly went to this blog post and click on view-source to verify this fact. It was indeed HTML Entity Encoded. So what was happening to me? How was this same exact JS code popping me when I was in my TypePad blog editor?

BurpProxy to the rescue

I quickly fired up BurpProxy and reconfigured my browser to use it. I set the interception proxy to not not stop traffic and under Options I enabled "Intercept Server Responses". My intent was to simply have a complete HTTP audit log of traffic I could analyze if/when this happened again. After this was setup, I then went back into my TypePad Editor session to work on my blog again and sure enough, off my browser went to that txt2pic.com site. I then quickly switched over to my Burp Proxy History tab and entered in the txt2pic.com text to search the saved data for that string:



The results screen quickly identified the following single transaction:

But where is the JS script tag data? I then clicked on the Response tab and found it in the JSON response body:


There is my web honeypot XSS log data... and it has been HTML Entity Decoded which turns it back into executable code!

"ZEMANTA!"

Zemanta Related Posts Plugin for TypePad

The Zemanta Related Posts plugin for TypePad appears at the bottom of the TypePad Editor and looks like this:


Here is how the Related Posts plugin is described:

The benefits of using Related Posts

You can add Related Posts to the bottom of your text with a click of a button to give your post more depth and provide clues to readers on where to get further opinions and information on the subject.

Other benefits of using related posts are again numerous. First and foremost your post gains on credibility but there is another hidden value: bloggers that you link out to will notice you. You will get attention from writers who are interested in the same topics as you. This often leads to more comments, reactions and backlinks but most of all to new important acquaintances.

Vulnerability Data Flow

"Published Post" Beacon Call to API

When you publish a blog post, it sends a beacon request from the Zemanta plugin in your browser session to the Zemanta API with URL information. Here is the RESTful API GET request notifying Zemanta of my new blog post URL -


Here is the corresponding Response confirming the new URL to be indexed:


Zemanta API Web Client Index

After receiving the Beason API call, Zemanta fires off their blog post scraper web client to gather the text from your blog post. This is the critical step in this XSS issue as the Zemanta API web client captured therendered text of the XSS payload from my Apache logs. At this point, the active JS data is sitting in the Zemanta API index waiting to be called up by the "Related Posts" plugin for TypePad posts.

Related Posts Plugin API Call

Then when other TypePad users are editing new blog posts, the Related Posts plugin will send a request to the Zemanta API (as shown in Burp screen shots above) with the text from the current draft blog.

API JSON Response

The Zemanta API will then return JSON data with title and blog post snippets of data that matched the text in the current draft blog post. It is in this response that the JS code from my previous blog post reflected back into my own browser session and executed. Here is the actual JS file with the vulnerability -


Here is the exact section of code with the problem -

                       var excerpt = $('<span />')                               .html(obj.text_preview || obj.text_highlight || obj.title_highlight || 'Excerpt is not available at this time.')                               .text()                               .replace(/\n/g, '<br />'),                               title = obj.title.replace(" and related posts", ""),

Notice the bolded section of code as it is treating the JSON response data elements as html.

Zemanta Fixes the Related Posts TypePad Plugin

I notified the Zemanta team of the stored XSS issue I had found. I want to thank both Andraz Tori and Dušan Omerčevič for working with me to understand the issues and for quickly updating the Related Posts plugin for TypePad users. Here is the updated section of JS code from above:

                      var excerpt = $('<span>' +                                       (obj.text_preview || obj.text_highlight || obj.title_highlight || 'Excerpt is not available at this time.') +                                       '</span>')                                      .text()                                      .replace(/\n/g, '<br />'),                              title = $('<span>' + obj.title.replace(" and related posts", "") + '</span>').text(),

As you can see from the highlighted section of code, the JSON data returned by Related Posts API call is now treated as text rather than html. In addition, the Zemanta team also updated their "Zemanta Suggests" API documentation warning other consumers of the data to treat this data with care:

Conclusions

The adventure has taught me a few key things:

  1. Use graphics for any executable code. Even though I took care to encode the data, I didn't have any control over other systems or clients that might process that data.
  2. Beware of APIs and Plugins - these may add extra value to your application and users however they may also introduce unexpected vulnerabilities.
  3. Burp Proxy is invaluable for HTTP forensics - without Burp Proxy it would have much more difficult to track this down.