AddToAny Share Buttons WordPress Plugin DOM-based XSS
The AddToAny Share Buttons WordPress Plugin was, until recently, vulnerable to a DOM-based cross-site scripting issue. The file in question is hosted on the author’s site, so you’re not vulnerable anymore (you’re welcome). If you just want the vulnerability details, go here. Now for the story of this bug.
This particular journey started with an email from XSSposed, a site intended to allow security researchers to responsibly report cross-site scripting issues in sites that don’t run formal bug bounty programs like those seen here. Having never heard of it before, I was immediately suspicious. I like the concept of bug bounties just fine, but I was a little surprised to learn that I had unknowingly opened one on my site.
Despite my profession, and my obsession with security generally, this is not the type of email I like to come home to after a long day of testing webapps. I felt fairly confident that this wasn’t a 0day in WordPress itself, so if this was legit, it would either have to be my simplistic template or a plugin I’d installed. I’ve learned to keep the number of templates and plugins installed on my site low, so I didn’t think that was likely either, but I figured it was more likely than a WordPress core bug being used in a free “bug bounty” situation.
I hadn’t aggressively audited my entire site for XSS issues, but on several occasions I had used my site to test XSS fuzzing tools I was building. I even reported a few issues to plugin authors as a result (see here for one public example). I thought that the remaining plugins were all issue-free. But I was wrong.
I went rummaging through my logs and quickly found what I was looking for:
95.x.x.x - - [13/Oct/2015:04:56:58 -0500] "GET /blog/?s=x%22%3E%3Csvg/onload=alert(/XSSPOSED/)%3E HTTP/1.1" 200 18324 "-" "Mozilla/5.0 (Windows NT 6.3; WOW64; rv:41.0) Gecko/20100101 Firefox/41.0" 178.x.x.x - - [13/Oct/2015:05:00:04 -0500] "GET /blog/?s=x\x22><svg/onload=alert(/XSSPOSED/)> HTTP/1.1" 200 18332 "https://techiavellian.com/" "Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 6.0; Trident/4.0)" 178.x.x.x - - [13/Oct/2015:05:00:05 -0500] "GET /blog/?s=x%22%3E%3Csvg/onload=alert(/XSSPOSED/)%3E HTTP/1.1" 200 18324 "-" "Mozilla/5.0 (compatible; MSIE 10.0; Windows NT 6.1; WOW64; Trident/6.0)"
I poked around some more and found similar, slightly earlier requests requests from the security researcher who reported the issue (I’m assuming). These were basically the same as those above, namely some HTML in the “s” GET parameter, or the search field, on the front page of my blog.
I tried out a few of the attack strings I found, fearing the worst. But nothing happened. I tried different browsers, I poked around in the source, nothing indicated to me that this was a working XSS vector. I was angry at this point. Here was someone claiming very publicly that my site was vulnerable to XSS, but I couldn’t reproduce the issue. The height of treachery!
I checked out the link in the email above, and decided to try to contact the researcher who had reported the issue. After a few days of back-and-forth (thank you, dim0k, for your patience), I was able to trigger the issue in Firefox with a slightly different payload. I’m not 100% sure what caused it not to work initially, but I think it might’ve been some combination of native XSS filters, Ghostery, and NoScript. Anyway, now the hunt was on.
I dug through the source again, hoping I had missed an obvious echo($_GET[‘s’]) type issue, but to no avail. All copies of the attack string in the page’s HTML were seemingly escaped. I knew this meant it was DOM-based, and therefore would probably take up the rest of my evening.
I started setting breakpoints around the source, only to have them disappoint me when they didn’t trigger before the alert box. After a while I started looking at a script used by the AddToAny Share Buttons plugin. It was hosted on their site, which irked me initially (YOU CAN’T JUST CHANGE MY SITE’S SOURCE ANYTIME YOU WANT!), but in this case it was arguably a good thing.
A few breakpoints later, I started narrowing in on this bit right here (edited to show most of the relevant bits):
var init = function(config, e) { ... var main = document.createElement("div"); ... var name = a2a.getData(item)["a2a-title"]; ... config.linkname = e.linkname = name || config.linkname; config.linkurl = e.linkurl = opt_button || config.linkurl; ... main.innerHTML = config.linkname; ... };
Basically, main’s innerHTML property is going to be set to config.linkname, where config is passed in as a parameter to the init function. Here’s the relevant variable definition and call to init:
self.linkname = a2a[self.type].last_linkname = options.linkname || (a2a[self.type].last_linkname || (document.title || location.href)); self.linkurl = a2a[self.type].last_linkurl = options.linkurl || (a2a[self.type].last_linkurl || location.href); self.linkname_escape = options.linkname_escape; ... if (a2a.locale && !dataAndEvents) { a2a.fn_queue.push(function(self, data) { return function() { init(self, data); }; }(self, d)); } else { init(self, d); ... }
Thank goodness for JSNice. Anyway, I was thinking it might be location.href, so I tried a simple proof-of-concept:
<html> <head> </head> <body> <script> y = document.createElement('a'); y.innerHTML = location.href; </script> </body> </html>
Playing around with this for a while got me nowhere fast. (If you want to get lost down that rabbit hole, here’s a fun place to start!) Next I turned to document.title. Finally, when I tried the following, I got a pop:
<html> <head> <title>You searched for "><img src=x onerror=alert(1)></title> </head> <body> <script> y = document.createElement('a'); y.innerHTML = document.title; </script> </body> </html>
That’s not an encoding error, if that’s what you’re thinking. The title attribute is escaped on purpose. And, sure enough, if you try this, you’ll get an alert box yourself (at least in Chrome and Firefox). Huh. Sure enough, even when properly encoded, the contents of document.title can be a source of chaos. You learn something new every day.
I reached out to the developers, and they were able to fix the issue quickly. And now I’m posting this with no worries about it being exploited elsewhere, exactly because that file is hosted on their servers and they’ve already patched it. They were even able to get it fixed before I was XSSPOSED. Guess everything worked out after all. Well, kind of.
Thanks to dim0k and Pat for making this whole process easier than it could’ve been, and to XSSposed for doing what they do (even if it makes me look dumb on occasion).
Like this post? Hit me up on Twitter @ccneill.